Mercurial > hg > orthanc
changeset 5995:984f46eb8e04 attach-custom-data
removed AdvancedStorage plugin (moved to its own repo)
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Fri, 31 Jan 2025 16:30:50 +0100 |
parents | bad103ab55c5 |
children | 384f46a99abf |
files | OrthancServer/CMakeLists.txt OrthancServer/Plugins/Samples/AdvancedStorage/CMakeLists.txt OrthancServer/Plugins/Samples/AdvancedStorage/OrthancFrameworkDependencies.cpp OrthancServer/Plugins/Samples/AdvancedStorage/Plugin.cpp TODO |
diffstat | 5 files changed, 0 insertions(+), 1191 deletions(-) [+] |
line wrap: on
line diff
--- a/OrthancServer/CMakeLists.txt Thu Jan 30 17:41:33 2025 +0100 +++ b/OrthancServer/CMakeLists.txt Fri Jan 31 16:30:50 2025 +0100 @@ -62,7 +62,6 @@ SET(BUILD_CONNECTIVITY_CHECKS ON CACHE BOOL "Whether to build the ConnectivityChecks plugin") SET(BUILD_HOUSEKEEPER ON CACHE BOOL "Whether to build the Housekeeper plugin") SET(BUILD_DELAYED_DELETION ON CACHE BOOL "Whether to build the DelayedDeletion plugin") -SET(BUILD_ADVANCED_STORAGE ON CACHE BOOL "Whether to build the AdvancedStorage plugin") SET(BUILD_MULTITENANT_DICOM ON CACHE BOOL "Whether to build the MultitenantDicom plugin") SET(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins") SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests") @@ -810,64 +809,6 @@ ##################################################################### -## Build the "AdvancedStorage" plugin -##################################################################### - -if (ENABLE_PLUGINS AND BUILD_ADVANCED_STORAGE) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - execute_process( - COMMAND - ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/../OrthancFramework/Resources/WindowsResources.py - ${ORTHANC_VERSION} AdvancedStorage AdvancedStorage.dll "Orthanc plugin to provide advanced file storage" - ERROR_VARIABLE Failure - OUTPUT_FILE ${AUTOGENERATED_DIR}/AdvancedStorage.rc - ) - - if (Failure) - message(FATAL_ERROR "Error while computing the version information: ${Failure}") - endif() - - list(APPEND ADVANCED_STORAGE_RESOURCES ${AUTOGENERATED_DIR}/AdvancedStorage.rc) - endif() - - EmbedResources( - --target=AdvancedStorageDicomResources - --namespace=Orthanc.FrameworkResources - --framework-path=${CMAKE_SOURCE_DIR}/../OrthancFramework/Sources - ${LIBICU_RESOURCES} - ${DCMTK_DICTIONARIES} - ) - - set_source_files_properties( - ${CMAKE_SOURCE_DIR}/Plugins/Samples/AdvancedStorage/Plugin.cpp - PROPERTIES COMPILE_DEFINITIONS "ADVANCED_STORAGE_VERSION=\"${ORTHANC_VERSION}\"" - ) - - add_library(AdvancedStorage SHARED - ${CMAKE_SOURCE_DIR}/Plugins/Samples/AdvancedStorage/Plugin.cpp - ${CMAKE_SOURCE_DIR}/Plugins/Samples/AdvancedStorage/OrthancFrameworkDependencies.cpp - ${AUTOGENERATED_DIR}/AdvancedStorageDicomResources.cpp - ${ADVANCED_STORAGE_RESOURCES} - ) - - DefineSourceBasenameForTarget(AdvancedStorage) - - target_link_libraries(AdvancedStorage PluginsDependencies) - - set_target_properties( - AdvancedStorage PROPERTIES - VERSION ${ORTHANC_VERSION} - SOVERSION ${ORTHANC_VERSION} - ) - - install( - TARGETS AdvancedStorage - RUNTIME DESTINATION lib # Destination for Windows - LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux - ) -endif() - -##################################################################### ## Build the "MultitenantDicom" plugin #####################################################################
--- a/OrthancServer/Plugins/Samples/AdvancedStorage/CMakeLists.txt Thu Jan 30 17:41:33 2025 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -# 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/>. - - -cmake_minimum_required(VERSION 2.8) -cmake_policy(SET CMP0058 NEW) - -project(AdvancedStorage) - -SET(PLUGIN_VERSION "mainline" CACHE STRING "Version of the plugin") - -include(${CMAKE_CURRENT_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake) -include(${CMAKE_CURRENT_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake) - -include(${CMAKE_CURRENT_LIST_DIR}/../Common/OrthancPluginsExports.cmake) - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - execute_process( - COMMAND - ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/WindowsResources.py - ${PLUGIN_VERSION} AdvancedStorage AdvancedStorage.dll "Orthanc plugin to extend Orthanc Storage" - ERROR_VARIABLE Failure - OUTPUT_FILE ${AUTOGENERATED_DIR}/AdvancedStorage.rc - ) - - if (Failure) - message(FATAL_ERROR "Error while computing the version information: ${Failure}") - endif() - - list(APPEND ADDITIONAL_RESOURCES ${AUTOGENERATED_DIR}/AdvancedStorage.rc) -endif() - -add_definitions( - -DHAS_ORTHANC_EXCEPTION=1 - -DORTHANC_PLUGIN_VERSION="${PLUGIN_VERSION}" - ) - -include_directories( - ${CMAKE_SOURCE_DIR}/../../Include/ - ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/ - ) - -add_library(AdvancedStorage SHARED - ${ADDITIONAL_RESOURCES} - ${AUTOGENERATED_SOURCES} - ${ORTHANC_CORE_SOURCES_DEPENDENCIES} - ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/Enumerations.cpp - ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/Logging.cpp - ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/OrthancException.cpp - ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/SystemToolbox.cpp - ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/Toolbox.cpp - ${CMAKE_SOURCE_DIR}/../../../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp - Plugin.cpp - ) - -set_target_properties( - AdvancedStorage PROPERTIES - VERSION ${PLUGIN_VERSION} - SOVERSION ${PLUGIN_VERSION} - ) - -install( - TARGETS AdvancedStorage - DESTINATION . - )
--- a/OrthancServer/Plugins/Samples/AdvancedStorage/OrthancFrameworkDependencies.cpp Thu Jan 30 17:41:33 2025 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2023 Osimis S.A., Belgium - * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium - * Copyright (C) 2021-2024 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/>. - **/ - - -#if defined(_WIN32) -# if !defined(NOMINMAX) -# define NOMINMAX -# endif -// Make sure that "winsock2.h" is included before "winsock.h" -# include <winsock2.h> -#endif - -#include "../../../../OrthancFramework/Sources/ChunkedBuffer.cpp" -#include "../../../../OrthancFramework/Sources/Compression/DeflateBaseCompressor.cpp" -#include "../../../../OrthancFramework/Sources/Compression/GzipCompressor.cpp" -#include "../../../../OrthancFramework/Sources/Compression/ZlibCompressor.cpp" -#include "../../../../OrthancFramework/Sources/DicomFormat/DicomArray.cpp" -#include "../../../../OrthancFramework/Sources/DicomFormat/DicomElement.cpp" -#include "../../../../OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp" -#include "../../../../OrthancFramework/Sources/DicomFormat/DicomInstanceHasher.cpp" -#include "../../../../OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp" -#include "../../../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp" -#include "../../../../OrthancFramework/Sources/DicomFormat/DicomPath.cpp" -#include "../../../../OrthancFramework/Sources/DicomFormat/DicomTag.cpp" -#include "../../../../OrthancFramework/Sources/DicomFormat/DicomValue.cpp" -// #include "../../../../OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp" -// #include "../../../../OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.cpp" -// #include "../../../../OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.cpp" -// #include "../../../../OrthancFramework/Sources/DicomNetworking/DicomServer.cpp" -// #include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp" -// #include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.cpp" -// #include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/FindScp.cpp" -// #include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/GetScp.cpp" -// #include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/MoveScp.cpp" -// #include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp" -// #include "../../../../OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp" -#include "../../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp" -#include "../../../../OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.cpp" -#include "../../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp" -#include "../../../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp" -#include "../../../../OrthancFramework/Sources/DicomParsing/ToDcmtkBridge.cpp" -#include "../../../../OrthancFramework/Sources/Enumerations.cpp" -#include "../../../../OrthancFramework/Sources/HttpServer/HttpOutput.cpp" -#include "../../../../OrthancFramework/Sources/Images/IImageWriter.cpp" -#include "../../../../OrthancFramework/Sources/Images/Image.cpp" -#include "../../../../OrthancFramework/Sources/Images/ImageAccessor.cpp" -#include "../../../../OrthancFramework/Sources/Images/ImageBuffer.cpp" -#include "../../../../OrthancFramework/Sources/Images/ImageProcessing.cpp" -#include "../../../../OrthancFramework/Sources/Images/JpegErrorManager.cpp" -#include "../../../../OrthancFramework/Sources/Images/JpegReader.cpp" -#include "../../../../OrthancFramework/Sources/Images/JpegWriter.cpp" -#include "../../../../OrthancFramework/Sources/Images/PamReader.cpp" -#include "../../../../OrthancFramework/Sources/Images/PamWriter.cpp" -#include "../../../../OrthancFramework/Sources/Images/PngReader.cpp" -#include "../../../../OrthancFramework/Sources/Images/PngWriter.cpp" -#include "../../../../OrthancFramework/Sources/Logging.cpp" -// #include "../../../../OrthancFramework/Sources/MultiThreading/RunnableWorkersPool.cpp" -// #include "../../../../OrthancFramework/Sources/MultiThreading/SharedMessageQueue.cpp" -#include "../../../../OrthancFramework/Sources/OrthancException.cpp" -#include "../../../../OrthancFramework/Sources/OrthancFramework.cpp" -#include "../../../../OrthancFramework/Sources/RestApi/RestApiOutput.cpp" -#include "../../../../OrthancFramework/Sources/SerializationToolbox.cpp" -#include "../../../../OrthancFramework/Sources/SystemToolbox.cpp" -#include "../../../../OrthancFramework/Sources/TemporaryFile.cpp" -#include "../../../../OrthancFramework/Sources/Toolbox.cpp" - -namespace Orthanc -{ - void HttpClient::GlobalInitialize() - { - } - - void HttpClient::GlobalFinalize() - { - } -}
--- a/OrthancServer/Plugins/Samples/AdvancedStorage/Plugin.cpp Thu Jan 30 17:41:33 2025 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,923 +0,0 @@ -/** - * 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/>. - **/ - -#define ORTHANC_PLUGIN_NAME "advanced-storage" - -#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 "../../../../OrthancFramework/Sources/DicomFormat/DicomInstanceHasher.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 rootPath_; -bool multipleStoragesEnabled_ = false; -std::map<std::string, fs::path> rootPaths_; -std::string currentStorageId_; -std::string namingScheme_; -std::string otherAttachmentsPrefix_; -bool fsyncOnWrite_ = true; -size_t maxPathLength_ = 256; -size_t legacyPathLength = 39; // ex "/00/f7/00f7fd8b-47bd8c3a-ff917804-d180cdbc-40cf9527" - -fs::path GetRootPath() -{ - if (multipleStoragesEnabled_) - { - return rootPaths_[currentStorageId_]; - } - - return rootPath_; -} - -fs::path GetRootPath(const std::string& storageId) -{ - if (multipleStoragesEnabled_) - { - if (rootPaths_.find(storageId) == rootPaths_.end()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, std::string("Advanced Storage - storage '" + storageId + "' is not defined in configuration")); - } - return rootPaths_[storageId]; - } - - return rootPath_; -} - - -fs::path GetLegacyRelativePath(const std::string& uuid) -{ - if (!Orthanc::Toolbox::IsUuid(uuid)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - fs::path path; - - if (!otherAttachmentsPrefix_.empty()) - { - path /= otherAttachmentsPrefix_; - } - - 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 GetPath(const std::string& uuid, const std::string& customDataString) -{ - fs::path path; - - if (!customDataString.empty()) - { - Json::Value customData; - Orthanc::Toolbox::ReadJson(customData, customDataString); - - if (customData["v"].asInt() == 1) // Version - { - if (customData.isMember("s")) // Storage ID - { - path = GetRootPath(customData["s"].asString()); - } - else - { - path = GetRootPath(); - } - - if (customData.isMember("p")) // Path - { - path /= customData["p"].asString(); - } - else - { // we are in "legacy mode" for the path part - path /= GetLegacyRelativePath(uuid); - } - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, std::string("Advanced Storage - unknown version for custom data '" + boost::lexical_cast<std::string>(customData["Version"].asInt()) + "'")); - } - } - else // we are in "legacy mode" - { - path = rootPath_; - path /= GetLegacyRelativePath(uuid); - } - - path.make_preferred(); - return path; -} - -void GetCustomData(std::string& output, const fs::path& path) -{ - // if we use defaults, non need to store anything in the metadata, the plugin has the same behavior as the core of Orthanc - if (namingScheme_ == "OrthancDefault" && !multipleStoragesEnabled_) - { - return; - } - - Json::Value customDataJson; - // Keep short field names to reduce SQL data usage - customDataJson["v"] = 1; // Version - - // no need to store the path since if we are in the default mode - if (namingScheme_ != "OrthancDefault") - { - customDataJson["p"] = path.string(); // Path - } - - if (multipleStoragesEnabled_) - { - customDataJson["s"] = currentStorageId_; // Storage id - } - - return Orthanc::Toolbox::WriteFastJson(output, customDataJson); -} - -std::string GetSplitDateDicomTagToPath(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(); - return date.substr(0, 4) + "/" + date.substr(4, 2) + "/" + date.substr(6, 2); - } - else if (defaultValue != NULL) - { - return defaultValue; - } - - return ""; -} - -std::string GetStringDicomTagForPath(const Json::Value& tags, const std::string& tagName, const char* defaultValue = NULL) -{ - if (tags.isMember(tagName) && tags[tagName].isString() && tags[tagName].asString().size() > 0) - { - return tags[tagName].asString(); - } - else if (defaultValue != NULL) - { - return defaultValue; - } - - return ""; -} - -std::string GetIntDicomTagForPath(const Json::Value& tags, const std::string& tagName, const char* defaultValue = NULL, size_t padding = 0) -{ - if (tags.isMember(tagName)) - { - std::string value; - if (tags[tagName].isInt()) - { - value = boost::lexical_cast<std::string>(tags[tagName].asInt()); - } - else if (tags[tagName].isString()) - { - value = tags[tagName].asString(); - } - - if (padding > 0 && padding > value.size()) - { - value = std::string(padding - value.size(), '0') + value; - } - return value; - } - else if (defaultValue != NULL) - { - return defaultValue; - } - - return ""; -} - -void ReplaceTagKeyword(std::string& folderName, const std::string& keyword, const Json::Value& tags, const char* defaultValue, const char* tagKey = NULL) -{ - if (folderName.find(keyword) != std::string::npos) - { - std::string key = keyword.substr(1, keyword.size() -2); - if (tagKey != NULL) - { - key = tagKey; - } - boost::replace_all(folderName, keyword, GetStringDicomTagForPath(tags, key, defaultValue)); - } -} - -void ReplaceIntTagKeyword(std::string& folderName, const std::string& keyword, const Json::Value& tags, const char* defaultValue, size_t padding, const char* tagKey = NULL) -{ - if (folderName.find(keyword) != std::string::npos) - { - std::string key = keyword.substr(1, keyword.size() -2); - if (tagKey != NULL) - { - key = tagKey; - } - boost::replace_all(folderName, keyword, GetIntDicomTagForPath(tags, key, defaultValue, padding)); - } -} - - -void ReplaceOrthancID(std::string& folderName, const std::string& keyword, const std::string& id, size_t from, size_t length) -{ - if (length == 0) - { - boost::replace_all(folderName, keyword, id); - } - else - { - boost::replace_all(folderName, keyword, id.substr(from, length)); - } -} - - - -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; - } -} - -std::string GetExtension(OrthancPluginContentType type, bool isCompressed) -{ - 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 - } - - return extension; -} - -fs::path GetRelativePathFromTags(const Json::Value& tags, const char* uuid, OrthancPluginContentType type, bool isCompressed) -{ - fs::path path; - - if (!tags.isNull()) - { - std::vector<std::string> folderNames; - Orthanc::Toolbox::SplitString(folderNames, namingScheme_, '/'); - - for (std::vector<std::string>::const_iterator it = folderNames.begin(); it != folderNames.end(); ++it) - { - std::string folderName = *it; - - if (folderName.find("{split(StudyDate)}") != std::string::npos) - { - boost::replace_all(folderName, "{split(StudyDate)}", GetSplitDateDicomTagToPath(tags, "StudyDate", "NO_STUDY_DATE")); - } - - if (folderName.find("{split(PatientBirthDate)}") != std::string::npos) - { - boost::replace_all(folderName, "{split(PatientBirthDate)}", GetSplitDateDicomTagToPath(tags, "PatientBirthDate", "NO_PATIENT_BIRTH_DATE")); - } - - ReplaceTagKeyword(folderName, "{PatientID}", tags, "NO_PATIENT_ID"); - ReplaceTagKeyword(folderName, "{PatientBirthDate}", tags, "NO_PATIENT_BIRTH_DATE"); - ReplaceTagKeyword(folderName, "{PatientName}", tags, "NO_PATIENT_NAME"); - ReplaceTagKeyword(folderName, "{PatientSex}", tags, "NO_PATIENT_SEX"); - ReplaceTagKeyword(folderName, "{StudyInstanceUID}", tags, "NO_STUDY_INSTANCE_UID"); - ReplaceTagKeyword(folderName, "{StudyDate}", tags, "NO_STUDY_DATE"); - ReplaceTagKeyword(folderName, "{StudyID}", tags, "NO_STUDY_ID"); - ReplaceTagKeyword(folderName, "{StudyDescription}", tags, "NO_STUDY_DESCRIPTION"); - ReplaceTagKeyword(folderName, "{AccessionNumber}", tags, "NO_ACCESSION_NUMBER"); - ReplaceTagKeyword(folderName, "{SeriesInstanceUID}", tags, "NO_SERIES_INSTANCE_UID"); - ReplaceTagKeyword(folderName, "{SeriesDate}", tags, "NO_SERIES_DATE"); - ReplaceTagKeyword(folderName, "{SeriesDescription}", tags, "NO_SERIES_DESCRIPTION"); - ReplaceTagKeyword(folderName, "{SOPInstanceUID}", tags, "NO_SOP_INSTANCE_UID"); - ReplaceIntTagKeyword(folderName, "{SeriesNumber}", tags, "NO_SERIES_NUMBER", 0); - ReplaceIntTagKeyword(folderName, "{InstanceNumber}", tags, "NO_INSTANCE_NUMBER", 0); - ReplaceIntTagKeyword(folderName, "{pad4(SeriesNumber)}", tags, "NO_SERIES_NUMBER", 4, "SeriesNumber"); - ReplaceIntTagKeyword(folderName, "{pad4(InstanceNumber)}", tags, "NO_INSTANCE_NUMBER", 4, "InstanceNumber"); - ReplaceIntTagKeyword(folderName, "{pad6(SeriesNumber)}", tags, "NO_SERIES_NUMBER", 6, "SeriesNumber"); - ReplaceIntTagKeyword(folderName, "{pad6(InstanceNumber)}", tags, "NO_INSTANCE_NUMBER", 6, "InstanceNumber"); - ReplaceIntTagKeyword(folderName, "{pad8(SeriesNumber)}", tags, "NO_SERIES_NUMBER", 8, "SeriesNumber"); - ReplaceIntTagKeyword(folderName, "{pad8(InstanceNumber)}", tags, "NO_INSTANCE_NUMBER", 8, "InstanceNumber"); - - Orthanc::DicomInstanceHasher hasher(tags["PatientID"].asString(), tags["StudyInstanceUID"].asString(), tags["SeriesInstanceUID"].asString(), tags["SOPInstanceUID"].asString()); - std::string orthancPatientId = hasher.HashPatient(); - std::string orthancStudyId = hasher.HashStudy(); - std::string orthancSeriesId = hasher.HashSeries(); - std::string orthancInstanceId = hasher.HashInstance(); - - ReplaceOrthancID(folderName, "{OrthancPatientID}", orthancPatientId, 0, 0); - ReplaceOrthancID(folderName, "{OrthancStudyID}", orthancStudyId, 0, 0); - ReplaceOrthancID(folderName, "{OrthancSeriesID}", orthancSeriesId, 0, 0); - ReplaceOrthancID(folderName, "{OrthancInstanceID}", orthancInstanceId, 0, 0); - - ReplaceOrthancID(folderName, "{01(OrthancPatientID)}", orthancPatientId, 0, 2); - ReplaceOrthancID(folderName, "{01(OrthancStudyID)}", orthancStudyId, 0, 2); - ReplaceOrthancID(folderName, "{01(OrthancSeriesID)}", orthancSeriesId, 0, 2); - ReplaceOrthancID(folderName, "{01(OrthancInstanceID)}", orthancInstanceId, 0, 2); - - ReplaceOrthancID(folderName, "{23(OrthancPatientID)}", orthancPatientId, 2, 2); - ReplaceOrthancID(folderName, "{23(OrthancStudyID)}", orthancStudyId, 2, 2); - ReplaceOrthancID(folderName, "{23(OrthancSeriesID)}", orthancSeriesId, 2, 2); - ReplaceOrthancID(folderName, "{23(OrthancInstanceID)}", orthancInstanceId, 2, 2); - - if (folderName.find("{UUID}") != std::string::npos) - { - boost::replace_all(folderName, "{UUID}", uuid); - } - - if (folderName.find("{.ext}") != std::string::npos) - { - boost::replace_all(folderName, "{.ext}", GetExtension(type, isCompressed)); - } - - path /= folderName; - } - - return path; - } - - return GetLegacyRelativePath(uuid); -} - - -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); - fs::path rootPath = GetRootPath(); - fs::path path = rootPath / relativePath; - - // check that the final path is not 'above' the root path (this could happen if e.g., a PatientName is ../../../../toto) - // fs::canonical() can not be used for that since the file needs to exist - // so far, we'll just forbid path containing '..' since they might be suspicious - if (path.string().find("..") != std::string::npos) - { - relativePath = GetLegacyRelativePath(uuid); - fs::path legacyPath = rootPath / relativePath; - LOG(WARNING) << "Advanced Storage - WAS02 - Path is suspicious since it contains '..': '" << path.string() << "' will be stored in '" << legacyPath << "'"; - path = legacyPath; - } - - // check path length !!!!!, if too long, go back to legacy path and issue a warning - if (path.string().size() > maxPathLength_) - { - relativePath = GetLegacyRelativePath(uuid); - fs::path legacyPath = rootPath / relativePath; - LOG(WARNING) << "Advanced Storage - WAS01 - Path is too long: '" << path.string() << "' will be stored in '" << legacyPath << "'"; - path = legacyPath; - } - - if (fs::exists(path)) - { - // 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, "Advanced Storage - path already exists"); - - // TODO for the future: handle duplicates path (e.g: there's no uuid in the path and we are uploading the same file again) - } - - std::string customDataString; - GetCustomData(customDataString, relativePath); - - LOG(INFO) << "Advanced Storage - creating attachment \"" << uuid << "\" of type " << static_cast<int>(type) << " (path = " + path.string() + ")"; - - - if (fs::exists(path.parent_path())) - { - if (!fs::is_directory(path.parent_path())) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_DirectoryOverFile); - } - } - else - { - if (!fs::create_directories(path.parent_path())) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_FileStorageCannotWrite); - } - } - - Orthanc::SystemToolbox::WriteFile(content, size, path.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::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 - { - LOG(INFO) << "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) -{ - std::string path = GetPath(uuid, customData).string(); - - LOG(INFO) << "Advanced Storage - Reading whole attachment \"" << uuid << "\" of type " << static_cast<int>(type) << " (path = " + path + ")"; - - if (!Orthanc::SystemToolbox::IsRegularFile(path)) - { - LOG(ERROR) << "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()) - { - LOG(ERROR) << "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) - { - LOG(ERROR) << "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 (...) - { - LOG(ERROR) << "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) -{ - std::string path = GetPath(uuid, customData).string(); - - LOG(INFO) << "Advanced Storage - Reading range of attachment \"" << uuid << "\" of type " << static_cast<int>(type) << " (path = " + path + ")"; - - if (!Orthanc::SystemToolbox::IsRegularFile(path)) - { - LOG(ERROR) << "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()) - { - LOG(ERROR) << "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 (...) - { - LOG(ERROR) << "Unexpected error while reading: " << path; - return OrthancPluginErrorCode_StorageAreaPlugin; - } - - return OrthancPluginErrorCode_Success; -} - - -OrthancPluginErrorCode StorageRemove (const char* uuid, - const char* customData, - OrthancPluginContentType type) -{ - fs::path path = GetPath(uuid, customData); - - LOG(INFO) << "Advanced Storage - Deleting attachment \"" << uuid << "\" of type " << static_cast<int>(type) << " (path = " + path.string() + ")"; - - try - { - fs::remove(path); - } - catch (...) - { - // Ignore the error - } - - // Remove the empty parent directories, (ignoring the error code if these directories are not empty) - - try - { - fs::path parent = path.parent_path(); - - while (parent != GetRootPath()) - { - 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; - } - - LOG(WARNING) << "AdvancedStorage plugin is initializing"; - OrthancPluginSetDescription2(context, ORTHANC_PLUGIN_NAME, "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, - - // Enables/disables support for multiple StorageDirectories (disabled by default) - // Note: when saving a file, the plugin stores only the storage-id in the SQL DB - "MultipleStorages" : { - "Storages" : { - // Therefore, storage path may change in case you move your data from one place to another. - // The storgae ids may never change since they are stored in DB; you can only add new ones. - "1" : "/var/lib/orthanc/db", - "2" : "/mnt/disk2/orthanc" - }, - - // The storage on which new data is stored. - // There's currently no automatic changes of disks - "CurrentStorage" : "2", - }, - - // Defines the storage structure and file namings. - // These keywords can be used to generate the path: - // Attachment info: - // {UUID} : A unique file identifier - // {01(UUID)} : The first 2 characters of the file UUID - // {23(UUID)} : The 3rd and 4th characters of the file UUID - // {.ext} : The file extension - // DICOM TAGS: - // {PatientID}, {PatientName}, {PatientBirthDate} - // {StudyInstanceUID}, {SeriesInstanceUID}, {SOPInstanceUID} - // {StudyDescription}, {SeriesDescription} - // {StudyDate}, {AccessionNumber}, {InstanceNumber}, {SeriesNumber} - // Transformed DICOM TAGS: - // {split(StudyDate)} : 3 subfolders: YYYY/MM/DD - // {split(PatientBirthDate)} : 3 subfolders: YYYY/MM/DD - // {pad4(InstanceNumber)} : the instance number padded with zeroes to have 4 characters - // {pad4(SeriesNumber)} : the instance number padded with zeroes to have 4 characters - // {pad6(InstanceNumber)} : the instance number padded with zeroes to have 6 characters - // {pad6(SeriesNumber)} : the instance number padded with zeroes to have 6 characters - // {pad8(InstanceNumber)} : the instance number padded with zeroes to have 8 characters - // {pad8(SeriesNumber)} : the instance number padded with zeroes to have 8 characters - // Orthanc IDs: - // {OrthancPatientID}, {OrthancStudyID}, {OrthancSeriesID}, {OrthancInstanceID} - // Transformed Orthanc IDs: - // {01(OrthancPatientID)}, {01(OrthancStudyID)}, ... : the first 2 characters of the Orthanc ID - // {23(OrthancPatientID)}, {23(OrthancStudyID)}, ... : the 3rd and 4th characters of the Orthanc ID - // Examples: - // "OrthancDefault" is a special value to use the same structure as the Orthanc core. - // This option consumes less space in the SQL DB since the path must not be saved in DB. - // "{01(UUID)}/{23(UUID)}/{UUID}{.ext}" is equivalent with the structure of the Orthanc core with and added file extension - // "{split(StudyDate)}/{StudyInstanceUID} - {PatientID}/{SeriesInstanceUID}/{pad6(InstanceNumber)} - {UUID}{.ext}" - // "{PatientID} - {PatientName}/{StudyDate} - {StudyInstanceUID} - {StudyDescription}/{SeriesInstanceUID}/{UUID}{.ext}" - // Notes: - // - To prevent files from being overwritten, it is very important that their path is unique ! - // Therefore, your NamingScheme must always include: - // - either the file {UUID} (this is mandatory in this Beta version !!!!!) - // - MAYBE IN A LATER BETA VERSION: at least a patient identifier {PatientID} or {OrthancPatientID}, - // a study identifier {StudyInstanceUID} or {OrthancStudyID}, - // a series identifier {SeriesInstanceUID} or {OrthancSeriesID}, - // an instance identifier {SOPInstanceUID} or {OrthancInstanceID} - // - The NamingScheme defines a RELATIVE path to either the "StorageDirectory" of Orthanc or one of - // the "MultipleStorages" of this plugin. - // - The relative path generated from the NamingScheme is stored in the SQL DB. Therefore, you may change the - // NamingScheme at any time and you'll still be able to access previously saved files. - "NamingScheme" : "OrthancDefault", - - // Defines the maximum length for path used in the storage. If a file is longer - // than this limit, it is stored with the default orthanc naming scheme - // (and a warning is issued). - // Note, on Windows, the maximum path length is 260 bytes by default but can be increased - // through a configuration. - "MaxPathLength" : 256, - - // When saving non DICOM attachments, Orthanc does not have access to the DICOM tags - // and can therefore not compute a path using the NamingScheme. - // Therefore, all non DICOM attachements are grouped in a subfolder using the - // legacy structure. With this option, you can define a root folder for these - // non DICOM attachments - // e.g: "OtherAttachmentsPrefix": "_attachments" - // Notes: - // - When using a prefix, the path is saved in the SQL DB. Therefore, you may change the OtherAttachmentsPrefix - // at any time and you'll still be able to access previously saved files. - "OtherAttachmentsPrefix": "", - } - } - */ - - fsyncOnWrite_ = orthancConfiguration.GetBooleanValue("SyncStorageArea", true); - - const Json::Value& pluginJson = advancedStorage.GetJson(); - - namingScheme_ = advancedStorage.GetStringValue("NamingScheme", "OrthancDefault"); - if (namingScheme_ != "OrthancDefault") - { - // when using a custom scheme, to avoid collisions, you must include, at least the attachment UUID - // or each of the DICOM IDs or orthanc IDs - if (namingScheme_.find("{UUID}") == std::string::npos) - { - LOG(ERROR) << "AdvancedStorage - To avoid files from being overwritten, your naming scheme shall alway contain the {UUID} (at least in this beta version !!!)."; - return -1; - - if (namingScheme_.find("PatientID") == std::string::npos && namingScheme_.find("OrthancPatientID") == std::string::npos) - { - LOG(ERROR) << "AdvancedStorage - To avoid files from being overwritten, your naming scheme shall alway contain either the {UUID} or 4 DICOM identifiers ({PatientID}, {StudyInstanceUID}, {SeriesInstanceUID}, {SOPInstanceUID}) or 4 Orthanc identifiers ({PatientOrthancID}, {StudyOrthancID}, {SeriesOrthancID}, {SOPInstanceUID})."; - return -1; - } - - if (namingScheme_.find("StudyInstanceUID") == std::string::npos && namingScheme_.find("OrthancStudyID") == std::string::npos) - { - LOG(ERROR) << "AdvancedStorage - To avoid files from being overwritten, your naming scheme shall alway contain either the {UUID} or 4 DICOM identifiers ({PatientID}, {StudyInstanceUID}, {SeriesInstanceUID}, {SOPInstanceUID}) or 4 Orthanc identifiers ({PatientOrthancID}, {StudyOrthancID}, {SeriesOrthancID}, {SOPInstanceUID})."; - return -1; - } - - if (namingScheme_.find("SeriesInstanceUID") == std::string::npos && namingScheme_.find("OrthancSeriesID") == std::string::npos) - { - LOG(ERROR) << "AdvancedStorage - To avoid files from being overwritten, your naming scheme shall alway contain either the {UUID} or 4 DICOM identifiers ({PatientID}, {StudyInstanceUID}, {SeriesInstanceUID}, {SOPInstanceUID}) or 4 Orthanc identifiers ({PatientOrthancID}, {StudyOrthancID}, {SeriesOrthancID}, {SOPInstanceUID})."; - return -1; - } - - if (namingScheme_.find("SOPInstanceUID") == std::string::npos && namingScheme_.find("OrthancInstanceID") == std::string::npos) - { - LOG(ERROR) << "AdvancedStorage - To avoid files from being overwritten, your naming scheme shall alway contain either the {UUID} or 4 DICOM identifiers ({PatientID}, {StudyInstanceUID}, {SeriesInstanceUID}, {SOPInstanceUID}) or 4 Orthanc identifiers ({PatientOrthancID}, {StudyOrthancID}, {SeriesOrthancID}, {SOPInstanceUID})."; - return -1; - } - } - } - - otherAttachmentsPrefix_ = advancedStorage.GetStringValue("OtherAttachmentsPrefix", ""); - LOG(WARNING) << "AdvancedStorage - Path to the other attachments root: " << otherAttachmentsPrefix_; - - // if we have enabled multiple storage after files have been saved without this plugin, we still need the default StorageDirectory - rootPath_ = fs::path(orthancConfiguration.GetStringValue("StorageDirectory", "OrthancStorage")); - LOG(WARNING) << "AdvancedStorage - Path to the default storage area: " << rootPath_.string(); - - maxPathLength_ = advancedStorage.GetIntegerValue("MaxPathLength", 256); - LOG(WARNING) << "AdvancedStorage - Maximum path length: " << maxPathLength_; - - if (!rootPath_.is_absolute()) - { - LOG(ERROR) << "AdvancedStorage - Path to the default storage area should be an absolute path " << rootPath_ << " (\"StorageDirectory\" in main Orthanc configuration)"; - return -1; - } - - if (rootPath_.size() > (maxPathLength_ - legacyPathLength)) - { - LOG(ERROR) << "AdvancedStorage - Path to the default storage is too long"; - return -1; - } - - if (pluginJson.isMember("MultipleStorages")) - { - multipleStoragesEnabled_ = true; - const Json::Value& multipleStoragesJson = pluginJson["MultipleStorages"]; - - if (multipleStoragesJson.isMember("Storages") && multipleStoragesJson.isObject() && multipleStoragesJson.isMember("CurrentStorage") && multipleStoragesJson["CurrentStorage"].isString()) - { - const Json::Value& storagesJson = multipleStoragesJson["Storages"]; - Json::Value::Members storageIds = storagesJson.getMemberNames(); - - for (Json::Value::Members::const_iterator it = storageIds.begin(); it != storageIds.end(); ++it) - { - const Json::Value& storagePath = storagesJson[*it]; - if (!storagePath.isString()) - { - LOG(ERROR) << "AdvancedStorage - Storage path is not a string " << *it; - return -1; - } - - rootPaths_[*it] = storagePath.asString(); - - if (!rootPaths_[*it].is_absolute()) - { - LOG(ERROR) << "AdvancedStorage - Storage path shall be absolute path '" << storagePath.asString() << "'"; - return -1; - } - - if (storagePath.asString().size() > (maxPathLength_ - legacyPathLength)) - { - LOG(ERROR) << "AdvancedStorage - Storage path is too long '" << storagePath.asString() << "'"; - return -1; - } - } - - currentStorageId_ = multipleStoragesJson["CurrentStorage"].asString(); - - if (rootPaths_.find(currentStorageId_) == rootPaths_.end()) - { - LOG(ERROR) << "AdvancedStorage - CurrentStorage is not defined in Storages list: " << currentStorageId_; - return -1; - } - - LOG(WARNING) << "AdvancedStorage - multiple storages enabled. Current storage : " << rootPaths_[currentStorageId_].string(); - } - } - - OrthancPluginRegisterStorageArea3(context, StorageCreateInstance, StorageCreateAttachment, StorageReadWhole, StorageReadRange, StorageRemove); - } - else - { - LOG(WARNING) << "AdvancedStorage plugin is disabled by the configuration file"; - } - - return 0; - } - - - ORTHANC_PLUGINS_API void OrthancPluginFinalize() - { - LOG(WARNING) << "AdvancedStorage plugin is finalizing"; - } - - - ORTHANC_PLUGINS_API const char* OrthancPluginGetName() - { - return ORTHANC_PLUGIN_NAME; - } - - - ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() - { - return ADVANCED_STORAGE_VERSION; - } -}
--- a/TODO Thu Jan 30 17:41:33 2025 +0100 +++ b/TODO Fri Jan 31 16:30:50 2025 +0100 @@ -1,36 +1,3 @@ -TODO_CUSTOM_DATA branch -- add REVISIONS in AttachedFiles + Metadata 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 -- refuse to instantiate a PluginStorage3 if a DBv4 is not instantiated ! -- handle all TODO_CUSTOM_DATA -- check /attachments/... routes for path returned -- AdvancedStoragePlugin - - show warning if a tag is missing when generating the path from tags (+ option to disable this warning) - - generate path from tags from resource (CreateAttachment) - - add an instanceId or parentSeriesId arg in CreateInstance ? - - implement a 'legacy' root path to group all files with missing tags or path too long - - avoid error AdvancedStorage - Path to the default storage area should be an absolute path '"OrthancStorage"' when using PG and no StorageDirectory has been defined - - document that, once you have used the AdvancedStoragePlugin and stored DICOM files, you can not downgrade Orthanc to a previous Orthanc - without loosing access to the DICOM files - - write integration test for advanced-storage: - - launch 1.11.2 - - upload 1 file - - launch 1.12.0 with advanced-storage plugin with a non default namingScheme - - upload 1 file - - access + delete initial file - -- write integration test for transitions from one DB to the other (for each DB plugin): - - launch 1.11.2, - - upload 2 files, - - launch 1.12.0, - - access + delete one file, - - upload one file, - - launch 1.11.2 again, - - access + delete last 2 files - - ======================= === Orthanc Roadmap === =======================