# HG changeset patch # User Alain Mazy # Date 1674231875 -3600 # Node ID b2b38f9fb9d12c26ca6453b68572bb93ea4ad4e5 # Parent 021d7fdcb65944271e7ff2e1dc28a39dbde9fa21# Parent d00db9fb48fb0f49555fc484ea0bc63e37b41cb0 merge diff -r 021d7fdcb659 -r b2b38f9fb9d1 NEWS --- a/NEWS Fri Jan 20 17:24:16 2023 +0100 +++ b/NEWS Fri Jan 20 17:24:35 2023 +0100 @@ -33,10 +33,13 @@ * Tolerance for "image/jpg" MIME type instead of "image/jpeg" in /tools/create-dicom * /system: added MaximumStorageMode and MaximumStorageSize -Common plugins code (C++) -------------------------- - -* Added a 'header' argument to all OrthancPeers::DoPost, DoPut, ... +Plugins +------- + +* Added a "header" argument to all OrthancPeers::DoPost, DoPut, ... in the "OrthancPluginCppWrapper" +* Added "OrthancPluginCreateJob2()" in the plugin SDK to avoid + possible crashes when "OrthancPluginJobGetContent()" or + "OrthancPluginJobGetSerialized()" get called Maintenance ----------- diff -r 021d7fdcb659 -r b2b38f9fb9d1 OrthancServer/Plugins/Engine/OrthancPlugins.cpp --- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Fri Jan 20 17:24:16 2023 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Fri Jan 20 17:24:35 2023 +0100 @@ -5111,6 +5111,14 @@ return true; } + case _OrthancPluginService_CreateJob2: + { + const _OrthancPluginCreateJob2& p = + *reinterpret_cast(parameters); + *(p.target) = reinterpret_cast(new PluginsJob(p)); + return true; + } + case _OrthancPluginService_FreeJob: { const _OrthancPluginFreeJob& p = diff -r 021d7fdcb659 -r b2b38f9fb9d1 OrthancServer/Plugins/Engine/PluginsJob.cpp --- a/OrthancServer/Plugins/Engine/PluginsJob.cpp Fri Jan 20 17:24:16 2023 +0100 +++ b/OrthancServer/Plugins/Engine/PluginsJob.cpp Fri Jan 20 17:24:35 2023 +0100 @@ -36,8 +36,7 @@ namespace Orthanc { - PluginsJob::PluginsJob(const _OrthancPluginCreateJob& parameters) : - parameters_(parameters) + void PluginsJob::Setup() { if (parameters_.job == NULL) { @@ -48,17 +47,47 @@ parameters_.finalize == NULL || parameters_.type == NULL || parameters_.getProgress == NULL || - parameters_.getContent == NULL || - parameters_.getSerialized == NULL || + (parameters_.getContent == NULL && deprecatedGetContent_ == NULL) || + (parameters_.getSerialized == NULL && deprecatedGetSerialized_ == NULL) || parameters_.step == NULL || parameters_.stop == NULL || parameters_.reset == NULL) { - parameters_.finalize(parameters.job); + parameters_.finalize(parameters_.job); throw OrthancException(ErrorCode_NullPointer); } - type_.assign(parameters.type); + type_.assign(parameters_.type); + } + + PluginsJob::PluginsJob(const _OrthancPluginCreateJob2& parameters) : + parameters_(parameters), + deprecatedGetContent_(NULL), + deprecatedGetSerialized_(NULL) + { + Setup(); + } + + PluginsJob::PluginsJob(const _OrthancPluginCreateJob& parameters) + { + LOG(WARNING) << "Your plugin is using the deprecated OrthancPluginCreateJob() function"; + + memset(¶meters_, 0, sizeof(parameters_)); + parameters_.target = parameters.target; + parameters_.job = parameters.job; + parameters_.finalize = parameters.finalize; + parameters_.type = parameters.type; + parameters_.getProgress = parameters.getProgress; + parameters_.getContent = NULL; + parameters_.getSerialized = NULL; + parameters_.step = parameters.step; + parameters_.stop = parameters.stop; + parameters_.reset = parameters.reset; + + deprecatedGetContent_ = parameters.getContent; + deprecatedGetSerialized_ = parameters.getSerialized; + + Setup(); } PluginsJob::~PluginsJob() @@ -122,53 +151,148 @@ return parameters_.getProgress(parameters_.job); } + + namespace + { + 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 ToJsonObject(Json::Value& target) const + { + if ((buffer_.data == NULL && buffer_.size != 0) || + (buffer_.data != NULL && buffer_.size == 0) || + !Toolbox::ReadJson(target, buffer_.data, buffer_.size) || + target.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_Plugin, + "A job plugin must provide a JSON object as its public content and as its serialization"); + } + } + }; + } + void PluginsJob::GetPublicContent(Json::Value& value) { - const char* content = parameters_.getContent(parameters_.job); + if (parameters_.getContent != NULL) + { + MemoryBufferRaii target; + + OrthancPluginErrorCode code = parameters_.getContent(target.GetObject(), parameters_.job); - if (content == NULL) - { - value = Json::objectValue; + if (code != OrthancPluginErrorCode_Success) + { + throw OrthancException(static_cast(code)); + } + else + { + target.ToJsonObject(value); + } } else { - if (!Toolbox::ReadJson(value, content) || - value.type() != Json::objectValue) + // This was the source code in Orthanc <= 1.11.2 + const char* content = deprecatedGetContent_(parameters_.job); + + if (content == NULL) { - throw OrthancException(ErrorCode_Plugin, - "A job plugin must provide a JSON object as its public content"); + value = Json::objectValue; + } + else + { + if (!Toolbox::ReadJson(value, content) || + value.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_Plugin, + "A job plugin must provide a JSON object as its public content"); + } } } } bool PluginsJob::Serialize(Json::Value& value) { - const char* serialized = parameters_.getSerialized(parameters_.job); + if (parameters_.getSerialized != NULL) + { + MemoryBufferRaii target; + + int32_t code = parameters_.getContent(target.GetObject(), parameters_.job); - if (serialized == NULL) - { - return false; + if (code < 0) + { + throw OrthancException(ErrorCode_Plugin, "Error during the serialization of a job"); + } + else if (code == 0) + { + return false; // Serialization is not implemented + } + else + { + target.ToJsonObject(value); + + static const char* KEY_TYPE = "Type"; + + if (value.isMember(KEY_TYPE)) + { + throw OrthancException(ErrorCode_Plugin, + "The \"Type\" field is for reserved use for serialized job"); + } + + value[KEY_TYPE] = type_; + return true; + } } else { - if (!Toolbox::ReadJson(value, serialized) || - value.type() != Json::objectValue) + // This was the source code in Orthanc <= 1.11.2 + const char* serialized = deprecatedGetSerialized_(parameters_.job); + + if (serialized == NULL) { - throw OrthancException(ErrorCode_Plugin, - "A job plugin must provide a JSON object as its serialized content"); + return false; } + else + { + if (!Toolbox::ReadJson(value, serialized) || + value.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_Plugin, + "A job plugin must provide a JSON object as its serialized content"); + } - static const char* KEY_TYPE = "Type"; + static const char* KEY_TYPE = "Type"; - if (value.isMember(KEY_TYPE)) - { - throw OrthancException(ErrorCode_Plugin, - "The \"Type\" field is for reserved use for serialized job"); + if (value.isMember(KEY_TYPE)) + { + throw OrthancException(ErrorCode_Plugin, + "The \"Type\" field is for reserved use for serialized job"); + } + + value[KEY_TYPE] = type_; + return true; } - - value[KEY_TYPE] = type_; - return true; } } } diff -r 021d7fdcb659 -r b2b38f9fb9d1 OrthancServer/Plugins/Engine/PluginsJob.h --- a/OrthancServer/Plugins/Engine/PluginsJob.h Fri Jan 20 17:24:16 2023 +0100 +++ b/OrthancServer/Plugins/Engine/PluginsJob.h Fri Jan 20 17:24:35 2023 +0100 @@ -33,12 +33,18 @@ class PluginsJob : public IJob { private: - _OrthancPluginCreateJob parameters_; - std::string type_; + _OrthancPluginCreateJob2 parameters_; + std::string type_; + OrthancPluginJobGetContent deprecatedGetContent_; + OrthancPluginJobGetSerialized deprecatedGetSerialized_; + + void Setup(); public: explicit PluginsJob(const _OrthancPluginCreateJob& parameters); + explicit PluginsJob(const _OrthancPluginCreateJob2& parameters); + virtual ~PluginsJob(); virtual void Start() ORTHANC_OVERRIDE diff -r 021d7fdcb659 -r b2b38f9fb9d1 OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h --- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Fri Jan 20 17:24:16 2023 +0100 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Fri Jan 20 17:24:35 2023 +0100 @@ -120,7 +120,7 @@ #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 11 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 2 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 3 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) @@ -578,10 +578,11 @@ _OrthancPluginService_GetPeerUserProperty = 8007, /* Primitives for handling jobs (new in 1.4.2) */ - _OrthancPluginService_CreateJob = 9000, + _OrthancPluginService_CreateJob = 9000, /* Deprecated since SDK 1.11.3 */ _OrthancPluginService_FreeJob = 9001, _OrthancPluginService_SubmitJob = 9002, _OrthancPluginService_RegisterJobsUnserializer = 9003, + _OrthancPluginService_CreateJob2 = 9004, /* New in SDK 1.11.3 */ _OrthancPluginService_INTERNAL = 0x7fffffff } _OrthancPluginService; @@ -1577,11 +1578,31 @@ * @param job The job of interest. * @return The statistics, as a JSON object encoded as a string. * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. **/ typedef const char* (*OrthancPluginJobGetContent) (void* job); /** + * @brief Callback to retrieve the content of one custom job. + * + * Signature of a callback function that returns human-readable + * statistics about the job. This statistics must be formatted as a + * JSON object. This information is notably displayed in the "Jobs" + * tab of "Orthanc Explorer". + * + * @param target The target memory buffer where to store the JSON string. + * This buffer must be allocated using OrthancPluginCreateMemoryBuffer() + * and will be freed by the Orthanc core. + * @param job The job of interest. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobGetContent2) (OrthancPluginMemoryBuffer* target, + void* job); + + + /** * @brief Callback to serialize one custom job. * * Signature of a callback function that returns a serialized @@ -1595,11 +1616,33 @@ * @return The serialized job, as a JSON object encoded as a string. * @see OrthancPluginRegisterJobsUnserializer() * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. **/ typedef const char* (*OrthancPluginJobGetSerialized) (void* job); /** + * @brief Callback to serialize one custom job. + * + * Signature of a callback function that returns a serialized + * version of the job, formatted as a JSON object. This + * serialization is stored in the Orthanc database, and is used to + * reload the job on the restart of Orthanc. The "unserialization" + * callback (with OrthancPluginJobsUnserializer signature) will + * receive this serialized object. + * + * @param target The target memory buffer where to store the JSON string. + * This buffer must be allocated using OrthancPluginCreateMemoryBuffer() + * and will be freed by the Orthanc core. + * @param job The job of interest. + * @return 1 if the serialization has succeeded, 0 if serialization is + * not implemented for this type of job, or -1 in case of error. + **/ + typedef int32_t (*OrthancPluginJobGetSerialized2) (OrthancPluginMemoryBuffer* target, + void* job); + + + /** * @brief Callback to execute one step of a custom job. * * Signature of a callback function that executes one step in the @@ -6692,6 +6735,7 @@ * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(), * as long as it is not submitted with OrthancPluginSubmitJob(). * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. **/ ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob( OrthancPluginContext *context, @@ -6736,6 +6780,92 @@ typedef struct { + OrthancPluginJob** target; + void *job; + OrthancPluginJobFinalize finalize; + const char *type; + OrthancPluginJobGetProgress getProgress; + OrthancPluginJobGetContent2 getContent; + OrthancPluginJobGetSerialized2 getSerialized; + OrthancPluginJobStep step; + OrthancPluginJobStop stop; + OrthancPluginJobReset reset; + } _OrthancPluginCreateJob2; + + /** + * @brief Create a custom job. + * + * This function creates a custom job to be run by the jobs engine + * of Orthanc. + * + * Orthanc starts one dedicated thread per custom job that is + * running. It is guaranteed that all the callbacks will only be + * called from this single dedicated thread, in mutual exclusion: As + * a consequence, it is *not* mandatory to protect the various + * callbacks by mutexes. + * + * The custom job can nonetheless launch its own processing threads + * on the first call to the "step()" callback, and stop them once + * the "stop()" callback is called. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job to be executed. + * @param finalize The finalization callback. + * @param type The type of the job, provided to the job unserializer. + * See OrthancPluginRegisterJobsUnserializer(). + * @param getProgress The progress callback. + * @param getContent The content callback. + * @param getSerialized The serialization callback. + * @param step The callback to execute the individual steps of the job. + * @param stop The callback that is invoked once the job leaves the "running" state. + * @param reset The callback that is invoked if a stopped job is started again. + * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(), + * as long as it is not submitted with OrthancPluginSubmitJob(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob2( + OrthancPluginContext *context, + void *job, + OrthancPluginJobFinalize finalize, + const char *type, + OrthancPluginJobGetProgress getProgress, + OrthancPluginJobGetContent2 getContent, + OrthancPluginJobGetSerialized2 getSerialized, + OrthancPluginJobStep step, + OrthancPluginJobStop stop, + OrthancPluginJobReset reset) + { + OrthancPluginJob* target = NULL; + + _OrthancPluginCreateJob2 params; + memset(¶ms, 0, sizeof(params)); + + params.target = ⌖ + params.job = job; + params.finalize = finalize; + params.type = type; + params.getProgress = getProgress; + params.getContent = getContent; + params.getSerialized = getSerialized; + params.step = step; + params.stop = stop; + params.reset = reset; + + if (context->InvokeService(context, _OrthancPluginService_CreateJob2, ¶ms) != OrthancPluginErrorCode_Success || + target == NULL) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { OrthancPluginJob* job; } _OrthancPluginFreeJob; diff -r 021d7fdcb659 -r b2b38f9fb9d1 OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp --- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Fri Jan 20 17:24:16 2023 +0100 +++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Fri Jan 20 17:24:35 2023 +0100 @@ -2172,6 +2172,36 @@ } +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + static OrthancPluginErrorCode CopyStringToMemoryBuffer(OrthancPluginMemoryBuffer* target, + const std::string& source) + { + if (OrthancPluginCreateMemoryBuffer(globalContext_, target, source.size()) != OrthancPluginErrorCode_Success) + { + return OrthancPluginErrorCode_NotEnoughMemory; + } + else + { + if (!source.empty()) + { + memcpy(target->data, source.c_str(), source.size()); + } + + return OrthancPluginErrorCode_Success; + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + OrthancPluginErrorCode OrthancJob::CallbackGetContent(OrthancPluginMemoryBuffer* target, + void* job) + { + assert(job != NULL); + OrthancJob& that = *reinterpret_cast(job); + return CopyStringToMemoryBuffer(target, that.content_); + } +#else const char* OrthancJob::CallbackGetContent(void* job) { assert(job != NULL); @@ -2185,8 +2215,33 @@ return 0; } } - - +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + int32_t OrthancJob::CallbackGetSerialized(OrthancPluginMemoryBuffer* target, + void* job) + { + assert(job != NULL); + OrthancJob& that = *reinterpret_cast(job); + + if (that.hasSerialized_) + { + if (CopyStringToMemoryBuffer(target, that.serialized_) == OrthancPluginErrorCode_Success) + { + return 1; + } + else + { + return -1; + } + } + else + { + return 0; + } + } +#else const char* OrthancJob::CallbackGetSerialized(void* job) { assert(job != NULL); @@ -2209,6 +2264,7 @@ return 0; } } +#endif OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job) @@ -2340,10 +2396,15 @@ ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); } - OrthancPluginJob* orthanc = OrthancPluginCreateJob( - GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(), - CallbackGetProgress, CallbackGetContent, CallbackGetSerialized, - CallbackStep, CallbackStop, CallbackReset); + OrthancPluginJob* orthanc = +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + OrthancPluginCreateJob2 +#else + OrthancPluginCreateJob +#endif + (GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(), + CallbackGetProgress, CallbackGetContent, CallbackGetSerialized, + CallbackStep, CallbackStop, CallbackReset); if (orthanc == NULL) { diff -r 021d7fdcb659 -r b2b38f9fb9d1 OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h --- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h Fri Jan 20 17:24:16 2023 +0100 +++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h Fri Jan 20 17:24:35 2023 +0100 @@ -833,9 +833,19 @@ static float CallbackGetProgress(void* job); +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + static OrthancPluginErrorCode CallbackGetContent(OrthancPluginMemoryBuffer* target, + void* job); +#else static const char* CallbackGetContent(void* job); +#endif +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + static int32_t CallbackGetSerialized(OrthancPluginMemoryBuffer* target, + void* job); +#else static const char* CallbackGetSerialized(void* job); +#endif static OrthancPluginJobStepStatus CallbackStep(void* job);