Mercurial > hg > orthanc
changeset 6211:55b41faaaec3
SDK: OrthancPluginSetStableStatus
| author | Alain Mazy <am@orthanc.team> |
|---|---|
| date | Wed, 25 Jun 2025 14:14:47 +0200 |
| parents | 6300c3adcb07 |
| children | 3a974dbf4740 |
| files | NEWS OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Engine/OrthancPlugins.h OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Plugins/Samples/Basic/Plugin.c OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.h OrthancServer/Sources/ServerIndex.cpp OrthancServer/Sources/ServerIndex.h |
| diffstat | 9 files changed, 247 insertions(+), 49 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Mon Jun 16 21:31:01 2025 +0200 +++ b/NEWS Wed Jun 25 14:14:47 2025 +0200 @@ -1,6 +1,13 @@ Pending changes in the mainline =============================== +Plugin SDK +---------- + +* Added new function OrthancPluginSetStableStatus to e.g force the + stabilization of a study from a plugin. + + Plugins -------
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Mon Jun 16 21:31:01 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed Jun 25 14:14:47 2025 +0200 @@ -4703,6 +4703,24 @@ *parameters.size = lock.GetContext().GetIndex().GetQueueSize(parameters.queueId); } + void OrthancPlugins::ApplySetStableStatus(const _OrthancPluginSetStableStatus& parameters) + { + PImpl::ServerContextReference lock(*pimpl_); + bool statusHasChanged = false; + + lock.GetContext().GetIndex().SetStableStatus(statusHasChanged, + parameters.resourceId, + (parameters.stableStatus == OrthancPluginStableStatus_Stable)); + if (statusHasChanged) + { + *(parameters.statusHasChanged) = 1; + } + else + { + *(parameters.statusHasChanged) = 0; + } + } + void OrthancPlugins::ApplyLoadDicomInstance(const _OrthancPluginLoadDicomInstance& params) { std::unique_ptr<IDicomInstance> target; @@ -5826,6 +5844,13 @@ return true; } + case _OrthancPluginService_SetStableStatus: + { + const _OrthancPluginSetStableStatus& p = *reinterpret_cast<const _OrthancPluginSetStableStatus*>(parameters); + ApplySetStableStatus(p); + return true; + } + default: return false; }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h Mon Jun 16 21:31:01 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h Wed Jun 25 14:14:47 2025 +0200 @@ -244,6 +244,8 @@ void ApplyGetQueueSize(const _OrthancPluginGetQueueSize& parameters); + void ApplySetStableStatus(const _OrthancPluginSetStableStatus& parameters); + void ComputeHash(_OrthancPluginService service, const void* parameters);
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Mon Jun 16 21:31:01 2025 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Wed Jun 25 14:14:47 2025 +0200 @@ -121,7 +121,7 @@ #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 12 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 8 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 9 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) @@ -483,6 +483,7 @@ _OrthancPluginService_EnqueueValue = 57, /* New in Orthanc 1.12.8 */ _OrthancPluginService_DequeueValue = 58, /* New in Orthanc 1.12.8 */ _OrthancPluginService_GetQueueSize = 59, /* New in Orthanc 1.12.8 */ + _OrthancPluginService_SetStableStatus = 60, /* New in Orthanc 1.12.9 */ /* Registration of callbacks */ _OrthancPluginService_RegisterRestCallback = 1000, @@ -1169,6 +1170,16 @@ _OrthancPluginQueueOrigin_INTERNAL = 0x7fffffff } OrthancPluginQueueOrigin; + /** + * The "stable" status of a resource. + **/ + typedef enum + { + OrthancPluginStableStatus_Stable = 0, /*!< The resource is stable */ + OrthancPluginStableStatus_Unstable = 1, /*!< The resource is unstable */ + + _OrthancPluginStableStatus_INTERNAL = 0x7fffffff + } OrthancPluginStableStatus; /** @@ -9969,6 +9980,7 @@ return context->InvokeService(context, _OrthancPluginService_StoreKeyValue, ¶ms); } + typedef struct { const char* storeId; @@ -9995,6 +10007,7 @@ return context->InvokeService(context, _OrthancPluginService_DeleteKeyValue, ¶ms); } + typedef struct { uint8_t* found; @@ -10039,7 +10052,6 @@ typedef struct _OrthancPluginKeysValuesIterator_t OrthancPluginKeysValuesIterator; - typedef struct { OrthancPluginKeysValuesIterator** target; @@ -10197,7 +10209,6 @@ } - typedef struct { const char* queueId; @@ -10228,6 +10239,7 @@ return context->InvokeService(context, _OrthancPluginService_EnqueueValue, ¶ms); } + typedef struct { uint8_t* found; @@ -10263,6 +10275,7 @@ return context->InvokeService(context, _OrthancPluginService_DequeueValue, ¶ms); } + typedef struct { const char* queueId; @@ -10289,6 +10302,43 @@ return context->InvokeService(context, _OrthancPluginService_GetQueueSize, ¶ms); } + + typedef struct + { + const char* resourceId; + OrthancPluginStableStatus stableStatus; + int32_t* statusHasChanged; + } _OrthancPluginSetStableStatus; + + /** + * @brief Change the "Stable" status of a resource. + * Forcing a resource to "Stable" if it is currently "Unstable" will change + * its Stable status AND trigger a new Stable change which will also trigger + * listener callbacks. + * Forcing a resource to "Stable" if it is already "Stable" is a no-op. + * Forcing a resource to "Unstable" will change its Stable status to "Unstable" + * AND reset its stabilization period, no matter of its initial state. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param statusHasChanged Wheter the status has changed (1) or not (0) during the execution of this command. + * @param resourceId The Orthanc identifier of the DICOM resource of interest. + * @param stableStatus The new stable status of the resource of interest. + * @return 0 if success, other value if error. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetStableStatus( + OrthancPluginContext* context, + int32_t* statusHasChanged, /* out */ + const char* resourceId, /* in */ + OrthancPluginStableStatus stableStatus /* in */) + { + _OrthancPluginSetStableStatus params; + params.resourceId = resourceId; + params.stableStatus= stableStatus; + params.statusHasChanged = statusHasChanged; + + return context->InvokeService(context, _OrthancPluginService_SetStableStatus, ¶ms); + } + #ifdef __cplusplus } #endif
--- a/OrthancServer/Plugins/Samples/Basic/Plugin.c Mon Jun 16 21:31:01 2025 +0200 +++ b/OrthancServer/Plugins/Samples/Basic/Plugin.c Wed Jun 25 14:14:47 2025 +0200 @@ -226,6 +226,44 @@ } + +OrthancPluginErrorCode CallbackStabilizeStudy(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + + if (request->method != OrthancPluginHttpMethod_Post) + { + OrthancPluginSendMethodNotAllowed(context, output, "POST"); + } + else + { + const char* studyId = request->groups[0]; + int32_t statusHasChanged = 0; + + if (strcmp(request->groups[1], "stabilize") == 0) + { + OrthancPluginSetStableStatus(context, &statusHasChanged, studyId, OrthancPluginStableStatus_Stable); + } + else + { + OrthancPluginSetStableStatus(context, &statusHasChanged, studyId, OrthancPluginStableStatus_Unstable); + } + + if (statusHasChanged) + { + OrthancPluginAnswerBuffer(context, output, "CHANGED\n", 8, "text/plain"); + } + else + { + OrthancPluginAnswerBuffer(context, output, "UNCHANGED\n", 10, "text/plain"); + } + } + + return OrthancPluginErrorCode_Success; +} + + OrthancPluginErrorCode CallbackCreateDicom(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request) @@ -572,6 +610,7 @@ OrthancPluginRegisterRestCallback(context, "/forward/(plugins)(/.+)", Callback5); OrthancPluginRegisterRestCallback(context, "/plugin/create", CallbackCreateDicom); OrthancPluginRegisterRestCallback(context, "/instances/([^/]+)/dicom-web", CallbackDicomWeb); + OrthancPluginRegisterRestCallback(context, "/studies/([^/]+)/(stabilize|unstabilize)", CallbackStabilizeStudy); OrthancPluginRegisterOnStoredInstanceCallback(context, OnStoredCallback); OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Mon Jun 16 21:31:01 2025 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Wed Jun 25 14:14:47 2025 +0200 @@ -1394,28 +1394,36 @@ } - bool StatelessDatabaseOperations::LookupResourceType(ResourceType& type, - const std::string& publicId) + bool StatelessDatabaseOperations::LookupResource(int64_t& internalId, + ResourceType& type, + const std::string& publicId) { - class Operations : public ReadOnlyOperationsT3<bool&, ResourceType&, const std::string&> + class Operations : public ReadOnlyOperationsT4<bool&, int64_t&, ResourceType&, const std::string&> { public: virtual void ApplyTuple(ReadOnlyTransaction& transaction, const Tuple& tuple) ORTHANC_OVERRIDE { // TODO - CANDIDATE FOR "TransactionType_Implicit" - int64_t id; - tuple.get<0>() = transaction.LookupResource(id, tuple.get<1>(), tuple.get<2>()); + tuple.get<0>() = transaction.LookupResource(tuple.get<1>(), tuple.get<2>(), tuple.get<3>()); } }; bool found; Operations operations; - operations.Apply(*this, found, type, publicId); + operations.Apply(*this, found, internalId, type, publicId); return found; } + bool StatelessDatabaseOperations::LookupResourceType(ResourceType& type, + const std::string& publicId) + { + int64_t internalId; + return LookupResource(internalId, type, publicId); + } + + bool StatelessDatabaseOperations::LookupParent(std::string& target, const std::string& publicId, ResourceType parentType)
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Mon Jun 16 21:31:01 2025 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Wed Jun 25 14:14:47 2025 +0200 @@ -691,6 +691,10 @@ bool GetAllMainDicomTags(DicomMap& result, const std::string& instancePublicId); + bool LookupResource(int64_t& id, + ResourceType& type, + const std::string& publicId); + bool LookupResourceType(ResourceType& type, const std::string& publicId);
--- a/OrthancServer/Sources/ServerIndex.cpp Mon Jun 16 21:31:01 2025 +0200 +++ b/OrthancServer/Sources/ServerIndex.cpp Wed Jun 25 14:14:47 2025 +0200 @@ -312,7 +312,7 @@ bool ServerIndex::IsUnstableResource(ResourceType type, int64_t id) { - boost::mutex::scoped_lock lock(monitoringMutex_); + boost::recursive_mutex::scoped_lock lock(monitoringMutex_); return unstableResources_.Contains(std::make_pair(type, id)); } @@ -394,7 +394,7 @@ void ServerIndex::SetMaximumPatientCount(unsigned int count) { { - boost::mutex::scoped_lock lock(monitoringMutex_); + boost::recursive_mutex::scoped_lock lock(monitoringMutex_); maximumPatients_ = count; if (count == 0) @@ -414,7 +414,7 @@ void ServerIndex::SetMaximumStorageSize(uint64_t size) { { - boost::mutex::scoped_lock lock(monitoringMutex_); + boost::recursive_mutex::scoped_lock lock(monitoringMutex_); maximumStorageSize_ = size; if (size == 0) @@ -433,7 +433,7 @@ void ServerIndex::SetMaximumStorageMode(MaxStorageMode mode) { { - boost::mutex::scoped_lock lock(monitoringMutex_); + boost::recursive_mutex::scoped_lock lock(monitoringMutex_); maximumStorageMode_ = mode; if (mode == MaxStorageMode_Recycle) @@ -486,7 +486,7 @@ int64_t stableId; { - boost::mutex::scoped_lock lock(that->monitoringMutex_); + boost::recursive_mutex::scoped_lock lock(that->monitoringMutex_); if (!that->unstableResources_.IsEmpty() && that->unstableResources_.GetOldestPayload().GetAge() > static_cast<unsigned int>(stableAge)) @@ -505,43 +505,52 @@ } } - try - { - /** - * WARNING: Don't protect the calls to "LogChange()" using - * "monitoringMutex_", as this could lead to deadlocks in - * other threads (typically, if "Store()" is being running in - * another thread, which leads to calls to "MarkAsUnstable()", - * which leads to two lockings of "monitoringMutex_"). - **/ - switch (stableLevel) - { - case ResourceType_Patient: - that->LogChange(stableId, ChangeType_StablePatient, stablePayload.GetPublicId(), ResourceType_Patient); - break; - - case ResourceType_Study: - that->LogChange(stableId, ChangeType_StableStudy, stablePayload.GetPublicId(), ResourceType_Study); - break; - - case ResourceType_Series: - that->LogChange(stableId, ChangeType_StableSeries, stablePayload.GetPublicId(), ResourceType_Series); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - catch (OrthancException& e) - { - LOG(ERROR) << "Cannot log a change about a stable resource into the database"; - } + // must not be protected by monitoringMutex_ + that->LogStableChange(stableLevel, stableId, stablePayload.GetPublicId()); } } LOG(INFO) << "Closing the monitor thread for stable resources"; } + void ServerIndex::LogStableChange(ResourceType stableLevel, + int64_t stableId, + const std::string& publicId) + { + try + { + /** + * WARNING: Don't protect the calls to "LogChange()" using + * "monitoringMutex_", as this could lead to deadlocks in + * other threads (typically, if "Store()" is being running in + * another thread, which leads to calls to "MarkAsUnstable()", + * which leads to two lockings of "monitoringMutex_"). + **/ + switch (stableLevel) + { + case ResourceType_Patient: + LogChange(stableId, ChangeType_StablePatient, publicId, ResourceType_Patient); + break; + + case ResourceType_Study: + LogChange(stableId, ChangeType_StableStudy, publicId, ResourceType_Study); + break; + + case ResourceType_Series: + LogChange(stableId, ChangeType_StableSeries, publicId, ResourceType_Series); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Cannot log a change about a stable resource into the database"; + } + } + + void ServerIndex::MarkAsUnstable(ResourceType type, int64_t id, @@ -552,13 +561,59 @@ type == ResourceType_Series); { - boost::mutex::scoped_lock lock(monitoringMutex_); + boost::recursive_mutex::scoped_lock lock(monitoringMutex_); UnstableResourcePayload payload(publicId); unstableResources_.AddOrMakeMostRecent(std::make_pair(type, id), payload); //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id; } } + bool ServerIndex::SetStableStatus(bool& statusHasChanged, + const std::string& resourceId, + bool setNewStatusToStable) + { + int64_t id; + ResourceType type; + + if (LookupResource(id, type, resourceId)) + { + if (setNewStatusToStable) + { + { + boost::recursive_mutex::scoped_lock lock(monitoringMutex_); + + if (IsUnstableResource(type, id)) + { + unstableResources_.Invalidate(std::pair<ResourceType, int64_t>(type, id)); + statusHasChanged = true; + } + } + + if (statusHasChanged) + { + // must not be protected by monitoringMutex_ + LogStableChange(type, id, resourceId); + } + } + else + { + { + boost::recursive_mutex::scoped_lock lock(monitoringMutex_); + + statusHasChanged = !IsUnstableResource(type, id); + + // no matter what was the status, we mark it as unstable to reset its stabilization period + MarkAsUnstable(type, id, resourceId); + } + + } + + return true; + } + + return false; + } + StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata, const DicomMap& dicomSummary, @@ -578,7 +633,7 @@ MaxStorageMode maximumStorageMode; { - boost::mutex::scoped_lock lock(monitoringMutex_); + boost::recursive_mutex::scoped_lock lock(monitoringMutex_); maximumStorageSize = maximumStorageSize_; maximumPatients = maximumPatients_; maximumStorageMode = maximumStorageMode_; @@ -602,7 +657,7 @@ unsigned int maximumPatients; { - boost::mutex::scoped_lock lock(monitoringMutex_); + boost::recursive_mutex::scoped_lock lock(monitoringMutex_); maximumStorageSize = maximumStorageSize_; maximumPatients = maximumPatients_; }
--- a/OrthancServer/Sources/ServerIndex.h Mon Jun 16 21:31:01 2025 +0200 +++ b/OrthancServer/Sources/ServerIndex.h Wed Jun 25 14:14:47 2025 +0200 @@ -40,7 +40,7 @@ class UnstableResourcePayload; bool done_; - boost::mutex monitoringMutex_; + boost::recursive_mutex monitoringMutex_; boost::thread flushThread_; boost::thread unstableResourcesMonitorThread_; @@ -61,6 +61,10 @@ int64_t id, const std::string& publicId); + void LogStableChange(ResourceType type, + int64_t id, + const std::string& publicId); + public: ServerIndex(ServerContext& context, IDatabaseWrapper& database, @@ -101,5 +105,9 @@ bool IsUnstableResource(ResourceType type, int64_t id); + + bool SetStableStatus(bool& statusHasChanged, + const std::string& resourceId, + bool setNewStatusToStable); }; }
