# HG changeset patch # User Alain Mazy <am@orthanc.team> # Date 1742491464 -3600 # Node ID 78f4c1da54d3d942dcaa21d3b7219a7e0630de11 # Parent 55f171e6ea4a123ed047a5d74c019e05a5140c02# Parent e83414b2b98dc1384cdc64a5d566de414fcbbddb merge diff -r e83414b2b98d -r 78f4c1da54d3 OrthancServer/Plugins/Engine/OrthancPlugins.cpp --- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Tue Mar 18 13:39:20 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Thu Mar 20 18:24:24 2025 +0100 @@ -4726,6 +4726,59 @@ reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendMultipartItem(p.answer, p.answerSize, headers); } + // static FileInfo CreateFileInfoFromPluginAttachment(OrthancPluginAttachment* attachment) + // { + // return FileInfo(attachment->uuid, + // Orthanc::Plugins::Convert(attachment->contentType), + // attachment->uncompressedSize, + // attachment->uncompressedHash + // ) fileInfo() + // } + + static FileInfo Convert(const OrthancPluginAttachment2& attachment) + { + std::string uuid, customData; + if (attachment.uuid != NULL) + { + uuid = attachment.uuid; + } + else + { + uuid = Toolbox::GenerateUuid(); + } + + if (attachment.customData != NULL) + { + customData = std::string(reinterpret_cast<const char*>(attachment.customData), attachment.customDataSize); + } + + return FileInfo(uuid, + Orthanc::Plugins::Convert(static_cast<OrthancPluginContentType>(attachment.contentType)), + attachment.uncompressedSize, + attachment.uncompressedHash, + Orthanc::Plugins::Convert(static_cast<OrthancPluginCompressionType>(attachment.compressionType)), + attachment.compressedSize, + attachment.compressedHash, + customData); + } + + void OrthancPlugins::ApplyAdoptAttachment(const _OrthancPluginAdoptAttachment& parameters) + { + PImpl::ServerContextReference lock(*pimpl_); + FileInfo adoptedFile = Convert(*(parameters.attachmentInfo)); + + if (adoptedFile.GetContentType() == FileContentType_Dicom) + { + std::unique_ptr<DicomInstanceToStore> dicom(DicomInstanceToStore::CreateFromBuffer(parameters.buffer, parameters.bufferSize)); + dicom->SetOrigin(DicomInstanceOrigin::FromPlugins()); + + std::string resultPublicId; + + ServerContext::StoreResult result = lock.GetContext().AdoptAttachment(resultPublicId, *dicom, StoreInstanceMode_Default, adoptedFile); + + // TODO_ATTACH_CUSTOM_DATA: handle result + } + } void OrthancPlugins::ApplyLoadDicomInstance(const _OrthancPluginLoadDicomInstance& params) { @@ -5816,6 +5869,14 @@ return true; } + case _OrthancPluginService_AdoptAttachment: + { + const _OrthancPluginAdoptAttachment& p = + *reinterpret_cast<const _OrthancPluginAdoptAttachment*>(parameters); + ApplyAdoptAttachment(p); + return true; + } + default: return false; } diff -r e83414b2b98d -r 78f4c1da54d3 OrthancServer/Plugins/Engine/OrthancPlugins.h --- a/OrthancServer/Plugins/Engine/OrthancPlugins.h Tue Mar 18 13:39:20 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h Thu Mar 20 18:24:24 2025 +0100 @@ -223,6 +223,8 @@ void ApplyLoadDicomInstance(const _OrthancPluginLoadDicomInstance& parameters); + void ApplyAdoptAttachment(const _OrthancPluginAdoptAttachment& parameters); + void ComputeHash(_OrthancPluginService service, const void* parameters); diff -r e83414b2b98d -r 78f4c1da54d3 OrthancServer/Plugins/Engine/PluginsEnumerations.cpp --- a/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp Tue Mar 18 13:39:20 2025 +0100 +++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp Thu Mar 20 18:24:24 2025 +0100 @@ -680,5 +680,21 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } + + CompressionType Convert(OrthancPluginCompressionType type) + { + switch (type) + { + case OrthancPluginCompressionType_None: + return CompressionType_None; + + case OrthancPluginCompressionType_ZlibWithSize: + return CompressionType_ZlibWithSize; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + } } diff -r e83414b2b98d -r 78f4c1da54d3 OrthancServer/Plugins/Engine/PluginsEnumerations.h --- a/OrthancServer/Plugins/Engine/PluginsEnumerations.h Tue Mar 18 13:39:20 2025 +0100 +++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.h Thu Mar 20 18:24:24 2025 +0100 @@ -75,6 +75,8 @@ OrthancPluginConstraintType Convert(ConstraintType constraint); OrthancPluginCompressionType Convert(CompressionType type); + + CompressionType Convert(OrthancPluginCompressionType type); } } diff -r e83414b2b98d -r 78f4c1da54d3 OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h --- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Tue Mar 18 13:39:20 2025 +0100 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Thu Mar 20 18:24:24 2025 +0100 @@ -469,6 +469,7 @@ _OrthancPluginService_SetMetricsIntegerValue = 43, /* New in Orthanc 1.12.1 */ _OrthancPluginService_SetCurrentThreadName = 44, /* New in Orthanc 1.12.2 */ _OrthancPluginService_LogMessage = 45, /* New in Orthanc 1.12.4 */ + _OrthancPluginService_AdoptAttachment = 46, /* New in Orthanc 1.12.7 */ /* Registration of callbacks */ @@ -1480,7 +1481,7 @@ * @param target Memory buffer where to store the content of the file. It must be allocated by the * plugin using OrthancPluginCreateMemoryBuffer64(). The core of Orthanc will free it. * @param uuid The UUID of the file of interest. - * @param customData The custom data of the file to be removed. + * @param customData The custom data of the file of interest. * @param type The content type corresponding to this file. * @ingroup Callbacks **/ @@ -1504,7 +1505,7 @@ * The memory buffer is allocated and freed by Orthanc. The length of the range * of interest corresponds to the size of this buffer. * @param uuid The UUID of the file of interest. - * @param customData The custom data of the file to be removed. + * @param customData The custom data of the file of interest. * @param type The content type corresponding to this file. * @param rangeStart Start position of the requested range in the file. * @return 0 if success, other value if error. @@ -9766,6 +9767,61 @@ return context->InvokeService(context, _OrthancPluginService_SendStreamChunk, ¶ms); } + typedef struct + { + const char* uuid; + int32_t contentType; + uint64_t uncompressedSize; + const char* uncompressedHash; + int32_t compressionType; + uint64_t compressedSize; + const char* compressedHash; + const void* customData; + uint64_t customDataSize; + } OrthancPluginAttachment2; + + + typedef struct + { + const void* buffer; /* in */ + uint64_t bufferSize; /* in, can be only the beginning of a DICOM file (until the pixel data) */ + // TODO_ATTACH_CUSTOM_DATA uint64_t pixelDataOffset; /* in, zero = undefined */ + OrthancPluginAttachment2* attachmentInfo; /* in, uuid may not be defined */ + OrthancPluginResourceType attachToResourceType; /* in */ + const char* attachToResourceId; /* in */ + OrthancPluginMemoryBuffer* createdResourceId; /* out */ + OrthancPluginMemoryBuffer* attachmentUuid; /* out */ + } _OrthancPluginAdoptAttachment; + + /** + * @brief Tell Orthanc to adopt an existing attachment. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). +TODO_ATTACH_CUSTOM_DATA TODO TODO + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginAdoptAttachment( + OrthancPluginContext* context, + const void* buffer, + uint64_t bufferSize, + // TODO_ATTACH_CUSTOM_DATA uint64_t pixelDataOffset, + OrthancPluginAttachment2* attachmentInfo, + OrthancPluginResourceType attachToResourceType, + const char* attachToResourceId, + OrthancPluginMemoryBuffer* createdResourceId, /* out */ + OrthancPluginMemoryBuffer* attachmentUuid) /* out */ + { + _OrthancPluginAdoptAttachment params; + params.buffer = buffer; + params.bufferSize = bufferSize; + // TODO_ATTACH_CUSTOM_DATA ? params.pixelDataOffset = pixelDataOffset; + params.attachmentInfo = attachmentInfo; + params.attachToResourceType = attachToResourceType; + params.attachToResourceId = attachToResourceId; + params.createdResourceId = createdResourceId; + params.attachmentUuid = attachmentUuid; + + return context->InvokeService(context, _OrthancPluginService_AdoptAttachment, ¶ms); + } #ifdef __cplusplus } diff -r e83414b2b98d -r 78f4c1da54d3 OrthancServer/Sources/ServerContext.cpp --- a/OrthancServer/Sources/ServerContext.cpp Tue Mar 18 13:39:20 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Thu Mar 20 18:24:24 2025 +0100 @@ -606,6 +606,37 @@ StoreInstanceMode mode, bool isReconstruct) { + FileInfo adoptedFileNotUsed; + + return StoreAfterTranscoding(resultPublicId, + dicom, + mode, + isReconstruct, + false, + adoptedFileNotUsed); + } + + ServerContext::StoreResult ServerContext::AdoptAttachment(std::string& resultPublicId, + DicomInstanceToStore& dicom, + StoreInstanceMode mode, + const FileInfo& adoptedFile) + { + return StoreAfterTranscoding(resultPublicId, + dicom, + mode, + false, + true, + adoptedFile); + } + + + ServerContext::StoreResult ServerContext::StoreAfterTranscoding(std::string& resultPublicId, + DicomInstanceToStore& dicom, + StoreInstanceMode mode, + bool isReconstruct, + bool isAdoption, + const FileInfo& adoptedFile) + { bool overwrite; switch (mode) { @@ -708,10 +739,18 @@ // TODO Should we use "gzip" instead? CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None); - FileInfo dicomInfo = accessor.Write(dicom.GetBufferData(), dicom.GetBufferSize(), FileContentType_Dicom, compression, storeMD5_, &dicom); + ServerIndex::Attachments attachments; + FileInfo dicomInfo; - ServerIndex::Attachments attachments; - attachments.push_back(dicomInfo); + if (!isAdoption) + { + dicomInfo = accessor.Write(dicom.GetBufferData(), dicom.GetBufferSize(), FileContentType_Dicom, compression, storeMD5_, &dicom); + attachments.push_back(dicomInfo); + } + else + { + attachments.push_back(adoptedFile); + } FileInfo dicomUntilPixelData; if (hasPixelDataOffset && @@ -763,7 +802,10 @@ if (result.GetStatus() != StoreStatus_Success) { - accessor.Remove(dicomInfo); + if (!isAdoption) + { + accessor.Remove(dicomInfo); + } if (dicomUntilPixelData.IsValid()) { @@ -777,7 +819,14 @@ switch (result.GetStatus()) { case StoreStatus_Success: - LOG(INFO) << "New instance stored (" << resultPublicId << ")"; + if (isAdoption) + { + LOG(INFO) << "New instance adopted (" << resultPublicId << ")"; + } + else + { + LOG(INFO) << "New instance stored (" << resultPublicId << ")"; + } break; case StoreStatus_AlreadyStored: diff -r e83414b2b98d -r 78f4c1da54d3 OrthancServer/Sources/ServerContext.h --- a/OrthancServer/Sources/ServerContext.h Tue Mar 18 13:39:20 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.h Thu Mar 20 18:24:24 2025 +0100 @@ -269,6 +269,13 @@ StoreInstanceMode mode, bool isReconstruct); + StoreResult StoreAfterTranscoding(std::string& resultPublicId, + DicomInstanceToStore& dicom, + StoreInstanceMode mode, + bool isReconstruct, + bool isAdoption, + const FileInfo& adoptedFile); + // This method must only be called from "ServerIndex"! void RemoveFile(const std::string& fileUuid, FileContentType type, @@ -357,6 +364,11 @@ DicomInstanceToStore& dicom, StoreInstanceMode mode); + StoreResult AdoptAttachment(std::string& resultPublicId, + DicomInstanceToStore& dicom, + StoreInstanceMode mode, + const FileInfo& adoptedFile); + StoreResult TranscodeAndStore(std::string& resultPublicId, DicomInstanceToStore* dicom, StoreInstanceMode mode,