# HG changeset patch # User Sebastien Jodogne # Date 1410270943 -7200 # Node ID 67c3c1e4a6e06c8a8ab4519097ddef8463f0d8a2 # Parent ba9fd42284d0d23d4a220500a5b66c30829f2c05 index-only mode, and custom storage area with plugins diff -r ba9fd42284d0 -r 67c3c1e4a6e0 Core/Enumerations.h --- a/Core/Enumerations.h Tue Sep 09 12:53:49 2014 +0200 +++ b/Core/Enumerations.h Tue Sep 09 15:55:43 2014 +0200 @@ -73,7 +73,8 @@ ErrorCode_ReadOnly, ErrorCode_IncompatibleImageFormat, ErrorCode_IncompatibleImageSize, - ErrorCode_SharedLibrary + ErrorCode_SharedLibrary, + ErrorCode_Plugin }; /** @@ -267,6 +268,8 @@ enum FileContentType { + // If you add a value below, insert it in "PluginStorageArea" in + // the file "Plugins/Engine/OrthancPlugins.cpp" FileContentType_Unknown = 0, FileContentType_Dicom = 1, FileContentType_DicomAsJson = 2, diff -r ba9fd42284d0 -r 67c3c1e4a6e0 Core/FileStorage/CompressedFileStorageAccessor.cpp --- a/Core/FileStorage/CompressedFileStorageAccessor.cpp Tue Sep 09 12:53:49 2014 +0200 +++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp Tue Sep 09 15:55:43 2014 +0200 @@ -36,6 +36,7 @@ #include "../OrthancException.h" #include "FileStorageAccessor.h" #include "../HttpServer/BufferHttpSender.h" +#include "../Uuid.h" #include @@ -45,6 +46,8 @@ size_t size, FileContentType type) { + std::string uuid = Toolbox::GenerateUuid(); + std::string md5; if (storeMD5_) @@ -56,7 +59,7 @@ { case CompressionType_None: { - std::string uuid = GetStorageArea().Create(data, size, type); + GetStorageArea().Create(uuid.c_str(), data, size, type); return FileInfo(uuid, type, size, md5); } @@ -72,14 +75,13 @@ Toolbox::ComputeMD5(compressedMD5, compressed); } - std::string uuid; if (compressed.size() > 0) { - uuid = GetStorageArea().Create(&compressed[0], compressed.size(), type); + GetStorageArea().Create(uuid.c_str(), &compressed[0], compressed.size(), type); } else { - uuid = GetStorageArea().Create(NULL, 0, type); + GetStorageArea().Create(uuid.c_str(), NULL, 0, type); } return FileInfo(uuid, type, size, md5, diff -r ba9fd42284d0 -r 67c3c1e4a6e0 Core/FileStorage/FileStorageAccessor.cpp --- a/Core/FileStorage/FileStorageAccessor.cpp Tue Sep 09 12:53:49 2014 +0200 +++ b/Core/FileStorage/FileStorageAccessor.cpp Tue Sep 09 15:55:43 2014 +0200 @@ -34,9 +34,9 @@ #include "FileStorageAccessor.h" #include "../HttpServer/BufferHttpSender.h" +#include "../Uuid.h" #include - #include namespace Orthanc @@ -52,7 +52,10 @@ Toolbox::ComputeMD5(md5, data, size); } - return FileInfo(storage_.Create(data, size, type), type, size, md5); + std::string uuid = Toolbox::GenerateUuid(); + storage_.Create(uuid.c_str(), data, size, type); + + return FileInfo(uuid, type, size, md5); } diff -r ba9fd42284d0 -r 67c3c1e4a6e0 Core/FileStorage/FilesystemStorage.cpp --- a/Core/FileStorage/FilesystemStorage.cpp Tue Sep 09 12:53:49 2014 +0200 +++ b/Core/FileStorage/FilesystemStorage.cpp Tue Sep 09 15:55:43 2014 +0200 @@ -85,26 +85,20 @@ Toolbox::CreateDirectory(root); } - std::string FilesystemStorage::Create(const void* content, - size_t size, - FileContentType /*type*/) + void FilesystemStorage::Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType /*type*/) { - std::string uuid; boost::filesystem::path path; - for (;;) - { - uuid = Toolbox::GenerateUuid(); - path = GetPath(uuid); + path = GetPath(uuid); - if (!boost::filesystem::exists(path)) - { - // OK, this is indeed a new file - break; - } - - // Extremely improbable case: This Uuid has already been created - // in the past. Try again. + if (boost::filesystem::exists(path)) + { + // Extremely unlikely case: This Uuid has already been created + // in the past. + throw OrthancException(ErrorCode_InternalError); } if (boost::filesystem::exists(path.parent_path())) @@ -140,8 +134,6 @@ } f.close(); - - return uuid; } diff -r ba9fd42284d0 -r 67c3c1e4a6e0 Core/FileStorage/FilesystemStorage.h --- a/Core/FileStorage/FilesystemStorage.h Tue Sep 09 12:53:49 2014 +0200 +++ b/Core/FileStorage/FilesystemStorage.h Tue Sep 09 15:55:43 2014 +0200 @@ -53,9 +53,10 @@ public: FilesystemStorage(std::string root); - virtual std::string Create(const void* content, - size_t size, - FileContentType type); + virtual void Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType type); virtual void Read(std::string& content, const std::string& uuid, diff -r ba9fd42284d0 -r 67c3c1e4a6e0 Core/FileStorage/IStorageArea.h --- a/Core/FileStorage/IStorageArea.h Tue Sep 09 12:53:49 2014 +0200 +++ b/Core/FileStorage/IStorageArea.h Tue Sep 09 15:55:43 2014 +0200 @@ -45,9 +45,10 @@ { } - virtual std::string Create(const void* content, - size_t size, - FileContentType type) = 0; + virtual void Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType type) = 0; virtual void Read(std::string& content, const std::string& uuid, diff -r ba9fd42284d0 -r 67c3c1e4a6e0 Core/OrthancException.cpp --- a/Core/OrthancException.cpp Tue Sep 09 12:53:49 2014 +0200 +++ b/Core/OrthancException.cpp Tue Sep 09 15:55:43 2014 +0200 @@ -127,6 +127,9 @@ case ErrorCode_SystemCommand: return "Error while calling a system command"; + case ErrorCode_Plugin: + return "Error encountered inside a plugin"; + case ErrorCode_Custom: default: return "???"; diff -r ba9fd42284d0 -r 67c3c1e4a6e0 NEWS --- a/NEWS Tue Sep 09 12:53:49 2014 +0200 +++ b/NEWS Tue Sep 09 15:55:43 2014 +0200 @@ -5,12 +5,14 @@ ------- * Creation of ZIP archives for media storage, with DICOMDIR -* Refactoring of HttpOutput ("Content-Length" header is now always sent) +* Support of index-only mode (using the "StoreDicom" option) +* Plugins can implement a custom storage area Minor ----- * Configuration option to enable HTTP Keep-Alive +* Refactoring of HttpOutput ("Content-Length" header is now always sent) * "/tools/create-dicom" now accepts the "PatientID" DICOM tag (+ updated sample) * Upgrade to Mongoose 3.8 * Fixes for Visual Studio 2013 and Windows 64bit diff -r ba9fd42284d0 -r 67c3c1e4a6e0 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Tue Sep 09 12:53:49 2014 +0200 +++ b/OrthancServer/main.cpp Tue Sep 09 15:55:43 2014 +0200 @@ -326,17 +326,14 @@ { } - virtual std::string Create(const void* content, - size_t size, - FileContentType type) + virtual void Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType type) { if (type != FileContentType_Dicom) { - return storage_.Create(content, size, type); - } - else - { - return Toolbox::GenerateUuid(); + storage_.Create(uuid, content, size, type); } } @@ -368,18 +365,14 @@ static bool StartOrthanc() { std::string storageDirectoryStr = Configuration::GetGlobalStringParameter("StorageDirectory", "OrthancStorage"); - boost::filesystem::path storageDirectory = Configuration::InterpretStringParameterAsPath(storageDirectoryStr); boost::filesystem::path indexDirectory = Configuration::InterpretStringParameterAsPath( Configuration::GetGlobalStringParameter("IndexDirectory", storageDirectoryStr)); - // TODO HERE - FilesystemStorage storage(storageDirectory.string()); - //FilesystemStorageWithoutDicom storage(storageDirectory.string()); + // "storage" must be declared BEFORE "ServerContext context", to + // avoid mess in the invokation order of the destructors. + std::auto_ptr storage; + ServerContext context(indexDirectory); - ServerContext context(indexDirectory); - context.SetStorageArea(storage); - - LOG(WARNING) << "Storage directory: " << storageDirectory; LOG(WARNING) << "Index directory: " << indexDirectory; context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false)); @@ -465,6 +458,31 @@ orthancPlugins.SetOrthancRestApi(restApi); context.SetOrthancPlugins(orthancPlugins); + + // Prepare the storage area + if (orthancPlugins.HasStorageArea()) + { + LOG(WARNING) << "Using a custom storage area from plugins"; + storage.reset(orthancPlugins.GetStorageArea()); + } + else + { + boost::filesystem::path storageDirectory = Configuration::InterpretStringParameterAsPath(storageDirectoryStr); + LOG(WARNING) << "Storage directory: " << storageDirectory; + if (Configuration::GetGlobalBoolParameter("StoreDicom", true)) + { + storage.reset(new FilesystemStorage(storageDirectory.string())); + } + else + { + LOG(WARNING) << "The DICOM files will not be stored, Orthanc running in index-only mode"; + storage.reset(new FilesystemStorageWithoutDicom(storageDirectory.string())); + } + } + + context.SetStorageArea(*storage); + + // GO !!! Start the requested servers if (Configuration::GetGlobalBoolParameter("HttpServerEnabled", true)) { diff -r ba9fd42284d0 -r 67c3c1e4a6e0 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Tue Sep 09 12:53:49 2014 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Tue Sep 09 15:55:43 2014 +0200 @@ -88,9 +88,15 @@ RestCallbacks restCallbacks_; OrthancRestApi* restApi_; OnStoredCallbacks onStoredCallbacks_; + bool hasStorageArea_; + _OrthancPluginRegisterStorageArea storageArea_; - PImpl(ServerContext& context) : context_(context), restApi_(NULL) + PImpl(ServerContext& context) : + context_(context), + restApi_(NULL), + hasStorageArea_(false) { + memset(&storageArea_, 0, sizeof(storageArea_)); } }; @@ -805,6 +811,15 @@ AccessDicomInstance(service, parameters); return true; + case _OrthancPluginService_RegisterStorageArea: + { + const _OrthancPluginRegisterStorageArea& p = + *reinterpret_cast(parameters); + + pimpl_->storageArea_ = p; + return true; + } + default: return false; } @@ -816,4 +831,108 @@ pimpl_->restApi_ = &restApi; } + + bool OrthancPlugins::HasStorageArea() const + { + return pimpl_->hasStorageArea_; + } + + + namespace + { + class PluginStorageArea : public IStorageArea + { + private: + _OrthancPluginRegisterStorageArea params_; + + void Free(void* buffer) const + { + if (buffer != NULL) + { + params_.free_(buffer); + } + } + + OrthancPluginContentType Convert(FileContentType type) const + { + switch (type) + { + case FileContentType_Dicom: + return OrthancPluginContentType_Dicom; + + case FileContentType_DicomAsJson: + return OrthancPluginContentType_DicomAsJson; + + default: + return OrthancPluginContentType_Unknown; + } + } + + public: + PluginStorageArea(const _OrthancPluginRegisterStorageArea& params) : params_(params) + { + } + + virtual void Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType type) + { + if (params_.create_(uuid.c_str(), content, size, Convert(type)) != 0) + { + throw OrthancException(ErrorCode_Plugin); + } + } + + virtual void Read(std::string& content, + const std::string& uuid, + FileContentType type) const + { + void* buffer = NULL; + int64_t size = 0; + + if (params_.read_(&buffer, &size, uuid.c_str(), Convert(type)) != 0) + { + throw OrthancException(ErrorCode_Plugin); + } + + try + { + content.resize(size); + } + catch (OrthancException&) + { + Free(buffer); + throw; + } + + if (size > 0) + { + memcpy(&content[0], buffer, size); + } + + Free(buffer); + } + + virtual void Remove(const std::string& uuid, + FileContentType type) + { + if (params_.remove_(uuid.c_str(), Convert(type)) != 0) + { + throw OrthancException(ErrorCode_Plugin); + } + } + }; + } + + + IStorageArea* OrthancPlugins::GetStorageArea() + { + if (!HasStorageArea()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + return new PluginStorageArea(pimpl_->storageArea_);; + } } diff -r ba9fd42284d0 -r 67c3c1e4a6e0 Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Tue Sep 09 12:53:49 2014 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Tue Sep 09 15:55:43 2014 +0200 @@ -47,7 +47,6 @@ { private: struct PImpl; - boost::shared_ptr pimpl_; void RegisterRestCallback(const void* parameters); @@ -98,5 +97,9 @@ const std::string& instanceId); void SetOrthancRestApi(OrthancRestApi& restApi); + + bool HasStorageArea() const; + + IStorageArea* GetStorageArea(); }; } diff -r ba9fd42284d0 -r 67c3c1e4a6e0 Plugins/OrthancCPlugin/OrthancCPlugin.h --- a/Plugins/OrthancCPlugin/OrthancCPlugin.h Tue Sep 09 12:53:49 2014 +0200 +++ b/Plugins/OrthancCPlugin/OrthancCPlugin.h Tue Sep 09 15:55:43 2014 +0200 @@ -15,6 +15,7 @@ * services of Orthanc. * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). * - Register all its callbacks for received instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). + * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea(). * -# void OrthancPluginFinalize(): * This function is invoked by Orthanc during its shutdown. The plugin * must free all its memory. @@ -87,7 +88,7 @@ #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 0 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 8 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 1 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 3 @@ -240,6 +241,7 @@ /* Registration of callbacks */ _OrthancPluginService_RegisterRestCallback = 1000, _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, + _OrthancPluginService_RegisterStorageArea = 1002, /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -321,6 +323,19 @@ } OrthancPluginPixelFormat; + + /** + * The content types that are supported by Orthanc plugins. + **/ + typedef enum + { + OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */ + OrthancPluginContentType_Dicom = 1, /*!< DICOM */ + OrthancPluginContentType_DicomAsJson = 2 /*!< JSON summary of a DICOM file */ + } OrthancPluginContentType; + + + /** * @brief A memory buffer allocated by the core system of Orthanc. * @@ -378,16 +393,76 @@ /** + * @brief Signature of a function to free dynamic memory. + **/ + typedef void (*OrthancPluginFree) (void* buffer); + + + + /** + * @brief Callback for writing to the storage area. + * + * Signature of a callback function that is triggered when Orthanc writes a file to the storage area. + * + * @param uuid The UUID of the file. + * @param content The content of the file. + * @param size The size of the file. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + **/ + typedef int32_t (*OrthancPluginStorageCreate) ( + const char* uuid, + const void* content, + int64_t size, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading from the storage area. + * + * Signature of a callback function that is triggered when Orthanc reads a file from the storage area. + * + * @param content The content of the file (output). + * @param size The size of the file (output). + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + **/ + typedef int32_t (*OrthancPluginStorageRead) ( + void** content, + int64_t* size, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for removing a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area. + * + * @param uuid The UUID of the file to be removed. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + **/ + typedef int32_t (*OrthancPluginStorageRemove) ( + const char* uuid, + OrthancPluginContentType type); + + + + /** * @brief Opaque structure that contains information about the Orthanc core. **/ typedef struct _OrthancPluginContext_t { - void* pluginsManager; - const char* orthancVersion; - void (*Free) (void* buffer); - int32_t (*InvokeService) (struct _OrthancPluginContext_t* context, - _OrthancPluginService service, - const void* params); + void* pluginsManager; + const char* orthancVersion; + OrthancPluginFree Free; + int32_t (*InvokeService) (struct _OrthancPluginContext_t* context, + _OrthancPluginService service, + const void* params); } OrthancPluginContext; @@ -1404,6 +1479,40 @@ + typedef struct + { + OrthancPluginStorageCreate create_; + OrthancPluginStorageRead read_; + OrthancPluginStorageRemove remove_; + OrthancPluginFree free_; + } _OrthancPluginRegisterStorageArea; + + /** + * @brief Register a custom storage area. + * + * This function registers a custom storage area, to replace the + * built-in way Orthanc stores its files on the filesystem. This + * function must be called during the initialization of the plugin, + * i.e. inside the OrthancPluginInitialize() public function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageRead read, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea params; + params.create_ = create; + params.read_ = read; + params.remove_ = remove; + params.free_ = free; + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, ¶ms); + } + + #ifdef __cplusplus } #endif diff -r ba9fd42284d0 -r 67c3c1e4a6e0 Resources/Configuration.json --- a/Resources/Configuration.json Tue Sep 09 12:53:49 2014 +0200 +++ b/Resources/Configuration.json Tue Sep 09 15:55:43 2014 +0200 @@ -191,5 +191,9 @@ // Enable or disable HTTP Keep-Alive (experimental). Set this option // to "true" only in the case of high HTTP loads. - "KeepAlive" : false + "KeepAlive" : false, + + // If this option is set to "false", Orthanc will run in index-only + // mode. The DICOM files will not be stored on the drive. + "StoreDicom" : true } diff -r ba9fd42284d0 -r 67c3c1e4a6e0 UnitTestsSources/FileStorageTests.cpp --- a/UnitTestsSources/FileStorageTests.cpp Tue Sep 09 12:53:49 2014 +0200 +++ b/UnitTestsSources/FileStorageTests.cpp Tue Sep 09 15:55:43 2014 +0200 @@ -63,7 +63,8 @@ FilesystemStorage s("UnitTestsStorage"); std::string data = Toolbox::GenerateUuid(); - std::string uid = s.Create(&data[0], data.size(), FileContentType_Unknown); + std::string uid = Toolbox::GenerateUuid(); + s.Create(uid.c_str(), &data[0], data.size(), FileContentType_Unknown); std::string d; s.Read(d, uid, FileContentType_Unknown); ASSERT_EQ(d.size(), data.size()); @@ -77,7 +78,8 @@ std::vector data; StringToVector(data, Toolbox::GenerateUuid()); - std::string uid = s.Create(&data[0], data.size(), FileContentType_Unknown); + std::string uid = Toolbox::GenerateUuid(); + s.Create(uid.c_str(), &data[0], data.size(), FileContentType_Unknown); std::string d; s.Read(d, uid, FileContentType_Unknown); ASSERT_EQ(d.size(), data.size()); @@ -94,7 +96,9 @@ for (unsigned int i = 0; i < 10; i++) { std::string t = Toolbox::GenerateUuid(); - u.push_back(s.Create(&t[0], t.size(), FileContentType_Unknown)); + std::string uid = Toolbox::GenerateUuid(); + s.Create(uid.c_str(), &t[0], t.size(), FileContentType_Unknown); + u.push_back(uid); } std::set ss;