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 ===
 =======================