changeset 15:2a02b21f0a19

migration + storage structure
author Alain Mazy
date Tue, 01 Sep 2020 13:08:49 +0200
parents 234946ea2038
children 0e509a47e010 f438e831d9ad
files Aws/AwsS3StoragePlugin.cpp Aws/AwsS3StoragePlugin.h Aws/CMakeLists.txt Azure/AzureBlobStoragePlugin.cpp Azure/AzureBlobStoragePlugin.h Azure/CMakeLists.txt Common/BaseStoragePlugin.cpp Common/BaseStoragePlugin.h Common/EncryptionHelpers.h Common/IStoragePlugin.h Common/Resources/DownloadOrthancFramework.cmake Common/StoragePlugin.cpp Google/CMakeLists.txt Google/GoogleStoragePlugin.cpp Google/GoogleStoragePlugin.h NEWS README.md
diffstat 17 files changed, 579 insertions(+), 184 deletions(-) [+]
line wrap: on
line diff
--- a/Aws/AwsS3StoragePlugin.cpp	Wed Aug 26 12:52:37 2020 +0200
+++ b/Aws/AwsS3StoragePlugin.cpp	Tue Sep 01 13:08:49 2020 +0200
@@ -32,8 +32,9 @@
 #include <fstream>
 
 const char* ALLOCATION_TAG = "OrthancS3";
+static const char* const PLUGIN_SECTION = "AwsS3Storage";
 
-class AwsS3StoragePlugin : public IStoragePlugin
+class AwsS3StoragePlugin : public BaseStoragePlugin
 {
 public:
 
@@ -42,13 +43,12 @@
 
 public:
 
-  AwsS3StoragePlugin(const Aws::S3::S3Client& client, const std::string& bucketName);
+  AwsS3StoragePlugin(const Aws::S3::S3Client& client, const std::string& bucketName, bool enableLegacyStorageStructure);
 
   virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
   virtual IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
   virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
-private:
-  virtual std::string GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
+  virtual const char* GetConfigurationSectionName() {return PLUGIN_SECTION;};
 };
 
 
@@ -178,7 +178,8 @@
 
 IStoragePlugin* AwsS3StoragePluginFactory::CreateStoragePlugin(const OrthancPlugins::OrthancConfiguration& orthancConfig)
 {
-  static const char* const PLUGIN_SECTION = "AwsS3Storage";
+  bool enableLegacyStorageStructure;
+
   if (!orthancConfig.IsSection(PLUGIN_SECTION))
   {
     OrthancPlugins::LogWarning(std::string(GetStoragePluginName()) + " plugin, section missing.  Plugin is not enabled.");
@@ -188,6 +189,11 @@
   OrthancPlugins::OrthancConfiguration pluginSection;
   orthancConfig.GetSection(pluginSection, PLUGIN_SECTION);
 
+  if (!BaseStoragePlugin::ReadCommonConfiguration(enableLegacyStorageStructure, pluginSection))
+  {
+    return nullptr;
+  }
+
   std::string bucketName;
   std::string region;
   std::string accessKey;
@@ -242,7 +248,7 @@
 
     OrthancPlugins::LogInfo("AWS S3 storage initialized");
 
-    return new AwsS3StoragePlugin(client, bucketName);
+    return new AwsS3StoragePlugin(client, bucketName, enableLegacyStorageStructure);
   }
   catch (const std::exception& e)
   {
@@ -252,8 +258,9 @@
 
 }
 
-AwsS3StoragePlugin::AwsS3StoragePlugin(const Aws::S3::S3Client& client, const std::string& bucketName)
-  : client_(client),
+AwsS3StoragePlugin::AwsS3StoragePlugin(const Aws::S3::S3Client& client, const std::string& bucketName, bool enableLegacyStorageStructure)
+  : BaseStoragePlugin(enableLegacyStorageStructure),
+    client_(client),
     bucketName_(bucketName)
 {
 
@@ -285,27 +292,3 @@
   }
 
 }
-
-std::string AwsS3StoragePlugin::GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
-{
-  std::string path = std::string(uuid);
-
-  if (type == OrthancPluginContentType_Dicom)
-  {
-    path += ".dcm";
-  }
-  else if (type == OrthancPluginContentType_DicomAsJson)
-  {
-    path += ".json";
-  }
-  else
-  {
-    path += ".unk";
-  }
-
-  if (encryptionEnabled)
-  {
-    path += ".enc";
-  }
-  return path;
-}
--- a/Aws/AwsS3StoragePlugin.h	Wed Aug 26 12:52:37 2020 +0200
+++ b/Aws/AwsS3StoragePlugin.h	Tue Sep 01 13:08:49 2020 +0200
@@ -18,7 +18,7 @@
 
 #pragma once
 
-#include "../Common/IStoragePlugin.h"
+#include "../Common/BaseStoragePlugin.h"
 
 class AwsS3StoragePluginFactory
 {
--- a/Aws/CMakeLists.txt	Wed Aug 26 12:52:37 2020 +0200
+++ b/Aws/CMakeLists.txt	Tue Sep 01 13:08:49 2020 +0200
@@ -9,32 +9,31 @@
 include(CheckIncludeFileCXX)
 
 set(ORTHANC_FRAMEWORK_SOURCE "hg" CACHE STRING "orthanc source")
-set(ORTHANC_FRAMEWORK_VERSION "7176ebf" CACHE STRING "orthanc framework version")
+set(ORTHANC_FRAMEWORK_VERSION "1.7.3" CACHE STRING "orthanc framework version")
 set(ALLOW_DOWNLOADS ON)
 
 # Download and setup the Orthanc framework
 include(${CMAKE_SOURCE_DIR}/../Common/Resources/DownloadOrthancFramework.cmake)
 
-include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
+include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
 
 set(ENABLE_GOOGLE_TEST ON)
 set(ORTHANC_FRAMEWORK_PLUGIN ON)
 
-include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
+include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
 
 
 add_definitions(
     -DHAS_ORTHANC_EXCEPTION=1
-    -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+    -DORTHANC_ENABLE_LOGGING=1
     -DAWS_STORAGE_PLUGIN=1
     )
 add_definitions(-DPLUGIN_VERSION="${PLUGIN_VERSION}")
 
 include_directories(
-  ${ORTHANC_ROOT}
-  ${ORTHANC_ROOT}/Core
-  ${ORTHANC_ROOT}/Plugins/Include
-  ${ORTHANC_ROOT}/Plugins/Samples/Common
+  ${ORTHANC_FRAMEWORK_ROOT}
+  ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Include
+  ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common
   )
 
 
@@ -45,11 +44,13 @@
 
 set(COMMON_SOURCES
     ${CMAKE_SOURCE_DIR}/../Common/IStoragePlugin.h
+    ${CMAKE_SOURCE_DIR}/../Common/BaseStoragePlugin.h
+    ${CMAKE_SOURCE_DIR}/../Common/BaseStoragePlugin.cpp
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionHelpers.cpp
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionHelpers.h
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionConfigurator.cpp
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionConfigurator.h
-    ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+    ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
 
     ${ORTHANC_CORE_SOURCES}
   )
--- a/Azure/AzureBlobStoragePlugin.cpp	Wed Aug 26 12:52:37 2020 +0200
+++ b/Azure/AzureBlobStoragePlugin.cpp	Tue Sep 01 13:08:49 2020 +0200
@@ -27,9 +27,9 @@
 
 // Create aliases to make the code easier to read.
 namespace as = azure::storage;
-
+static const char* const PLUGIN_SECTION = "AzureBlobStorage";
 
-class AzureBlobStoragePlugin : public IStoragePlugin
+class AzureBlobStoragePlugin : public BaseStoragePlugin
 {
 public:
 
@@ -40,13 +40,12 @@
 //  AzureBlobStoragePlugin(const std::string& connectionString,
 //                         const std::string& containerName
 //                        );
-  AzureBlobStoragePlugin(const as::cloud_blob_client& blobClient, const as::cloud_blob_container& blobContainer);
+  AzureBlobStoragePlugin(const as::cloud_blob_client& blobClient, const as::cloud_blob_container& blobContainer, bool enableLegacyStorageStructure);
 
   virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
   virtual IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
   virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
-private:
-  virtual std::string GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
+  virtual const char* GetConfigurationSectionName() {return PLUGIN_SECTION;};
 };
 
 
@@ -152,13 +151,18 @@
 {
   std::string connectionString;
   std::string containerName;
+  bool enableLegacyStorageStructure;
 
-  static const char* const PLUGIN_SECTION = "AzureBlobStorage";
   if (orthancConfig.IsSection(PLUGIN_SECTION))
   {
     OrthancPlugins::OrthancConfiguration pluginSection;
     orthancConfig.GetSection(pluginSection, PLUGIN_SECTION);
 
+    if (!BaseStoragePlugin::ReadCommonConfiguration(enableLegacyStorageStructure, pluginSection))
+    {
+      return nullptr;
+    }
+
     if (!pluginSection.LookupStringValue(connectionString, "ConnectionString"))
     {
       OrthancPlugins::LogError("AzureBlobStorage/ConnectionString configuration missing.  Unable to initialize plugin");
@@ -235,7 +239,7 @@
 
     OrthancPlugins::LogInfo("Blob storage initialized");
 
-    return new AzureBlobStoragePlugin(blobClient, blobContainer);
+    return new AzureBlobStoragePlugin(blobClient, blobContainer, enableLegacyStorageStructure);
   }
   catch (const std::exception& e)
   {
@@ -245,8 +249,9 @@
 
 }
 
-AzureBlobStoragePlugin::AzureBlobStoragePlugin(const as::cloud_blob_client& blobClient, const as::cloud_blob_container& blobContainer) //const std::string &containerName) //, google::cloud::storage::Client& mainClient)
-  : blobClient_(blobClient),
+AzureBlobStoragePlugin::AzureBlobStoragePlugin(const as::cloud_blob_client& blobClient, const as::cloud_blob_container& blobContainer, bool enableLegacyStorageStructure)
+  : BaseStoragePlugin(enableLegacyStorageStructure),
+    blobClient_(blobClient),
     blobContainer_(blobContainer)
 {
 
@@ -277,27 +282,3 @@
     throw StoragePluginException("AzureBlobStorage: error while deleting file " + std::string(path) + ": " + ex.what());
   }
 }
-
-std::string AzureBlobStoragePlugin::GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
-{
-  std::string path = std::string(uuid);
-
-  if (type == OrthancPluginContentType_Dicom)
-  {
-    path += ".dcm";
-  }
-  else if (type == OrthancPluginContentType_DicomAsJson)
-  {
-    path += ".json";
-  }
-  else
-  {
-    path += ".unk";
-  }
-
-  if (encryptionEnabled)
-  {
-    path += ".enc";
-  }
-  return path;
-}
--- a/Azure/AzureBlobStoragePlugin.h	Wed Aug 26 12:52:37 2020 +0200
+++ b/Azure/AzureBlobStoragePlugin.h	Tue Sep 01 13:08:49 2020 +0200
@@ -18,7 +18,7 @@
 
 #pragma once
 
-#include "../Common/IStoragePlugin.h"
+#include "../Common/BaseStoragePlugin.h"
 
 class AzureBlobStoragePluginFactory
 {
--- a/Azure/CMakeLists.txt	Wed Aug 26 12:52:37 2020 +0200
+++ b/Azure/CMakeLists.txt	Tue Sep 01 13:08:49 2020 +0200
@@ -7,32 +7,31 @@
 include(CheckIncludeFileCXX)
 
 set(ORTHANC_FRAMEWORK_SOURCE "hg" CACHE STRING "orthanc source")
-set(ORTHANC_FRAMEWORK_VERSION "7176ebf" CACHE STRING "orthanc framework version")
+set(ORTHANC_FRAMEWORK_VERSION "1.7.3" CACHE STRING "orthanc framework version")
 set(ALLOW_DOWNLOADS ON)
 
 # Download and setup the Orthanc framework
 include(${CMAKE_SOURCE_DIR}/../Common/Resources/DownloadOrthancFramework.cmake)
 
-include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
+include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
 
 set(ENABLE_GOOGLE_TEST ON)
 set(ORTHANC_FRAMEWORK_PLUGIN ON)
 
-include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
+include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
 
 
 add_definitions(
     -DHAS_ORTHANC_EXCEPTION=1
-    -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+    -DORTHANC_ENABLE_LOGGING=1
     -DAZURE_STORAGE_PLUGIN=1
     )
 add_definitions(-DPLUGIN_VERSION="${PLUGIN_VERSION}")
 
 include_directories(
-  ${ORTHANC_ROOT}
-  ${ORTHANC_ROOT}/Core
-  ${ORTHANC_ROOT}/Plugins/Include
-  ${ORTHANC_ROOT}/Plugins/Samples/Common
+  ${ORTHANC_FRAMEWORK_ROOT}
+  ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Include
+  ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common
   )
 
 
@@ -50,11 +49,13 @@
 
 set(COMMON_SOURCES
     ${CMAKE_SOURCE_DIR}/../Common/IStoragePlugin.h
+    ${CMAKE_SOURCE_DIR}/../Common/BaseStoragePlugin.h
+    ${CMAKE_SOURCE_DIR}/../Common/BaseStoragePlugin.cpp
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionHelpers.cpp
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionHelpers.h
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionConfigurator.cpp
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionConfigurator.h
-    ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+    ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
 
     ${ORTHANC_CORE_SOURCES}
   )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Common/BaseStoragePlugin.cpp	Tue Sep 01 13:08:49 2020 +0200
@@ -0,0 +1,67 @@
+#include "BaseStoragePlugin.h"
+#include <boost/filesystem/fstream.hpp>
+
+std::string BaseStoragePlugin::GetOrthancFileSystemPath(const std::string& uuid, const std::string& fileSystemRootPath)
+{
+  boost::filesystem::path path = fileSystemRootPath;
+
+  path /= std::string(&uuid[0], &uuid[2]);
+  path /= std::string(&uuid[2], &uuid[4]);
+  path /= uuid;
+
+  path.make_preferred();
+
+  return path.string();
+}
+
+
+std::string BaseStoragePlugin::GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
+{
+  if (enableLegacyStorageStructure_)
+  {
+    return GetOrthancFileSystemPath(uuid, std::string()); // there's no "root" path in an object store
+  }
+  else
+  {
+    std::string path = std::string(uuid);
+
+    if (type == OrthancPluginContentType_Dicom)
+    {
+      path += ".dcm";
+    }
+    else if (type == OrthancPluginContentType_DicomAsJson)
+    {
+      path += ".json";
+    }
+    else
+    {
+      path += ".unk";
+    }
+
+    if (encryptionEnabled)
+    {
+      path += ".enc";
+    }
+    return path;
+  }
+}
+
+bool BaseStoragePlugin::ReadCommonConfiguration(bool& enableLegacyStorageStructure, const OrthancPlugins::OrthancConfiguration& pluginSection)
+{
+  std::string storageStructure = pluginSection.GetStringValue("StorageStructure", "flat");
+  if (storageStructure == "flat")
+  {
+    enableLegacyStorageStructure = false;
+  }
+  else
+  {
+    enableLegacyStorageStructure = true;
+    if (storageStructure != "legacy")
+    {
+      OrthancPlugins::LogError("ObjectStorage/StorageStructure configuration invalid value: " + storageStructure + ", allowed values are 'flat' and 'legacy'");
+      return false;
+    }
+  }
+
+  return true;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Common/BaseStoragePlugin.h	Tue Sep 01 13:08:49 2020 +0200
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "IStoragePlugin.h"
+
+class BaseStoragePlugin : public IStoragePlugin
+{
+  bool enableLegacyStorageStructure_;
+
+protected:
+
+  BaseStoragePlugin(bool enableLegacyStorageStructure):
+    enableLegacyStorageStructure_(enableLegacyStorageStructure)
+  {}
+
+  std::string GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
+
+public:
+  static std::string GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled, bool legacyFileStructure, const std::string& rootFolder);
+  static std::string GetOrthancFileSystemPath(const std::string& uuid, const std::string& fileSystemRootPath);
+
+  static bool ReadCommonConfiguration(bool& enableLegacyStorageStructure, const OrthancPlugins::OrthancConfiguration& pluginSection);
+};
--- a/Common/EncryptionHelpers.h	Wed Aug 26 12:52:37 2020 +0200
+++ b/Common/EncryptionHelpers.h	Tue Sep 01 13:08:49 2020 +0200
@@ -21,7 +21,7 @@
 #include <cryptopp/secblock.h>
 #include "cryptopp/osrng.h"
 #include <boost/thread/mutex.hpp>
-#include "Core/MultiThreading/Semaphore.h"
+#include <MultiThreading/Semaphore.h>
 
 class EncryptionException : public std::runtime_error
 {
--- a/Common/IStoragePlugin.h	Wed Aug 26 12:52:37 2020 +0200
+++ b/Common/IStoragePlugin.h	Tue Sep 01 13:08:49 2020 +0200
@@ -70,4 +70,5 @@
   virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) = 0;
   virtual IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) = 0;
   virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) = 0;
+  virtual const char* GetConfigurationSectionName() = 0;
 };
--- a/Common/Resources/DownloadOrthancFramework.cmake	Wed Aug 26 12:52:37 2020 +0200
+++ b/Common/Resources/DownloadOrthancFramework.cmake	Tue Sep 01 13:08:49 2020 +0200
@@ -4,29 +4,18 @@
 # Copyright (C) 2017-2020 Osimis S.A., 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.
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
 #
-# In addition, as a special exception, the copyright holders of this
-# program give permission to link the code of its release with the
-# OpenSSL project's "OpenSSL" library (or with modified versions of it
-# that use the same license as the "OpenSSL" library), and distribute
-# the linked executables. You must obey the GNU General Public License
-# in all respects for all of the code used other than "OpenSSL". If you
-# modify file(s) with this exception, you may extend this exception to
-# your version of the file(s), but you are not obligated to do so. If
-# you do not wish to do so, delete this exception statement from your
-# version. If you delete this exception statement from all source files
-# in the program, then also delete it here.
-# 
 # 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.
+# Lesser 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/>.
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
 
 
 
@@ -35,11 +24,12 @@
 ##
 
 if (NOT DEFINED ORTHANC_FRAMEWORK_SOURCE OR
-    (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" AND
+    (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system" AND
+     NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" AND
      NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "web" AND
      NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" AND
      NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "path"))
-  message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_SOURCE must be set to \"hg\", \"web\", \"archive\" or \"path\"")
+  message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_SOURCE must be set to \"system\", \"hg\", \"web\", \"archive\" or \"path\"")
 endif()
 
 
@@ -118,6 +108,12 @@
         set(ORTHANC_FRAMEWORK_MD5 "3971f5de96ba71dc9d3f3690afeaa7c0")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.0")
         set(ORTHANC_FRAMEWORK_MD5 "ce5f689e852b01d3672bd3d2f952a5ef")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.1")
+        set(ORTHANC_FRAMEWORK_MD5 "3c171217f930abe80246997bdbcaf7cc")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.2")
+        set(ORTHANC_FRAMEWORK_MD5 "328f94dcbd78c169655a13f7ad58a2c2")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.3")
+        set(ORTHANC_FRAMEWORK_MD5 "3f1ba9502ec7c5449971d3b56087bcde")
 
       # Below this point are development snapshots that were used to
       # release some plugin, before an official release of the Orthanc
@@ -132,7 +128,8 @@
       endif()
     endif()
   endif()
-else()
+
+elseif (ORTHANC_FRAMEWORK_SOURCE STREQUAL "path")
   message("Using the Orthanc framework from a path of the filesystem. Assuming mainline version.")
   set(ORTHANC_FRAMEWORK_MAJOR 999)
   set(ORTHANC_FRAMEWORK_MINOR 999)
@@ -182,19 +179,14 @@
 ##
 
 if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "path")
-  if (NOT DEFINED ORTHANC_FRAMEWORK_ROOT)
+  if (NOT DEFINED ORTHANC_FRAMEWORK_ROOT OR
+      ORTHANC_FRAMEWORK_ROOT STREQUAL "")
     message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_ROOT must provide the path to the sources of Orthanc")
   endif()
   
   if (NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT})
     message(FATAL_ERROR "Non-existing directory: ${ORTHANC_FRAMEWORK_ROOT}")
   endif()
-  
-  if (NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
-    message(FATAL_ERROR "Directory not containing the source code of Orthanc: ${ORTHANC_FRAMEWORK_ROOT}")
-  endif()
-  
-  set(ORTHANC_ROOT ${ORTHANC_FRAMEWORK_ROOT})
 endif()
 
 
@@ -251,7 +243,8 @@
 ##
 
 if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive")
-  if (NOT DEFINED ORTHANC_FRAMEWORK_ARCHIVE)
+  if (NOT DEFINED ORTHANC_FRAMEWORK_ARCHIVE OR
+      ORTHANC_FRAMEWORK_ARCHIVE STREQUAL "")
     message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_ARCHIVE must provide the path to the sources of Orthanc")
   endif()
 endif()
@@ -370,3 +363,206 @@
   endif()
 endif()
 
+
+
+##
+## Determine the path to the sources of the Orthanc framework
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+  if (NOT DEFINED ORTHANC_ROOT OR
+      NOT DEFINED ORTHANC_FRAMEWORK_MAJOR OR
+      NOT DEFINED ORTHANC_FRAMEWORK_MINOR OR
+      NOT DEFINED ORTHANC_FRAMEWORK_REVISION)
+    message(FATAL_ERROR "Internal error in the DownloadOrthancFramework.cmake file")
+  endif()
+
+  unset(ORTHANC_FRAMEWORK_ROOT CACHE)
+
+  if ("${ORTHANC_FRAMEWORK_MAJOR}.${ORTHANC_FRAMEWORK_MINOR}.${ORTHANC_FRAMEWORK_REVISION}" VERSION_LESS "1.7.2")
+    set(ORTHANC_FRAMEWORK_ROOT "${ORTHANC_ROOT}/Core" CACHE
+      STRING "Path to the Orthanc framework source directory")
+    set(ENABLE_PLUGINS_VERSION_SCRIPT OFF)
+  else()
+    set(ORTHANC_FRAMEWORK_ROOT "${ORTHANC_ROOT}/OrthancFramework/Sources" CACHE
+      STRING "Path to the Orthanc framework source directory")
+  endif()
+
+  unset(ORTHANC_ROOT)
+endif()
+
+if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  if (NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT}/OrthancException.h OR
+      NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
+    message(FATAL_ERROR "Directory not containing the source code of the Orthanc framework: ${ORTHANC_FRAMEWORK_ROOT}")
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework installed as a shared library in a
+## GNU/Linux distribution (typically Debian). New in Orthanc 1.7.2.
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
+  set(ORTHANC_FRAMEWORK_LIBDIR "" CACHE PATH "")
+
+  if (CMAKE_SYSTEM_NAME STREQUAL "Windows" AND
+      CMAKE_COMPILER_IS_GNUCXX) # MinGW
+    set(DYNAMIC_MINGW_STDLIB ON)   # Disable static linking against libc (to throw exceptions)
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libstdc++")
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++")
+  endif()
+  
+  include(CheckIncludeFile)
+  include(CheckIncludeFileCXX)
+  include(FindPythonInterp)
+  include(${CMAKE_CURRENT_LIST_DIR}/Compiler.cmake)
+  include(${CMAKE_CURRENT_LIST_DIR}/DownloadPackage.cmake)
+  include(${CMAKE_CURRENT_LIST_DIR}/AutoGeneratedCode.cmake)
+  set(EMBED_RESOURCES_PYTHON ${CMAKE_CURRENT_LIST_DIR}/EmbedResources.py)
+
+  if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows" OR
+      ORTHANC_FRAMEWORK_STATIC)
+    include_directories(${ORTHANC_FRAMEWORK_ROOT}/..)
+  else()
+    # Look for mandatory dependency JsonCpp (cf. JsonCppConfiguration.cmake)
+    find_path(JSONCPP_INCLUDE_DIR json/reader.h
+      /usr/include/jsoncpp
+      /usr/local/include/jsoncpp
+      )
+
+    message("JsonCpp include dir: ${JSONCPP_INCLUDE_DIR}")
+    include_directories(${JSONCPP_INCLUDE_DIR})
+    link_libraries(jsoncpp)
+
+    CHECK_INCLUDE_FILE_CXX(${JSONCPP_INCLUDE_DIR}/json/reader.h HAVE_JSONCPP_H)
+    if (NOT HAVE_JSONCPP_H)
+      message(FATAL_ERROR "Please install the libjsoncpp-dev package")
+    endif()
+
+    # Switch to the C++11 standard if the version of JsonCpp is 1.y.z
+    # (same as variable JSONCPP_CXX11 in the source code of Orthanc)
+    if (EXISTS ${JSONCPP_INCLUDE_DIR}/json/version.h)
+      file(STRINGS
+        "${JSONCPP_INCLUDE_DIR}/json/version.h" 
+        JSONCPP_VERSION_MAJOR1 REGEX
+        ".*define JSONCPP_VERSION_MAJOR.*")
+
+      if (NOT JSONCPP_VERSION_MAJOR1)
+        message(FATAL_ERROR "Unable to extract the major version of JsonCpp")
+      endif()
+      
+      string(REGEX REPLACE
+        ".*JSONCPP_VERSION_MAJOR.*([0-9]+)$" "\\1" 
+        JSONCPP_VERSION_MAJOR ${JSONCPP_VERSION_MAJOR1})
+      message("JsonCpp major version: ${JSONCPP_VERSION_MAJOR}")
+
+      if (JSONCPP_VERSION_MAJOR GREATER 0)
+        message("Switching to C++11 standard, as version of JsonCpp is >= 1.0.0")
+        if (CMAKE_COMPILER_IS_GNUCXX)
+          set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
+        elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+          set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+        endif()
+      endif()
+    else()
+      message("Unable to detect the major version of JsonCpp, assuming < 1.0.0")
+    endif()
+
+    # Look for mandatory dependency Boost (cf. BoostConfiguration.cmake)
+    include(FindBoost)
+    find_package(Boost COMPONENTS filesystem thread system date_time regex ${ORTHANC_BOOST_COMPONENTS})
+
+    if (NOT Boost_FOUND)
+      message(FATAL_ERROR "Unable to locate Boost on this system")
+    endif()
+    
+    include_directories(${Boost_INCLUDE_DIRS})
+    link_libraries(${Boost_LIBRARIES})
+
+    # Optional component - Lua
+    if (ENABLE_LUA)
+      include(FindLua)
+
+      if (NOT LUA_FOUND)
+        message(FATAL_ERROR "Please install the liblua-dev package")
+      endif()
+
+      include_directories(${LUA_INCLUDE_DIR})
+      link_libraries(${LUA_LIBRARIES})
+    endif()
+
+    # Optional component - SQLite
+    if (ENABLE_SQLITE)    
+      CHECK_INCLUDE_FILE(sqlite3.h HAVE_SQLITE_H)
+      if (NOT HAVE_SQLITE_H)
+        message(FATAL_ERROR "Please install the libsqlite3-dev package")
+      endif()
+      link_libraries(sqlite3)
+    endif()
+
+    # Optional component - Pugixml
+    if (ENABLE_PUGIXML)
+      CHECK_INCLUDE_FILE_CXX(pugixml.hpp HAVE_PUGIXML_H)
+      if (NOT HAVE_PUGIXML_H)
+        message(FATAL_ERROR "Please install the libpugixml-dev package")
+      endif()      
+      link_libraries(pugixml)
+    endif()
+
+    # Optional component - DCMTK
+    if (ENABLE_DCMTK)
+      include(FindDCMTK)
+      include_directories(${DCMTK_INCLUDE_DIRS})
+      link_libraries(${DCMTK_LIBRARIES})
+    endif()
+  endif()
+
+  # Look for Orthanc framework shared library
+  include(CheckCXXSymbolExists)
+
+  if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
+    set(ORTHANC_FRAMEWORK_INCLUDE_DIR ${ORTHANC_FRAMEWORK_ROOT})
+  else()
+    find_path(ORTHANC_FRAMEWORK_INCLUDE_DIR OrthancFramework.h
+      /usr/include/orthanc-framework
+      /usr/local/include/orthanc-framework
+      ${ORTHANC_FRAMEWORK_ROOT}
+      )
+  endif()
+
+  if (${ORTHANC_FRAMEWORK_INCLUDE_DIR} STREQUAL "ORTHANC_FRAMEWORK_INCLUDE_DIR-NOTFOUND")
+    message(FATAL_ERROR "Cannot locate the OrthancFramework.h header")
+  endif()
+  
+  message("Orthanc framework include dir: ${ORTHANC_FRAMEWORK_INCLUDE_DIR}")
+  include_directories(${ORTHANC_FRAMEWORK_INCLUDE_DIR})
+  
+  if ("${ORTHANC_FRAMEWORK_LIBDIR}" STREQUAL "")
+    set(ORTHANC_FRAMEWORK_LIBRARIES OrthancFramework)
+  else()
+    if (MSVC)
+      set(Suffix ".lib")
+      set(Prefix "")
+    else()
+      list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
+      list(GET CMAKE_FIND_LIBRARY_SUFFIXES 0 Suffix)
+    endif()
+    set(ORTHANC_FRAMEWORK_LIBRARIES ${ORTHANC_FRAMEWORK_LIBDIR}/${Prefix}OrthancFramework${Suffix})
+  endif()
+
+  set(CMAKE_REQUIRED_INCLUDES "${ORTHANC_FRAMEWORK_INCLUDE_DIR}")
+  set(CMAKE_REQUIRED_LIBRARIES "${ORTHANC_FRAMEWORK_LIBRARIES}")
+  
+  check_cxx_symbol_exists("Orthanc::InitializeFramework" "OrthancFramework.h" HAVE_ORTHANC_FRAMEWORK)
+  if (NOT HAVE_ORTHANC_FRAMEWORK)
+    message(FATAL_ERROR "Cannot find the Orthanc framework")
+  endif()
+
+  unset(CMAKE_REQUIRED_INCLUDES)
+  unset(CMAKE_REQUIRED_LIBRARIES)
+endif()
--- a/Common/StoragePlugin.cpp	Wed Aug 26 12:52:37 2020 +0200
+++ b/Common/StoragePlugin.cpp	Tue Sep 01 13:08:49 2020 +0200
@@ -34,14 +34,43 @@
 #include <string>
 
 #include <iostream>
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
+
 #include "../Common/EncryptionHelpers.h"
 #include "../Common/EncryptionConfigurator.h"
+#include <SystemToolbox.h>
 
 static std::unique_ptr<IStoragePlugin> plugin;
 
 static std::unique_ptr<EncryptionHelpers> crypto;
 static bool cryptoEnabled = false;
+static std::string fileSystemRootPath;
+static bool migrationFromFileSystemEnabled = false;
 
+// class to free memory allocated by malloc if an exception occurs
+// This is to avoid an issue in which the blob storage read method
+// crashed if the buffer was allocated through:
+//   auto buffer = std::unique_ptr<void, void(*)(void*)>{malloc(static_cast<uint64_t>(*size)), free};
+
+class ScopedFree
+{
+  void* buffer_;
+public:
+  ScopedFree(void* buffer)
+  : buffer_(buffer)
+  {
+  }
+  ~ScopedFree()
+  {
+    free(buffer_);
+  }
+
+  void Release() // abandon ownership
+  {
+    buffer_ = nullptr;
+  }
+};
 
 static OrthancPluginErrorCode StorageCreate(const char* uuid,
                                             const void* content,
@@ -110,6 +139,8 @@
     }
 
     *content = malloc(static_cast<uint64_t>(*size));
+    ScopedFree freeContent(*content);
+
     if (*content == nullptr)
     {
       OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ", cannot allocate memory of size " + boost::lexical_cast<std::string>(*size) + " bytes");
@@ -135,15 +166,52 @@
     {
       reader->Read(*(reinterpret_cast<char**>(content)), fileSize);
     }
+
+    // transmit ownership to content
+    freeContent.Release();
   }
   catch (StoragePluginException& ex)
   {
-    OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while creating object " + std::string(uuid) + ": " + ex.what());
-    return OrthancPluginErrorCode_StorageAreaPlugin;
+    if (migrationFromFileSystemEnabled)
+    {
+      try
+      {
+        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ": " + ex.what() + ", will now try to read it from legacy orthanc storage");
+        std::string path = BaseStoragePlugin::GetOrthancFileSystemPath(uuid, fileSystemRootPath);
+
+        std::string stringBuffer;
+        Orthanc::SystemToolbox::ReadFile(stringBuffer, path);
+
+        *content = malloc(static_cast<uint64_t>(stringBuffer.size()));
+        ScopedFree freeContent(*content);
+
+        if (*content == nullptr)
+        {
+          OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ", cannot allocate memory of size " + boost::lexical_cast<std::string>(*size) + " bytes");
+          return OrthancPluginErrorCode_StorageAreaPlugin;
+        }
+        *size = stringBuffer.size();
+        memcpy(*content, stringBuffer.data(), stringBuffer.size());
+
+        // transmit ownership to content
+        freeContent.Release();
+
+        return OrthancPluginErrorCode_Success;
+      }
+      catch(Orthanc::OrthancException& e)
+      {
+        OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ": " + std::string(e.What()));
+        return OrthancPluginErrorCode_StorageAreaPlugin;
+      }
+    }
+    else
+    {
+      OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ": " + ex.what());
+      return OrthancPluginErrorCode_StorageAreaPlugin;
+    }
   }
 
   return OrthancPluginErrorCode_Success;
-
 }
 
 
@@ -156,8 +224,51 @@
   }
   catch (StoragePluginException& ex)
   {
-    OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while deleting object " + std::string(uuid) + ": " + ex.what());
-    return OrthancPluginErrorCode_StorageAreaPlugin;
+    if (migrationFromFileSystemEnabled)
+    {
+      try
+      {
+        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while deleting object " + std::string(uuid) + ": " + ex.what() + ", will now try to delete it from legacy orthanc storage");
+        namespace fs = boost::filesystem;
+
+        fs::path path = BaseStoragePlugin::GetOrthancFileSystemPath(uuid, fileSystemRootPath);
+
+        try
+        {
+          fs::remove(path);
+        }
+        catch (...)
+        {
+          // Ignore the error
+        }
+
+        // Remove the two parent directories, ignoring the error code if
+        // these directories are not empty
+
+        try
+        {
+          boost::system::error_code err;
+          fs::remove(path.parent_path(), err);
+          fs::remove(path.parent_path().parent_path(), err);
+        }
+        catch (...)
+        {
+          // Ignore the error
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      catch(Orthanc::OrthancException& e)
+      {
+        OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while deleting object " + std::string(uuid) + ": " + std::string(e.What()));
+        return OrthancPluginErrorCode_StorageAreaPlugin;
+      }
+    }
+    else
+    {
+      OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while deleting object " + std::string(uuid) + ": " + ex.what());
+      return OrthancPluginErrorCode_StorageAreaPlugin;
+    }
   }
 
   return OrthancPluginErrorCode_Success;
@@ -170,6 +281,8 @@
   {
     OrthancPlugins::SetGlobalContext(context);
 
+    Orthanc::Logging::InitializePluginContext(context);
+
     OrthancPlugins::OrthancConfiguration orthancConfig;
 
     OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + " plugin is initializing");
@@ -194,19 +307,39 @@
       return -1;
     }
 
+    const char* pluginSectionName = plugin->GetConfigurationSectionName();
     static const char* const ENCRYPTION_SECTION = "StorageEncryption";
 
-    if (orthancConfig.IsSection(ENCRYPTION_SECTION))
+    if (orthancConfig.IsSection(pluginSectionName))
     {
-      OrthancPlugins::OrthancConfiguration cryptoSection;
-      orthancConfig.GetSection(cryptoSection, ENCRYPTION_SECTION);
+      OrthancPlugins::OrthancConfiguration pluginSection;
+      orthancConfig.GetSection(pluginSection, pluginSectionName);
+
+      migrationFromFileSystemEnabled = pluginSection.GetBooleanValue("MigrationFromFileSystemEnabled", false);
+
+      if (migrationFromFileSystemEnabled)
+      {
+        fileSystemRootPath = orthancConfig.GetStringValue("StorageDirectory", "OrthancStorageNotDefined");
+        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": migration from file system enabled, source: " + fileSystemRootPath);
+      }
 
-      crypto.reset(EncryptionConfigurator::CreateEncryptionHelpers(cryptoSection));
-      cryptoEnabled = crypto.get() != nullptr;
-    }
-    else
-    {
-      OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": client-side encryption is disabled");
+      if (pluginSection.IsSection(ENCRYPTION_SECTION))
+      {
+        OrthancPlugins::OrthancConfiguration cryptoSection;
+        pluginSection.GetSection(cryptoSection, ENCRYPTION_SECTION);
+
+        crypto.reset(EncryptionConfigurator::CreateEncryptionHelpers(cryptoSection));
+        cryptoEnabled = crypto.get() != nullptr;
+      }
+
+      if (cryptoEnabled)
+      {
+        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": client-side encryption is enabled");
+      }
+      else
+      {
+        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": client-side encryption is disabled");
+      }
     }
 
     OrthancPluginRegisterStorageArea(context, StorageCreate, StorageRead, StorageRemove);
--- a/Google/CMakeLists.txt	Wed Aug 26 12:52:37 2020 +0200
+++ b/Google/CMakeLists.txt	Tue Sep 01 13:08:49 2020 +0200
@@ -7,32 +7,31 @@
 include(CheckIncludeFileCXX)
 
 set(ORTHANC_FRAMEWORK_SOURCE "hg" CACHE STRING "orthanc source")
-set(ORTHANC_FRAMEWORK_VERSION "7176ebf" CACHE STRING "orthanc framework version")
+set(ORTHANC_FRAMEWORK_VERSION "1.7.3" CACHE STRING "orthanc framework version")
 set(ALLOW_DOWNLOADS ON)
 
 # Download and setup the Orthanc framework
 include(${CMAKE_SOURCE_DIR}/../Common/Resources/DownloadOrthancFramework.cmake)
 
-include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
+include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
 
 set(ENABLE_GOOGLE_TEST ON)
 set(ORTHANC_FRAMEWORK_PLUGIN ON)
 
-include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
+include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
 
 
 add_definitions(
     -DHAS_ORTHANC_EXCEPTION=1
-    -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+    -DORTHANC_ENABLE_LOGGING=1
     -DGOOGLE_STORAGE_PLUGIN=1
     )
 add_definitions(-DPLUGIN_VERSION="${PLUGIN_VERSION}")
 
 include_directories(
-  ${ORTHANC_ROOT}
-  ${ORTHANC_ROOT}/Core
-  ${ORTHANC_ROOT}/Plugins/Include
-  ${ORTHANC_ROOT}/Plugins/Samples/Common
+  ${ORTHANC_FRAMEWORK_ROOT}
+  ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Include
+  ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common
   )
 
 find_package(CURL REQUIRED)
@@ -41,11 +40,13 @@
 
 set(COMMON_SOURCES
     ${CMAKE_SOURCE_DIR}/../Common/IStoragePlugin.h
+    ${CMAKE_SOURCE_DIR}/../Common/BaseStoragePlugin.h
+    ${CMAKE_SOURCE_DIR}/../Common/BaseStoragePlugin.cpp
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionHelpers.cpp
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionHelpers.h
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionConfigurator.cpp
     ${CMAKE_SOURCE_DIR}/../Common/EncryptionConfigurator.h
-    ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+    ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
 
     ${ORTHANC_CORE_SOURCES}
   )
--- a/Google/GoogleStoragePlugin.cpp	Wed Aug 26 12:52:37 2020 +0200
+++ b/Google/GoogleStoragePlugin.cpp	Tue Sep 01 13:08:49 2020 +0200
@@ -22,6 +22,12 @@
 
 // Create aliases to make the code easier to read.
 namespace gcs = google::cloud::storage;
+static const char* const PLUGIN_SECTION = "GoogleCloudStorage";
+
+const char* GoogleStoragePlugin::GetConfigurationSectionName() 
+{
+  return PLUGIN_SECTION;
+}
 
 class Writer : public IStoragePlugin::IWriter
 {
@@ -125,7 +131,8 @@
 
 IStoragePlugin* GoogleStoragePluginFactory::CreateStoragePlugin(const OrthancPlugins::OrthancConfiguration& orthancConfig)
 {
-  static const char* const PLUGIN_SECTION = "GoogleCloudStorage";
+  bool enableLegacyStorageStructure;
+
   if (!orthancConfig.IsSection(PLUGIN_SECTION))
   {
     OrthancPlugins::LogWarning(std::string(GetStoragePluginName()) + " plugin, section missing.  Plugin is not enabled.");
@@ -135,6 +142,11 @@
   OrthancPlugins::OrthancConfiguration pluginSection;
   orthancConfig.GetSection(pluginSection, PLUGIN_SECTION);
 
+  if (!BaseStoragePlugin::ReadCommonConfiguration(enableLegacyStorageStructure, pluginSection))
+  {
+    return nullptr;
+  }
+
   std::string pathToGoogleCredentials;
 
   if (!pluginSection.LookupStringValue(pathToGoogleCredentials, "ServiceAccountFile"))
@@ -167,11 +179,12 @@
     return nullptr;
   }
 
-  return new GoogleStoragePlugin(googleBucketName, mainClient.value());
+  return new GoogleStoragePlugin(googleBucketName, mainClient.value(), enableLegacyStorageStructure);
 }
 
-GoogleStoragePlugin::GoogleStoragePlugin(const std::string &bucketName, google::cloud::storage::Client& mainClient)
-  : bucketName_(bucketName),
+GoogleStoragePlugin::GoogleStoragePlugin(const std::string &bucketName, google::cloud::storage::Client& mainClient, bool enableLegacyStorageStructure)
+  : BaseStoragePlugin(enableLegacyStorageStructure),
+    bucketName_(bucketName),
     mainClient_(mainClient)
 {
 
@@ -201,27 +214,3 @@
   }
 
 }
-
-std::string GoogleStoragePlugin::GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
-{
-  std::string path = std::string(uuid);
-
-  if (type == OrthancPluginContentType_Dicom)
-  {
-    path += ".dcm";
-  }
-  else if (type == OrthancPluginContentType_DicomAsJson)
-  {
-    path += ".json";
-  }
-  else
-  {
-    path += ".unk";
-  }
-
-  if (encryptionEnabled)
-  {
-    path += ".enc";
-  }
-  return path;
-}
--- a/Google/GoogleStoragePlugin.h	Wed Aug 26 12:52:37 2020 +0200
+++ b/Google/GoogleStoragePlugin.h	Tue Sep 01 13:08:49 2020 +0200
@@ -18,7 +18,7 @@
 
 #pragma once
 
-#include "../Common/IStoragePlugin.h"
+#include "../Common/BaseStoragePlugin.h"
 
 #include "google/cloud/storage/client.h"
 
@@ -30,7 +30,7 @@
 };
 
 
-class GoogleStoragePlugin : public IStoragePlugin
+class GoogleStoragePlugin : public BaseStoragePlugin
 {
 public:
 
@@ -40,12 +40,12 @@
 public:
 
   GoogleStoragePlugin(const std::string& bucketName,
-                      google::cloud::storage::Client& mainClient
+                      google::cloud::storage::Client& mainClient,
+                      bool enableLegacyStorageStructure
                       );
 
   virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
   virtual IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
   virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
-private:
-  virtual std::string GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
+  virtual const char* GetConfigurationSectionName();
 };
--- a/NEWS	Wed Aug 26 12:52:37 2020 +0200
+++ b/NEWS	Tue Sep 01 13:08:49 2020 +0200
@@ -1,6 +1,15 @@
 Pending changes in the mainline
 ===============================
 
+2020-09-01 - v 1.0.0
+====================
+
+* BREAKING CHANGE: the "StorageEncryption" configuration section is now a subsection of the "AwsS3Storage"
+Azure: now removing spaces and CR at the end of the connectionString since it prevented the plugin to initialize.
+* Added "MigrationFromFileSystemEnabled" configuration
+* Added "StorageStructure" configuration (flat-legacy)
+
+
 2020-08-26 - v 0.9.3
 ====================
 
--- a/README.md	Wed Aug 26 12:52:37 2020 +0200
+++ b/README.md	Tue Sep 01 13:08:49 2020 +0200
@@ -10,14 +10,16 @@
 
 ```
 {
-    "StorageEncryption" : {
-        "Enable": true,
-        "MasterKey": [3, "/path/to/master.key"], // key id - path to the base64 encoded key
-        "PreviousMasterKeys" : [
-            [ 1, "/path/to/previous1.key"],
-            [ 2, "/path/to/previous2.key"]
-        ],
-        "MaxConcurrentInputSize" : 1024   // size in MB 
+    "GoogleCloudStorage" : {
+        "StorageEncryption" : {
+            "Enable": true,
+            "MasterKey": [3, "/path/to/master.key"], // key id - path to the base64 encoded key
+            "PreviousMasterKeys" : [
+                [ 1, "/path/to/previous1.key"],
+                [ 2, "/path/to/previous2.key"]
+            ],
+            "MaxConcurrentInputSize" : 1024   // size in MB 
+        }
     }
 }
 ```
@@ -29,14 +31,17 @@
 * `hg clone ...`
 * `mkdir -p build/google`
 * `cd build/google` 
-* `cmake -DCMAKE_TOOLCHAIN_FILE=[vcpkg root]\scripts\buildsystems\vcpkg.cmake ../../orthanc-object-storage/google`
+* `cmake -DCMAKE_TOOLCHAIN_FILE=[vcpkg root]\scripts\buildsystems\vcpkg.cmake ../../orthanc-object-storage/Google`
 
 ### Google plugin configuration ###
 
 ```
     "GoogleCloudStorage" : {
-        "ServiceAccountFile": "/.../googleServiceAccountFile.json",
-        "BucketName": "test-orthanc-storage-plugin"
+        "ServiceAccountFile" : "/.../googleServiceAccountFile.json",
+        "BucketName": "test-orthanc-storage-plugin",
+        "StorageEncryption" : {...},
+        "StorageStructure" : "flat",
+        "MigrationFromFileSystemEnabled" : false
     }
 
 ```
@@ -60,7 +65,10 @@
 ```
     "AzureBlobStorage" : {
     	"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=xxxxxxxxx;AccountKey=yyyyyyyy===;EndpointSuffix=core.windows.net",
-    	"ContainerName" : "test-orthanc-storage-plugin"
+    	"ContainerName" : "test-orthanc-storage-plugin",
+        "StorageEncryption" : {...},
+        "StorageStructure" : "flat",
+        "MigrationFromFileSystemEnabled" : false
     }
 ```
 
@@ -103,6 +111,9 @@
         "SecretKey" : "RhYYYY",
         "Endpoint": "",                 // optional: custom endpoint
         "ConnectionTimeout": 30,        // optional: connection timeout in seconds
-        "RequestTimeout": 1200          // optional: request timeout in seconds (max time to upload/download a file)
+        "RequestTimeout": 1200,         // optional: request timeout in seconds (max time to upload/download a file)
+        "StorageEncryption" : {...},
+        "StorageStructure" : "flat",
+        "MigrationFromFileSystemEnabled" : false
     }
 ```
\ No newline at end of file