# HG changeset patch # User Alain Mazy # Date 1663168305 -7200 # Node ID 75e949689c0857a6f29da634627bb5bb4381dbcd # Parent 4af5f496a0ddd4a5f426f39a28f108034a807bbb PluginDatabaseV4 to handle customData diff -r 4af5f496a0dd -r 75e949689c08 OrthancServer/CMakeLists.txt --- a/OrthancServer/CMakeLists.txt Wed Sep 14 17:11:05 2022 +0200 +++ b/OrthancServer/CMakeLists.txt Wed Sep 14 17:11:45 2022 +0200 @@ -181,6 +181,7 @@ list(APPEND ORTHANC_SERVER_SOURCES ${CMAKE_SOURCE_DIR}/Plugins/Engine/OrthancPluginDatabase.cpp ${CMAKE_SOURCE_DIR}/Plugins/Engine/OrthancPluginDatabaseV3.cpp + ${CMAKE_SOURCE_DIR}/Plugins/Engine/OrthancPluginDatabaseV4.cpp ${CMAKE_SOURCE_DIR}/Plugins/Engine/OrthancPlugins.cpp ${CMAKE_SOURCE_DIR}/Plugins/Engine/PluginsEnumerations.cpp ${CMAKE_SOURCE_DIR}/Plugins/Engine/PluginsErrorDictionary.cpp diff -r 4af5f496a0dd -r 75e949689c08 OrthancServer/Plugins/Engine/OrthancPluginDatabase.h --- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.h Wed Sep 14 17:11:05 2022 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.h Wed Sep 14 17:11:45 2022 +0200 @@ -113,6 +113,11 @@ return false; // No support for revisions in old API } + virtual bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE + { + return false; // No support for custom data in old API + } + void AnswerReceived(const _OrthancPluginDatabaseAnswer& answer); }; } diff -r 4af5f496a0dd -r 75e949689c08 OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h --- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h Wed Sep 14 17:11:05 2022 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h Wed Sep 14 17:11:45 2022 +0200 @@ -82,6 +82,12 @@ IStorageArea& storageArea) ORTHANC_OVERRIDE; virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE; + + virtual bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE + { + return false; // introduced in V4 + } + }; } diff -r 4af5f496a0dd -r 75e949689c08 OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Wed Sep 14 17:11:45 2022 +0200 @@ -0,0 +1,1256 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../../Sources/PrecompiledHeadersServer.h" +#include "OrthancPluginDatabaseV4.h" + +#if ORTHANC_ENABLE_PLUGINS != 1 +# error The plugin support is disabled +#endif + +#include "../../../OrthancFramework/Sources/Logging.h" +#include "../../../OrthancFramework/Sources/OrthancException.h" +#include "../../Sources/Database/ResourcesContent.h" +#include "../../Sources/Database/VoidDatabaseListener.h" +#include "PluginsEnumerations.h" + +#include + + +#define CHECK_FUNCTION_EXISTS(backend, func) \ + if (backend.func == NULL) \ + { \ + throw OrthancException( \ + ErrorCode_DatabasePlugin, "Missing primitive: " #func "()"); \ + } + +namespace Orthanc +{ + class OrthancPluginDatabaseV4::Transaction : public IDatabaseWrapper::ITransaction + { + private: + OrthancPluginDatabaseV4& that_; + IDatabaseListener& listener_; + OrthancPluginDatabaseTransaction* transaction_; + + + void CheckSuccess(OrthancPluginErrorCode code) const + { + that_.CheckSuccess(code); + } + + + static FileInfo Convert(const OrthancPluginAttachment& attachment) + { + std::string customData; + return FileInfo(attachment.uuid, + static_cast(attachment.contentType), + attachment.uncompressedSize, + attachment.uncompressedHash, + static_cast(attachment.compressionType), + attachment.compressedSize, + attachment.compressedHash, + customData); + } + + static FileInfo Convert(const OrthancPluginAttachment2& attachment) + { + return FileInfo(attachment.uuid, + static_cast(attachment.contentType), + attachment.uncompressedSize, + attachment.uncompressedHash, + static_cast(attachment.compressionType), + attachment.compressedSize, + attachment.compressedHash, + attachment.customData); + } + + void ReadStringAnswers(std::list& target) + { + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + target.clear(); + for (uint32_t i = 0; i < count; i++) + { + const char* value = NULL; + CheckSuccess(that_.backend_.readAnswerString(transaction_, &value, i)); + if (value == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + target.push_back(value); + } + } + } + + + bool ReadSingleStringAnswer(std::string& target) + { + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + if (count == 0) + { + return false; + } + else if (count == 1) + { + const char* value = NULL; + CheckSuccess(that_.backend_.readAnswerString(transaction_, &value, 0)); + if (value == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + target.assign(value); + return true; + } + } + else + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + bool ReadSingleInt64Answer(int64_t& target) + { + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + if (count == 0) + { + return false; + } + else if (count == 1) + { + CheckSuccess(that_.backend_.readAnswerInt64(transaction_, &target, 0)); + return true; + } + else + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + ExportedResource ReadAnswerExportedResource(uint32_t answerIndex) + { + OrthancPluginExportedResource exported; + CheckSuccess(that_.backend_.readAnswerExportedResource(transaction_, &exported, answerIndex)); + + if (exported.publicId == NULL || + exported.modality == NULL || + exported.date == NULL || + exported.patientId == NULL || + exported.studyInstanceUid == NULL || + exported.seriesInstanceUid == NULL || + exported.sopInstanceUid == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + return ExportedResource(exported.seq, + Plugins::Convert(exported.resourceType), + exported.publicId, + exported.modality, + exported.date, + exported.patientId, + exported.studyInstanceUid, + exported.seriesInstanceUid, + exported.sopInstanceUid); + } + } + + + ServerIndexChange ReadAnswerChange(uint32_t answerIndex) + { + OrthancPluginChange change; + CheckSuccess(that_.backend_.readAnswerChange(transaction_, &change, answerIndex)); + + if (change.publicId == NULL || + change.date == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + return ServerIndexChange(change.seq, + static_cast(change.changeType), + Plugins::Convert(change.resourceType), + change.publicId, + change.date); + } + } + + + void CheckNoEvent() + { + uint32_t count; + CheckSuccess(that_.backend_.readEventsCount(transaction_, &count)); + if (count != 0) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + void ProcessEvents(bool isDeletingAttachment) + { + uint32_t count; + CheckSuccess(that_.backend_.readEventsCount(transaction_, &count)); + + for (uint32_t i = 0; i < count; i++) + { + OrthancPluginDatabaseEvent2 event; + CheckSuccess(that_.backend_.readEvent2(transaction_, &event, i)); + + switch (event.type) + { + case OrthancPluginDatabaseEventType_DeletedAttachment: + listener_.SignalAttachmentDeleted(Convert(event.content.attachment)); + break; + + case OrthancPluginDatabaseEventType_DeletedResource: + if (isDeletingAttachment) + { + // This event should only be triggered by "DeleteResource()" + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + listener_.SignalResourceDeleted(Plugins::Convert(event.content.resource.level), event.content.resource.publicId); + } + break; + + case OrthancPluginDatabaseEventType_RemainingAncestor: + if (isDeletingAttachment) + { + // This event should only triggered by "DeleteResource()" + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + listener_.SignalRemainingAncestor(Plugins::Convert(event.content.resource.level), event.content.resource.publicId); + } + break; + + default: + break; // Unhandled event + } + } + } + + + public: + Transaction(OrthancPluginDatabaseV4& that, + IDatabaseListener& listener, + OrthancPluginDatabaseTransactionType type) : + that_(that), + listener_(listener) + { + CheckSuccess(that.backend_.startTransaction(that.database_, &transaction_, type)); + if (transaction_ == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + virtual ~Transaction() + { + OrthancPluginErrorCode code = that_.backend_.destructTransaction(transaction_); + if (code != OrthancPluginErrorCode_Success) + { + // Don't throw exception in destructors + that_.errorDictionary_.LogError(code, true); + } + } + + + virtual void Rollback() ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.rollback(transaction_)); + CheckNoEvent(); + } + + + virtual void Commit(int64_t fileSizeDelta) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.commit(transaction_, fileSizeDelta)); + CheckNoEvent(); + } + + + virtual void AddAttachment(int64_t id, + const FileInfo& attachment, + int64_t revision) ORTHANC_OVERRIDE + { + OrthancPluginAttachment2 tmp; + tmp.uuid = attachment.GetUuid().c_str(); + tmp.contentType = static_cast(attachment.GetContentType()); + tmp.uncompressedSize = attachment.GetUncompressedSize(); + tmp.uncompressedHash = attachment.GetUncompressedMD5().c_str(); + tmp.compressionType = static_cast(attachment.GetCompressionType()); + tmp.compressedSize = attachment.GetCompressedSize(); + tmp.compressedHash = attachment.GetCompressedMD5().c_str(); + tmp.customData = attachment.GetCustomData().c_str(); + + CheckSuccess(that_.backend_.addAttachment2(transaction_, id, &tmp, revision)); + CheckNoEvent(); + } + + + virtual void ClearChanges() ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.clearChanges(transaction_)); + CheckNoEvent(); + } + + + virtual void ClearExportedResources() ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.clearExportedResources(transaction_)); + CheckNoEvent(); + } + + + virtual void DeleteAttachment(int64_t id, + FileContentType attachment) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.deleteAttachment(transaction_, id, static_cast(attachment))); + ProcessEvents(true); + } + + + virtual void DeleteMetadata(int64_t id, + MetadataType type) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.deleteMetadata(transaction_, id, static_cast(type))); + CheckNoEvent(); + } + + + virtual void DeleteResource(int64_t id) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.deleteResource(transaction_, id)); + ProcessEvents(false); + } + + + virtual void GetAllMetadata(std::map& target, + int64_t id) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.getAllMetadata(transaction_, id)); + CheckNoEvent(); + + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + target.clear(); + for (uint32_t i = 0; i < count; i++) + { + int32_t metadata; + const char* value = NULL; + CheckSuccess(that_.backend_.readAnswerMetadata(transaction_, &metadata, &value, i)); + + if (value == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + target[static_cast(metadata)] = value; + } + } + } + + + virtual void GetAllPublicIds(std::list& target, + ResourceType resourceType) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.getAllPublicIds(transaction_, Plugins::Convert(resourceType))); + CheckNoEvent(); + + ReadStringAnswers(target); + } + + + virtual void GetAllPublicIds(std::list& target, + ResourceType resourceType, + size_t since, + size_t limit) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.getAllPublicIdsWithLimit( + transaction_, Plugins::Convert(resourceType), + static_cast(since), static_cast(limit))); + CheckNoEvent(); + + ReadStringAnswers(target); + } + + + virtual void GetChanges(std::list& target /*out*/, + bool& done /*out*/, + int64_t since, + uint32_t maxResults) ORTHANC_OVERRIDE + { + uint8_t tmpDone = true; + CheckSuccess(that_.backend_.getChanges(transaction_, &tmpDone, since, maxResults)); + CheckNoEvent(); + + done = (tmpDone != 0); + + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + target.clear(); + for (uint32_t i = 0; i < count; i++) + { + target.push_back(ReadAnswerChange(i)); + } + } + + + virtual void GetChildrenInternalId(std::list& target, + int64_t id) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.getChildrenInternalId(transaction_, id)); + CheckNoEvent(); + + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + target.clear(); + for (uint32_t i = 0; i < count; i++) + { + int64_t value; + CheckSuccess(that_.backend_.readAnswerInt64(transaction_, &value, i)); + target.push_back(value); + } + } + + + virtual void GetChildrenPublicId(std::list& target, + int64_t id) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.getChildrenPublicId(transaction_, id)); + CheckNoEvent(); + + ReadStringAnswers(target); + } + + + virtual void GetExportedResources(std::list& target /*out*/, + bool& done /*out*/, + int64_t since, + uint32_t maxResults) ORTHANC_OVERRIDE + { + uint8_t tmpDone = true; + CheckSuccess(that_.backend_.getExportedResources(transaction_, &tmpDone, since, maxResults)); + CheckNoEvent(); + + done = (tmpDone != 0); + + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + target.clear(); + for (uint32_t i = 0; i < count; i++) + { + target.push_back(ReadAnswerExportedResource(i)); + } + } + + + virtual void GetLastChange(std::list& target /*out*/) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.getLastChange(transaction_)); + CheckNoEvent(); + + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + target.clear(); + if (count == 1) + { + target.push_back(ReadAnswerChange(0)); + } + else if (count > 1) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + virtual void GetLastExportedResource(std::list& target /*out*/) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.getLastExportedResource(transaction_)); + CheckNoEvent(); + + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + target.clear(); + if (count == 1) + { + target.push_back(ReadAnswerExportedResource(0)); + } + else if (count > 1) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + virtual void GetMainDicomTags(DicomMap& target, + int64_t id) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.getMainDicomTags(transaction_, id)); + CheckNoEvent(); + + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + target.Clear(); + for (uint32_t i = 0; i < count; i++) + { + uint16_t group, element; + const char* value = NULL; + CheckSuccess(that_.backend_.readAnswerDicomTag(transaction_, &group, &element, &value, i)); + + if (value == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + target.SetValue(group, element, std::string(value), false); + } + } + } + + + virtual std::string GetPublicId(int64_t resourceId) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.getPublicId(transaction_, resourceId)); + CheckNoEvent(); + + std::string s; + if (ReadSingleStringAnswer(s)) + { + return s; + } + else + { + throw OrthancException(ErrorCode_InexistentItem); + } + } + + + virtual uint64_t GetResourcesCount(ResourceType resourceType) ORTHANC_OVERRIDE + { + uint64_t value; + CheckSuccess(that_.backend_.getResourcesCount(transaction_, &value, Plugins::Convert(resourceType))); + CheckNoEvent(); + return value; + } + + + virtual ResourceType GetResourceType(int64_t resourceId) ORTHANC_OVERRIDE + { + OrthancPluginResourceType type; + CheckSuccess(that_.backend_.getResourceType(transaction_, &type, resourceId)); + CheckNoEvent(); + return Plugins::Convert(type); + } + + + virtual uint64_t GetTotalCompressedSize() ORTHANC_OVERRIDE + { + uint64_t s; + CheckSuccess(that_.backend_.getTotalCompressedSize(transaction_, &s)); + CheckNoEvent(); + return s; + } + + + virtual uint64_t GetTotalUncompressedSize() ORTHANC_OVERRIDE + { + uint64_t s; + CheckSuccess(that_.backend_.getTotalUncompressedSize(transaction_, &s)); + CheckNoEvent(); + return s; + } + + + virtual bool IsExistingResource(int64_t internalId) ORTHANC_OVERRIDE + { + uint8_t b; + CheckSuccess(that_.backend_.isExistingResource(transaction_, &b, internalId)); + CheckNoEvent(); + return (b != 0); + } + + + virtual bool IsProtectedPatient(int64_t internalId) ORTHANC_OVERRIDE + { + uint8_t b; + CheckSuccess(that_.backend_.isProtectedPatient(transaction_, &b, internalId)); + CheckNoEvent(); + return (b != 0); + } + + + virtual void ListAvailableAttachments(std::set& target, + int64_t id) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.listAvailableAttachments(transaction_, id)); + CheckNoEvent(); + + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + target.clear(); + for (uint32_t i = 0; i < count; i++) + { + int32_t value; + CheckSuccess(that_.backend_.readAnswerInt32(transaction_, &value, i)); + target.insert(static_cast(value)); + } + } + + + virtual void LogChange(int64_t internalId, + const ServerIndexChange& change) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.logChange(transaction_, static_cast(change.GetChangeType()), + internalId, Plugins::Convert(change.GetResourceType()), + change.GetDate().c_str())); + CheckNoEvent(); + } + + + virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.logExportedResource(transaction_, Plugins::Convert(resource.GetResourceType()), + resource.GetPublicId().c_str(), + resource.GetModality().c_str(), + resource.GetDate().c_str(), + resource.GetPatientId().c_str(), + resource.GetStudyInstanceUid().c_str(), + resource.GetSeriesInstanceUid().c_str(), + resource.GetSopInstanceUid().c_str())); + CheckNoEvent(); + } + + + virtual bool LookupAttachment(FileInfo& attachment, + int64_t& revision, + int64_t id, + FileContentType contentType) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.lookupAttachment(transaction_, &revision, id, static_cast(contentType))); + CheckNoEvent(); + + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + if (count == 0) + { + return false; + } + else if (count == 1) + { + OrthancPluginAttachment2 tmp; + CheckSuccess(that_.backend_.readAnswerAttachment2(transaction_, &tmp, 0)); + attachment = Convert(tmp); + return true; + } + else + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + virtual bool LookupGlobalProperty(std::string& target, + GlobalProperty property, + bool shared) ORTHANC_OVERRIDE + { + const char* id = (shared ? "" : that_.serverIdentifier_.c_str()); + + CheckSuccess(that_.backend_.lookupGlobalProperty(transaction_, id, static_cast(property))); + CheckNoEvent(); + return ReadSingleStringAnswer(target); + } + + + virtual bool LookupMetadata(std::string& target, + int64_t& revision, + int64_t id, + MetadataType type) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.lookupMetadata(transaction_, &revision, id, static_cast(type))); + CheckNoEvent(); + return ReadSingleStringAnswer(target); + } + + + virtual bool LookupParent(int64_t& parentId, + int64_t resourceId) ORTHANC_OVERRIDE + { + uint8_t existing; + CheckSuccess(that_.backend_.lookupParent(transaction_, &existing, &parentId, resourceId)); + CheckNoEvent(); + return (existing != 0); + } + + + virtual bool LookupResource(int64_t& id, + ResourceType& type, + const std::string& publicId) ORTHANC_OVERRIDE + { + uint8_t existing; + OrthancPluginResourceType t; + CheckSuccess(that_.backend_.lookupResource(transaction_, &existing, &id, &t, publicId.c_str())); + CheckNoEvent(); + + if (existing == 0) + { + return false; + } + else + { + type = Plugins::Convert(t); + return true; + } + } + + + virtual bool SelectPatientToRecycle(int64_t& internalId) ORTHANC_OVERRIDE + { + uint8_t available; + CheckSuccess(that_.backend_.selectPatientToRecycle(transaction_, &available, &internalId)); + CheckNoEvent(); + return (available != 0); + } + + + virtual bool SelectPatientToRecycle(int64_t& internalId, + int64_t patientIdToAvoid) ORTHANC_OVERRIDE + { + uint8_t available; + CheckSuccess(that_.backend_.selectPatientToRecycle2(transaction_, &available, &internalId, patientIdToAvoid)); + CheckNoEvent(); + return (available != 0); + } + + + virtual void SetGlobalProperty(GlobalProperty property, + bool shared, + const std::string& value) ORTHANC_OVERRIDE + { + const char* id = (shared ? "" : that_.serverIdentifier_.c_str()); + + CheckSuccess(that_.backend_.setGlobalProperty(transaction_, id, static_cast(property), value.c_str())); + CheckNoEvent(); + } + + + virtual void ClearMainDicomTags(int64_t id) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.clearMainDicomTags(transaction_, id)); + CheckNoEvent(); + } + + + virtual void SetMetadata(int64_t id, + MetadataType type, + const std::string& value, + int64_t revision) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.setMetadata(transaction_, id, static_cast(type), value.c_str(), revision)); + CheckNoEvent(); + } + + + virtual void SetProtectedPatient(int64_t internalId, + bool isProtected) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.setProtectedPatient(transaction_, internalId, (isProtected ? 1 : 0))); + CheckNoEvent(); + } + + + virtual bool IsDiskSizeAbove(uint64_t threshold) ORTHANC_OVERRIDE + { + uint8_t tmp; + CheckSuccess(that_.backend_.isDiskSizeAbove(transaction_, &tmp, threshold)); + CheckNoEvent(); + return (tmp != 0); + } + + + virtual void ApplyLookupResources(std::list& resourcesId, + std::list* instancesId, // Can be NULL if not needed + const std::vector& lookup, + ResourceType queryLevel, + size_t limit) ORTHANC_OVERRIDE + { + std::vector constraints; + std::vector< std::vector > constraintsValues; + + constraints.resize(lookup.size()); + constraintsValues.resize(lookup.size()); + + for (size_t i = 0; i < lookup.size(); i++) + { + lookup[i].EncodeForPlugins(constraints[i], constraintsValues[i]); + } + + CheckSuccess(that_.backend_.lookupResources(transaction_, lookup.size(), + (lookup.empty() ? NULL : &constraints[0]), + Plugins::Convert(queryLevel), + limit, (instancesId == NULL ? 0 : 1))); + CheckNoEvent(); + + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + resourcesId.clear(); + + if (instancesId != NULL) + { + instancesId->clear(); + } + + for (uint32_t i = 0; i < count; i++) + { + OrthancPluginMatchingResource resource; + CheckSuccess(that_.backend_.readAnswerMatchingResource(transaction_, &resource, i)); + + if (resource.resourceId == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + resourcesId.push_back(resource.resourceId); + + if (instancesId != NULL) + { + if (resource.someInstanceId == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + instancesId->push_back(resource.someInstanceId); + } + } + } + } + + + virtual bool CreateInstance(CreateInstanceResult& result, /* out */ + int64_t& instanceId, /* out */ + const std::string& patient, + const std::string& study, + const std::string& series, + const std::string& instance) ORTHANC_OVERRIDE + { + OrthancPluginCreateInstanceResult output; + memset(&output, 0, sizeof(output)); + + CheckSuccess(that_.backend_.createInstance(transaction_, &output, patient.c_str(), + study.c_str(), series.c_str(), instance.c_str())); + CheckNoEvent(); + + instanceId = output.instanceId; + + if (output.isNewInstance) + { + result.isNewPatient_ = output.isNewPatient; + result.isNewStudy_ = output.isNewStudy; + result.isNewSeries_ = output.isNewSeries; + result.patientId_ = output.patientId; + result.studyId_ = output.studyId; + result.seriesId_ = output.seriesId; + return true; + } + else + { + return false; + } + + } + + + virtual void SetResourcesContent(const ResourcesContent& content) ORTHANC_OVERRIDE + { + std::vector identifierTags; + std::vector mainDicomTags; + std::vector metadata; + + identifierTags.reserve(content.GetListTags().size()); + mainDicomTags.reserve(content.GetListTags().size()); + metadata.reserve(content.GetListMetadata().size()); + + for (ResourcesContent::ListTags::const_iterator + it = content.GetListTags().begin(); it != content.GetListTags().end(); ++it) + { + OrthancPluginResourcesContentTags tmp; + tmp.resource = it->resourceId_; + tmp.group = it->tag_.GetGroup(); + tmp.element = it->tag_.GetElement(); + tmp.value = it->value_.c_str(); + + if (it->isIdentifier_) + { + identifierTags.push_back(tmp); + } + else + { + mainDicomTags.push_back(tmp); + } + } + + for (ResourcesContent::ListMetadata::const_iterator + it = content.GetListMetadata().begin(); it != content.GetListMetadata().end(); ++it) + { + OrthancPluginResourcesContentMetadata tmp; + tmp.resource = it->resourceId_; + tmp.metadata = it->metadata_; + tmp.value = it->value_.c_str(); + metadata.push_back(tmp); + } + + assert(identifierTags.size() + mainDicomTags.size() == content.GetListTags().size() && + metadata.size() == content.GetListMetadata().size()); + + CheckSuccess(that_.backend_.setResourcesContent(transaction_, + identifierTags.size(), + (identifierTags.empty() ? NULL : &identifierTags[0]), + mainDicomTags.size(), + (mainDicomTags.empty() ? NULL : &mainDicomTags[0]), + metadata.size(), + (metadata.empty() ? NULL : &metadata[0]))); + CheckNoEvent(); + } + + + virtual void GetChildrenMetadata(std::list& target, + int64_t resourceId, + MetadataType metadata) ORTHANC_OVERRIDE + { + CheckSuccess(that_.backend_.getChildrenMetadata(transaction_, resourceId, static_cast(metadata))); + CheckNoEvent(); + ReadStringAnswers(target); + } + + + virtual int64_t GetLastChangeIndex() ORTHANC_OVERRIDE + { + int64_t tmp; + CheckSuccess(that_.backend_.getLastChangeIndex(transaction_, &tmp)); + CheckNoEvent(); + return tmp; + } + + + virtual bool LookupResourceAndParent(int64_t& id, + ResourceType& type, + std::string& parentPublicId, + const std::string& publicId) ORTHANC_OVERRIDE + { + uint8_t isExisting; + OrthancPluginResourceType tmpType; + CheckSuccess(that_.backend_.lookupResourceAndParent(transaction_, &isExisting, &id, &tmpType, publicId.c_str())); + CheckNoEvent(); + + if (isExisting) + { + type = Plugins::Convert(tmpType); + + uint32_t count; + CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); + + if (count > 1) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + switch (type) + { + case ResourceType_Patient: + // A patient has no parent + if (count == 1) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + break; + + case ResourceType_Study: + case ResourceType_Series: + case ResourceType_Instance: + if (count == 0) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + const char* value = NULL; + CheckSuccess(that_.backend_.readAnswerString(transaction_, &value, 0)); + if (value == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + parentPublicId.assign(value); + } + } + break; + + default: + throw OrthancException(ErrorCode_DatabasePlugin); + } + + return true; + } + else + { + return false; + } + } + }; + + + void OrthancPluginDatabaseV4::CheckSuccess(OrthancPluginErrorCode code) const + { + if (code != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast(code)); + } + } + + + OrthancPluginDatabaseV4::OrthancPluginDatabaseV4(SharedLibrary& library, + PluginsErrorDictionary& errorDictionary, + const OrthancPluginDatabaseBackendV4* backend, + size_t backendSize, + void* database, + const std::string& serverIdentifier) : + library_(library), + errorDictionary_(errorDictionary), + database_(database), + serverIdentifier_(serverIdentifier) + { + CLOG(INFO, PLUGINS) << "Identifier of this Orthanc server for the global properties " + << "of the custom database: \"" << serverIdentifier << "\""; + + if (backendSize >= sizeof(backend_)) + { + memcpy(&backend_, backend, sizeof(backend_)); + } + else + { + // Not all the primitives are implemented by the plugin + memset(&backend_, 0, sizeof(backend_)); + memcpy(&backend_, backend, backendSize); + } + + // Sanity checks + CHECK_FUNCTION_EXISTS(backend_, readAnswersCount); + CHECK_FUNCTION_EXISTS(backend_, readAnswerAttachment2); + CHECK_FUNCTION_EXISTS(backend_, readAnswerChange); + CHECK_FUNCTION_EXISTS(backend_, readAnswerDicomTag); + CHECK_FUNCTION_EXISTS(backend_, readAnswerExportedResource); + CHECK_FUNCTION_EXISTS(backend_, readAnswerInt32); + CHECK_FUNCTION_EXISTS(backend_, readAnswerInt64); + CHECK_FUNCTION_EXISTS(backend_, readAnswerMatchingResource); + CHECK_FUNCTION_EXISTS(backend_, readAnswerMetadata); + CHECK_FUNCTION_EXISTS(backend_, readAnswerString); + + CHECK_FUNCTION_EXISTS(backend_, readEventsCount); + CHECK_FUNCTION_EXISTS(backend_, readEvent2); + + CHECK_FUNCTION_EXISTS(backend_, open); + CHECK_FUNCTION_EXISTS(backend_, close); + CHECK_FUNCTION_EXISTS(backend_, destructDatabase); + CHECK_FUNCTION_EXISTS(backend_, getDatabaseVersion); + CHECK_FUNCTION_EXISTS(backend_, upgradeDatabase); + CHECK_FUNCTION_EXISTS(backend_, startTransaction); + CHECK_FUNCTION_EXISTS(backend_, destructTransaction); + CHECK_FUNCTION_EXISTS(backend_, hasRevisionsSupport); + CHECK_FUNCTION_EXISTS(backend_, hasAttachmentCustomDataSupport); // new in v4 + + CHECK_FUNCTION_EXISTS(backend_, rollback); + CHECK_FUNCTION_EXISTS(backend_, commit); + + CHECK_FUNCTION_EXISTS(backend_, addAttachment2); + CHECK_FUNCTION_EXISTS(backend_, clearChanges); + CHECK_FUNCTION_EXISTS(backend_, clearExportedResources); + CHECK_FUNCTION_EXISTS(backend_, clearMainDicomTags); + CHECK_FUNCTION_EXISTS(backend_, createInstance); + CHECK_FUNCTION_EXISTS(backend_, deleteAttachment); + CHECK_FUNCTION_EXISTS(backend_, deleteMetadata); + CHECK_FUNCTION_EXISTS(backend_, deleteResource); + CHECK_FUNCTION_EXISTS(backend_, getAllMetadata); + CHECK_FUNCTION_EXISTS(backend_, getAllPublicIds); + CHECK_FUNCTION_EXISTS(backend_, getAllPublicIdsWithLimit); + CHECK_FUNCTION_EXISTS(backend_, getChanges); + CHECK_FUNCTION_EXISTS(backend_, getChildrenInternalId); + CHECK_FUNCTION_EXISTS(backend_, getChildrenMetadata); + CHECK_FUNCTION_EXISTS(backend_, getChildrenPublicId); + CHECK_FUNCTION_EXISTS(backend_, getExportedResources); + CHECK_FUNCTION_EXISTS(backend_, getLastChange); + CHECK_FUNCTION_EXISTS(backend_, getLastChangeIndex); + CHECK_FUNCTION_EXISTS(backend_, getLastExportedResource); + CHECK_FUNCTION_EXISTS(backend_, getMainDicomTags); + CHECK_FUNCTION_EXISTS(backend_, getPublicId); + CHECK_FUNCTION_EXISTS(backend_, getResourceType); + CHECK_FUNCTION_EXISTS(backend_, getResourcesCount); + CHECK_FUNCTION_EXISTS(backend_, getTotalCompressedSize); + CHECK_FUNCTION_EXISTS(backend_, getTotalUncompressedSize); + CHECK_FUNCTION_EXISTS(backend_, isDiskSizeAbove); + CHECK_FUNCTION_EXISTS(backend_, isExistingResource); + CHECK_FUNCTION_EXISTS(backend_, isProtectedPatient); + CHECK_FUNCTION_EXISTS(backend_, listAvailableAttachments); + CHECK_FUNCTION_EXISTS(backend_, logChange); + CHECK_FUNCTION_EXISTS(backend_, logExportedResource); + CHECK_FUNCTION_EXISTS(backend_, lookupAttachment); + CHECK_FUNCTION_EXISTS(backend_, lookupGlobalProperty); + CHECK_FUNCTION_EXISTS(backend_, lookupMetadata); + CHECK_FUNCTION_EXISTS(backend_, lookupParent); + CHECK_FUNCTION_EXISTS(backend_, lookupResource); + CHECK_FUNCTION_EXISTS(backend_, lookupResourceAndParent); + CHECK_FUNCTION_EXISTS(backend_, lookupResources); + CHECK_FUNCTION_EXISTS(backend_, selectPatientToRecycle); + CHECK_FUNCTION_EXISTS(backend_, selectPatientToRecycle2); + CHECK_FUNCTION_EXISTS(backend_, setGlobalProperty); + CHECK_FUNCTION_EXISTS(backend_, setMetadata); + CHECK_FUNCTION_EXISTS(backend_, setProtectedPatient); + CHECK_FUNCTION_EXISTS(backend_, setResourcesContent); + } + + + OrthancPluginDatabaseV4::~OrthancPluginDatabaseV4() + { + if (database_ != NULL) + { + OrthancPluginErrorCode code = backend_.destructDatabase(database_); + if (code != OrthancPluginErrorCode_Success) + { + // Don't throw exception in destructors + errorDictionary_.LogError(code, true); + } + } + } + + + void OrthancPluginDatabaseV4::Open() + { + CheckSuccess(backend_.open(database_)); + } + + + void OrthancPluginDatabaseV4::Close() + { + CheckSuccess(backend_.close(database_)); + } + + + IDatabaseWrapper::ITransaction* OrthancPluginDatabaseV4::StartTransaction(TransactionType type, + IDatabaseListener& listener) + { + switch (type) + { + case TransactionType_ReadOnly: + return new Transaction(*this, listener, OrthancPluginDatabaseTransactionType_ReadOnly); + + case TransactionType_ReadWrite: + return new Transaction(*this, listener, OrthancPluginDatabaseTransactionType_ReadWrite); + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + unsigned int OrthancPluginDatabaseV4::GetDatabaseVersion() + { + uint32_t version = 0; + CheckSuccess(backend_.getDatabaseVersion(database_, &version)); + return version; + } + + + void OrthancPluginDatabaseV4::Upgrade(unsigned int targetVersion, + IStorageArea& storageArea) + { + VoidDatabaseListener listener; + + if (backend_.upgradeDatabase != NULL) + { + Transaction transaction(*this, listener, OrthancPluginDatabaseTransactionType_ReadWrite); + + OrthancPluginErrorCode code = backend_.upgradeDatabase( + database_, reinterpret_cast(&storageArea), + static_cast(targetVersion)); + + if (code == OrthancPluginErrorCode_Success) + { + transaction.Commit(0); + } + else + { + transaction.Rollback(); + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast(code)); + } + } + } + + + bool OrthancPluginDatabaseV4::HasRevisionsSupport() const + { + // WARNING: This method requires "Open()" to have been called + uint8_t hasRevisions; + CheckSuccess(backend_.hasRevisionsSupport(database_, &hasRevisions)); + return (hasRevisions != 0); + } + + bool OrthancPluginDatabaseV4::HasAttachmentCustomDataSupport() const + { + // WARNING: This method requires "Open()" to have been called + uint8_t hasAttachmentCustomDataSupport; + CheckSuccess(backend_.hasAttachmentCustomDataSupport(database_, &hasAttachmentCustomDataSupport)); + return (hasAttachmentCustomDataSupport != 0); + } +} diff -r 4af5f496a0dd -r 75e949689c08 OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h Wed Sep 14 17:11:45 2022 +0200 @@ -0,0 +1,91 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if ORTHANC_ENABLE_PLUGINS == 1 + +#include "../../../OrthancFramework/Sources/SharedLibrary.h" +#include "../../Sources/Database/IDatabaseWrapper.h" +#include "../Include/orthanc/OrthancCDatabasePlugin.h" +#include "PluginsErrorDictionary.h" + +namespace Orthanc +{ + class OrthancPluginDatabaseV4 : public IDatabaseWrapper + { + private: + class Transaction; + + SharedLibrary& library_; + PluginsErrorDictionary& errorDictionary_; + OrthancPluginDatabaseBackendV4 backend_; + void* database_; + std::string serverIdentifier_; + + void CheckSuccess(OrthancPluginErrorCode code) const; + + public: + OrthancPluginDatabaseV4(SharedLibrary& library, + PluginsErrorDictionary& errorDictionary, + const OrthancPluginDatabaseBackendV4* backend, + size_t backendSize, + void* database, + const std::string& serverIdentifier); + + virtual ~OrthancPluginDatabaseV4(); + + virtual void Open() ORTHANC_OVERRIDE; + + virtual void Close() ORTHANC_OVERRIDE; + + const SharedLibrary& GetSharedLibrary() const + { + return library_; + } + + virtual void FlushToDisk() ORTHANC_OVERRIDE + { + } + + virtual bool HasFlushToDisk() const ORTHANC_OVERRIDE + { + return false; + } + + virtual IDatabaseWrapper::ITransaction* StartTransaction(TransactionType type, + IDatabaseListener& listener) + ORTHANC_OVERRIDE; + + virtual unsigned int GetDatabaseVersion() ORTHANC_OVERRIDE; + + virtual void Upgrade(unsigned int targetVersion, + IStorageArea& storageArea) ORTHANC_OVERRIDE; + + virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE; + + virtual bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE; + + }; +} + +#endif diff -r 4af5f496a0dd -r 75e949689c08 OrthancServer/Plugins/Engine/OrthancPlugins.cpp --- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed Sep 14 17:11:05 2022 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed Sep 14 17:11:45 2022 +0200 @@ -64,6 +64,7 @@ #include "../../Sources/ServerToolbox.h" #include "OrthancPluginDatabase.h" #include "OrthancPluginDatabaseV3.h" +#include "OrthancPluginDatabaseV4.h" #include "PluginsEnumerations.h" #include "PluginsJob.h" @@ -1890,6 +1891,7 @@ char** argv_; std::unique_ptr database_; std::unique_ptr databaseV3_; // New in Orthanc 1.9.2 + std::unique_ptr databaseV4_; // New in Orthanc 1.12.0 PluginsErrorDictionary dictionary_; std::string databaseServerIdentifier_; // New in Orthanc 1.9.2 unsigned int maxDatabaseRetries_; // New in Orthanc 1.9.2 @@ -5746,7 +5748,8 @@ *reinterpret_cast(parameters); if (pimpl_->database_.get() == NULL && - pimpl_->databaseV3_.get() == NULL) + pimpl_->databaseV3_.get() == NULL && + pimpl_->databaseV4_.get() == NULL) { pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(), *p.backend, NULL, 0, p.payload)); @@ -5770,7 +5773,8 @@ *reinterpret_cast(parameters); if (pimpl_->database_.get() == NULL && - pimpl_->databaseV3_.get() == NULL) + pimpl_->databaseV3_.get() == NULL && + pimpl_->databaseV4_.get() == NULL) { pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(), *p.backend, p.extensions, @@ -5788,13 +5792,14 @@ case _OrthancPluginService_RegisterDatabaseBackendV3: { - CLOG(INFO, PLUGINS) << "Plugin has registered a custom database back-end"; + CLOG(INFO, PLUGINS) << "Plugin has registered a custom database back-end (v3)"; const _OrthancPluginRegisterDatabaseBackendV3& p = *reinterpret_cast(parameters); if (pimpl_->database_.get() == NULL && - pimpl_->databaseV3_.get() == NULL) + pimpl_->databaseV3_.get() == NULL && + pimpl_->databaseV4_.get() == NULL) { pimpl_->databaseV3_.reset(new OrthancPluginDatabaseV3(plugin, GetErrorDictionary(), p.backend, p.backendSize, p.database, pimpl_->databaseServerIdentifier_)); @@ -5808,6 +5813,30 @@ return true; } + + case _OrthancPluginService_RegisterDatabaseBackendV4: + { + CLOG(INFO, PLUGINS) << "Plugin has registered a custom database back-end (v4)"; + + const _OrthancPluginRegisterDatabaseBackendV4& p = + *reinterpret_cast(parameters); + + if (pimpl_->database_.get() == NULL && + pimpl_->databaseV3_.get() == NULL && + pimpl_->databaseV4_.get() == NULL) + { + pimpl_->databaseV4_.reset(new OrthancPluginDatabaseV4(plugin, GetErrorDictionary(), p.backend, + p.backendSize, p.database, pimpl_->databaseServerIdentifier_)); + pimpl_->maxDatabaseRetries_ = p.maxDatabaseRetries; + } + else + { + throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered); + } + + return true; + } + case _OrthancPluginService_DatabaseAnswer: throw OrthancException(ErrorCode_InternalError); // Implemented before locking (*) @@ -5947,7 +5976,8 @@ { boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); return (pimpl_->database_.get() != NULL || - pimpl_->databaseV3_.get() != NULL); + pimpl_->databaseV3_.get() != NULL || + pimpl_->databaseV4_.get() != NULL); } @@ -5987,6 +6017,10 @@ { return *pimpl_->databaseV3_; } + else if (pimpl_->databaseV4_.get() != NULL) + { + return *pimpl_->databaseV4_; + } else { throw OrthancException(ErrorCode_BadSequenceOfCalls); @@ -6004,6 +6038,10 @@ { return pimpl_->databaseV3_->GetSharedLibrary(); } + else if (pimpl_->databaseV4_.get() != NULL) + { + return pimpl_->databaseV4_->GetSharedLibrary(); + } else { throw OrthancException(ErrorCode_BadSequenceOfCalls); diff -r 4af5f496a0dd -r 75e949689c08 OrthancServer/Plugins/Include/orthanc/OrthancCDatabasePlugin.h --- a/OrthancServer/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Wed Sep 14 17:11:05 2022 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Wed Sep 14 17:11:45 2022 +0200 @@ -62,6 +62,7 @@ _OrthancPluginDatabaseAnswerType_DeletedAttachment = 1, _OrthancPluginDatabaseAnswerType_DeletedResource = 2, _OrthancPluginDatabaseAnswerType_RemainingAncestor = 3, + _OrthancPluginDatabaseAnswerType_DeletedAttachment2 = 4, /* Return value */ _OrthancPluginDatabaseAnswerType_Attachment = 10, @@ -74,6 +75,7 @@ _OrthancPluginDatabaseAnswerType_String = 17, _OrthancPluginDatabaseAnswerType_MatchingResource = 18, /* New in Orthanc 1.5.2 */ _OrthancPluginDatabaseAnswerType_Metadata = 19, /* New in Orthanc 1.5.4 */ + _OrthancPluginDatabaseAnswerType_Attachment2 = 20, /* New in Orthanc 1.12.0 */ _OrthancPluginDatabaseAnswerType_INTERNAL = 0x7fffffff } _OrthancPluginDatabaseAnswerType; @@ -92,6 +94,18 @@ typedef struct { + const char* uuid; + int32_t contentType; + uint64_t uncompressedSize; + const char* uncompressedHash; + int32_t compressionType; + uint64_t compressedSize; + const char* compressedHash; + const char* customData; + } OrthancPluginAttachment2; + + typedef struct + { uint16_t group; uint16_t element; const char* value; @@ -305,6 +319,19 @@ context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); } + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerAttachment2( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginAttachment2* attachment) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Attachment2; + params.valueGeneric = attachment; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerResource( OrthancPluginContext* context, OrthancPluginDatabaseContext* database, @@ -365,6 +392,19 @@ context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); } + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedAttachment2( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginAttachment2* attachment) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DeletedAttachment2; + params.valueGeneric = attachment; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedResource( OrthancPluginContext* context, OrthancPluginDatabaseContext* database, @@ -1360,7 +1400,374 @@ return context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV3, ¶ms); } + + + typedef struct + { + OrthancPluginDatabaseEventType type; + + union + { + struct + { + /* For ""DeletedResource" and "RemainingAncestor" */ + OrthancPluginResourceType level; + const char* publicId; + } resource; + + /* For "DeletedAttachment" */ + OrthancPluginAttachment2 attachment; + + } content; + + } OrthancPluginDatabaseEvent2; + + + typedef struct + { + /** + * Functions to read the answers inside a transaction + **/ + + OrthancPluginErrorCode (*readAnswersCount) (OrthancPluginDatabaseTransaction* transaction, + uint32_t* target /* out */); + + OrthancPluginErrorCode (*readAnswerAttachment2) (OrthancPluginDatabaseTransaction* transaction, + OrthancPluginAttachment2* target /* out */, // new in v4 + uint32_t index); + + OrthancPluginErrorCode (*readAnswerChange) (OrthancPluginDatabaseTransaction* transaction, + OrthancPluginChange* target /* out */, + uint32_t index); + + OrthancPluginErrorCode (*readAnswerDicomTag) (OrthancPluginDatabaseTransaction* transaction, + uint16_t* group, + uint16_t* element, + const char** value, + uint32_t index); + + OrthancPluginErrorCode (*readAnswerExportedResource) (OrthancPluginDatabaseTransaction* transaction, + OrthancPluginExportedResource* target /* out */, + uint32_t index); + + OrthancPluginErrorCode (*readAnswerInt32) (OrthancPluginDatabaseTransaction* transaction, + int32_t* target /* out */, + uint32_t index); + + OrthancPluginErrorCode (*readAnswerInt64) (OrthancPluginDatabaseTransaction* transaction, + int64_t* target /* out */, + uint32_t index); + + OrthancPluginErrorCode (*readAnswerMatchingResource) (OrthancPluginDatabaseTransaction* transaction, + OrthancPluginMatchingResource* target /* out */, + uint32_t index); + + OrthancPluginErrorCode (*readAnswerMetadata) (OrthancPluginDatabaseTransaction* transaction, + int32_t* metadata /* out */, + const char** value /* out */, + uint32_t index); + + OrthancPluginErrorCode (*readAnswerString) (OrthancPluginDatabaseTransaction* transaction, + const char** target /* out */, + uint32_t index); + + OrthancPluginErrorCode (*readEventsCount) (OrthancPluginDatabaseTransaction* transaction, + uint32_t* target /* out */); + + OrthancPluginErrorCode (*readEvent2) (OrthancPluginDatabaseTransaction* transaction, + OrthancPluginDatabaseEvent2* event /* out */, // new in v4 + uint32_t index); + + + + /** + * Functions to access the global database object + * (cf. "IDatabaseWrapper" class in Orthanc) + **/ + + OrthancPluginErrorCode (*open) (void* database); + + OrthancPluginErrorCode (*close) (void* database); + + OrthancPluginErrorCode (*destructDatabase) (void* database); + + OrthancPluginErrorCode (*getDatabaseVersion) (void* database, + uint32_t* target /* out */); + + OrthancPluginErrorCode (*hasRevisionsSupport) (void* database, + uint8_t* target /* out */); + + OrthancPluginErrorCode (*hasAttachmentCustomDataSupport) (void* database, // new in v4 + uint8_t* target /* out */); + + OrthancPluginErrorCode (*upgradeDatabase) (void* database, + OrthancPluginStorageArea* storageArea, + uint32_t targetVersion); + + OrthancPluginErrorCode (*startTransaction) (void* database, + OrthancPluginDatabaseTransaction** target /* out */, + OrthancPluginDatabaseTransactionType type); + + OrthancPluginErrorCode (*destructTransaction) (OrthancPluginDatabaseTransaction* transaction); + + + /** + * Functions to run operations within a database transaction + * (cf. "IDatabaseWrapper::ITransaction" class in Orthanc) + **/ + + OrthancPluginErrorCode (*rollback) (OrthancPluginDatabaseTransaction* transaction); + + OrthancPluginErrorCode (*commit) (OrthancPluginDatabaseTransaction* transaction, + int64_t fileSizeDelta); + + /* A call to "addAttachment()" guarantees that this attachment is not already existing ("INSERT") */ + OrthancPluginErrorCode (*addAttachment2) (OrthancPluginDatabaseTransaction* transaction, + int64_t id, + const OrthancPluginAttachment2* attachment, // new in v4 + int64_t revision); + + OrthancPluginErrorCode (*clearChanges) (OrthancPluginDatabaseTransaction* transaction); + + OrthancPluginErrorCode (*clearExportedResources) (OrthancPluginDatabaseTransaction* transaction); + + OrthancPluginErrorCode (*clearMainDicomTags) (OrthancPluginDatabaseTransaction* transaction, + int64_t resourceId); + + OrthancPluginErrorCode (*createInstance) (OrthancPluginDatabaseTransaction* transaction, + OrthancPluginCreateInstanceResult* target /* out */, + const char* hashPatient, + const char* hashStudy, + const char* hashSeries, + const char* hashInstance); + + OrthancPluginErrorCode (*deleteAttachment) (OrthancPluginDatabaseTransaction* transaction, + int64_t id, + int32_t contentType); + + OrthancPluginErrorCode (*deleteMetadata) (OrthancPluginDatabaseTransaction* transaction, + int64_t id, + int32_t metadataType); + + OrthancPluginErrorCode (*deleteResource) (OrthancPluginDatabaseTransaction* transaction, + int64_t id); + + /* Answers are read using "readAnswerMetadata()" */ + OrthancPluginErrorCode (*getAllMetadata) (OrthancPluginDatabaseTransaction* transaction, + int64_t id); + + /* Answers are read using "readAnswerString()" */ + OrthancPluginErrorCode (*getAllPublicIds) (OrthancPluginDatabaseTransaction* transaction, + OrthancPluginResourceType resourceType); + + /* Answers are read using "readAnswerString()" */ + OrthancPluginErrorCode (*getAllPublicIdsWithLimit) (OrthancPluginDatabaseTransaction* transaction, + OrthancPluginResourceType resourceType, + uint64_t since, + uint64_t limit); + + /* Answers are read using "readAnswerChange()" */ + OrthancPluginErrorCode (*getChanges) (OrthancPluginDatabaseTransaction* transaction, + uint8_t* targetDone /* out */, + int64_t since, + uint32_t maxResults); + + /* Answers are read using "readAnswerInt64()" */ + OrthancPluginErrorCode (*getChildrenInternalId) (OrthancPluginDatabaseTransaction* transaction, + int64_t id); + + /* Answers are read using "readAnswerString()" */ + OrthancPluginErrorCode (*getChildrenMetadata) (OrthancPluginDatabaseTransaction* transaction, + int64_t resourceId, + int32_t metadata); + + /* Answers are read using "readAnswerString()" */ + OrthancPluginErrorCode (*getChildrenPublicId) (OrthancPluginDatabaseTransaction* transaction, + int64_t id); + + /* Answers are read using "readAnswerExportedResource()" */ + OrthancPluginErrorCode (*getExportedResources) (OrthancPluginDatabaseTransaction* transaction, + uint8_t* targetDone /* out */, + int64_t since, + uint32_t maxResults); + + /* Answer is read using "readAnswerChange()" */ + OrthancPluginErrorCode (*getLastChange) (OrthancPluginDatabaseTransaction* transaction); + + OrthancPluginErrorCode (*getLastChangeIndex) (OrthancPluginDatabaseTransaction* transaction, + int64_t* target /* out */); + + /* Answer is read using "readAnswerExportedResource()" */ + OrthancPluginErrorCode (*getLastExportedResource) (OrthancPluginDatabaseTransaction* transaction); + + /* Answers are read using "readAnswerDicomTag()" */ + OrthancPluginErrorCode (*getMainDicomTags) (OrthancPluginDatabaseTransaction* transaction, + int64_t id); + + /* Answer is read using "readAnswerString()" */ + OrthancPluginErrorCode (*getPublicId) (OrthancPluginDatabaseTransaction* transaction, + int64_t internalId); + + OrthancPluginErrorCode (*getResourcesCount) (OrthancPluginDatabaseTransaction* transaction, + uint64_t* target /* out */, + OrthancPluginResourceType resourceType); + + OrthancPluginErrorCode (*getResourceType) (OrthancPluginDatabaseTransaction* transaction, + OrthancPluginResourceType* target /* out */, + uint64_t resourceId); + + OrthancPluginErrorCode (*getTotalCompressedSize) (OrthancPluginDatabaseTransaction* transaction, + uint64_t* target /* out */); + + OrthancPluginErrorCode (*getTotalUncompressedSize) (OrthancPluginDatabaseTransaction* transaction, + uint64_t* target /* out */); + + OrthancPluginErrorCode (*isDiskSizeAbove) (OrthancPluginDatabaseTransaction* transaction, + uint8_t* target /* out */, + uint64_t threshold); + + OrthancPluginErrorCode (*isExistingResource) (OrthancPluginDatabaseTransaction* transaction, + uint8_t* target /* out */, + int64_t resourceId); + + OrthancPluginErrorCode (*isProtectedPatient) (OrthancPluginDatabaseTransaction* transaction, + uint8_t* target /* out */, + int64_t resourceId); + + /* Answers are read using "readAnswerInt32()" */ + OrthancPluginErrorCode (*listAvailableAttachments) (OrthancPluginDatabaseTransaction* transaction, + int64_t internalId); + + OrthancPluginErrorCode (*logChange) (OrthancPluginDatabaseTransaction* transaction, + int32_t changeType, + int64_t resourceId, + OrthancPluginResourceType resourceType, + const char* date); + + OrthancPluginErrorCode (*logExportedResource) (OrthancPluginDatabaseTransaction* transaction, + OrthancPluginResourceType resourceType, + const char* publicId, + const char* modality, + const char* date, + const char* patientId, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid); + + /* Answer is read using "readAnswerAttachment()" */ + OrthancPluginErrorCode (*lookupAttachment) (OrthancPluginDatabaseTransaction* transaction, + int64_t* revision /* out */, + int64_t resourceId, + int32_t contentType); + + /* Answer is read using "readAnswerString()" */ + OrthancPluginErrorCode (*lookupGlobalProperty) (OrthancPluginDatabaseTransaction* transaction, + const char* serverIdentifier, + int32_t property); + + /* Answer is read using "readAnswerString()" */ + OrthancPluginErrorCode (*lookupMetadata) (OrthancPluginDatabaseTransaction* transaction, + int64_t* revision /* out */, + int64_t id, + int32_t metadata); + + OrthancPluginErrorCode (*lookupParent) (OrthancPluginDatabaseTransaction* transaction, + uint8_t* isExisting /* out */, + int64_t* parentId /* out */, + int64_t id); + + OrthancPluginErrorCode (*lookupResource) (OrthancPluginDatabaseTransaction* transaction, + uint8_t* isExisting /* out */, + int64_t* id /* out */, + OrthancPluginResourceType* type /* out */, + const char* publicId); + + /* Answers are read using "readAnswerMatchingResource()" */ + OrthancPluginErrorCode (*lookupResources) (OrthancPluginDatabaseTransaction* transaction, + uint32_t constraintsCount, + const OrthancPluginDatabaseConstraint* constraints, + OrthancPluginResourceType queryLevel, + uint32_t limit, + uint8_t requestSomeInstanceId); + + /* The public ID of the parent resource is read using "readAnswerString()" */ + OrthancPluginErrorCode (*lookupResourceAndParent) (OrthancPluginDatabaseTransaction* transaction, + uint8_t* isExisting /* out */, + int64_t* id /* out */, + OrthancPluginResourceType* type /* out */, + const char* publicId); + + OrthancPluginErrorCode (*selectPatientToRecycle) (OrthancPluginDatabaseTransaction* transaction, + uint8_t* patientAvailable /* out */, + int64_t* patientId /* out */); + + OrthancPluginErrorCode (*selectPatientToRecycle2) (OrthancPluginDatabaseTransaction* transaction, + uint8_t* patientAvailable /* out */, + int64_t* patientId /* out */, + int64_t patientIdToAvoid); + + OrthancPluginErrorCode (*setGlobalProperty) (OrthancPluginDatabaseTransaction* transaction, + const char* serverIdentifier, + int32_t property, + const char* value); + + /* In "setMetadata()", the metadata might already be existing ("INSERT OR REPLACE") */ + OrthancPluginErrorCode (*setMetadata) (OrthancPluginDatabaseTransaction* transaction, + int64_t id, + int32_t metadata, + const char* value, + int64_t revision); + + OrthancPluginErrorCode (*setProtectedPatient) (OrthancPluginDatabaseTransaction* transaction, + int64_t id, + uint8_t isProtected); + + OrthancPluginErrorCode (*setResourcesContent) (OrthancPluginDatabaseTransaction* transaction, + uint32_t countIdentifierTags, + const OrthancPluginResourcesContentTags* identifierTags, + uint32_t countMainDicomTags, + const OrthancPluginResourcesContentTags* mainDicomTags, + uint32_t countMetadata, + const OrthancPluginResourcesContentMetadata* metadata); + + + } OrthancPluginDatabaseBackendV4; + +/*InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV4, ¶ms); + } + #ifdef __cplusplus } #endif diff -r 4af5f496a0dd -r 75e949689c08 OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h --- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Wed Sep 14 17:11:05 2022 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Wed Sep 14 17:11:45 2022 +0200 @@ -119,8 +119,8 @@ #endif #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_MINOR_NUMBER 12 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) @@ -536,6 +536,7 @@ _OrthancPluginService_StorageAreaRead = 5004, _OrthancPluginService_StorageAreaRemove = 5005, _OrthancPluginService_RegisterDatabaseBackendV3 = 5006, /* New in Orthanc 1.9.2 */ + _OrthancPluginService_RegisterDatabaseBackendV4 = 5007, /* New in Orthanc 1.12.0 */ /* Primitives for handling images */ _OrthancPluginService_GetImagePixelFormat = 6000, diff -r 4af5f496a0dd -r 75e949689c08 OrthancServer/Sources/Database/IDatabaseWrapper.h --- a/OrthancServer/Sources/Database/IDatabaseWrapper.h Wed Sep 14 17:11:05 2022 +0200 +++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h Wed Sep 14 17:11:45 2022 +0200 @@ -259,5 +259,7 @@ IStorageArea& storageArea) = 0; virtual bool HasRevisionsSupport() const = 0; + + virtual bool HasAttachmentCustomDataSupport() const = 0; }; } diff -r 4af5f496a0dd -r 75e949689c08 OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h --- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h Wed Sep 14 17:11:05 2022 +0200 +++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h Wed Sep 14 17:11:45 2022 +0200 @@ -97,6 +97,10 @@ return false; // TODO - REVISIONS } + virtual bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE + { + return true; + } /** * The "StartTransaction()" method is guaranteed to return a class diff -r 4af5f496a0dd -r 75e949689c08 TODO --- a/TODO Wed Sep 14 17:11:05 2022 +0200 +++ b/TODO Wed Sep 14 17:11:45 2022 +0200 @@ -1,5 +1,7 @@ TODO_CUSTOM_DATA branch - add REVISIONS in SQLite since we change the DB schema +- add revisions and customData in all Database plugins +- check if we can play with GlobalProperty_DatabasePatchLevel instead of upgrading the DB version ! - upgrade DB automatically such that it does not need a specific launch with --update , add --downgrade + --no-auto-upgrade command lines - expand the DB plugin SDK to handle customDATA - implement OrthancPluginDataBaseV4 and refuse to instantiate a PluginStorage3 if a DBv4 is not instantiated !