# HG changeset patch # User Sebastien Jodogne # Date 1411573064 -7200 # Node ID 5b2d8c280ac20bc3429db8326dea6530021437aa # Parent f24e048380548090e6b8f823c736f71ba0801c13 Plugins can monitor changes through callbacks diff -r f24e04838054 -r 5b2d8c280ac2 NEWS --- a/NEWS Mon Sep 22 14:11:37 2014 +0200 +++ b/NEWS Wed Sep 24 17:37:44 2014 +0200 @@ -1,6 +1,9 @@ Pending changes in the mainline =============================== +* Speed-up thanks to a new database schema +* Plugins can monitor changes through callbacks + Version 0.8.4 (2014/09/19) ========================== diff -r f24e04838054 -r 5b2d8c280ac2 OrthancServer/DatabaseWrapper.cpp --- a/OrthancServer/DatabaseWrapper.cpp Mon Sep 22 14:11:37 2014 +0200 +++ b/OrthancServer/DatabaseWrapper.cpp Wed Sep 24 17:37:44 2014 +0200 @@ -116,7 +116,7 @@ virtual void Compute(SQLite::FunctionContext& context) { ResourceType type = static_cast(context.GetIntValue(1)); - listener_.SignalResourceDeleted(type, context.GetStringValue(0)); + listener_.SignalChange(ChangeType_Deleted, type, context.GetStringValue(0)); } }; @@ -258,7 +258,7 @@ throw OrthancException(ErrorCode_InternalError); } - LogChange(changeType, id, type); + LogChange(changeType, id, type, publicId); return id; } @@ -650,14 +650,23 @@ void DatabaseWrapper::LogChange(ChangeType changeType, int64_t internalId, ResourceType resourceType, - const boost::posix_time::ptime& date) + const std::string& publicId) { + if (changeType == ChangeType_Deleted) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + const boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); s.BindInt(0, changeType); s.BindInt64(1, internalId); s.BindInt(2, resourceType); - s.BindString(3, boost::posix_time::to_iso_string(date)); - s.Run(); + s.BindString(3, boost::posix_time::to_iso_string(now)); + s.Run(); + + listener_.SignalChange(changeType, resourceType, publicId); } diff -r f24e04838054 -r 5b2d8c280ac2 OrthancServer/DatabaseWrapper.h --- a/OrthancServer/DatabaseWrapper.h Mon Sep 22 14:11:37 2014 +0200 +++ b/OrthancServer/DatabaseWrapper.h Wed Sep 24 17:37:44 2014 +0200 @@ -157,7 +157,7 @@ void LogChange(ChangeType changeType, int64_t internalId, ResourceType resourceType, - const boost::posix_time::ptime& date = boost::posix_time::second_clock::local_time()); + const std::string& publicId); void GetChanges(Json::Value& target, int64_t since, diff -r f24e04838054 -r 5b2d8c280ac2 OrthancServer/IServerIndexListener.h --- a/OrthancServer/IServerIndexListener.h Mon Sep 22 14:11:37 2014 +0200 +++ b/OrthancServer/IServerIndexListener.h Wed Sep 24 17:37:44 2014 +0200 @@ -47,9 +47,10 @@ virtual void SignalRemainingAncestor(ResourceType parentType, const std::string& publicId) = 0; - virtual void SignalResourceDeleted(ResourceType type, - const std::string& publicId) = 0; + virtual void SignalFileDeleted(const FileInfo& info) = 0; - virtual void SignalFileDeleted(const FileInfo& info) = 0; + virtual void SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId) = 0; }; } diff -r f24e04838054 -r 5b2d8c280ac2 OrthancServer/ServerEnumerations.cpp --- a/OrthancServer/ServerEnumerations.cpp Mon Sep 22 14:11:37 2014 +0200 +++ b/OrthancServer/ServerEnumerations.cpp Wed Sep 24 17:37:44 2014 +0200 @@ -231,6 +231,9 @@ case ChangeType_StableSeries: return "StableSeries"; + case ChangeType_Deleted: + return "Deleted"; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } diff -r f24e04838054 -r 5b2d8c280ac2 OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Mon Sep 22 14:11:37 2014 +0200 +++ b/OrthancServer/ServerEnumerations.h Wed Sep 24 17:37:44 2014 +0200 @@ -134,7 +134,8 @@ ChangeType_ModifiedPatient = 11, ChangeType_StablePatient = 12, ChangeType_StableStudy = 13, - ChangeType_StableSeries = 14 + ChangeType_StableSeries = 14, + ChangeType_Deleted = 15 // Not logged in the index }; diff -r f24e04838054 -r 5b2d8c280ac2 OrthancServer/ServerIndex.cpp --- a/OrthancServer/ServerIndex.cpp Mon Sep 22 14:11:37 2014 +0200 +++ b/OrthancServer/ServerIndex.cpp Wed Sep 24 17:37:44 2014 +0200 @@ -61,13 +61,58 @@ private: struct FileToRemove { + private: std::string uuid_; FileContentType type_; + public: FileToRemove(const FileInfo& info) : uuid_(info.GetUuid()), type_(info.GetContentType()) { } + + const std::string& GetUuid() const + { + return uuid_; + } + + FileContentType GetContentType() const + { + return type_; + } + }; + + struct Change + { + private: + ChangeType changeType_; + ResourceType resourceType_; + std::string publicId_; + + public: + Change(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId) : + changeType_(changeType), + resourceType_(resourceType), + publicId_(publicId) + { + } + + ChangeType GetChangeType() const + { + return changeType_; + } + + ResourceType GetResourceType() const + { + return resourceType_; + } + + const std::string& GetPublicId() const + { + return publicId_; + } }; ServerContext& context_; @@ -75,6 +120,7 @@ ResourceType remainingType_; std::string remainingPublicId_; std::list pendingFilesToRemove_; + std::list pendingChanges_; uint64_t sizeOfFilesToRemove_; public: @@ -105,7 +151,7 @@ it = pendingFilesToRemove_.begin(); it != pendingFilesToRemove_.end(); ++it) { - context_.RemoveFile(it->uuid_, it->type_); + context_.RemoveFile(it->GetUuid(), it->GetContentType()); } } @@ -137,13 +183,16 @@ sizeOfFilesToRemove_ += info.GetCompressedSize(); } - virtual void SignalResourceDeleted(ResourceType type, - const std::string& publicId) + virtual void SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId) { - LOG(INFO) << "Resource " << publicId << " of type " << EnumerationToString(type) << " is deleted"; + LOG(INFO) << "Change related to resource " << publicId << " of type " + << EnumerationToString(resourceType) << ": " << EnumerationToString(changeType); + + pendingChanges_.push_back(Change(changeType, resourceType, publicId)); } - bool HasRemainingLevel() const { return hasRemainingLevel_; @@ -207,16 +256,22 @@ }; - struct ServerIndex::UnstableResourcePayload + class ServerIndex::UnstableResourcePayload { - Orthanc::ResourceType type_; + private: + ResourceType type_; + std::string publicId_; boost::posix_time::ptime time_; - UnstableResourcePayload() : type_(Orthanc::ResourceType_Instance) + public: + UnstableResourcePayload() : type_(ResourceType_Instance) { } - UnstableResourcePayload(Orthanc::ResourceType type) : type_(type) + UnstableResourcePayload(Orthanc::ResourceType type, + const std::string& publicId) : + type_(type), + publicId_(publicId) { time_ = boost::posix_time::second_clock::local_time(); } @@ -225,6 +280,16 @@ { return (boost::posix_time::second_clock::local_time() - time_).total_seconds(); } + + ResourceType GetResourceType() const + { + return type_; + } + + const std::string& GetPublicId() const + { + return publicId_; + } }; @@ -604,13 +669,13 @@ SeriesStatus seriesStatus = GetSeriesStatus(series); if (seriesStatus == SeriesStatus_Complete) { - db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series); + db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series, hasher.HashSeries()); } // Mark the parent resources of this instance as unstable - MarkAsUnstable(series, ResourceType_Series); - MarkAsUnstable(study, ResourceType_Study); - MarkAsUnstable(patient, ResourceType_Patient); + MarkAsUnstable(series, ResourceType_Series, hasher.HashSeries()); + MarkAsUnstable(study, ResourceType_Study, hasher.HashStudy()); + MarkAsUnstable(patient, ResourceType_Patient, hasher.HashPatient()); t.Commit(instanceSize); @@ -1393,7 +1458,7 @@ throw OrthancException(ErrorCode_UnknownResource); } - db_->LogChange(changeType, id, type); + db_->LogChange(changeType, id, type, publicId); transaction->Commit(); } @@ -1590,18 +1655,18 @@ // Ensure that the resource is still existing before logging the change if (that->db_->IsExistingResource(id)) { - switch (payload.type_) + switch (payload.GetResourceType()) { - case Orthanc::ResourceType_Patient: - that->db_->LogChange(ChangeType_StablePatient, id, ResourceType_Patient); + case ResourceType_Patient: + that->db_->LogChange(ChangeType_StablePatient, id, ResourceType_Patient, payload.GetPublicId()); break; - case Orthanc::ResourceType_Study: - that->db_->LogChange(ChangeType_StableStudy, id, ResourceType_Study); + case ResourceType_Study: + that->db_->LogChange(ChangeType_StableStudy, id, ResourceType_Study, payload.GetPublicId()); break; - case Orthanc::ResourceType_Series: - that->db_->LogChange(ChangeType_StableSeries, id, ResourceType_Series); + case ResourceType_Series: + that->db_->LogChange(ChangeType_StableSeries, id, ResourceType_Series, payload.GetPublicId()); break; default: @@ -1618,7 +1683,8 @@ void ServerIndex::MarkAsUnstable(int64_t id, - Orthanc::ResourceType type) + Orthanc::ResourceType type, + const std::string& publicId) { // WARNING: Before calling this method, "mutex_" must be locked. @@ -1626,7 +1692,8 @@ type == Orthanc::ResourceType_Study || type == Orthanc::ResourceType_Series); - unstableResources_.AddOrMakeMostRecent(id, type); + UnstableResourcePayload payload(type, publicId); + unstableResources_.AddOrMakeMostRecent(id, payload); //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id; } @@ -1787,6 +1854,4 @@ return true; } - - } diff -r f24e04838054 -r 5b2d8c280ac2 OrthancServer/ServerIndex.h --- a/OrthancServer/ServerIndex.h Mon Sep 22 14:11:37 2014 +0200 +++ b/OrthancServer/ServerIndex.h Wed Sep 24 17:37:44 2014 +0200 @@ -60,7 +60,7 @@ private: class Transaction; - struct UnstableResourcePayload; + class UnstableResourcePayload; bool done_; boost::mutex mutex_; @@ -92,7 +92,8 @@ void StandaloneRecycling(); void MarkAsUnstable(int64_t id, - Orthanc::ResourceType type); + Orthanc::ResourceType type, + const std::string& publicId); void GetStatisticsInternal(/* out */ uint64_t& compressedSize, /* out */ uint64_t& uncompressedSize, diff -r f24e04838054 -r 5b2d8c280ac2 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Mon Sep 22 14:11:37 2014 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Wed Sep 24 17:37:44 2014 +0200 @@ -84,11 +84,13 @@ typedef std::pair RestCallback; typedef std::list RestCallbacks; typedef std::list OnStoredCallbacks; + typedef std::list OnChangeCallbacks; ServerContext& context_; RestCallbacks restCallbacks_; OrthancRestApi* restApi_; OnStoredCallbacks onStoredCallbacks_; + OnChangeCallbacks onChangeCallbacks_; bool hasStorageArea_; _OrthancPluginRegisterStorageArea storageArea_; @@ -123,6 +125,83 @@ } + static OrthancPluginResourceType Convert(ResourceType type) + { + switch (type) + { + case ResourceType_Patient: + return OrthancPluginResourceType_Patient; + + case ResourceType_Study: + return OrthancPluginResourceType_Study; + + case ResourceType_Series: + return OrthancPluginResourceType_Series; + + case ResourceType_Instance: + return OrthancPluginResourceType_Instance; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + static OrthancPluginChangeType Convert(ChangeType type) + { + switch (type) + { + case ChangeType_AnonymizedPatient: + return OrthancPluginChangeType_AnonymizedPatient; + + case ChangeType_AnonymizedSeries: + return OrthancPluginChangeType_AnonymizedSeries; + + case ChangeType_AnonymizedStudy: + return OrthancPluginChangeType_AnonymizedStudy; + + case ChangeType_CompletedSeries: + return OrthancPluginChangeType_CompletedSeries; + + case ChangeType_Deleted: + return OrthancPluginChangeType_Deleted; + + case ChangeType_ModifiedPatient: + return OrthancPluginChangeType_ModifiedPatient; + + case ChangeType_ModifiedSeries: + return OrthancPluginChangeType_ModifiedSeries; + + case ChangeType_ModifiedStudy: + return OrthancPluginChangeType_ModifiedStudy; + + case ChangeType_NewInstance: + return OrthancPluginChangeType_NewInstance; + + case ChangeType_NewPatient: + return OrthancPluginChangeType_NewPatient; + + case ChangeType_NewSeries: + return OrthancPluginChangeType_NewSeries; + + case ChangeType_NewStudy: + return OrthancPluginChangeType_NewStudy; + + case ChangeType_StablePatient: + return OrthancPluginChangeType_StablePatient; + + case ChangeType_StableSeries: + return OrthancPluginChangeType_StableSeries; + + case ChangeType_StableStudy: + return OrthancPluginChangeType_StableStudy; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + OrthancPlugins::OrthancPlugins(ServerContext& context) { pimpl_.reset(new PImpl(context)); @@ -279,7 +358,7 @@ void OrthancPlugins::SignalStoredInstance(DicomInstanceToStore& instance, - const std::string& instanceId) + const std::string& instanceId) { for (PImpl::OnStoredCallbacks::const_iterator callback = pimpl_->onStoredCallbacks_.begin(); @@ -292,6 +371,27 @@ + void OrthancPlugins::SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId) + { + for (PImpl::OnChangeCallbacks::const_iterator + callback = pimpl_->onChangeCallbacks_.begin(); + callback != pimpl_->onChangeCallbacks_.end(); ++callback) + { + try + { + (*callback) (Convert(changeType), Convert(resourceType), publicId.c_str()); + } + catch (OrthancException&) + { + // This change type or resource type is not supported by the plugin SDK + } + } + } + + + static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target, const void* data, size_t size) @@ -353,6 +453,16 @@ } + void OrthancPlugins::RegisterOnChangeCallback(const void* parameters) + { + const _OrthancPluginOnChangeCallback& p = + *reinterpret_cast(parameters); + + LOG(INFO) << "Plugin has registered an OnChange callback"; + pimpl_->onChangeCallbacks_.push_back(p.callback); + } + + void OrthancPlugins::AnswerBuffer(const void* parameters) { @@ -777,6 +887,10 @@ RegisterOnStoredInstanceCallback(parameters); return true; + case _OrthancPluginService_RegisterOnChangeCallback: + RegisterOnChangeCallback(parameters); + return true; + case _OrthancPluginService_AnswerBuffer: AnswerBuffer(parameters); return true; diff -r f24e04838054 -r 5b2d8c280ac2 Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Mon Sep 22 14:11:37 2014 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Wed Sep 24 17:37:44 2014 +0200 @@ -53,6 +53,8 @@ void RegisterOnStoredInstanceCallback(const void* parameters); + void RegisterOnChangeCallback(const void* parameters); + void AnswerBuffer(const void* parameters); void Redirect(const void* parameters); @@ -95,6 +97,10 @@ virtual bool InvokeService(_OrthancPluginService service, const void* parameters); + void SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId); + void SignalStoredInstance(DicomInstanceToStore& instance, const std::string& instanceId); diff -r f24e04838054 -r 5b2d8c280ac2 Plugins/OrthancCPlugin/OrthancCPlugin.h --- a/Plugins/OrthancCPlugin/OrthancCPlugin.h Mon Sep 22 14:11:37 2014 +0200 +++ b/Plugins/OrthancCPlugin/OrthancCPlugin.h Wed Sep 24 17:37:44 2014 +0200 @@ -88,7 +88,7 @@ #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 0 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 8 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 3 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 5 @@ -245,6 +245,7 @@ _OrthancPluginService_RegisterRestCallback = 1000, _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, _OrthancPluginService_RegisterStorageArea = 1002, + _OrthancPluginService_RegisterOnChangeCallback = 1003, /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -341,6 +342,43 @@ /** + * The supported type of DICOM resources. + **/ + typedef enum + { + OrthancPluginResourceType_Patient = 0, /*!< Patient */ + OrthancPluginResourceType_Study = 1, /*!< Study */ + OrthancPluginResourceType_Series = 2, /*!< Series */ + OrthancPluginResourceType_Instance = 3 /*!< Instance */ + } OrthancPluginResourceType; + + + + /** + * The supported type of changes that can happen to DICOM resources. + **/ + typedef enum + { + OrthancPluginChangeType_AnonymizedPatient = 0, /*!< Patient resulting from an anomyization */ + OrthancPluginChangeType_AnonymizedSeries = 1, /*!< Series resulting from an anonymization */ + OrthancPluginChangeType_AnonymizedStudy = 2, /*!< Study resulting from an anomyization */ + OrthancPluginChangeType_CompletedSeries = 3, /*!< Series is now complete */ + OrthancPluginChangeType_Deleted = 4, /*!< Deleted resource */ + OrthancPluginChangeType_ModifiedPatient = 5, /*!< Patient resulting from a modification */ + OrthancPluginChangeType_ModifiedSeries = 6, /*!< Series resulting from a modification */ + OrthancPluginChangeType_ModifiedStudy = 7, /*!< Study resulting from a modification */ + OrthancPluginChangeType_NewInstance = 8, /*!< New instance received */ + OrthancPluginChangeType_NewPatient = 9, /*!< New patient created */ + OrthancPluginChangeType_NewSeries = 10, /*!< New series created */ + OrthancPluginChangeType_NewStudy = 11, /*!< New study created */ + OrthancPluginChangeType_StablePatient = 12, /*!< No new instance received for this patient */ + OrthancPluginChangeType_StableSeries = 13, /*!< No new instance received for this series */ + OrthancPluginChangeType_StableStudy = 14 /*!< No new instance received for this study */ + } OrthancPluginChangeType; + + + + /** * @brief A memory buffer allocated by the core system of Orthanc. * * A memory buffer allocated by the core system of Orthanc. When the @@ -397,6 +435,16 @@ /** + * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. + **/ + typedef int32_t (*OrthancPluginOnChangeCallback) ( + OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId); + + + + /** * @brief Signature of a function to free dynamic memory. **/ typedef void (*OrthancPluginFree) (void* buffer); @@ -1642,6 +1690,33 @@ + typedef struct + { + OrthancPluginOnChangeCallback callback; + } _OrthancPluginOnChangeCallback; + + /** + * @brief Register a callback to monitor changes. + * + * This function registers a callback function that is called + * whenever a change happens to some DICOM resource. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( + OrthancPluginContext* context, + OrthancPluginOnChangeCallback callback) + { + _OrthancPluginOnChangeCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); + } + + + + #ifdef __cplusplus } #endif diff -r f24e04838054 -r 5b2d8c280ac2 UnitTestsSources/ServerIndexTests.cpp --- a/UnitTestsSources/ServerIndexTests.cpp Mon Sep 22 14:11:37 2014 +0200 +++ b/UnitTestsSources/ServerIndexTests.cpp Wed Sep 24 17:37:44 2014 +0200 @@ -82,13 +82,19 @@ LOG(INFO) << "A file must be removed: " << fileUuid; } - virtual void SignalResourceDeleted(ResourceType type, - const std::string& publicId) + virtual void SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId) { - deletedResources_.push_back(publicId); - LOG(INFO) << "A resource was removed: " << publicId; + if (changeType == ChangeType_Deleted) + { + deletedResources_.push_back(publicId); + } + + LOG(INFO) << "Change related to resource " << publicId << " of type " + << EnumerationToString(resourceType) << ": " << EnumerationToString(changeType); } - + };