# HG changeset patch # User Sebastien Jodogne # Date 1610128413 -3600 # Node ID eddb212b2df9e1817b97bb9a33b58e1692177871 # Parent 5209a9ff6e389b0bbd7a0f52844edacb083fa5df New functions in the SDK: OrthancPluginCreateMemoryBuffer64() and OrthancPluginRegisterStorageArea2() diff -r 5209a9ff6e38 -r eddb212b2df9 NEWS --- a/NEWS Thu Jan 07 18:18:39 2021 +0100 +++ b/NEWS Fri Jan 08 18:53:33 2021 +0100 @@ -13,6 +13,13 @@ - "UseDicomTls" in "DicomModalities" to enable DICOM TLS in outgoing SCU on a per-modality basis * New command-line option: "--openapi" to write the OpenAPI documentation of the REST API to a file +Plugins +------- + +* New functions in the SDK: + - OrthancPluginCreateMemoryBuffer64() + - OrthancPluginRegisterStorageArea2() + Maintenance ----------- diff -r 5209a9ff6e38 -r eddb212b2df9 OrthancServer/Plugins/Engine/OrthancPlugins.cpp --- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Thu Jan 07 18:18:39 2021 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Fri Jan 08 18:53:33 2021 +0100 @@ -153,35 +153,125 @@ namespace { - class PluginStorageArea : public IStorageArea + class MemoryBufferRaii : public boost::noncopyable + { + private: + OrthancPluginMemoryBuffer buffer_; + + public: + MemoryBufferRaii() + { + buffer_.size = 0; + buffer_.data = NULL; + } + + ~MemoryBufferRaii() + { + if (buffer_.size != 0) + { + free(buffer_.data); + } + } + + OrthancPluginMemoryBuffer* GetObject() + { + return &buffer_; + } + + void ToString(std::string& target) const + { + if ((buffer_.data == NULL && buffer_.size != 0) || + (buffer_.data != NULL && buffer_.size == 0)) + { + throw OrthancException(ErrorCode_Plugin); + } + else + { + target.resize(buffer_.size); + + if (buffer_.size != 0) + { + memcpy(&target[0], buffer_.data, buffer_.size); + } + } + } + }; + + + class MemoryBuffer64Raii : public boost::noncopyable { private: - _OrthancPluginRegisterStorageArea callbacks_; - PluginsErrorDictionary& errorDictionary_; - - void Free(void* buffer) const - { - if (buffer != NULL) - { - callbacks_.free(buffer); - } - } + OrthancPluginMemoryBuffer64 buffer_; public: - PluginStorageArea(const _OrthancPluginRegisterStorageArea& callbacks, - PluginsErrorDictionary& errorDictionary) : - callbacks_(callbacks), + MemoryBuffer64Raii() + { + buffer_.size = 0; + buffer_.data = NULL; + } + + ~MemoryBuffer64Raii() + { + if (buffer_.size != 0) + { + free(buffer_.data); + } + } + + OrthancPluginMemoryBuffer64* GetObject() + { + return &buffer_; + } + + void ToString(std::string& target) const + { + if ((buffer_.data == NULL && buffer_.size != 0) || + (buffer_.data != NULL && buffer_.size == 0)) + { + throw OrthancException(ErrorCode_Plugin); + } + else + { + target.resize(buffer_.size); + + if (buffer_.size != 0) + { + memcpy(&target[0], buffer_.data, buffer_.size); + } + } + } + }; + + + class StorageAreaBase : public IStorageArea + { + private: + OrthancPluginStorageCreate create_; + OrthancPluginStorageRemove remove_; + PluginsErrorDictionary& errorDictionary_; + + protected: + PluginsErrorDictionary& GetErrorDictionary() const + { + return errorDictionary_; + } + + public: + StorageAreaBase(OrthancPluginStorageCreate create, + OrthancPluginStorageRemove remove, + PluginsErrorDictionary& errorDictionary) : + create_(create), + remove_(remove), errorDictionary_(errorDictionary) { } - virtual void Create(const std::string& uuid, const void* content, size_t size, - FileContentType type) - { - OrthancPluginErrorCode error = callbacks_.create + FileContentType type) ORTHANC_OVERRIDE + { + OrthancPluginErrorCode error = create_ (uuid.c_str(), content, size, Plugins::Convert(type)); if (error != OrthancPluginErrorCode_Success) @@ -191,20 +281,57 @@ } } + virtual void Remove(const std::string& uuid, + FileContentType type) ORTHANC_OVERRIDE + { + OrthancPluginErrorCode error = remove_ + (uuid.c_str(), Plugins::Convert(type)); + + if (error != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast(error)); + } + } + }; + + + class PluginStorageArea : public StorageAreaBase + { + private: + OrthancPluginStorageRead read_; + OrthancPluginFree free_; + + void Free(void* buffer) const + { + if (buffer != NULL) + { + free_(buffer); + } + } + + public: + PluginStorageArea(const _OrthancPluginRegisterStorageArea& callbacks, + PluginsErrorDictionary& errorDictionary) : + StorageAreaBase(callbacks.create, callbacks.remove, errorDictionary), + read_(callbacks.read), + free_(callbacks.free) + { + } virtual void Read(std::string& content, const std::string& uuid, - FileContentType type) + FileContentType type) ORTHANC_OVERRIDE { void* buffer = NULL; int64_t size = 0; - OrthancPluginErrorCode error = callbacks_.read + OrthancPluginErrorCode error = read_ (&buffer, &size, uuid.c_str(), Plugins::Convert(type)); if (error != OrthancPluginErrorCode_Success) { - errorDictionary_.LogError(error, true); + GetErrorDictionary().LogError(error, true); throw OrthancException(static_cast(error)); } @@ -225,19 +352,45 @@ Free(buffer); } - - - virtual void Remove(const std::string& uuid, - FileContentType type) - { - OrthancPluginErrorCode error = callbacks_.remove - (uuid.c_str(), Plugins::Convert(type)); + }; + + + // New in Orthanc 1.9.0 + class PluginStorageArea2 : public StorageAreaBase + { + private: + OrthancPluginStorageReadWhole readWhole_; + OrthancPluginStorageReadRange readRange_; + + public: + PluginStorageArea2(const _OrthancPluginRegisterStorageArea2& callbacks, + PluginsErrorDictionary& errorDictionary) : + StorageAreaBase(callbacks.create, callbacks.remove, errorDictionary), + readWhole_(callbacks.readWhole), + readRange_(callbacks.readRange) + { + if (readRange_) + { + LOG(WARNING) << "Performance warning: The storage area plugin doesn't implement reading of file ranges"; + } + } + + virtual void Read(std::string& content, + const std::string& uuid, + FileContentType type) ORTHANC_OVERRIDE + { + MemoryBuffer64Raii buffer; + + OrthancPluginErrorCode error = readWhole_ + (buffer.GetObject(), uuid.c_str(), Plugins::Convert(type)); if (error != OrthancPluginErrorCode_Success) { - errorDictionary_.LogError(error, true); + GetErrorDictionary().LogError(error, true); throw OrthancException(static_cast(error)); } + + buffer.ToString(content); } }; @@ -245,18 +398,39 @@ class StorageAreaFactory : public boost::noncopyable { private: - SharedLibrary& sharedLibrary_; - _OrthancPluginRegisterStorageArea callbacks_; - PluginsErrorDictionary& errorDictionary_; + enum Version + { + Version1, + Version2 + }; + + SharedLibrary& sharedLibrary_; + Version version_; + _OrthancPluginRegisterStorageArea callbacks_; + _OrthancPluginRegisterStorageArea2 callbacks2_; + PluginsErrorDictionary& errorDictionary_; public: StorageAreaFactory(SharedLibrary& sharedLibrary, const _OrthancPluginRegisterStorageArea& callbacks, PluginsErrorDictionary& errorDictionary) : sharedLibrary_(sharedLibrary), + version_(Version1), callbacks_(callbacks), errorDictionary_(errorDictionary) { + LOG(WARNING) << "Performance warning: The storage area plugin doesn't " + << "use OrthancPluginRegisterStorageArea2()"; + } + + StorageAreaFactory(SharedLibrary& sharedLibrary, + const _OrthancPluginRegisterStorageArea2& callbacks, + PluginsErrorDictionary& errorDictionary) : + sharedLibrary_(sharedLibrary), + version_(Version2), + callbacks2_(callbacks), + errorDictionary_(errorDictionary) + { } SharedLibrary& GetSharedLibrary() @@ -266,7 +440,17 @@ IStorageArea* Create() const { - return new PluginStorageArea(callbacks_, errorDictionary_); + switch (version_) + { + case Version1: + return new PluginStorageArea(callbacks_, errorDictionary_); + + case Version2: + return new PluginStorageArea2(callbacks2_, errorDictionary_); + + default: + throw OrthancException(ErrorCode_InternalError); + } } }; @@ -977,7 +1161,7 @@ const std::string& remoteIp, const std::string& remoteAet, const std::string& calledAet, - ModalityManufacturer manufacturer) + ModalityManufacturer manufacturer) ORTHANC_OVERRIDE { { static const char* LUA_CALLBACK = "IncomingWorklistRequestFilter"; @@ -1098,7 +1282,7 @@ const std::string& remoteIp, const std::string& remoteAet, const std::string& calledAet, - ModalityManufacturer manufacturer) + ModalityManufacturer manufacturer) ORTHANC_OVERRIDE { DicomMap tmp; tmp.Assign(input); @@ -1217,12 +1401,12 @@ } } - virtual unsigned int GetSubOperationCount() const + virtual unsigned int GetSubOperationCount() const ORTHANC_OVERRIDE { return count_; } - virtual Status DoNext() + virtual Status DoNext() ORTHANC_OVERRIDE { if (pos_ >= count_) { @@ -1288,7 +1472,7 @@ const std::string& originatorIp, const std::string& originatorAet, const std::string& calledAet, - uint16_t originatorId) + uint16_t originatorId) ORTHANC_OVERRIDE { std::string levelString = ReadTag(input, DICOM_TAG_QUERY_RETRIEVE_LEVEL); std::string patientId = ReadTag(input, DICOM_TAG_PATIENT_ID); @@ -4411,19 +4595,44 @@ const _OrthancPluginCreateMemoryBuffer& p = *reinterpret_cast(parameters); - p.target->size = p.size; + p.target->data = NULL; + p.target->size = 0; - if (p.size == 0) - { - p.target->data = NULL; - } - else + if (p.size != 0) { p.target->data = malloc(p.size); + if (p.target->data == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + p.target->size = p.size; } return true; } + + case _OrthancPluginService_CreateMemoryBuffer64: + { + const _OrthancPluginCreateMemoryBuffer64& p = + *reinterpret_cast(parameters); + + p.target->data = NULL; + p.target->size = 0; + + if (p.size != 0) + { + p.target->data = malloc(p.size); + if (p.target->data == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + p.target->size = p.size; + } + + return true; + } case _OrthancPluginService_RegisterIncomingHttpRequestFilter: RegisterIncomingHttpRequestFilter(parameters); @@ -4507,14 +4716,28 @@ return true; case _OrthancPluginService_RegisterStorageArea: + case _OrthancPluginService_RegisterStorageArea2: { CLOG(INFO, PLUGINS) << "Plugin has registered a custom storage area"; - const _OrthancPluginRegisterStorageArea& p = - *reinterpret_cast(parameters); if (pimpl_->storageArea_.get() == NULL) { - pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary())); + if (service == _OrthancPluginService_RegisterStorageArea) + { + const _OrthancPluginRegisterStorageArea& p = + *reinterpret_cast(parameters); + pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary())); + } + else if (service == _OrthancPluginService_RegisterStorageArea2) + { + const _OrthancPluginRegisterStorageArea2& p = + *reinterpret_cast(parameters); + pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary())); + } + else + { + throw OrthancException(ErrorCode_InternalError); + } } else { @@ -5197,43 +5420,6 @@ } - class MemoryBufferRaii : public boost::noncopyable - { - private: - OrthancPluginMemoryBuffer buffer_; - - public: - MemoryBufferRaii() - { - buffer_.size = 0; - buffer_.data = NULL; - } - - ~MemoryBufferRaii() - { - if (buffer_.size != 0) - { - free(buffer_.data); - } - } - - OrthancPluginMemoryBuffer* GetObject() - { - return &buffer_; - } - - void ToString(std::string& target) const - { - target.resize(buffer_.size); - - if (buffer_.size != 0) - { - memcpy(&target[0], buffer_.data, buffer_.size); - } - } - }; - - bool OrthancPlugins::TranscodeBuffer(std::string& target, const void* buffer, size_t size, diff -r 5209a9ff6e38 -r eddb212b2df9 OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h --- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Thu Jan 07 18:18:39 2021 +0100 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Fri Jan 08 18:53:33 2021 +0100 @@ -16,7 +16,7 @@ * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). - * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea(). + * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea2(). * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2(). * - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback(). * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). @@ -116,8 +116,8 @@ #endif #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 -#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 8 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 1 +#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 9 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) @@ -437,6 +437,7 @@ _OrthancPluginService_EncodeDicomWebXml2 = 37, /* New in Orthanc 1.7.0 */ _OrthancPluginService_CreateMemoryBuffer = 38, /* New in Orthanc 1.7.0 */ _OrthancPluginService_GenerateRestApiAuthorizationToken = 39, /* New in Orthanc 1.8.1 */ + _OrthancPluginService_CreateMemoryBuffer64 = 40, /* New in Orthanc 1.9.0 */ /* Registration of callbacks */ _OrthancPluginService_RegisterRestCallback = 1000, @@ -455,6 +456,7 @@ _OrthancPluginService_RegisterStorageCommitmentScpCallback = 1013, _OrthancPluginService_RegisterIncomingDicomInstanceFilter = 1014, _OrthancPluginService_RegisterTranscoderCallback = 1015, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_RegisterStorageArea2 = 1016, /* New in Orthanc 1.9.0 */ /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -991,7 +993,7 @@ /** - * @brief A memory buffer allocated by the core system of Orthanc. + * @brief A 32-bit memory buffer allocated by the core system of Orthanc. * * A memory buffer allocated by the core system of Orthanc. When the * content of the buffer is not useful anymore, it must be free by a @@ -1012,6 +1014,28 @@ + /** + * @brief A 64-bit memory buffer allocated by the core system of Orthanc. + * + * A memory buffer allocated by the core system of Orthanc. When the + * content of the buffer is not useful anymore, it must be free by a + * call to ::OrthancPluginFreeMemoryBuffer64(). + **/ + typedef struct + { + /** + * @brief The content of the buffer. + **/ + void* data; + + /** + * @brief The number of bytes in the buffer. + **/ + uint64_t size; + } OrthancPluginMemoryBuffer64; + + + /** * @brief Opaque structure that represents the HTTP connection to the client application. @@ -1206,6 +1230,7 @@ * @param type The content type corresponding to this file. * @return 0 if success, other value if error. * @ingroup Callbacks + * @deprecated New plugins should use OrthancPluginStorageRead2 * * @warning The "content" buffer *must* have been allocated using * the "malloc()" function of your C standard library (i.e. nor @@ -1222,6 +1247,50 @@ /** + * @brief Callback for reading a whole file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc + * reads a whole file from the storage area. + * + * @param target Memory buffer where to store the content of the file. It must be allocated by the + * plugin using OrthancPluginCreateMemoryBuffer64(). The core of Orthanc will free it. + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageReadWhole) ( + OrthancPluginMemoryBuffer64* target, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading a range of a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc + * reads a portion of a file from the storage area. Orthanc + * indicates the start position and the length of the range. + * + * @param target Memory buffer where to store the content of the range. It must be allocated by the + * plugin using OrthancPluginCreateMemoryBuffer64(). The core of Orthanc will free it. + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @param rangeStart Start position of the requested range in the file. + * @param rangeSize Length of the range of interest. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageReadRange) ( + OrthancPluginMemoryBuffer64* target, + const char* uuid, + OrthancPluginContentType type, + uint64_t rangeStart, + uint64_t rangeSize); + + + + /** * @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. @@ -1869,6 +1938,22 @@ /** + * @brief Free a memory buffer. + * + * Free a memory buffer that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer to release. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer64( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer64* buffer) + { + context->Free(buffer->data); + } + + + /** * @brief Log an error. * * Log an error message using the Orthanc logging system. @@ -3055,6 +3140,7 @@ * @param read The callback function to read a file from the custom storage area. * @param remove The callback function to remove a file from the custom storage area. * @ingroup Callbacks + * @deprecated Please use OrthancPluginRegisterStorageArea2() **/ ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( OrthancPluginContext* context, @@ -4554,6 +4640,8 @@ * @param type The type of the file content. * @return 0 if success, other value if error. * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiPut()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. **/ ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate( OrthancPluginContext* context, @@ -4596,6 +4684,8 @@ * @param type The type of the file content. * @return 0 if success, other value if error. * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiGet()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. **/ ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead( OrthancPluginContext* context, @@ -4633,6 +4723,8 @@ * @param type The type of the file content. * @return 0 if success, other value if error. * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiDelete()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. **/ ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove( OrthancPluginContext* context, @@ -8178,7 +8270,7 @@ } _OrthancPluginCreateMemoryBuffer; /** - * @brief Create a memory buffer. + * @brief Create a 32-bit memory buffer. * * This function creates a memory buffer that is managed by the * Orthanc core. The main use case of this function is for plugins @@ -8252,6 +8344,84 @@ return result; } } + + + + typedef struct + { + OrthancPluginMemoryBuffer64* target; + uint64_t size; + } _OrthancPluginCreateMemoryBuffer64; + + /** + * @brief Create a 64-bit memory buffer. + * + * This function creates a 64-bit memory buffer that is managed by + * the Orthanc core. The main use case of this function is for + * plugins that read files from the storage area. + * + * Your plugin should never call "free()" on the resulting memory + * buffer, as the C library that is used by the plugin is in general + * not the same as the one used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param size Size of the memory buffer to be created. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateMemoryBuffer64( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer64* target, + uint64_t size) + { + _OrthancPluginCreateMemoryBuffer64 params; + params.target = target; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_CreateMemoryBuffer64, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageCreate create; + OrthancPluginStorageReadWhole readWhole; + OrthancPluginStorageReadRange readRange; + OrthancPluginStorageRemove remove; + } _OrthancPluginRegisterStorageArea2; + + /** + * @brief Register a custom storage area, with support for range request. + * + * 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 create The callback function to store a file on the custom storage area. + * @param readWhole The callback function to read a whole file from the custom storage area. + * @param readRange The callback function to read some range of a file from the custom storage area. + * If this feature is not supported by the plugin, this value can be set to NULL. + * @param remove The callback function to remove a file from the custom storage area. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea2( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageReadWhole readWhole, + OrthancPluginStorageReadRange readRange, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea2 params; + params.create = create; + params.readWhole = readWhole; + params.readRange = readRange; + params.remove = remove; + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea2, ¶ms); + } + #ifdef __cplusplus } #endif