Mercurial > hg > orthanc
view OrthancServer/Plugins/Samples/AdvancedStorage/Plugin.cpp @ 5080:d7274e43ea7c attach-custom-data
allow plugins to store a customData in the Attachments table to e.g. store custom paths without requiring an external DB
author | Alain Mazy <am@osimis.io> |
---|---|
date | Thu, 08 Sep 2022 17:42:08 +0200 |
parents | |
children | c673997507ea |
line wrap: on
line source
/** * 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 <http://www.gnu.org/licenses/>. **/ #include "../../../../OrthancFramework/Sources/Compatibility.h" #include "../../../../OrthancFramework/Sources/OrthancException.h" #include "../../../../OrthancFramework/Sources/SystemToolbox.h" #include "../../../../OrthancFramework/Sources/Toolbox.h" #include "../../../../OrthancFramework/Sources/Logging.h" #include "../Common/OrthancPluginCppWrapper.h" #include <boost/filesystem.hpp> #include <boost/filesystem/fstream.hpp> #include <boost/iostreams/device/file_descriptor.hpp> #include <boost/iostreams/stream.hpp> #include <json/value.h> #include <json/writer.h> #include <string.h> #include <iostream> #include <algorithm> #include <map> #include <list> #include <time.h> namespace fs = boost::filesystem; fs::path absoluteRootPath_; bool fsyncOnWrite_ = true; fs::path GetLegacyRelativePath(const std::string& uuid) { if (!Orthanc::Toolbox::IsUuid(uuid)) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } fs::path path = absoluteRootPath_; path /= std::string(&uuid[0], &uuid[2]); path /= std::string(&uuid[2], &uuid[4]); path /= uuid; #if BOOST_HAS_FILESYSTEM_V3 == 1 path.make_preferred(); #endif return path; } fs::path GetAbsolutePath(const std::string& uuid, const std::string& customData) { fs::path path = absoluteRootPath_; if (!customData.empty()) { if (customData.substr(0, 2) == "1.") // version 1 { path /= customData.substr(2); } else { throw "TODO: unknown version"; } } else { path /= GetLegacyRelativePath(uuid); } path.make_preferred(); return path; } std::string GetCustomData(const fs::path& path) { return std::string("1.") + path.string(); // prefix the relative path with a version } void AddDateDicomTagToPath(fs::path& path, const Json::Value& tags, const char* tagName, const char* defaultValue = NULL) { if (tags.isMember(tagName) && tags[tagName].asString().size() == 8) { std::string date = tags[tagName].asString(); path /= date.substr(0, 4); path /= date.substr(4, 2); path /= date.substr(6, 2); } else if (defaultValue != NULL) { path /= defaultValue; } } void AddSringDicomTagToPath(fs::path& path, const Json::Value& tags, const char* tagName, const char* defaultValue = NULL) { if (tags.isMember(tagName) && tags[tagName].isString() && tags[tagName].asString().size() > 0) { path /= tags[tagName].asString(); } else if (defaultValue != NULL) { path /= defaultValue; } } void AddIntDicomTagToPath(fs::path& path, const Json::Value& tags, const char* tagName, size_t zeroPaddingWidth = 0, const char* defaultValue = NULL) { if (tags.isMember(tagName) && tags[tagName].isString() && tags[tagName].asString().size() > 0) { std::string tagValue = tags[tagName].asString(); if (zeroPaddingWidth > 0 && tagValue.size() < zeroPaddingWidth) { std::string padding(zeroPaddingWidth - tagValue.size(), '0'); path /= padding + tagValue; } else { path /= tagValue; } } else if (defaultValue != NULL) { path /= defaultValue; } } fs::path GetRelativePathFromTags(const Json::Value& tags, const char* uuid, OrthancPluginContentType type, bool isCompressed) { fs::path path; if (type == OrthancPluginContentType_Dicom || type == OrthancPluginContentType_DicomUntilPixelData) { // TODO: allow customization ... note: right now, we always need the uuid in the path !! AddDateDicomTagToPath(path, tags, "StudyDate", "NO_STUDY_DATE"); AddSringDicomTagToPath(path, tags, "PatientID"); // no default value, tag is always present if the instance is accepted by Orthanc AddSringDicomTagToPath(path, tags, "StudyInstanceUID"); AddSringDicomTagToPath(path, tags, "SeriesInstanceUID"); //AddIntDicomTagToPath(path, tags, "InstanceNumber", 8, uuid); path /= uuid; } else { path = GetLegacyRelativePath(uuid); } std::string extension; switch (type) { case OrthancPluginContentType_Dicom: extension = ".dcm"; break; case OrthancPluginContentType_DicomUntilPixelData: extension = ".dcm.head"; break; default: extension = ".unk"; } if (isCompressed) { extension = extension + ".cmp"; // compression is zlib + size -> we can not use the .zip extension } path += extension; return path; } OrthancPluginErrorCode StorageCreate(OrthancPluginMemoryBuffer* customData, const char* uuid, const Json::Value& tags, const void* content, int64_t size, OrthancPluginContentType type, bool isCompressed) { fs::path relativePath = GetRelativePathFromTags(tags, uuid, type, isCompressed); std::string customDataString = GetCustomData(relativePath); fs::path absolutePath = absoluteRootPath_ / relativePath; if (fs::exists(absolutePath)) { // Extremely unlikely case if uuid is included in the path: This Uuid has already been created // in the past. throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // TODO for the future: handle duplicates path (e.g: there's no uuid in the path and we are uploading the same file again) // OrthancPlugins::LogWarning(std::string("Overwriting file \"") + path.string() + "\" (" + uuid + ")"); } if (fs::exists(absolutePath.parent_path())) { if (!fs::is_directory(absolutePath.parent_path())) { throw Orthanc::OrthancException(Orthanc::ErrorCode_DirectoryOverFile); } } else { if (!fs::create_directories(absolutePath.parent_path())) { throw Orthanc::OrthancException(Orthanc::ErrorCode_FileStorageCannotWrite); } } Orthanc::SystemToolbox::WriteFile(content, size, absolutePath.string(), fsyncOnWrite_); OrthancPluginCreateMemoryBuffer(OrthancPlugins::GetGlobalContext(), customData, customDataString.size()); memcpy(customData->data, customDataString.data(), customDataString.size()); return OrthancPluginErrorCode_Success; } OrthancPluginErrorCode StorageCreateInstance(OrthancPluginMemoryBuffer* customData, const char* uuid, const OrthancPluginDicomInstance* instance, const void* content, int64_t size, OrthancPluginContentType type, bool isCompressed) { try { OrthancPlugins::LogInfo(std::string("Creating instance attachment \"") + uuid + "\""); OrthancPlugins::DicomInstance dicomInstance(instance); Json::Value tags; dicomInstance.GetSimplifiedJson(tags); return StorageCreate(customData, uuid, tags, content, size, type, isCompressed); } catch (Orthanc::OrthancException& e) { return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); } catch (...) { return OrthancPluginErrorCode_StorageAreaPlugin; } return OrthancPluginErrorCode_Success; } OrthancPluginErrorCode StorageCreateAttachment(OrthancPluginMemoryBuffer* customData, const char* uuid, const char* resourceId, OrthancPluginResourceType resourceType, const void* content, int64_t size, OrthancPluginContentType type, bool isCompressed) { try { OrthancPlugins::LogInfo(std::string("Creating attachment \"") + uuid + "\""); //TODO_CUSTOM_DATA: get tags from the Rest API... Json::Value tags; return StorageCreate(customData, uuid, tags, content, size, type, isCompressed); } catch (Orthanc::OrthancException& e) { return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); } catch (...) { return OrthancPluginErrorCode_StorageAreaPlugin; } return OrthancPluginErrorCode_Success; } OrthancPluginErrorCode StorageReadWhole(OrthancPluginMemoryBuffer64* target, const char* uuid, const char* customData, OrthancPluginContentType type) { OrthancPlugins::LogInfo(std::string("Reading attachment \"") + uuid + "\""); std::string path = GetAbsolutePath(uuid, customData).string(); if (!Orthanc::SystemToolbox::IsRegularFile(path)) { OrthancPlugins::LogError(std::string("The path does not point to a regular file: ") + path); return OrthancPluginErrorCode_InexistentFile; } try { fs::ifstream f; f.open(path, std::ifstream::in | std::ifstream::binary); if (!f.good()) { OrthancPlugins::LogError(std::string("The path does not point to a regular file: ") + path); return OrthancPluginErrorCode_InexistentFile; } // get file size f.seekg(0, std::ios::end); std::streamsize fileSize = f.tellg(); f.seekg(0, std::ios::beg); // The ReadWhole must allocate the buffer itself if (OrthancPluginCreateMemoryBuffer64(OrthancPlugins::GetGlobalContext(), target, fileSize) != OrthancPluginErrorCode_Success) { OrthancPlugins::LogError(std::string("Unable to allocate memory to read file: ") + path); return OrthancPluginErrorCode_NotEnoughMemory; } if (fileSize != 0) { f.read(reinterpret_cast<char*>(target->data), fileSize); } f.close(); } catch (...) { OrthancPlugins::LogError(std::string("Unexpected error while reading: ") + path); return OrthancPluginErrorCode_StorageAreaPlugin; } return OrthancPluginErrorCode_Success; } OrthancPluginErrorCode StorageReadRange (OrthancPluginMemoryBuffer64* target, const char* uuid, const char* customData, OrthancPluginContentType type, uint64_t rangeStart) { OrthancPlugins::LogInfo(std::string("Reading attachment \"") + uuid + "\""); std::string path = GetAbsolutePath(uuid, customData).string(); if (!Orthanc::SystemToolbox::IsRegularFile(path)) { OrthancPlugins::LogError(std::string("The path does not point to a regular file: ") + path); return OrthancPluginErrorCode_InexistentFile; } try { fs::ifstream f; f.open(path, std::ifstream::in | std::ifstream::binary); if (!f.good()) { OrthancPlugins::LogError(std::string("The path does not point to a regular file: ") + path); return OrthancPluginErrorCode_InexistentFile; } f.seekg(rangeStart, std::ios::beg); // The ReadRange uses a target that has already been allocated by orthanc f.read(reinterpret_cast<char*>(target->data), target->size); f.close(); } catch (...) { OrthancPlugins::LogError(std::string("Unexpected error while reading: ") + path); return OrthancPluginErrorCode_StorageAreaPlugin; } return OrthancPluginErrorCode_Success; } OrthancPluginErrorCode StorageRemove (const char* uuid, const char* customData, OrthancPluginContentType type) { // LOG(INFO) << "Deleting attachment \"" << uuid << "\" of type " << static_cast<int>(type); fs::path p = GetAbsolutePath(uuid, customData); try { fs::remove(p); } catch (...) { // Ignore the error } // Remove the empty parent directories, (ignoring the error code if these directories are not empty) try { fs::path parent = p.parent_path(); while (parent != absoluteRootPath_) { fs::remove(parent); parent = parent.parent_path(); } } catch (...) { // Ignore the error } return OrthancPluginErrorCode_Success; } extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { OrthancPlugins::SetGlobalContext(context); Orthanc::Logging::InitializePluginContext(context); /* Check the version of the Orthanc core */ if (OrthancPluginCheckVersion(context) == 0) { OrthancPlugins::ReportMinimalOrthancVersion(ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); return -1; } OrthancPlugins::LogWarning("AdvancedStorage plugin is initializing"); OrthancPluginSetDescription(context, "Provides alternative layout for your storage."); OrthancPlugins::OrthancConfiguration orthancConfiguration; OrthancPlugins::OrthancConfiguration advancedStorage; orthancConfiguration.GetSection(advancedStorage, "AdvancedStorage"); bool enabled = advancedStorage.GetBooleanValue("Enable", false); if (enabled) { /* { "AdvancedStorage": { // Enables/disables the plugin "Enable": false, } } */ absoluteRootPath_ = fs::absolute(fs::path(orthancConfiguration.GetStringValue("StorageDirectory", "OrthancStorage"))); LOG(WARNING) << "AdvancedStorage - Path to the storage area: " << absoluteRootPath_.string(); OrthancPluginRegisterStorageArea3(context, StorageCreateInstance, StorageCreateAttachment, StorageReadWhole, StorageReadRange, StorageRemove); } else { OrthancPlugins::LogWarning("AdvancedStorage plugin is disabled by the configuration file"); } return 0; } ORTHANC_PLUGINS_API void OrthancPluginFinalize() { OrthancPlugins::LogWarning("AdvancedStorage plugin is finalizing"); } ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { return "advanced-storage"; } ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() { return ORTHANC_PLUGIN_VERSION; } }