changeset 197:df0b6998fd27

integration 2.3.0: back to mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 25 Jun 2024 12:38:31 +0200
parents 6987a96423c7 (diff) addccb7ad900 (current diff)
children 63d0ffa8e2f2
files Aws/AwsS3StoragePlugin.cpp Aws/CMakeLists.txt Azure/CMakeLists.txt Common/StoragePlugin.cpp Google/CMakeLists.txt NEWS
diffstat 60 files changed, 1489 insertions(+), 341 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Tue Nov 21 09:50:24 2023 +0100
+++ b/.hgignore	Tue Jun 25 12:38:31 2024 +0200
@@ -2,4 +2,6 @@
 CMakeLists.txt.user*
 *~
 */ThirdPartyDownloads/*
-.vscode/
\ No newline at end of file
+.vscode/
+*.orig
+Aws/i/
--- a/AUTHORS	Tue Nov 21 09:50:24 2023 +0100
+++ b/AUTHORS	Tue Jun 25 12:38:31 2024 +0200
@@ -5,7 +5,23 @@
 Authors
 -------
 
-* Osimis <info@osimis.io>
+* Sebastien Jodogne <s.jodogne@orthanc-labs.com>
+
+  Overall design and lead developer.
+
+* Osimis S.A.
   Quai Banning 6
-  4000 Liège
+  4000 Liege
+  Belgium
+
+* Orthanc Team SRL <info@orthanc.team>
+  Rue Joseph Marchal 14
+  4910 Theux
   Belgium
+  https://orthanc.team/
+
+* ICTEAM, UCLouvain
+  Place de l'Universite 1
+  1348 Ottignies-Louvain-la-Neuve
+  Belgium
+  https://uclouvain.be/icteam
--- a/Aws/AwsS3StoragePlugin.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/Aws/AwsS3StoragePlugin.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -17,6 +19,7 @@
  **/
 
 #include "AwsS3StoragePlugin.h"
+#include <Logging.h>
 
 #include <aws/core/Aws.h>
 #include <aws/s3/S3Client.h>
@@ -26,6 +29,9 @@
 #include <aws/s3/model/DeleteObjectRequest.h>
 #include <aws/core/auth/AWSCredentialsProvider.h>
 #include <aws/core/utils/HashingUtils.h>
+#include <aws/core/utils/logging/DefaultLogSystem.h>
+#include <aws/core/utils/logging/DefaultCRTLogSystem.h>
+#include <aws/core/utils/logging/AWSLogging.h>
 #include <aws/core/utils/memory/stl/AWSStreamFwd.h>
 #include <aws/core/utils/memory/stl/AWSStringStream.h>
 #include <aws/core/utils/memory/AWSMemory.h>
@@ -33,6 +39,7 @@
 #include <aws/core/utils/StringUtils.h>
 #include <aws/transfer/TransferManager.h>
 #include <aws/crt/Api.h>
+#include <iostream>
 #include <fstream>
 
 #include <boost/lexical_cast.hpp>
@@ -59,10 +66,14 @@
 
   virtual ~AwsS3StoragePlugin();
 
-  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);
-  virtual bool HasFileExists() {return false;};
+  virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+  virtual IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+  virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+
+  virtual bool HasFileExists() ORTHANC_OVERRIDE
+  {
+    return false;
+  }
 };
 
 
@@ -84,7 +95,7 @@
   {
   }
 
-  virtual void Write(const char* data, size_t size)
+  virtual void Write(const char* data, size_t size) ORTHANC_OVERRIDE
   {
     Aws::S3::Model::PutObjectRequest putObjectRequest;
 
@@ -132,7 +143,7 @@
 
   }
 
-  virtual size_t GetSize()
+  virtual size_t GetSize() ORTHANC_OVERRIDE
   {
     std::string firstExceptionMessage;
 
@@ -154,12 +165,12 @@
     throw StoragePluginException(firstExceptionMessage);
   }
 
-  virtual void ReadWhole(char* data, size_t size)
+  virtual void ReadWhole(char* data, size_t size) ORTHANC_OVERRIDE
   {
     _Read(data, size, 0, false);
   }
 
-  virtual void ReadRange(char* data, size_t size, size_t fromOffset)
+  virtual void ReadRange(char* data, size_t size, size_t fromOffset) ORTHANC_OVERRIDE
   {
     _Read(data, size, fromOffset, true);
   }
@@ -276,7 +287,7 @@
   {
   }
 
-  virtual void Write(const char* data, size_t size)
+  virtual void Write(const char* data, size_t size) ORTHANC_OVERRIDE
   {
     boost::interprocess::bufferstream buffer(const_cast<char*>(static_cast<const char*>(data)), static_cast<size_t>(size));
     std::shared_ptr<Aws::IOStream> body = Aws::MakeShared<Aws::IOStream>(ALLOCATION_TAG, buffer.rdbuf());
@@ -308,7 +319,7 @@
 
   }
 
-  virtual void ReadWhole(char* data, size_t size)
+  virtual void ReadWhole(char* data, size_t size) ORTHANC_OVERRIDE
   {
     std::string firstExceptionMessage;
 
@@ -378,6 +389,87 @@
 static std::unique_ptr<Aws::Crt::ApiHandle>  api_;
 static std::unique_ptr<Aws::SDKOptions>  sdkOptions_;
 
+#include <stdarg.h>
+
+class AwsOrthancLogger : public Aws::Utils::Logging::LogSystemInterface
+{
+public:
+    virtual ~AwsOrthancLogger() {}
+
+    /**
+     * Gets the currently configured log level for this logger.
+     */
+    virtual Aws::Utils::Logging::LogLevel GetLogLevel() const ORTHANC_OVERRIDE
+    {
+      return Aws::Utils::Logging::LogLevel::Trace;
+    }
+    /**
+     * Does a printf style output to the output stream. Don't use this, it's unsafe. See LogStream
+     */
+    virtual void Log(Aws::Utils::Logging::LogLevel logLevel, const char* tag, const char* formatStr, ...) ORTHANC_OVERRIDE
+    {
+      Aws::StringStream ss;
+
+      va_list args;
+      va_start(args, formatStr);
+
+      va_list tmp_args; //unfortunately you cannot consume a va_list twice
+      va_copy(tmp_args, args); //so we have to copy it
+      #ifdef _WIN32
+          const int requiredLength = _vscprintf(formatStr, tmp_args) + 1;
+      #else
+          const int requiredLength = vsnprintf(nullptr, 0, formatStr, tmp_args) + 1;
+      #endif
+      va_end(tmp_args);
+
+      assert(requiredLength > 0);
+      std::string outputBuff;
+      outputBuff.resize(requiredLength);
+      #ifdef _WIN32
+          vsnprintf_s(&outputBuff[0], requiredLength, _TRUNCATE, formatStr, args);
+      #else
+          vsnprintf(&outputBuff[0], requiredLength, formatStr, args);
+      #endif // _WIN32
+
+      if (logLevel == Aws::Utils::Logging::LogLevel::Debug || logLevel == Aws::Utils::Logging::LogLevel::Trace)
+      {
+        LOG(INFO) << outputBuff;
+      }
+      else if (logLevel == Aws::Utils::Logging::LogLevel::Warn)
+      {
+        LOG(WARNING) << outputBuff;
+      }
+      else
+      {
+        LOG(ERROR) << outputBuff;
+      }
+
+      va_end(args);
+    }
+    /**
+    * Writes the stream to the output stream.
+    */
+    virtual void LogStream(Aws::Utils::Logging::LogLevel logLevel, const char* tag, const Aws::OStringStream &messageStream) ORTHANC_OVERRIDE
+    {
+      if (logLevel == Aws::Utils::Logging::LogLevel::Debug || logLevel == Aws::Utils::Logging::LogLevel::Trace)
+      {
+        LOG(INFO) << tag << messageStream.str();
+      }
+      else if (logLevel == Aws::Utils::Logging::LogLevel::Warn)
+      {
+        LOG(WARNING) << tag << messageStream.str();
+      }
+      else
+      {
+        LOG(ERROR) << tag << messageStream.str();
+      }
+
+    }
+    /**
+     * Writes any buffered messages to the underlying device if the logger supports buffering.
+     */
+    virtual void Flush() ORTHANC_OVERRIDE {}
+};
 
 IStorage* AwsS3StoragePluginFactory::CreateStorage(const std::string& nameForLogs, const OrthancPlugins::OrthancConfiguration& orthancConfig)
 {
@@ -386,20 +478,12 @@
     throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, "Cannot initialize twice");
   }
 
-  api_.reset(new Aws::Crt::ApiHandle);
-
-  sdkOptions_.reset(new Aws::SDKOptions);
-  sdkOptions_->cryptoOptions.initAndCleanupOpenSSL = false;  // Done by the Orthanc framework
-  sdkOptions_->httpOptions.initAndCleanupCurl = false;  // Done by the Orthanc framework
-  
-  Aws::InitAPI(*sdkOptions_);
-
   bool enableLegacyStorageStructure;
   bool storageContainsUnknownFiles;
 
   if (!orthancConfig.IsSection(GetConfigurationSectionName()))
   {
-    OrthancPlugins::LogWarning(std::string(GetStoragePluginName()) + " plugin, section missing.  Plugin is not enabled.");
+    LOG(WARNING) << GetStoragePluginName() << " plugin, section missing.  Plugin is not enabled.";
     return nullptr;
   }
 
@@ -418,13 +502,13 @@
 
   if (!pluginSection.LookupStringValue(bucketName, "BucketName"))
   {
-    OrthancPlugins::LogError("AwsS3Storage/BucketName configuration missing.  Unable to initialize plugin");
+    LOG(ERROR) << "AwsS3Storage/BucketName configuration missing.  Unable to initialize plugin";
     return nullptr;
   }
 
   if (!pluginSection.LookupStringValue(region, "Region"))
   {
-    OrthancPlugins::LogError("AwsS3Storage/Region configuration missing.  Unable to initialize plugin");
+    LOG(ERROR) << "AwsS3Storage/Region configuration missing.  Unable to initialize plugin";
     return nullptr;
   }
 
@@ -432,8 +516,26 @@
   const unsigned int connectTimeout = pluginSection.GetUnsignedIntegerValue("ConnectTimeout", 30);
   const unsigned int requestTimeout = pluginSection.GetUnsignedIntegerValue("RequestTimeout", 1200);
   const bool virtualAddressing = pluginSection.GetBooleanValue("VirtualAddressing", true);
+  const bool enableAwsSdkLogs = pluginSection.GetBooleanValue("EnableAwsSdkLogs", false);
   const std::string caFile = orthancConfig.GetStringValue("HttpsCACertificates", "");
-  
+
+
+  api_.reset(new Aws::Crt::ApiHandle);
+
+  sdkOptions_.reset(new Aws::SDKOptions);
+  sdkOptions_->cryptoOptions.initAndCleanupOpenSSL = false;  // Done by the Orthanc framework
+  sdkOptions_->httpOptions.initAndCleanupCurl = false;  // Done by the Orthanc framework
+
+  if (enableAwsSdkLogs)
+  {
+    // Set up logging
+    Aws::Utils::Logging::InitializeAWSLogging(Aws::MakeShared<AwsOrthancLogger>(ALLOCATION_TAG));
+    // strangely, this seems to disable logging !!!! sdkOptions_->loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Trace;
+  }
+
+  Aws::InitAPI(*sdkOptions_);
+
+
   try
   {
     Aws::Client::ClientConfiguration configuration;
@@ -467,7 +569,7 @@
 
     if (pluginSection.LookupStringValue(accessKey, "AccessKey") && pluginSection.LookupStringValue(secretKey, "SecretKey"))
     {
-      OrthancPlugins::LogInfo("AWS S3 Storage: using credentials from the configuration file");
+      LOG(INFO) << "AWS S3 Storage: using credentials from the configuration file";
       Aws::Auth::AWSCredentials credentials(accessKey.c_str(), secretKey.c_str());
       
       client = Aws::MakeShared<Aws::S3::S3Client>(ALLOCATION_TAG, credentials, configuration, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, virtualAddressing);
@@ -475,20 +577,20 @@
     else
     {
       // when using default credentials, credentials are not checked at startup but only the first time you try to access the bucket !
-      OrthancPlugins::LogInfo("AWS S3 Storage: using default credentials provider");
+      LOG(INFO) << "AWS S3 Storage: using default credentials provider";
       client = Aws::MakeShared<Aws::S3::S3Client>(ALLOCATION_TAG, configuration, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, virtualAddressing);
     }  
 
-    OrthancPlugins::LogInfo("AWS S3 storage initialized");
+    LOG(INFO) << "AWS S3 storage initialized";
 
     return new AwsS3StoragePlugin(nameForLogs, client, bucketName, enableLegacyStorageStructure, storageContainsUnknownFiles, useTransferManager, transferPoolSize, transferBufferSizeMB);
   }
   catch (const std::exception& e)
   {
-    OrthancPlugins::LogError(std::string("AWS S3 Storage plugin: failed to initialize plugin: ") + e.what());
+    Aws::ShutdownAPI(*sdkOptions_);
+    LOG(ERROR) << "AWS S3 Storage plugin: failed to initialize plugin: " << e.what();
     return nullptr;
   }
-
 }
 
 
@@ -497,6 +599,7 @@
   assert(sdkOptions_.get() != NULL);
   Aws::ShutdownAPI(*sdkOptions_);
   api_.reset();
+  sdkOptions_.reset();
 }
 
 
--- a/Aws/AwsS3StoragePlugin.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Aws/AwsS3StoragePlugin.h	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
--- a/Aws/AwsStaticConfiguration.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Aws/AwsStaticConfiguration.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -1,5 +1,7 @@
 # Cloud storage plugins for Orthanc
-# Copyright (C) 2020-2021 Osimis S.A., Belgium
+# Copyright (C) 2020-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 Affero General Public License
@@ -15,72 +17,156 @@
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
 
-# version numbers can be obtained from https://github.com/aws/aws-sdk-cpp/blob/main/prefetch_crt_dependency.sh
+# The versions below must match those in "./DownloadStaticPackages.sh"
+SET(AWS_CHECKSUMS_VERSION      0.1.18)
+SET(AWS_CRT_CPP_VERSION        0.24.1)
+SET(AWS_C_AUTH_VERSION         0.7.22)
+SET(AWS_C_CAL_VERSION          0.7.0)
+SET(AWS_C_COMMON_VERSION       0.9.23)
+SET(AWS_C_COMPRESSION_VERSION  0.2.18)
+SET(AWS_C_EVENT_STREAM_VERSION 0.4.2)
+SET(AWS_C_HTTP_VERSION         0.8.2)
+SET(AWS_C_IO_VERSION           0.14.9)
+SET(AWS_C_MQTT_VERSION         0.10.4)
+SET(AWS_C_S3_VERSION           0.5.10)
+SET(AWS_C_SDKUTILS_VERSION     0.1.16)
+SET(AWS_SDK_CPP_VERSION        1.11.178)
 
-SET(AWS_C_COMMON_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-common-0.9.3)  # source =  https://github.com/awslabs/aws-c-common/archive/refs/tags/v0.9.3.tar.gz
-SET(AWS_C_COMMON_URL "https://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-c-common-0.9.3.tar.gz")
-SET(AWS_C_COMMON_MD5 "d872ff682d5beca8ada6b19464235ca0")
-DownloadPackage(${AWS_C_COMMON_MD5} ${AWS_C_COMMON_URL} "${AWS_C_COMMON_SOURCES_DIR}")
+SET(AWS_CHECKSUMS_MD5       "ab640e05f6e8ffc20d619599b5ff7c92")
+SET(AWS_CRT_CPP_MD5         "fa2cda44386bd56f1d4609c6a54a59f9")
+SET(AWS_C_AUTH_MD5          "d2655feb998f57af8f9e985bf358face")
+SET(AWS_C_CAL_MD5           "522cad001d0b259e792b7468cd4d4c84")
+SET(AWS_C_COMMON_MD5        "a36562c3eb99227cbb131c7c56602b86")
+SET(AWS_C_COMPRESSION_MD5   "f1510a8769637d4997e43d2783807113")
+SET(AWS_C_EVENT_STREAM_MD5  "9d0f9a4f81927fcffccfa989baebf024")
+SET(AWS_C_HTTP_MD5          "20156836ae45b982cc56f2762cc9f27e")
+SET(AWS_C_IO_MD5            "c95a51d07c1eace423205af3a5407121")
+SET(AWS_C_MQTT_MD5          "43de8c035d383ac33652e0de0e4b2b7a")
+SET(AWS_C_S3_MD5            "5db9d3ec2caa4ebe1b4b3bf27a45c513")
+SET(AWS_C_SDKUTILS_MD5      "a215ee5a0d082df1bff812b24456c0a2")
+SET(AWS_SDK_CPP_MD5         "a3f45888e939bb71506e0f7eaa630e48")
 
-SET(AWS_CHECKSUMS_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-checksums-0.1.17)  # source =  https://github.com/awslabs/aws-checksums/archive/refs/tags/v0.1.17.tar.gz
-SET(AWS_CHECKSUMS_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-checksums-0.1.17.tar.gz")
-SET(AWS_CHECKSUMS_MD5 "c256144404dc74349a8344662111e353")
+
+SET(AWS_CHECKSUMS_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-checksums-${AWS_CHECKSUMS_VERSION})
+SET(AWS_CHECKSUMS_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-checksums-${AWS_CHECKSUMS_VERSION}.tar.gz")
 DownloadPackage(${AWS_CHECKSUMS_MD5} ${AWS_CHECKSUMS_URL} "${AWS_CHECKSUMS_SOURCES_DIR}")
 
-SET(AWS_C_AUTH_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-auth-0.7.1)  # source =  https://github.com/awslabs/aws-c-auth/archive/refs/tags/v0.7.1.tar.gz
-SET(AWS_C_AUTH_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-c-auth-0.7.1.tar.gz")
-SET(AWS_C_AUTH_MD5 "aa690622a5f697d47ca3095ba2ef3cca")
+SET(AWS_CRT_CPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-crt-cpp-${AWS_CRT_CPP_VERSION})
+SET(AWS_CRT_CPP_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-crt-cpp-${AWS_CRT_CPP_VERSION}.tar.gz")
+DownloadPackage(${AWS_CRT_CPP_MD5} ${AWS_CRT_CPP_URL} "${AWS_CRT_CPP_SOURCES_DIR}")
+
+SET(AWS_C_AUTH_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-auth-${AWS_C_AUTH_VERSION})
+SET(AWS_C_AUTH_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-c-auth-${AWS_C_AUTH_VERSION}.tar.gz")
 DownloadPackage(${AWS_C_AUTH_MD5} ${AWS_C_AUTH_URL} "${AWS_C_AUTH_SOURCES_DIR}")
 
-SET(AWS_C_CAL_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-cal-0.6.1)   # source =  https://github.com/awslabs/aws-c-cal/archive/refs/tags/v0.6.1.tar.gz
-SET(AWS_C_CAL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-c-cal-0.6.1.tar.gz")
-SET(AWS_C_CAL_MD5 "6d7c05e2f1c173b923e77b6021cb660d")
+SET(AWS_C_CAL_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-cal-${AWS_C_CAL_VERSION})
+SET(AWS_C_CAL_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-c-cal-${AWS_C_CAL_VERSION}.tar.gz")
 DownloadPackage(${AWS_C_CAL_MD5} ${AWS_C_CAL_URL} "${AWS_C_CAL_SOURCES_DIR}")
 
-SET(AWS_C_COMPRESSION_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-compression-0.2.17)  # source =  https://github.com/awslabs/aws-c-compression/archive/refs/tags/v0.2.17.tar.gz
-SET(AWS_C_COMPRESSION_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-c-compression-0.2.17.tar.gz")
-SET(AWS_C_COMPRESSION_MD5 "b8bc8bc9cf3749eb2d3f36098c9ead27")
+
+SET(AWS_C_COMMON_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-common-${AWS_C_COMMON_VERSION})
+SET(AWS_C_COMMON_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-c-common-${AWS_C_COMMON_VERSION}.tar.gz")
+
+if (IS_DIRECTORY "${AWS_C_COMMON_SOURCES_DIR}")
+  set(FirstRun OFF)
+else()
+  set(FirstRun ON)
+endif()
+
+DownloadPackage(${AWS_C_COMMON_MD5} ${AWS_C_COMMON_URL} "${AWS_C_COMMON_SOURCES_DIR}")
+
+if (FirstRun)
+  # This is a patch for MinGW
+  execute_process(
+    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+    ${CMAKE_CURRENT_LIST_DIR}/aws-c-common-${AWS_C_COMMON_VERSION}.patch
+    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+    RESULT_VARIABLE Failure
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while patching a file")
+  endif()
+endif()
+
+
+
+SET(AWS_C_COMPRESSION_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-compression-${AWS_C_COMPRESSION_VERSION})
+SET(AWS_C_COMPRESSION_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-c-compression-${AWS_C_COMPRESSION_VERSION}.tar.gz")
 DownloadPackage(${AWS_C_COMPRESSION_MD5} ${AWS_C_COMPRESSION_URL} "${AWS_C_COMPRESSION_SOURCES_DIR}")
 
-SET(AWS_C_EVENT_STREAM_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-event-stream-0.3.1)  # source =  https://github.com/awslabs/aws-c-event-stream/archive/refs/tags/v0.3.1.tar.gz
-SET(AWS_C_EVENT_STREAM_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-c-event-stream-0.3.1.tar.gz")
-SET(AWS_C_EVENT_STREAM_MD5 "e3261d89598a3331eebcd0476b523c73")
+SET(AWS_C_EVENT_STREAM_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-event-stream-${AWS_C_EVENT_STREAM_VERSION})
+SET(AWS_C_EVENT_STREAM_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-c-event-stream-${AWS_C_EVENT_STREAM_VERSION}.tar.gz")
 DownloadPackage(${AWS_C_EVENT_STREAM_MD5} ${AWS_C_EVENT_STREAM_URL} "${AWS_C_EVENT_STREAM_SOURCES_DIR}")
 
-SET(AWS_C_HTTP_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-http-0.7.11)  # source =  https://github.com/awslabs/aws-c-http/archive/refs/tags/v0.7.11.tar.gz
-SET(AWS_C_HTTP_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-c-http-0.7.11.tar.gz")
-SET(AWS_C_HTTP_MD5 "b8ef3268d4d64b7fabe743b03518d2d7")
+SET(AWS_C_HTTP_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-http-${AWS_C_HTTP_VERSION})
+SET(AWS_C_HTTP_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-c-http-${AWS_C_HTTP_VERSION}.tar.gz")
 DownloadPackage(${AWS_C_HTTP_MD5} ${AWS_C_HTTP_URL} "${AWS_C_HTTP_SOURCES_DIR}")
 
-SET(AWS_C_IO_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-io-0.13.31)  # source =  https://github.com/awslabs/aws-c-io/archive/refs/tags/v0.13.31.tar.gz
-SET(AWS_C_IO_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-c-io-0.13.31.tar.gz")
-SET(AWS_C_IO_MD5 "9ae415cb81404e1b76d8e8267e585e34")
+
+SET(AWS_C_IO_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-io-${AWS_C_IO_VERSION})
+SET(AWS_C_IO_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-c-io-${AWS_C_IO_VERSION}.tar.gz")
+
+if (IS_DIRECTORY "${AWS_C_IO_SOURCES_DIR}")
+  set(FirstRun OFF)
+else()
+  set(FirstRun ON)
+endif()
+
 DownloadPackage(${AWS_C_IO_MD5} ${AWS_C_IO_URL} "${AWS_C_IO_SOURCES_DIR}")
 
-SET(AWS_C_MQTT_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-mqtt-0.9.5)  # source =  https://github.com/awslabs/aws-c-mqtt/archive/refs/tags/v0.9.5.tar.gz
-SET(AWS_C_MQTT_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-c-mqtt-0.9.5.tar.gz")
-SET(AWS_C_MQTT_MD5 "76d59d9f14f3999b0f9618e386e71db6")
+if (FirstRun)
+  # This is a patch for MinGW
+  execute_process(
+    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+    ${CMAKE_CURRENT_LIST_DIR}/aws-c-io-${AWS_C_IO_VERSION}.patch
+    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+    RESULT_VARIABLE Failure
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while patching a file")
+  endif()
+endif()
+
+
+SET(AWS_C_MQTT_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-mqtt-${AWS_C_MQTT_VERSION})
+SET(AWS_C_MQTT_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-c-mqtt-${AWS_C_MQTT_VERSION}.tar.gz")
 DownloadPackage(${AWS_C_MQTT_MD5} ${AWS_C_MQTT_URL} "${AWS_C_MQTT_SOURCES_DIR}")
 
-SET(AWS_C_S3_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-s3-0.3.14)  # source =  https://github.com/awslabs/aws-c-s3/archive/refs/tags/v0.3.14.tar.gz
-SET(AWS_C_S3_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-c-s3-0.3.14.tar.gz")
-SET(AWS_C_S3_MD5 "564bb0df1184ebe9ce41c23a0c18cf1a")
+SET(AWS_C_S3_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-s3-${AWS_C_S3_VERSION})
+SET(AWS_C_S3_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-c-s3-${AWS_C_S3_VERSION}.tar.gz")
 DownloadPackage(${AWS_C_S3_MD5} ${AWS_C_S3_URL} "${AWS_C_S3_SOURCES_DIR}")
 
-SET(AWS_C_SDKUTILS_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-sdkutils-0.1.11)  # source =  https://github.com/awslabs/aws-c-sdkutils/archive/refs/tags/v0.1.11.tar.gz
-SET(AWS_C_SDKUTILS_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-c-sdkutils-0.1.11.tar.gz")
-SET(AWS_C_SDKUTILS_MD5 "713cac3392aa20d5dda852cf21ac1957")
+SET(AWS_C_SDKUTILS_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-c-sdkutils-${AWS_C_SDKUTILS_VERSION})
+SET(AWS_C_SDKUTILS_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-c-sdkutils-${AWS_C_SDKUTILS_VERSION}.tar.gz")
 DownloadPackage(${AWS_C_SDKUTILS_MD5} ${AWS_C_SDKUTILS_URL} "${AWS_C_SDKUTILS_SOURCES_DIR}")
 
-SET(AWS_CRT_CPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-crt-cpp-0.24.1)  # source =  https://github.com/awslabs/aws-crt-cpp/archive/refs/tags/v0.24.1.tar.gz
-SET(AWS_CRT_CPP_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-crt-cpp-0.24.1.tar.gz")
-SET(AWS_CRT_CPP_MD5 "fa2cda44386bd56f1d4609c6a54a59f9")
-DownloadPackage(${AWS_CRT_CPP_MD5} ${AWS_CRT_CPP_URL} "${AWS_CRT_CPP_SOURCES_DIR}")
+
+SET(AWS_SDK_CPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-sdk-cpp-${AWS_SDK_CPP_VERSION})
+SET(AWS_SDK_CPP_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/aws/aws-sdk-cpp-${AWS_SDK_CPP_VERSION}.tar.gz")
+
+if (IS_DIRECTORY "${AWS_SDK_CPP_SOURCES_DIR}")
+  set(FirstRun OFF)
+else()
+  set(FirstRun ON)
+endif()
+
+DownloadPackage(${AWS_SDK_CPP_MD5} ${AWS_SDK_CPP_URL} "${AWS_SDK_CPP_SOURCES_DIR}")
 
-SET(AWS_SDK_CPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/aws-sdk-cpp-1.11.178)  # source =  https://github.com/aws/aws-sdk-cpp/archive/refs/tags/1.11.178.tar.gz
-SET(AWS_SDK_CPP_URL "http://orthanc.osimis.io/ThirdPartyDownloads/aws/aws-sdk-cpp-1.11.178.tar.gz")
-SET(AWS_SDK_CPP_MD5 "a3f45888e939bb71506e0f7eaa630e48")
-DownloadPackage(${AWS_SDK_CPP_MD5} ${AWS_SDK_CPP_URL} "${AWS_SDK_CPP_SOURCES_DIR}")
+if (FirstRun)
+  # This is a patch for Microsoft Visual Studio 2015 and MinGW
+  execute_process(
+    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+    ${CMAKE_CURRENT_LIST_DIR}/aws-sdk-cpp-${AWS_SDK_CPP_VERSION}.patch
+    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+    RESULT_VARIABLE Failure
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while patching a file")
+  endif()
+endif()
 
 
 configure_file(
@@ -101,6 +187,7 @@
 
 include_directories(
   ${AWS_C_COMMON_SOURCES_DIR}/include/
+  ${AWS_C_COMMON_SOURCES_DIR}/source/external/libcbor
   ${AWS_C_AUTH_SOURCES_DIR}/include/
   ${AWS_C_CAL_SOURCES_DIR}/include/
   ${AWS_C_COMPRESSION_SOURCES_DIR}/include/
@@ -123,6 +210,9 @@
 list(APPEND AWS_SOURCES_SUBDIRS
   ${AWS_C_COMMON_SOURCES_DIR}/source/
   ${AWS_C_COMMON_SOURCES_DIR}/source/external/
+  ${AWS_C_COMMON_SOURCES_DIR}/source/external/libcbor/
+  ${AWS_C_COMMON_SOURCES_DIR}/source/external/libcbor/cbor/
+  ${AWS_C_COMMON_SOURCES_DIR}/source/external/libcbor/cbor/internal/
 
   ## C libraries
   
@@ -166,8 +256,6 @@
   ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/internal/
   ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/monitoring/
   ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/net/
-  ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/platform/linux-shared/
-  # ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/platform/windows/
   ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/smithy/tracing/
   ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/utils/
   ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/utils/base64/
@@ -194,16 +282,46 @@
   -DBYO_CRYPTO  # To have "aws_tls_server_ctx_new()" defined
   -DENABLE_OPENSSL_ENCRYPTION=1
   -DENABLE_CURL_CLIENT=1
+  -DINTEL_NO_ITTNOTIFY_API=1
   )
 
-list(APPEND AWS_SOURCES_SUBDIRS
-  #${AWS_C_CAL_SOURCES_DIR}/source/unix/
-  #${AWS_SDK_CPP_SOURCES_DIR}/aws-cpp-sdk-core/source/net/linux-shared/
-  ${AWS_C_COMMON_SOURCES_DIR}/source/posix/
-  ${AWS_C_IO_SOURCES_DIR}/source/linux/
-  ${AWS_C_IO_SOURCES_DIR}/source/posix/
-  ${AWS_SDK_CPP_SOURCES_DIR}/aws-cpp-sdk-core/source/platform/linux-shared/
-  )
+if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
+  list(APPEND AWS_SOURCES_SUBDIRS
+    ${AWS_C_COMMON_SOURCES_DIR}/source/windows/
+    ${AWS_C_IO_SOURCES_DIR}/source/windows/
+    ${AWS_C_IO_SOURCES_DIR}/source/windows/iocp/
+    ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/platform/windows/
+    )
+
+  add_definitions(
+    -DAWS_USE_IO_COMPLETION_PORTS=1
+    # -DCJSON_AS4CPP_HIDE_SYMBOLS=1    # Disable "dllexport" in cJSON => doesn't seem to work
+    -DCJSON_HIDE_SYMBOLS=1
+    )
+
+  set(AWSSDK_LINK_LIBRARIES
+    # secur32
+    ncrypt
+    shlwapi
+    userenv
+    version
+    )
+
+  # Target Windows 7, as "PPROCESSOR_NUMBER" is needed by AWS
+  remove_definitions(-DWINVER=0x0501 -D_WIN32_WINNT=0x0501)
+  add_definitions(-DWINVER=0x0601 -D_WIN32_WINNT=0x0601)
+
+else()
+  list(APPEND AWS_SOURCES_SUBDIRS
+    #${AWS_C_CAL_SOURCES_DIR}/source/unix/
+    #${AWS_SDK_CPP_SOURCES_DIR}/aws-cpp-sdk-core/source/net/linux-shared/
+    ${AWS_C_COMMON_SOURCES_DIR}/source/linux/
+    ${AWS_C_COMMON_SOURCES_DIR}/source/posix/
+    ${AWS_C_IO_SOURCES_DIR}/source/linux/
+    ${AWS_C_IO_SOURCES_DIR}/source/posix/
+    ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/platform/linux-shared/
+    )
+endif()
 
 
 foreach(d ${AWS_SOURCES_SUBDIRS})
@@ -215,3 +333,26 @@
   ${AWS_C_COMMON_SOURCES_DIR}/source/arch/generic/cpuid.c
   ${AWS_CHECKSUMS_SOURCES_DIR}/source/generic/crc32c_null.c
   )
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
+  # WARNING: "//" *are* important below (don't replace them with "/")
+  if (MINGW)
+    list(REMOVE_ITEM AWS_SOURCES
+      ${AWS_C_COMMON_SOURCES_DIR}/source/windows//system_info.c
+      ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/platform/windows//Environment.cpp
+      )
+    list(APPEND AWS_SOURCES
+      ${AWS_C_COMMON_SOURCES_DIR}/source/posix/system_info.c
+      ${AWS_SDK_CPP_SOURCES_DIR}/src/aws-cpp-sdk-core/source/platform/linux-shared/Environment.cpp
+      )
+  endif()
+
+  list(APPEND AWS_SOURCES
+    ${AWS_C_COMMON_SOURCES_DIR}/source/platform_fallback_stubs/system_info.c
+    )
+
+  list(REMOVE_ITEM AWS_SOURCES
+    ${AWS_C_IO_SOURCES_DIR}/source/windows//secure_channel_tls_handler.c
+    )
+endif()
--- a/Aws/CMakeLists.txt	Tue Nov 21 09:50:24 2023 +0100
+++ b/Aws/CMakeLists.txt	Tue Jun 25 12:38:31 2024 +0200
@@ -1,5 +1,7 @@
 # Cloud storage plugins for Orthanc
-# Copyright (C) 2020-2021 Osimis S.A., Belgium
+# Copyright (C) 2020-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 Affero General Public License
@@ -19,13 +21,13 @@
 
 project(OrthancAwsS3Storage)
 
-set(PLUGIN_VERSION "2.3.0")
+set(PLUGIN_VERSION "mainline")
 
 if (PLUGIN_VERSION STREQUAL "mainline")
   set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
 else()
-  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "daf4807631c5")
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.12.4")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
 endif()
 
@@ -81,6 +83,7 @@
   set(ENABLE_MODULE_DICOM OFF)
   set(ENABLE_MODULE_IMAGES OFF)
   set(ENABLE_MODULE_JOBS OFF)
+  set(ENABLE_OPENSSL_ENGINES ON)  # Necessary since OpenSSL 3.1.x
   set(ENABLE_WEB_CLIENT ON)  # Access options related to curl
   set(ORTHANC_FRAMEWORK_PLUGIN ON)
 
@@ -148,8 +151,6 @@
   ${COMMON_SOURCES}
   )
 
-DefineSourceBasenameForTarget(OrthancAwsS3Storage)
-
 set_target_properties(OrthancAwsS3Storage PROPERTIES
   VERSION ${PLUGIN_VERSION}
   SOVERSION ${PLUGIN_VERSION}
@@ -162,6 +163,14 @@
   ${ORTHANC_FRAMEWORK_LIBRARIES}
   )
 
+
+install(
+  TARGETS OrthancAwsS3Storage
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+)
+
+
 add_executable(UnitTests
   ${GOOGLE_TEST_SOURCES}
   ${COMMON_SOURCES}
@@ -182,3 +191,8 @@
   add_dependencies(OrthancAwsS3Storage AwsSdkCpp)
   add_dependencies(UnitTests AwsSdkCpp)
 endif()
+
+if (COMMAND DefineSourceBasenameForTarget)
+  DefineSourceBasenameForTarget(OrthancAwsS3Storage)
+  DefineSourceBasenameForTarget(UnitTests)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Aws/DownloadStaticPackages.sh	Tue Jun 25 12:38:31 2024 +0200
@@ -0,0 +1,78 @@
+#!/bin/bash
+
+# Cloud storage plugins for Orthanc
+# Copyright (C) 2020-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 Affero 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
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# This maintenance script downloads the AWS packages.
+#
+# NB: Older version numbers can be obtained from
+# https://github.com/aws/aws-sdk-cpp/blob/main/prefetch_crt_dependency.sh
+
+
+set -e
+
+AWS_CHECKSUMS_VERSION=0.1.18       # https://github.com/awslabs/aws-checksums/tags
+AWS_CRT_CPP_VERSION=0.24.1         # https://github.com/awslabs/aws-crt-cpp/tags
+AWS_C_AUTH_VERSION=0.7.22          # https://github.com/awslabs/aws-c-auth/tags
+AWS_C_CAL_VERSION=0.7.0            # https://github.com/awslabs/aws-c-cal/tags
+AWS_C_COMMON_VERSION=0.9.23        # https://github.com/awslabs/aws-c-common/tags
+AWS_C_COMPRESSION_VERSION=0.2.18   # https://github.com/awslabs/aws-c-compression/tags
+AWS_C_EVENT_STREAM_VERSION=0.4.2   # https://github.com/awslabs/aws-c-event-stream/tags
+AWS_C_HTTP_VERSION=0.8.2           # https://github.com/awslabs/aws-c-http/tags
+AWS_C_IO_VERSION=0.14.9            # https://github.com/awslabs/aws-c-io/tags
+AWS_C_MQTT_VERSION=0.10.4          # https://github.com/awslabs/aws-c-mqtt/tags
+AWS_C_S3_VERSION=0.5.10            # https://github.com/awslabs/aws-c-s3/tags
+AWS_C_SDKUTILS_VERSION=0.1.16      # https://github.com/awslabs/aws-c-sdkutils/tags
+AWS_SDK_CPP_VERSION=1.11.178       # https://github.com/awslabs/aws-sdk-cpp/tags
+
+TARGET=/tmp/aws
+
+if [ -d "${TARGET}" ]; then
+    echo "Directory exists, giving up: ${TARGET}"
+    exit -1
+fi
+
+mkdir -p ${TARGET}
+
+wget https://github.com/awslabs/aws-checksums/archive/refs/tags/v${AWS_CHECKSUMS_VERSION}.tar.gz \
+     -O ${TARGET}/aws-checksums-${AWS_CHECKSUMS_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-crt-cpp/archive/refs/tags/v${AWS_CRT_CPP_VERSION}.tar.gz \
+     -O ${TARGET}/aws-crt-cpp-${AWS_CRT_CPP_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-c-auth/archive/refs/tags/v${AWS_C_AUTH_VERSION}.tar.gz \
+     -O ${TARGET}/aws-c-auth-${AWS_C_AUTH_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-c-cal/archive/refs/tags/v${AWS_C_CAL_VERSION}.tar.gz \
+     -O ${TARGET}/aws-c-cal-${AWS_C_CAL_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-c-common/archive/refs/tags/v${AWS_C_COMMON_VERSION}.tar.gz \
+     -O ${TARGET}/aws-c-common-${AWS_C_COMMON_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-c-compression/archive/refs/tags/v${AWS_C_COMPRESSION_VERSION}.tar.gz \
+     -O ${TARGET}/aws-c-compression-${AWS_C_COMPRESSION_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-c-event-stream/archive/refs/tags/v${AWS_C_EVENT_STREAM_VERSION}.tar.gz \
+     -O ${TARGET}/aws-c-event-stream-${AWS_C_EVENT_STREAM_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-c-http/archive/refs/tags/v${AWS_C_HTTP_VERSION}.tar.gz \
+     -O ${TARGET}/aws-c-http-${AWS_C_HTTP_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-c-io/archive/refs/tags/v${AWS_C_IO_VERSION}.tar.gz \
+     -O ${TARGET}/aws-c-io-${AWS_C_IO_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-c-mqtt/archive/refs/tags/v${AWS_C_MQTT_VERSION}.tar.gz \
+     -O ${TARGET}/aws-c-mqtt-${AWS_C_MQTT_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-c-s3/archive/refs/tags/v${AWS_C_S3_VERSION}.tar.gz \
+     -O ${TARGET}/aws-c-s3-${AWS_C_S3_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-c-sdkutils/archive/refs/tags/v${AWS_C_SDKUTILS_VERSION}.tar.gz \
+     -O ${TARGET}/aws-c-sdkutils-${AWS_C_SDKUTILS_VERSION}.tar.gz
+wget https://github.com/awslabs/aws-sdk-cpp/archive/refs/tags/${AWS_SDK_CPP_VERSION}.tar.gz \
+     -O ${TARGET}/aws-sdk-cpp-${AWS_SDK_CPP_VERSION}.tar.gz
--- a/Aws/Graveyard/AwsExternalProject.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Aws/Graveyard/AwsExternalProject.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -1,5 +1,7 @@
 # Cloud storage plugins for Orthanc
-# Copyright (C) 2020-2021 Osimis S.A., Belgium
+# Copyright (C) 2020-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 Affero General Public License
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Aws/README.txt	Tue Jun 25 12:38:31 2024 +0200
@@ -0,0 +1,39 @@
+
+Native compilation under Ubuntu 22.04
+=====================================
+
+# mkdir Build
+# cd Build
+# cmake .. -DCMAKE_BUILD_TYPE=Release -DUSE_VCPKG_PACKAGES=OFF -G Ninja
+# ninja
+
+
+Linux Standard Base compilation
+===============================
+
+# ./holy-build-box-compile.sh Release
+
+
+MinGW
+=====
+
+The minimum MinGW version to compile the AWS C++ SDK is 13.x. 
+
+To build Windows 32 binaries:
+
+# ./mingw-compile.sh Release i686
+# ls ../mingw-binaries-i686/
+
+To build Windows 64 binaries:
+
+# ./mingw-compile.sh Release x86_64
+# ls ../mingw-binaries-x86_64/
+
+
+Microsoft Visual Studio
+=======================
+
+The minimum version to compile the AWS C++ SDK is Visual Studio 2015.
+
+WARNING: The binaries that are produced with Visual Studio currently
+do not work. They write empty files to the S3 storage.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Aws/aws-c-common-0.9.23.patch	Tue Jun 25 12:38:31 2024 +0200
@@ -0,0 +1,12 @@
+diff -urEb aws-c-common-0.9.23.orig/include/aws/common/byte_order.inl aws-c-common-0.9.23/include/aws/common/byte_order.inl
+--- aws-c-common-0.9.23.orig/include/aws/common/byte_order.inl	2024-06-20 20:25:49.000000000 +0200
++++ aws-c-common-0.9.23/include/aws/common/byte_order.inl	2024-06-23 14:55:38.563418806 +0200
+@@ -39,7 +39,7 @@
+     uint64_t v;
+     __asm__("bswap %q0" : "=r"(v) : "0"(x));
+     return v;
+-#elif defined(_MSC_VER)
++#elif defined(_MSC_VER) || defined(__MINGW32__)
+     return _byteswap_uint64(x);
+ #else
+     uint32_t low = x & UINT32_MAX;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Aws/aws-c-io-0.14.9.patch	Tue Jun 25 12:38:31 2024 +0200
@@ -0,0 +1,92 @@
+diff -urEb aws-c-io-0.14.9.orig/include/aws/io/private/pki_utils.h aws-c-io-0.14.9/include/aws/io/private/pki_utils.h
+--- aws-c-io-0.14.9.orig/include/aws/io/private/pki_utils.h	2024-06-04 18:27:58.000000000 +0200
++++ aws-c-io-0.14.9/include/aws/io/private/pki_utils.h	2024-06-23 10:39:15.047978000 +0200
+@@ -9,7 +9,7 @@
+ #ifdef _WIN32
+ /* It's ok to include external headers because this is a PRIVATE header file
+  * (it is usually a crime to include windows.h from header file) */
+-#    include <Windows.h>
++#    include <windows.h>
+ #endif /* _WIN32 */
+ 
+ #ifdef AWS_OS_APPLE
+diff -urEb aws-c-io-0.14.9.orig/source/windows/iocp/iocp_event_loop.c aws-c-io-0.14.9/source/windows/iocp/iocp_event_loop.c
+--- aws-c-io-0.14.9.orig/source/windows/iocp/iocp_event_loop.c	2024-06-04 18:27:58.000000000 +0200
++++ aws-c-io-0.14.9/source/windows/iocp/iocp_event_loop.c	2024-06-23 10:39:15.047978000 +0200
+@@ -12,7 +12,7 @@
+ 
+ #include <aws/io/logging.h>
+ 
+-#include <Windows.h>
++#include <windows.h>
+ 
+ /* The next set of struct definitions are taken directly from the
+     windows documentation. We can't include the header files directly
+diff -urEb aws-c-io-0.14.9.orig/source/windows/iocp/pipe.c aws-c-io-0.14.9/source/windows/iocp/pipe.c
+--- aws-c-io-0.14.9.orig/source/windows/iocp/pipe.c	2024-06-04 18:27:58.000000000 +0200
++++ aws-c-io-0.14.9/source/windows/iocp/pipe.c	2024-06-23 10:39:15.047978000 +0200
+@@ -11,7 +11,7 @@
+ #include <stdbool.h>
+ #include <stdio.h>
+ 
+-#include <Windows.h>
++#include <windows.h>
+ 
+ enum read_end_state {
+     /* Pipe is open. */
+diff -urEb aws-c-io-0.14.9.orig/source/windows/iocp/socket.c aws-c-io-0.14.9/source/windows/iocp/socket.c
+--- aws-c-io-0.14.9.orig/source/windows/iocp/socket.c	2024-06-04 18:27:58.000000000 +0200
++++ aws-c-io-0.14.9/source/windows/iocp/socket.c	2024-06-23 10:39:15.047978000 +0200
+@@ -9,9 +9,9 @@
+ below, clang-format doesn't work (at least on my version) with the c-style comments.*/
+ 
+ // clang-format off
+-#include <WS2tcpip.h>
+-#include <MSWSock.h>
+-#include <Mstcpip.h>
++#include <ws2tcpip.h>
++#include <mswsock.h>
++#include <mstcpip.h>
+ // clang-format on
+ 
+ #include <aws/io/socket.h>
+diff -urEb aws-c-io-0.14.9.orig/source/windows/shared_library.c aws-c-io-0.14.9/source/windows/shared_library.c
+--- aws-c-io-0.14.9.orig/source/windows/shared_library.c	2024-06-04 18:27:58.000000000 +0200
++++ aws-c-io-0.14.9/source/windows/shared_library.c	2024-06-23 10:39:15.047978000 +0200
+@@ -4,7 +4,7 @@
+  */
+ 
+ // clang-format off
+-#include <Windows.h>
++#include <windows.h>
+ #include <libloaderapi.h>
+ // clang-format on
+ 
+diff -urEb aws-c-io-0.14.9.orig/source/windows/windows_pki_utils.c aws-c-io-0.14.9/source/windows/windows_pki_utils.c
+--- aws-c-io-0.14.9.orig/source/windows/windows_pki_utils.c	2024-06-04 18:27:58.000000000 +0200
++++ aws-c-io-0.14.9/source/windows/windows_pki_utils.c	2024-06-23 10:39:15.047978000 +0200
+@@ -10,7 +10,7 @@
+ 
+ #include <aws/io/logging.h>
+ 
+-#include <Windows.h>
++#include <windows.h>
+ #include <stdio.h>
+ #include <string.h>
+ 
+diff -urEb aws-c-io-0.14.9.orig/source/windows/winsock_init.c aws-c-io-0.14.9/source/windows/winsock_init.c
+--- aws-c-io-0.14.9.orig/source/windows/winsock_init.c	2024-06-04 18:27:58.000000000 +0200
++++ aws-c-io-0.14.9/source/windows/winsock_init.c	2024-06-23 10:39:15.047978000 +0200
+@@ -8,9 +8,9 @@
+ below, clang-format doesn't work (at least on my version) with the c-style comments. */
+ 
+ // clang-format off
+-#include <WinSock2.h>
+-#include <WS2tcpip.h>
+-#include <MSWSock.h>
++#include <winsock2.h>
++#include <ws2tcpip.h>
++#include <mswsock.h>
+ // clang-format on
+ 
+ #include <aws/io/logging.h>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Aws/aws-sdk-cpp-1.11.178.patch	Tue Jun 25 12:38:31 2024 +0200
@@ -0,0 +1,116 @@
+Only in aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-core/include/aws/core: SDKConfig.h
+diff -urEb aws-sdk-cpp-1.11.178.orig/src/aws-cpp-sdk-core/include/aws/core/utils/Array.h aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-core/include/aws/core/utils/Array.h
+--- aws-sdk-cpp-1.11.178.orig/src/aws-cpp-sdk-core/include/aws/core/utils/Array.h	2023-10-06 20:16:49.000000000 +0200
++++ aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-core/include/aws/core/utils/Array.h	2024-06-23 10:47:33.294537700 +0200
+@@ -14,7 +14,7 @@
+ #include <cstring>
+ #include <algorithm>
+ 
+-#ifdef _WIN32
++#ifdef _MSC_VER
+ 
+ #include <iterator>
+ 
+@@ -54,7 +54,7 @@
+                 {
+                     m_data.reset(Aws::NewArray<T>(m_size, ARRAY_ALLOCATION_TAG));
+ 
+-#ifdef _WIN32
++#ifdef _MSC_VER
+                     std::copy(arrayToCopy, arrayToCopy + arraySize, stdext::checked_array_iterator< T * >(m_data.get(), m_size));
+ #else
+                     std::copy(arrayToCopy, arrayToCopy + arraySize, m_data.get());
+@@ -82,7 +82,7 @@
+                     if(arr->m_size > 0 && arr->m_data)
+                     {
+                         size_t arraySize = arr->m_size;
+-#ifdef _WIN32
++#ifdef _MSC_VER
+                         std::copy(arr->m_data.get(), arr->m_data.get() + arraySize, stdext::checked_array_iterator< T * >(m_data.get() + location, m_size));
+ #else
+                         std::copy(arr->m_data.get(), arr->m_data.get() + arraySize, m_data.get() + location);
+@@ -101,7 +101,7 @@
+                 {
+                     m_data.reset(Aws::NewArray<T>(m_size, ARRAY_ALLOCATION_TAG));
+ 
+-#ifdef _WIN32
++#ifdef _MSC_VER
+                     std::copy(other.m_data.get(), other.m_data.get() + other.m_size, stdext::checked_array_iterator< T * >(m_data.get(), m_size));
+ #else
+                     std::copy(other.m_data.get(), other.m_data.get() + other.m_size, m_data.get());
+@@ -134,7 +134,7 @@
+                 {
+                     m_data.reset(Aws::NewArray<T>(m_size, ARRAY_ALLOCATION_TAG));
+ 
+-#ifdef _WIN32
++#ifdef _MSC_VER
+                     std::copy(other.m_data.get(), other.m_data.get() + other.m_size, stdext::checked_array_iterator< T * >(m_data.get(), m_size));
+ #else
+                     std::copy(other.m_data.get(), other.m_data.get() + other.m_size, m_data.get());
+Only in aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-core/include/aws/core/utils: Array.h~
+diff -urEb aws-sdk-cpp-1.11.178.orig/src/aws-cpp-sdk-core/source/platform/windows/FileSystem.cpp aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-core/source/platform/windows/FileSystem.cpp
+--- aws-sdk-cpp-1.11.178.orig/src/aws-cpp-sdk-core/source/platform/windows/FileSystem.cpp	2023-10-06 20:16:49.000000000 +0200
++++ aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-core/source/platform/windows/FileSystem.cpp	2024-06-22 19:00:43.200750034 +0200
+@@ -10,7 +10,7 @@
+ #include <aws/core/utils/memory/stl/AWSVector.h>
+ #include <cassert>
+ #include <iostream>
+-#include <Userenv.h>
++#include <userenv.h>
+ 
+ #pragma warning( disable : 4996)
+ 
+diff -urEb aws-sdk-cpp-1.11.178.orig/src/aws-cpp-sdk-core/source/utils/crypto/openssl/CryptoImpl.cpp aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-core/source/utils/crypto/openssl/CryptoImpl.cpp
+--- aws-sdk-cpp-1.11.178.orig/src/aws-cpp-sdk-core/source/utils/crypto/openssl/CryptoImpl.cpp	2023-10-06 20:16:49.000000000 +0200
++++ aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-core/source/utils/crypto/openssl/CryptoImpl.cpp	2024-06-22 18:38:51.196880348 +0200
+@@ -221,7 +221,7 @@
+                 EVP_DigestInit_ex(ctx, EVP_md5(), nullptr);
+ 
+                 auto currentPos = stream.tellg();
+-                if (currentPos == -1)
++                if (currentPos == std::streampos(-1))
+                 {
+                     currentPos = 0;
+                     stream.clear();
+@@ -298,7 +298,7 @@
+                 EVP_DigestInit_ex(ctx, EVP_sha1(), nullptr);
+ 
+                 auto currentPos = stream.tellg();
+-                if (currentPos == -1)
++                if (currentPos == std::streampos(-1))
+                 {
+                     currentPos = 0;
+                     stream.clear();
+@@ -376,7 +376,7 @@
+                 EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr);
+ 
+                 auto currentPos = stream.tellg();
+-                if (currentPos == -1)
++                if (currentPos == std::streampos(-1))
+                 {
+                     currentPos = 0;
+                     stream.clear();
+diff -urEb aws-sdk-cpp-1.11.178.orig/src/aws-cpp-sdk-core/source/utils/StringUtils.cpp aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-core/source/utils/StringUtils.cpp
+--- aws-sdk-cpp-1.11.178.orig/src/aws-cpp-sdk-core/source/utils/StringUtils.cpp	2023-10-06 20:16:49.000000000 +0200
++++ aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-core/source/utils/StringUtils.cpp	2024-06-22 18:55:21.912052713 +0200
+@@ -14,7 +14,7 @@
+ #include <functional>
+ 
+ #ifdef _WIN32
+-#include <Windows.h>
++#include <windows.h>
+ #endif
+ 
+ using namespace Aws::Utils;
+diff -urEb aws-sdk-cpp-1.11.178.orig/src/aws-cpp-sdk-text-to-speech/include/aws/text-to-speech/windows/WaveOutPCMOutputDriver.h aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-text-to-speech/include/aws/text-to-speech/windows/WaveOutPCMOutputDriver.h
+--- aws-sdk-cpp-1.11.178.orig/src/aws-cpp-sdk-text-to-speech/include/aws/text-to-speech/windows/WaveOutPCMOutputDriver.h	2023-10-06 20:16:49.000000000 +0200
++++ aws-sdk-cpp-1.11.178/src/aws-cpp-sdk-text-to-speech/include/aws/text-to-speech/windows/WaveOutPCMOutputDriver.h	2024-06-22 18:57:00.963064735 +0200
+@@ -10,7 +10,7 @@
+ 
+ #include <mutex>
+ 
+-#include <Windows.h>
++#include <windows.h>
+ 
+ namespace Aws
+ {
--- a/Aws/holy-build-box-compile.sh	Tue Nov 21 09:50:24 2023 +0100
+++ b/Aws/holy-build-box-compile.sh	Tue Jun 25 12:38:31 2024 +0200
@@ -1,7 +1,9 @@
 #!/bin/bash
 
 # Cloud storage plugins for Orthanc
-# Copyright (C) 2020-2021 Osimis S.A., Belgium
+# Copyright (C) 2020-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 Affero General Public License
@@ -41,7 +43,7 @@
 
 ROOT_DIR=`dirname $(readlink -f $0)`/..
 
-DOCKER_IMAGE=phusion/holy-build-box-64:3.0.0
+DOCKER_IMAGE=phusion/holy-build-box-64:3.0.5
 
 mkdir -p ${ROOT_DIR}/holy-build-box
 
--- a/Aws/holy-build-box-internal.sh	Tue Nov 21 09:50:24 2023 +0100
+++ b/Aws/holy-build-box-internal.sh	Tue Jun 25 12:38:31 2024 +0200
@@ -1,7 +1,9 @@
 #!/bin/bash
 
 # Cloud storage plugins for Orthanc
-# Copyright (C) 2020-2021 Osimis S.A., Belgium
+# Copyright (C) 2020-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 Affero General Public License
@@ -44,12 +46,14 @@
 cp /source/Aws/* /tmp/source-writeable/Aws/ || true  # Ignore error about omitting directories
 
 cmake /tmp/source-writeable/Aws/ \
-      -DORTHANC_FRAMEWORK_VERSION=1.9.3 \
+      -DORTHANC_FRAMEWORK_VERSION=1.12.3 \
       -DORTHANC_FRAMEWORK_SOURCE=web \
       -DCMAKE_BUILD_TYPE=$1 -DSTATIC_BUILD=ON
 
 # Use only 2 processes (not `nproc`), as this is a very heavy compilation
-make -j2
+# make -j2
+
+make -j`nproc --all`
 
 ./UnitTests
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Aws/mingw-compile.sh	Tue Jun 25 12:38:31 2024 +0200
@@ -0,0 +1,71 @@
+#!/bin/bash
+
+# Cloud storage plugins for Orthanc
+# Copyright (C) 2020-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 Affero 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
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+set -ex
+
+
+if [ "$1" != "Debug" -a "$1" != "Release" ]; then
+    echo "Please provide build type as first argument: Debug or Release"
+    exit -1
+fi
+
+if [ "$2" != "i686" -a "$2" != "x86_64" ]; then
+    echo "Please provide architecture as second argument: i686 or x86_64"
+    exit -1
+fi
+
+
+##
+## Prepare a Docker container with mingw
+##
+
+if [ -t 1 ]; then
+    # TTY is available => use interactive mode
+    DOCKER_FLAGS='-i'
+fi
+
+ROOT_DIR=`dirname $(readlink -f $0)`/..
+IMAGE=orthanc-aws-mingw
+TARGET=${ROOT_DIR}/mingw-binaries-$2/
+
+if [ -e "${TARGET}" ]; then
+    echo "Target folder is already existing, aborting"
+    exit -1
+fi
+
+mkdir -p ${TARGET}
+
+( cd ${ROOT_DIR}/Aws/mingw-compile && \
+      docker build -t ${IMAGE} . )
+
+
+##
+## Build
+##
+
+docker run -t ${DOCKER_FLAGS} --rm \
+       --user $(id -u):$(id -g) \
+       -v ${ROOT_DIR}/:/source:ro \
+       -v ${TARGET}:/target:rw \
+       ${IMAGE} \
+       bash /source/Aws/mingw-compile/build.sh $1 $2
+
+ls -lR ${TARGET}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Aws/mingw-compile/Dockerfile	Tue Jun 25 12:38:31 2024 +0200
@@ -0,0 +1,31 @@
+# Cloud storage plugins for Orthanc
+# Copyright (C) 2020-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 Affero 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
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# Debian 12.5 contains mingw 12.x, which is insufficient
+# FROM debian:12.5
+
+# Ubuntu 24.04 contains mingw 13.x, which is OK
+FROM ubuntu:24.04
+
+MAINTAINER Sebastien Jodogne <s.jodogne@gmail.com>
+LABEL Description="Orthanc, free DICOM server" Vendor="The Orthanc project"
+
+RUN apt-get -y clean && apt-get -y update && \
+    apt-get -y install cmake patch gcc-mingw-w64 g++-mingw-w64 unzip mercurial && \
+    apt-get clean && rm -rf /var/lib/apt/lists/*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Aws/mingw-compile/build.sh	Tue Jun 25 12:38:31 2024 +0200
@@ -0,0 +1,50 @@
+# Cloud storage plugins for Orthanc
+# Copyright (C) 2020-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 Affero 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
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+set -ex
+
+if [ "$2" = "i686"  ]; then
+    TOOLCHAIN=MinGW-W64-Toolchain32.cmake
+elif [ "$2" = "x86_64"  ]; then
+    TOOLCHAIN=MinGW-W64-Toolchain64.cmake
+else
+    exit -1
+fi
+
+
+mkdir /tmp/aws
+mkdir /tmp/aws/Aws/
+mkdir /tmp/aws/Build/
+
+# Ignore possible build directories in "Aws" folder
+find /source/Aws/ -type f -maxdepth 1 | while read f
+do
+    cp "$f" /tmp/aws/Aws/
+done
+
+cp -r /source/Common /tmp/aws/
+cp -r /source/UnitTestsSources /tmp/aws/
+
+cd /tmp/aws/Build/
+cmake ../Aws -DCMAKE_BUILD_TYPE=Release -DUSE_VCPKG_PACKAGES=OFF -DSTATIC_BUILD=ON \
+      -DCMAKE_TOOLCHAIN_FILE=/source/Common/Resources/Orthanc/Toolchains/${TOOLCHAIN}
+make -j`nproc --all`
+
+cp ./libOrthancAwsS3Storage.dll /target
+cp ./UnitTests.exe /target
--- a/Azure/AzureBlobStoragePlugin.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/Azure/AzureBlobStoragePlugin.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -25,6 +27,8 @@
 // #include "cpprest/rawptrstream.h"
 // #include "cpprest/details/basic_types.h"
 
+#include <Logging.h>
+
 // Create aliases to make the code easier to read.
 namespace as = Azure::Storage::Blobs;
 
@@ -43,10 +47,10 @@
                          bool storageContainsUnknownFiles
                          );
 
-  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);
-  virtual bool HasFileExists() {return false;};
+  virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+  virtual IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+  virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+  virtual bool HasFileExists() ORTHANC_OVERRIDE {return false;};
 };
 
 
@@ -66,7 +70,7 @@
   {
   }
 
-  virtual void Write(const char* data, size_t size)
+  virtual void Write(const char* data, size_t size) ORTHANC_OVERRIDE
   {
     try
     {
@@ -119,7 +123,7 @@
   {
   }
 
-  virtual size_t GetSize()
+  virtual size_t GetSize() ORTHANC_OVERRIDE
   {
     try
     {
@@ -131,7 +135,7 @@
     }
   }
 
-  virtual void ReadWhole(char* data, size_t size)
+  virtual void ReadWhole(char* data, size_t size) ORTHANC_OVERRIDE
   {
     try
     {
@@ -144,7 +148,7 @@
     }
   }
 
-  virtual void ReadRange(char* data, size_t size, size_t fromOffset)
+  virtual void ReadRange(char* data, size_t size, size_t fromOffset) ORTHANC_OVERRIDE
   {
     try
     {
@@ -222,13 +226,13 @@
 
     if (!pluginSection.LookupStringValue(connectionString, "ConnectionString"))
     {
-      OrthancPlugins::LogError("AzureBlobStorage/ConnectionString configuration missing.  Unable to initialize plugin");
+      LOG(ERROR) << "AzureBlobStorage/ConnectionString configuration missing.  Unable to initialize plugin";
       return nullptr;
     }
 
     if (!pluginSection.LookupStringValue(containerName, "ContainerName"))
     {
-      OrthancPlugins::LogError("AzureBlobStorage/ContainerName configuration missing.  Unable to initialize plugin");
+      LOG(ERROR) << "AzureBlobStorage/ContainerName configuration missing.  Unable to initialize plugin";
       return nullptr;
     }
 
@@ -239,7 +243,7 @@
   }
   else if (orthancConfig.IsSection("BlobStorage")) // backward compatibility with the old plugin:
   {
-    OrthancPlugins::LogWarning("AzureBlobStorage: you're using an old configuration format for the plugin.");
+    LOG(WARNING) << "AzureBlobStorage: you're using an old configuration format for the plugin.";
 
     OrthancPlugins::OrthancConfiguration pluginSection;
     orthancConfig.GetSection(pluginSection, "BlobStorage");
@@ -249,19 +253,19 @@
 
     if (!pluginSection.LookupStringValue(containerName, "ContainerName"))
     {
-      OrthancPlugins::LogError("BlobStorage/AccountName configuration missing.  Unable to initialize plugin");
+      LOG(ERROR) << "BlobStorage/AccountName configuration missing.  Unable to initialize plugin";
       return nullptr;
     }
 
     if (!pluginSection.LookupStringValue(accountName, "AccountName"))
     {
-      OrthancPlugins::LogError("BlobStorage/AccountName configuration missing.  Unable to initialize plugin");
+      LOG(ERROR) << "BlobStorage/AccountName configuration missing.  Unable to initialize plugin";
       return nullptr;
     }
 
     if (!pluginSection.LookupStringValue(accountKey, "AccountKey"))
     {
-      OrthancPlugins::LogError("BlobStorage/ContainerName configuration missing.  Unable to initialize plugin");
+      LOG(ERROR) << "BlobStorage/ContainerName configuration missing.  Unable to initialize plugin";
       return nullptr;
     }
 
@@ -273,16 +277,16 @@
   }
   else
   {
-    OrthancPlugins::LogWarning(std::string(GetStoragePluginName()) + " plugin, section missing.  Plugin is not enabled.");
+    LOG(WARNING) << GetStoragePluginName() << " plugin, section missing.  Plugin is not enabled.";
     return nullptr;
   }
 
   try
   {
-    OrthancPlugins::LogInfo("Connecting to Azure storage ...");
+    LOG(INFO) << "Connecting to Azure storage ...";
 
     as::BlobContainerClient client = as::BlobContainerClient::CreateFromConnectionString(connectionString, containerName);
-    OrthancPlugins::LogInfo("Blob client created");
+    LOG(INFO) << "Blob client created";
 
     if (createContainerIfNotExists)
     {
@@ -298,17 +302,17 @@
       auto createResult = client.CreateIfNotExists();
       if (createResult.Value.Created)
       {
-        OrthancPlugins::LogWarning("Blob Storage Area container has been created.  **** check in the Azure console that your container is private ****");
+        LOG(WARNING) << "Blob Storage Area container has been created.  **** check in the Azure console that your container is private ****";
       }
     }
 
-    OrthancPlugins::LogInfo("Blob storage initialized");
+    LOG(INFO) << "Blob storage initialized";
 
     return new AzureBlobStoragePlugin(nameForLogs, client, enableLegacyStorageStructure, storageContainsUnknownFiles);
   }
   catch (const std::exception& e)
   {
-    OrthancPlugins::LogError(std::string("AzureBlobStorage plugin: failed to initialize plugin: ") + e.what());
+    LOG(ERROR) << "AzureBlobStorage plugin: failed to initialize plugin: " << e.what();
     return nullptr;
   }
 
--- a/Azure/AzureBlobStoragePlugin.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Azure/AzureBlobStoragePlugin.h	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
--- a/Azure/CMakeLists.txt	Tue Nov 21 09:50:24 2023 +0100
+++ b/Azure/CMakeLists.txt	Tue Jun 25 12:38:31 2024 +0200
@@ -1,5 +1,7 @@
 # Cloud storage plugins for Orthanc
-# Copyright (C) 2020-2021 Osimis S.A., Belgium
+# Copyright (C) 2020-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 Affero General Public License
@@ -19,14 +21,24 @@
 
 project(OrthancAzureBlobStorage)
 
-set(PLUGIN_VERSION "2.3.0")
+set(PLUGIN_VERSION "mainline")
+
+if (PLUGIN_VERSION STREQUAL "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+else()
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.12.4")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+endif()
+
+set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc framework (can be \"system\", \"hg\", \"archive\", \"web\" or \"path\")")
+set(ORTHANC_FRAMEWORK_VERSION "${ORTHANC_FRAMEWORK_DEFAULT_VERSION}" CACHE STRING "Version of the Orthanc framework")
+set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"")
+set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"")
+set(ALLOW_DOWNLOADS ON)
 
 include(CheckIncludeFileCXX)
 
-set(ORTHANC_FRAMEWORK_SOURCE "web" CACHE STRING "orthanc source")
-set(ORTHANC_FRAMEWORK_VERSION "daf4807631c5" CACHE STRING "orthanc framework version")  # TODO: update to 1.12.2 when available
-set(ALLOW_DOWNLOADS ON)
-
 # Download and setup the Orthanc framework
 
 include(${CMAKE_SOURCE_DIR}/../Common/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake)
@@ -111,7 +123,9 @@
     ${COMMON_SOURCES}
   )
 
-DefineSourceBasenameForTarget(OrthancAzureBlobStorage)
+if (COMMAND DefineSourceBasenameForTarget)
+  DefineSourceBasenameForTarget(OrthancAzureBlobStorage)
+endif()
 
 set_target_properties(OrthancAzureBlobStorage PROPERTIES
   VERSION ${PLUGIN_VERSION}
@@ -140,6 +154,12 @@
 
 endif()
 
+install(
+  TARGETS OrthancAzureBlobStorage
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+)
+
 
 # add_executable(UnitTests
 #     ${GOOGLE_TEST_SOURCES}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CITATION.cff	Tue Jun 25 12:38:31 2024 +0200
@@ -0,0 +1,14 @@
+cff-version: "1.1.0"
+message: "If you use this software, please cite it using these metadata."
+title: Orthanc
+abstract: "Orthanc is a lightweight open-source DICOM server for medical imaging supporting representational state transfer (REST)."
+authors:
+  -
+    affiliation: UCLouvain
+    family-names: Jodogne
+    given-names: "Sébastien"
+doi: "10.1007/s10278-018-0082-y"
+license: "GPL-3.0-or-later"
+repository-code: "https://orthanc.uclouvain.be/hg/orthanc/"
+version: 1.12.3
+date-released: 2024-01-31
--- a/Common/BaseStorage.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/BaseStorage.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -18,6 +20,9 @@
 
 
 #include "BaseStorage.h"
+
+#include <Logging.h>
+
 #include <boost/filesystem/fstream.hpp>
 
 boost::filesystem::path BaseStorage::GetOrthancFileSystemPath(const std::string& uuid, const std::string& fileSystemRootPath)
@@ -84,7 +89,7 @@
     enableLegacyStorageStructure = true;
     if (storageStructure != "legacy")
     {
-      OrthancPlugins::LogError("ObjectStorage/StorageStructure configuration invalid value: " + storageStructure + ", allowed values are 'flat' and 'legacy'");
+      LOG(ERROR) << "ObjectStorage/StorageStructure configuration invalid value: " << storageStructure << ", allowed values are 'flat' and 'legacy'";
       return false;
     }
   }
--- a/Common/BaseStorage.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/BaseStorage.h	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -20,6 +22,9 @@
 #pragma once
 
 #include "IStorage.h"
+
+#include <Compatibility.h>
+
 #include <boost/filesystem.hpp>
 
 namespace fs = boost::filesystem;
@@ -39,7 +44,7 @@
   std::string GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled, bool useAlternateExtension = false);
 
 public:
-  virtual void SetRootPath(const std::string& rootPath)
+  virtual void SetRootPath(const std::string& rootPath) ORTHANC_OVERRIDE
   {
     rootPath_ = rootPath;
   }
--- a/Common/CryptoPPConfiguration.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/CryptoPPConfiguration.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -1,5 +1,7 @@
 # Cloud storage plugins for Orthanc
-# Copyright (C) 2020-2021 Osimis S.A., Belgium
+# Copyright (C) 2020-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 Affero General Public License
@@ -18,7 +20,7 @@
 if (STATIC_BUILD OR NOT USE_SYSTEM_CRYPTOPP)
   # The .tar.gz package was created by "./CryptoPPPackage.sh"
   SET(CRYPTOPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/cryptopp-840)
-  SET(CRYPTOPP_URL "http://orthanc.osimis.io/ThirdPartyDownloads/cryptopp-840.tar.gz")
+  SET(CRYPTOPP_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/cryptopp-840.tar.gz")
   SET(CRYPTOPP_MD5 "d42363e8a12c06a000720335a4da70d3")
 
   DownloadPackage(${CRYPTOPP_MD5} ${CRYPTOPP_URL} "${CRYPTOPP_SOURCES_DIR}")
--- a/Common/EncryptionConfigurator.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/EncryptionConfigurator.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -22,11 +24,13 @@
 
 #include "EncryptionConfigurator.h"
 
+#include <Logging.h>
+
 bool ReadMasterKey(uint32_t& id, std::string& keyPath, const Json::Value& node)
 {
   if (!node.isArray() || node.size() != 2 || !node[0].isUInt() || !node[1].isString())
   {
-    OrthancPlugins::LogWarning("Encryption: Invalid master key configuration");
+    LOG(WARNING) << "Encryption: Invalid master key configuration";
     return false;
   }
 
@@ -50,7 +54,7 @@
 
   if (!cryptoJson.isMember("MasterKey") || !cryptoJson["MasterKey"].isArray())
   {
-    OrthancPlugins::LogWarning("Encryption: MasterKey missing.  Unable to initialize encryption");
+    LOG(WARNING) << "Encryption: MasterKey missing.  Unable to initialize encryption";
     return nullptr;
   }
 
--- a/Common/EncryptionConfigurator.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/EncryptionConfigurator.h	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -20,10 +22,7 @@
 
 #include "EncryptionHelpers.h"
 
-class EncryptionConfigurator
+namespace EncryptionConfigurator
 {
-
-public:
-  static EncryptionHelpers* CreateEncryptionHelpers(const OrthancPlugins::OrthancConfiguration& encryptionSection);
-
-};
+  EncryptionHelpers* CreateEncryptionHelpers(const OrthancPlugins::OrthancConfiguration& encryptionSection);
+}
--- a/Common/EncryptionHelpers.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/EncryptionHelpers.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
--- a/Common/EncryptionHelpers.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/EncryptionHelpers.h	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -28,7 +30,7 @@
 class EncryptionException : public std::runtime_error
 {
 public:
-  EncryptionException(const std::string& what)
+  explicit EncryptionException(const std::string& what)
     : std::runtime_error(what)
   {
   }
--- a/Common/FileSystemStorage.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/FileSystemStorage.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -19,7 +21,9 @@
 #include "FileSystemStorage.h"
 #include "BaseStorage.h"
 
+#include <Logging.h>
 #include <SystemToolbox.h>
+
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
 
@@ -141,7 +145,7 @@
   }
   catch(Orthanc::OrthancException& e)
   {
-    OrthancPlugins::LogError(GetNameForLogs() + ": error while deleting object " + std::string(uuid) + ": " + std::string(e.What()));
+    LOG(ERROR) << GetNameForLogs() << ": error while deleting object " << uuid << ": " << e.What();
   }
 
 }
--- a/Common/FileSystemStorage.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/FileSystemStorage.h	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -39,21 +41,21 @@
     {}
 
     virtual ~FileSystemWriter() {}
-    virtual void Write(const char* data, size_t size);
+    virtual void Write(const char* data, size_t size) ORTHANC_OVERRIDE;
   };
 
   class FileSystemReader: public IStorage::IReader
   {
     const fs::path path_;
   public:
-    FileSystemReader(const fs::path& path)
+    explicit FileSystemReader(const fs::path& path)
     : path_(path)
     {}
 
     virtual ~FileSystemReader() {}
-    virtual size_t GetSize();
-    virtual void ReadWhole(char* data, size_t size);
-    virtual void ReadRange(char* data, size_t size, size_t fromOffset);
+    virtual size_t GetSize() ORTHANC_OVERRIDE;
+    virtual void ReadWhole(char* data, size_t size) ORTHANC_OVERRIDE;
+    virtual void ReadRange(char* data, size_t size, size_t fromOffset) ORTHANC_OVERRIDE;
   };
 
   std::string fileSystemRootPath_;
@@ -65,12 +67,12 @@
     fsync_(fsync)
   {}
 
-  virtual void SetRootPath(const std::string& rootPath) {}
+  virtual void SetRootPath(const std::string& rootPath) ORTHANC_OVERRIDE {}
 
-  virtual IStorage::IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
-  virtual IStorage::IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
-  virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
+  virtual IStorage::IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+  virtual IStorage::IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+  virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
 
-  virtual bool HasFileExists() {return true;};
-  virtual bool FileExists(const std::string& uuid, OrthancPluginContentType type, bool encryptionEnabled);
-};
\ No newline at end of file
+  virtual bool HasFileExists() ORTHANC_OVERRIDE {return true;};
+  virtual bool FileExists(const std::string& uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+};
--- a/Common/IStorage.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/IStorage.h	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -25,7 +27,7 @@
 class StoragePluginException : public std::runtime_error
 {
 public:
-  StoragePluginException(const std::string& what)
+  explicit StoragePluginException(const std::string& what)
     : std::runtime_error(what)
   {
   }
@@ -42,7 +44,7 @@
 //  IStorage* CreateStorage(const OrthancPlugins::OrthancConfiguration& orthancConfig);
 //};
 
-class IStorage
+class IStorage : public boost::noncopyable
 {
 public:
   class IWriter
@@ -73,6 +75,10 @@
     nameForLogs_(nameForLogs)
   {}
 
+  virtual ~IStorage()
+  {
+  }
+
   virtual void SetRootPath(const std::string& rootPath) = 0;
 
   virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) = 0;
--- a/Common/MoveStorageJob.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/MoveStorageJob.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -69,17 +71,20 @@
   {
     if (sourceStorage->HasFileExists() && !sourceStorage->FileExists(uuid, static_cast<OrthancPluginContentType>(type), cryptoEnabled))
     {
-      OrthancPlugins::LogInfo("Move attachment: " + sourceStorage->GetNameForLogs() + " " + std::string(uuid) + " of type " + boost::lexical_cast<std::string>(type) + ", skipping, file is not on the source anymore");
+      LOG(INFO) << "Move attachment: " << sourceStorage->GetNameForLogs() << " " << uuid
+                << " of type " << boost::lexical_cast<std::string>(type) << ", skipping, file is not on the source anymore";
       return true;
     }
     else if (targetStorage->HasFileExists() && targetStorage->FileExists(uuid, static_cast<OrthancPluginContentType>(type), cryptoEnabled))
     {
-      OrthancPlugins::LogInfo("Move attachment: " + targetStorage->GetNameForLogs() + " " + std::string(uuid) + " of type " + boost::lexical_cast<std::string>(type) + ", skipping, file already on the target");
+      LOG(INFO) << "Move attachment: " << targetStorage->GetNameForLogs() << " " << uuid
+                << " of type " << boost::lexical_cast<std::string>(type) << ", skipping, file already on the target";
       return true;
     }
     else
     {
-      OrthancPlugins::LogInfo("Move attachment: " + sourceStorage->GetNameForLogs() + ": reading attachment " + std::string(uuid) + " of type " + boost::lexical_cast<std::string>(type));
+      LOG(INFO) << "Move attachment: " << sourceStorage->GetNameForLogs() << ": reading attachment "
+                << uuid << " of type " << boost::lexical_cast<std::string>(type);
     }
 
     std::unique_ptr<IStorage::IReader> reader(sourceStorage->GetReaderForObject(uuid.c_str(), static_cast<OrthancPluginContentType>(type), cryptoEnabled));
@@ -91,7 +96,8 @@
   }
   catch (StoragePluginException& ex)
   {
-    OrthancPlugins::LogInfo("Move attachment: " + sourceStorage->GetNameForLogs() + ": error while reading attachment " + std::string(uuid) + " of type " + boost::lexical_cast<std::string>(type) + ", this likely means that the file is already on the right storage");
+    LOG(INFO) << "Move attachment: " << sourceStorage->GetNameForLogs() << ": error while reading attachment " << uuid
+              << " of type " << boost::lexical_cast<std::string>(type) << ", this likely means that the file is already on the right storage";
     return true;
   }
 
@@ -106,7 +112,8 @@
     }
     catch (StoragePluginException& ex)
     {
-      OrthancPlugins::LogError("Move attachment: " + targetStorage->GetNameForLogs() + ": error while writing attachment " + std::string(uuid) + " of type " + boost::lexical_cast<std::string>(type) + ": " + ex.what());
+      LOG(ERROR) << "Move attachment: " << targetStorage->GetNameForLogs() << ": error while writing attachment "
+                 << uuid << " of type " << boost::lexical_cast<std::string>(type) << ": " << ex.what();
       return false;
     }
   }
@@ -120,7 +127,8 @@
     }
     catch (StoragePluginException& ex)
     {
-      OrthancPlugins::LogError("Move attachment: " + sourceStorage->GetNameForLogs() + ": error while deleting attachment " + std::string(uuid) + " of type " + boost::lexical_cast<std::string>(type) + ": " + ex.what());
+      LOG(ERROR) << "Move attachment: " << sourceStorage->GetNameForLogs() << ": error while deleting attachment "
+                 << uuid << " of type " << boost::lexical_cast<std::string>(type) << ": " << ex.what();
       return false;
     }
   }
--- a/Common/MoveStorageJob.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/MoveStorageJob.h	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
--- a/Common/Resources/Orthanc/CMake/AutoGeneratedCode.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/CMake/AutoGeneratedCode.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -2,7 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/Common/Resources/Orthanc/CMake/Compiler.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/CMake/Compiler.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -2,7 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -43,6 +44,13 @@
   # use by "ExternalProject" in CMake
   SET(CMAKE_LSB_CC $ENV{LSB_CC} CACHE STRING "")
   SET(CMAKE_LSB_CXX $ENV{LSB_CXX} CACHE STRING "")
+
+  # This is necessary to build "Orthanc mainline - Framework LSB
+  # Release" on "buildbot-worker-debian11"
+  set(LSB_PTHREAD_NONSHARED "${LSB_PATH}/lib64-${LSB_TARGET_VERSION}/libpthread_nonshared.a")
+  if (EXISTS ${LSB_PTHREAD_NONSHARED})
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LSB_PTHREAD_NONSHARED}")
+  endif()
 endif()
 
 
@@ -124,12 +132,17 @@
     ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
     ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
 
-  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" AND
+  if (# NOT ${CMAKE_SYSTEM_VERSION} STREQUAL "LinuxStandardBase" AND
+      NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" AND
       NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
     # The "--no-undefined" linker flag makes the shared libraries
     # (plugins ModalityWorklists and ServeFolders) fail to compile on
     # OpenBSD, and make the PostgreSQL plugin complain about missing
-    # "environ" global variable in FreeBSD
+    # "environ" global variable in FreeBSD.
+    #
+    # TODO - Furthermore, on Linux Standard Base running on Debian 12,
+    # the "-Wl,--no-undefined" seems to break the compilation (added
+    # after Orthanc 1.12.2). This is disabled for now.
     set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
     set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
   endif()
--- a/Common/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -2,7 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -153,11 +154,17 @@
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.2")
         set(ORTHANC_FRAMEWORK_MD5 "ede3de356493a8868545f8cb4b8bc8b5")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.3")
-        set(ORTHANC_FRAMEWORK_MD5 "f941c0f5771db7616e7b7961026a60e2")
+        set(ORTHANC_FRAMEWORK_MD5 "e48fc0cb09c4856803791a1be28c07dc")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.0")
         set(ORTHANC_FRAMEWORK_MD5 "d32a0cde03b6eb603d8dd2b33d38bf1b")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.1")
         set(ORTHANC_FRAMEWORK_MD5 "8a435140efc8ff4a01d8242f092f21de")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.2")
+        set(ORTHANC_FRAMEWORK_MD5 "d2476b9e796e339ac320b5333489bdb3")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.3")
+        set(ORTHANC_FRAMEWORK_MD5 "975f5bf2142c22cb1777b4f6a0a614c5")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.4")
+        set(ORTHANC_FRAMEWORK_MD5 "1e61779ea4a7cd705720bdcfed8a6a73")
 
       # Below this point are development snapshots that were used to
       # release some plugin, before an official release of the Orthanc
@@ -168,22 +175,28 @@
       #
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "ae0e3fd609df")
         # DICOMweb 1.1 (framework pre-1.6.0)
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
         set(ORTHANC_FRAMEWORK_MD5 "7e09e9b530a2f527854f0b782d7e0645")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "82652c5fc04f")
         # Stone Web viewer 1.0 (framework pre-1.8.1)
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
         set(ORTHANC_FRAMEWORK_MD5 "d77331d68917e66a3f4f9b807bbdab7f")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "4a3ba4bf4ba7")
         # PostgreSQL 3.3 (framework pre-1.8.2)
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
         set(ORTHANC_FRAMEWORK_MD5 "2d82bddf06f9cfe82095495cb3b8abde")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "23ad1b9c7800")
         # For "Toolbox::ReadJson()" and "Toolbox::Write{...}Json()" (pre-1.9.0)
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
         set(ORTHANC_FRAMEWORK_MD5 "9af92080e57c60dd288eba46ce606c00")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "b2e08d83e21d")
         # WSI 1.1 (framework pre-1.10.0), to remove "-std=c++11"
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
         set(ORTHANC_FRAMEWORK_MD5 "2eaa073cbb4b44ffba199ad93393b2b1")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "daf4807631c5")
         # DICOMweb 1.15 (framework pre-1.12.2)
-        set(ORTHANC_FRAMEWORK_MD5 "c644aff2817306b3207c98c92e43f35f")
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
+        set(ORTHANC_FRAMEWORK_MD5 "ebe8bdf388319f1c9536b2b680451848")
       endif()
     endif()
   endif()
@@ -320,7 +333,11 @@
   else()
     # Default case: Download from the official Web site
     set(ORTHANC_FRAMEMORK_FILENAME Orthanc-${ORTHANC_FRAMEWORK_VERSION}.tar.gz)
-    set(ORTHANC_FRAMEWORK_URL "https://orthanc.uclouvain.be/third-party-downloads/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}")
+    if (ORTHANC_FRAMEWORK_PRE_RELEASE)
+      set(ORTHANC_FRAMEWORK_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}")
+    else()
+      set(ORTHANC_FRAMEWORK_URL "https://orthanc.uclouvain.be/downloads/sources/orthanc/${ORTHANC_FRAMEMORK_FILENAME}")
+    endif()
   endif()
 
   set(ORTHANC_FRAMEWORK_ARCHIVE "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${ORTHANC_FRAMEMORK_FILENAME}")
--- a/Common/Resources/Orthanc/CMake/DownloadPackage.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/CMake/DownloadPackage.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -2,7 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/Common/Resources/Orthanc/CMake/EmbedResources.py	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/CMake/EmbedResources.py	Tue Jun 25 12:38:31 2024 +0200
@@ -4,7 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/Common/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -2,7 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -50,7 +51,7 @@
 
 elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST)
   set(GOOGLE_TEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/googletest-release-1.8.1)
-  set(GOOGLE_TEST_URL "https://orthanc.uclouvain.be/third-party-downloads/gtest-1.8.1.tar.gz")
+  set(GOOGLE_TEST_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/gtest-1.8.1.tar.gz")
   set(GOOGLE_TEST_MD5 "2e6fbeb6a91310a16efe181886c59596")
 
   DownloadPackage(${GOOGLE_TEST_MD5} ${GOOGLE_TEST_URL} "${GOOGLE_TEST_SOURCES_DIR}")
--- a/Common/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -3,7 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -61,6 +62,7 @@
 namespace OrthancPlugins
 {
   static OrthancPluginContext* globalContext_ = NULL;
+  static std::string pluginName_;
 
 
   void SetGlobalContext(OrthancPluginContext* context)
@@ -79,9 +81,19 @@
     }
   }
 
+
+  void SetGlobalContext(OrthancPluginContext* context,
+                        const char* pluginName)
+  {
+    SetGlobalContext(context);
+    pluginName_ = pluginName;
+  }
+
+
   void ResetGlobalContext()
   {
     globalContext_ = NULL;
+    pluginName_.clear();
   }
 
   bool HasGlobalContext()
@@ -103,6 +115,66 @@
   }
 
 
+#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
+  void LogMessage(OrthancPluginLogLevel level,
+                  const char* file,
+                  uint32_t line,
+                  const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
+      const char* pluginName = (pluginName_.empty() ? NULL : pluginName_.c_str());
+      OrthancPluginLogMessage(GetGlobalContext(), message.c_str(), pluginName, file, line, OrthancPluginLogCategory_Generic, level);
+#else
+      switch (level)
+      {
+        case OrthancPluginLogLevel_Error:
+          OrthancPluginLogError(GetGlobalContext(), message.c_str());
+          break;
+
+        case OrthancPluginLogLevel_Warning:
+          OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
+          break;
+
+        case OrthancPluginLogLevel_Info:
+          OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
+          break;
+
+        default:
+          ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+      }
+#endif
+    }
+  }
+#endif
+
+
+  void LogError(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogError(GetGlobalContext(), message.c_str());
+    }
+  }
+
+  void LogWarning(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
+    }
+  }
+
+  void LogInfo(const std::string& message)
+  {
+    if (HasGlobalContext())
+    {
+      OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
+    }
+  }
+
+
   void MemoryBuffer::Check(OrthancPluginErrorCode code)
   {
     if (code != OrthancPluginErrorCode_Success)
@@ -233,7 +305,7 @@
 
     if (!ReadJson(target, buffer_.data, buffer_.size))
     {
-      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert some memory buffer to JSON");
       ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
   }
@@ -265,7 +337,7 @@
     explicit PluginHttpHeaders(const std::map<std::string, std::string>& httpHeaders)
     {
       for (std::map<std::string, std::string>::const_iterator
-           it = httpHeaders.begin(); it != httpHeaders.end(); ++it)
+             it = httpHeaders.begin(); it != httpHeaders.end(); ++it)
       {
         headersKeys_.push_back(it->first.c_str());
         headersValues_.push_back(it->second.c_str());
@@ -404,7 +476,7 @@
     }
     else
     {
-      LogError("Cannot parse JSON: " + std::string(err));
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot parse JSON: " + std::string(err));
       return false;
     }
 #endif
@@ -565,13 +637,13 @@
   {
     if (str_ == NULL)
     {
-      LogError("Cannot convert an empty memory buffer to JSON");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert an empty memory buffer to JSON");
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
     if (!ReadJson(target, str_))
     {
-      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert some memory buffer to JSON");
       ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
   }
@@ -581,13 +653,13 @@
   {
     if (str_ == NULL)
     {
-      LogError("Cannot convert an empty memory buffer to JSON");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert an empty memory buffer to JSON");
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
     if (!ReadJsonWithoutComments(target, str_))
     {
-      LogError("Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert some memory buffer to JSON");
       ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
   }
@@ -625,7 +697,7 @@
 
     if (body.size() > 0xffffffffu)
     {
-      LogError("Cannot handle body size > 4GB");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
@@ -645,7 +717,7 @@
 
     if (body.size() > 0xffffffffu)
     {
-      LogError("Cannot handle body size > 4GB");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
@@ -688,34 +760,6 @@
     }
   }
 
-
-  void LogError(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogError(GetGlobalContext(), message.c_str());
-    }
-  }
-
-
-  void LogWarning(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
-    }
-  }
-
-
-  void LogInfo(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
-    }
-  }
-
-
   void OrthancConfiguration::LoadConfiguration()
   {
     OrthancString str;
@@ -723,7 +767,7 @@
 
     if (str.GetContent() == NULL)
     {
-      LogError("Cannot access the Orthanc configuration");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot access the Orthanc configuration");
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
@@ -731,7 +775,7 @@
 
     if (configuration_.type() != Json::objectValue)
     {
-      LogError("Unable to read the Orthanc configuration");
+      ORTHANC_PLUGINS_LOG_ERROR("Unable to read the Orthanc configuration");
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
   }
@@ -799,8 +843,8 @@
     {
       if (configuration_[key].type() != Json::objectValue)
       {
-        LogError("The configuration section \"" + target.path_ +
-                 "\" is not an associative array as expected");
+        ORTHANC_PLUGINS_LOG_ERROR("The configuration section \"" + target.path_ +
+                                  "\" is not an associative array as expected");
 
         ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
       }
@@ -822,8 +866,8 @@
 
     if (configuration_[key].type() != Json::stringValue)
     {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a string as expected");
+      ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
+                                "\" is not a string as expected");
 
       ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
@@ -854,8 +898,8 @@
         return true;
 
       default:
-        LogError("The configuration option \"" + GetPath(key) +
-                 "\" is not an integer as expected");
+        ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
+                                  "\" is not an integer as expected");
 
         ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
@@ -873,8 +917,8 @@
 
     if (tmp < 0)
     {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a positive integer as expected");
+      ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
+                                "\" is not a positive integer as expected");
 
       ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
@@ -898,8 +942,8 @@
 
     if (configuration_[key].type() != Json::booleanValue)
     {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a Boolean as expected");
+      ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
+                                "\" is not a Boolean as expected");
 
       ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
@@ -934,8 +978,8 @@
         return true;
 
       default:
-        LogError("The configuration option \"" + GetPath(key) +
-                 "\" is not an integer as expected");
+        ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
+                                  "\" is not an integer as expected");
 
         ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
@@ -994,8 +1038,8 @@
         break;
     }
 
-    LogError("The configuration option \"" + GetPath(key) +
-             "\" is not a list of strings as expected");
+    ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
+                              "\" is not a list of strings as expected");
 
     ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
   }
@@ -1115,8 +1159,8 @@
 
     if (configuration_[key].type() != Json::objectValue)
     {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not an object as expected");
+      ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
+                                "\" is not an object as expected");
 
       ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
@@ -1133,8 +1177,8 @@
       }
       else
       {
-        LogError("The configuration option \"" + GetPath(key) +
-                 "\" is not a dictionary mapping strings to strings");
+        ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
+                                  "\" is not a dictionary mapping strings to strings");
 
         ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
       }
@@ -1156,7 +1200,7 @@
   {
     if (image_ == NULL)
     {
-      LogError("Trying to access a NULL image");
+      ORTHANC_PLUGINS_LOG_ERROR("Trying to access a NULL image");
       ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
     }
   }
@@ -1182,7 +1226,7 @@
 
     if (image_ == NULL)
     {
-      LogError("Cannot create an image");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot create an image");
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
   }
@@ -1199,7 +1243,7 @@
 
     if (image_ == NULL)
     {
-      LogError("Cannot create an image accessor");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot create an image accessor");
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
   }
@@ -1213,7 +1257,7 @@
 
     if (image_ == NULL)
     {
-      LogError("Cannot uncompress a PNG image");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot uncompress a PNG image");
       ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
     }
   }
@@ -1226,7 +1270,7 @@
     image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg);
     if (image_ == NULL)
     {
-      LogError("Cannot uncompress a JPEG image");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot uncompress a JPEG image");
       ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
     }
   }
@@ -1240,7 +1284,7 @@
     image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame);
     if (image_ == NULL)
     {
-      LogError("Cannot uncompress a DICOM image");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot uncompress a DICOM image");
       ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
     }
   }
@@ -1654,13 +1698,13 @@
                                    unsigned int minor,
                                    unsigned int revision)
   {
-    LogError("Your version of the Orthanc core (" +
-             std::string(GetGlobalContext()->orthancVersion) +
-             ") is too old to run this plugin (version " +
-             boost::lexical_cast<std::string>(major) + "." +
-             boost::lexical_cast<std::string>(minor) + "." +
-             boost::lexical_cast<std::string>(revision) +
-             " is required)");
+    ORTHANC_PLUGINS_LOG_ERROR("Your version of the Orthanc core (" +
+                              std::string(GetGlobalContext()->orthancVersion) +
+                              ") is too old to run this plugin (version " +
+                              boost::lexical_cast<std::string>(major) + "." +
+                              boost::lexical_cast<std::string>(minor) + "." +
+                              boost::lexical_cast<std::string>(revision) +
+                              " is required)");
   }
 
   bool CheckMinimalVersion(const char* version,
@@ -1684,9 +1728,9 @@
     int aa, bb, cc = 0;
     if ((ORTHANC_SCANF(version, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 &&
          ORTHANC_SCANF(version, "%4d.%4d", &aa, &bb) != 2) ||
-      aa < 0 ||
-      bb < 0 ||
-      cc < 0)
+        aa < 0 ||
+        bb < 0 ||
+        cc < 0)
     {
       return false;
     }
@@ -1740,7 +1784,7 @@
   {
     if (!HasGlobalContext())
     {
-      LogError("Bad Orthanc context in the plugin");
+      ORTHANC_PLUGINS_LOG_ERROR("Bad Orthanc context in the plugin");
       return false;
     }
 
@@ -1777,7 +1821,7 @@
     }
     else
     {
-      LogError("Inexistent peer: " + name);
+      ORTHANC_PLUGINS_LOG_ERROR("Inexistent peer: " + name);
       ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource);
     }
   }
@@ -2061,7 +2105,7 @@
 
     if (body.size() > 0xffffffffu)
     {
-      LogError("Cannot handle body size > 4GB");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
@@ -2098,7 +2142,7 @@
 
     if (body.size() > 0xffffffffu)
     {
-      LogError("Cannot handle body size > 4GB");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
@@ -2464,7 +2508,7 @@
 
     if (id == NULL)
     {
-      LogError("Plugin cannot submit job");
+      ORTHANC_PLUGINS_LOG_ERROR("Plugin cannot submit job");
       OrthancPluginFreeJob(GetGlobalContext(), orthanc);
       ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
     }
@@ -2533,7 +2577,7 @@
           throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()),
                                           status["ErrorDescription"].asString());
 #else
-          LogError("Exception while executing the job: " + status["ErrorDescription"].asString());
+          ORTHANC_PLUGINS_LOG_ERROR("Exception while executing the job: " + status["ErrorDescription"].asString());
           ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());          
 #endif
         }
@@ -2558,7 +2602,7 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
                                       "Expected a JSON object in the body");
 #else
-      LogError("Expected a JSON object in the body");
+      ORTHANC_PLUGINS_LOG_ERROR("Expected a JSON object in the body");
       ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
 #endif
     }
@@ -2574,7 +2618,7 @@
                                         "Option \"" + std::string(KEY_SYNCHRONOUS) +
                                         "\" must be Boolean");
 #else
-        LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_LOG_ERROR("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean");
         ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
 #endif
       }
@@ -2593,7 +2637,7 @@
                                         "Option \"" + std::string(KEY_ASYNCHRONOUS) +
                                         "\" must be Boolean");
 #else
-        LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_LOG_ERROR("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean");
         ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
 #endif
       }
@@ -2607,14 +2651,14 @@
 
     if (body.isMember(KEY_PRIORITY))
     {
-      if (body[KEY_PRIORITY].type() != Json::booleanValue)
+      if (body[KEY_PRIORITY].type() != Json::intValue)
       {
 #if HAS_ORTHANC_EXCEPTION == 1
         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
                                         "Option \"" + std::string(KEY_PRIORITY) +
                                         "\" must be an integer");
 #else
-        LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer");
+        ORTHANC_PLUGINS_LOG_ERROR("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer");
         ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
 #endif
       }
@@ -3135,7 +3179,7 @@
 
     if (body.size() > 0xffffffffu)
     {
-      LogError("Cannot handle body size > 4GB");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
@@ -3280,7 +3324,7 @@
     
     if (!ReadJson(answerBody, body))
     {
-      LogError("Cannot convert HTTP answer body to JSON");
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert HTTP answer body to JSON");
       ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
   }
@@ -4026,4 +4070,48 @@
       result[request->headersKeys[i]] = request->headersValues[i];
     }    
   }
+
+#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
+  static void SetPluginProperty(const std::string& pluginIdentifier,
+                                _OrthancPluginProperty property,
+                                const std::string& value)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = pluginIdentifier.c_str();
+    params.property = property;
+    params.value = value.c_str();
+
+    GetGlobalContext()->InvokeService(GetGlobalContext(), _OrthancPluginService_SetPluginProperty, &params);
+  }
+#endif
+
+  void SetRootUri(const std::string& pluginIdentifier,
+                  const std::string& uri)
+  {
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
+    OrthancPluginSetRootUri2(GetGlobalContext(), pluginIdentifier.c_str(), uri.c_str());
+#else
+    SetPluginProperty(pluginIdentifier, _OrthancPluginProperty_RootUri, uri);
+#endif
+  }
+
+  void SetDescription(const std::string& pluginIdentifier,
+                      const std::string& description)
+  {
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
+    OrthancPluginSetDescription2(GetGlobalContext(), pluginIdentifier.c_str(), description.c_str());
+#else
+    SetPluginProperty(pluginIdentifier, _OrthancPluginProperty_Description, description);
+#endif
+  }
+
+  void ExtendOrthancExplorer(const std::string& pluginIdentifier,
+                             const std::string& javascript)
+  {
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
+    OrthancPluginExtendOrthancExplorer2(GetGlobalContext(), pluginIdentifier.c_str(), javascript.c_str());
+#else
+    SetPluginProperty(pluginIdentifier, _OrthancPluginProperty_OrthancExplorer, javascript);
+#endif
+  }
 }
--- a/Common/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Tue Jun 25 12:38:31 2024 +0200
@@ -3,7 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -127,6 +128,44 @@
 #  define HAS_ORTHANC_PLUGIN_WEBDAV  0
 #endif
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
+#  define HAS_ORTHANC_PLUGIN_LOG_MESSAGE  1
+#else
+#  define HAS_ORTHANC_PLUGIN_LOG_MESSAGE  0
+#endif
+
+
+// Macro to tag a function as having been deprecated
+#if (__cplusplus >= 201402L)  // C++14
+#  define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(f) [[deprecated]] f
+#elif defined(__GNUC__) || defined(__clang__)
+#  define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(f) f __attribute__((deprecated))
+#elif defined(_MSC_VER)
+#  define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(f) __declspec(deprecated) f
+#else
+#  define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED
+#endif
+
+
+#if !defined(__ORTHANC_FILE__)
+#  if defined(_MSC_VER)
+#    pragma message("Warning: Macro __ORTHANC_FILE__ is not defined, this will leak the full path of the source files in the binaries")
+#  else
+#    warning Warning: Macro __ORTHANC_FILE__ is not defined, this will leak the full path of the source files in the binaries
+#  endif
+#  define __ORTHANC_FILE__ __FILE__
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
+#  define ORTHANC_PLUGINS_LOG_ERROR(msg)   ::OrthancPlugins::LogMessage(OrthancPluginLogLevel_Error, __ORTHANC_FILE__, __LINE__, msg)
+#  define ORTHANC_PLUGINS_LOG_WARNING(msg) ::OrthancPlugins::LogMessage(OrthancPluginLogLevel_Warning, __ORTHANC_FILE__, __LINE__, msg)
+#  define ORTHANC_PLUGINS_LOG_INFO(msg)    ::OrthancPlugins::LogMessage(OrthancPluginLogLevel_Info, __ORTHANC_FILE__, __LINE__, msg)
+#else
+#  define ORTHANC_PLUGINS_LOG_ERROR(msg)   ::OrthancPlugins::LogError(msg)
+#  define ORTHANC_PLUGINS_LOG_WARNING(msg) ::OrthancPlugins::LogWarning(msg)
+#  define ORTHANC_PLUGINS_LOG_INFO(msg)    ::OrthancPlugins::LogInfo(msg)
+#endif
 
 
 namespace OrthancPlugins
@@ -137,6 +176,9 @@
 
   void SetGlobalContext(OrthancPluginContext* context);
 
+  void SetGlobalContext(OrthancPluginContext* context,
+                        const char* pluginName);
+
   void ResetGlobalContext();
 
   bool HasGlobalContext();
@@ -637,11 +679,33 @@
   const char* AutodetectMimeType(const std::string& path);
 #endif
 
+#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
+  void LogMessage(OrthancPluginLogLevel level,
+                  const char* file,
+                  uint32_t line,
+                  const std::string& message);
+#endif
+
+#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
+  // Use macro ORTHANC_PLUGINS_LOG_ERROR() instead
+  ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(void LogError(const std::string& message));
+#else
   void LogError(const std::string& message);
+#endif
 
+#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
+  // Use macro ORTHANC_PLUGINS_LOG_WARNING() instead
+  ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(void LogWarning(const std::string& message));
+#else
   void LogWarning(const std::string& message);
+#endif
 
+#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
+  // Use macro ORTHANC_PLUGINS_LOG_INFO() instead
+  ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(void LogInfo(const std::string& message));
+#else
   void LogInfo(const std::string& message);
+#endif
 
   void ReportMinimalOrthancVersion(unsigned int major,
                                    unsigned int minor,
@@ -1435,4 +1499,13 @@
                          IWebDavCollection& collection);
   };
 #endif
+
+  void SetRootUri(const std::string& pluginIdentifier,
+                  const std::string& uri);
+
+  void SetDescription(const std::string& pluginIdentifier,
+                      const std::string& description);
+
+  void ExtendOrthancExplorer(const std::string& pluginIdentifier,
+                             const std::string& javascript);
 }
--- a/Common/Resources/Orthanc/Plugins/OrthancPluginException.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/Plugins/OrthancPluginException.h	Tue Jun 25 12:38:31 2024 +0200
@@ -3,7 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/Common/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -2,7 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/Common/Resources/Orthanc/Toolchains/LinuxStandardBaseToolchain.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/Toolchains/LinuxStandardBaseToolchain.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -2,7 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/Common/Resources/Orthanc/Toolchains/MinGW-W64-Toolchain32.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/Toolchains/MinGW-W64-Toolchain32.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -2,7 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/Common/Resources/Orthanc/Toolchains/MinGW-W64-Toolchain64.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/Toolchains/MinGW-W64-Toolchain64.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -2,7 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/Common/Resources/Orthanc/Toolchains/MinGWToolchain.cmake	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/Orthanc/Toolchains/MinGWToolchain.cmake	Tue Jun 25 12:38:31 2024 +0200
@@ -2,7 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/Common/Resources/SyncOrthancFolder.py	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/Resources/SyncOrthancFolder.py	Tue Jun 25 12:38:31 2024 +0200
@@ -11,7 +11,7 @@
 import urllib.request
 
 TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc')
-PLUGIN_SDK_VERSION = '1.12.1'
+PLUGIN_SDK_VERSION = '1.12.3'
 REPOSITORY = 'https://orthanc.uclouvain.be/hg/orthanc/raw-file'
 
 FILES = [
--- a/Common/StoragePlugin.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/StoragePlugin.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -48,6 +50,12 @@
 #include "StoragePlugin.h"
 #include <Toolbox.h>
 
+#ifdef _WIN32
+// This is a hotfix for: https://orthanc.uclouvain.be/hg/orthanc/rev/e4d9a872998f
+#  undef ORTHANC_PLUGINS_API
+#  define ORTHANC_PLUGINS_API __declspec(dllexport)
+#endif
+
 
 static std::unique_ptr<IStorage> primaryStorage;
 static std::unique_ptr<IStorage> secondaryStorage;
@@ -79,6 +87,16 @@
 
 typedef void LogErrorFunction(const std::string& message);
 
+static void LogErrorAsWarning(const std::string& message)
+{
+  LOG(WARNING) << message;
+}
+
+static void LogErrorAsError(const std::string& message)
+{
+  LOG(ERROR) << message;
+}
+
 
 
 static OrthancPluginErrorCode StorageCreate(const char* uuid,
@@ -88,7 +106,9 @@
 {
   try
   {
-    OrthancPlugins::LogInfo(primaryStorage->GetNameForLogs() + ": creating attachment " + std::string(uuid) + " of type " + boost::lexical_cast<std::string>(type));
+    Orthanc::Toolbox::ElapsedTimer timer;
+    LOG(INFO) << primaryStorage->GetNameForLogs() << ": creating attachment " << uuid
+              << " of type " << boost::lexical_cast<std::string>(type);
     std::unique_ptr<IStorage::IWriter> writer(primaryStorage->GetWriterForObject(uuid, type, cryptoEnabled));
 
     if (cryptoEnabled)
@@ -101,7 +121,7 @@
       }
       catch (EncryptionException& ex)
       {
-        OrthancPlugins::LogError(primaryStorage->GetNameForLogs() + ": error while encrypting object " + std::string(uuid) + ": " + ex.what());
+        LOG(ERROR) << primaryStorage->GetNameForLogs() << ": error while encrypting object " << uuid << ": " << ex.what();
         return OrthancPluginErrorCode_StorageAreaPlugin;
       }
 
@@ -111,10 +131,12 @@
     {
       writer->Write(reinterpret_cast<const char*>(content), size);
     }
+    LOG(INFO) << primaryStorage->GetNameForLogs() << ": created attachment " << uuid
+              << " (" << timer.GetHumanTransferSpeed(true, size) << ")";
   }
   catch (StoragePluginException& ex)
   {
-    OrthancPlugins::LogError(primaryStorage->GetNameForLogs() + ": error while creating object " + std::string(uuid) + ": " + ex.what());
+    LOG(ERROR) << primaryStorage->GetNameForLogs() << ": error while creating object " << uuid << ": " << ex.what();
     return OrthancPluginErrorCode_StorageAreaPlugin;
   }
 
@@ -133,11 +155,15 @@
 
   try
   {
-    OrthancPlugins::LogInfo(storage->GetNameForLogs() + ": reading range of attachment " + std::string(uuid) + " of type " + boost::lexical_cast<std::string>(type));
+    Orthanc::Toolbox::ElapsedTimer timer;
+    LOG(INFO) << storage->GetNameForLogs() << ": reading range of attachment " << uuid
+              << " of type " << boost::lexical_cast<std::string>(type);
     
     std::unique_ptr<IStorage::IReader> reader(storage->GetReaderForObject(uuid, type, cryptoEnabled));
     reader->ReadRange(reinterpret_cast<char*>(target->data), target->size, rangeStart);
     
+    LOG(INFO) << storage->GetNameForLogs() << ": read range of attachment " << uuid
+              << " (" << timer.GetHumanTransferSpeed(true, target->size) << ")";
     return OrthancPluginErrorCode_Success;
   }
   catch (StoragePluginException& ex)
@@ -153,7 +179,7 @@
                                                uint64_t rangeStart)
 {
   OrthancPluginErrorCode res = StorageReadRange(primaryStorage.get(),
-                                                (IsHybridModeEnabled() ? OrthancPlugins::LogWarning : OrthancPlugins::LogError), // log errors as warning on first try
+                                                (IsHybridModeEnabled() ? LogErrorAsWarning : LogErrorAsError), // log errors as warning on first try
                                                 target,
                                                 uuid,
                                                 type,
@@ -162,7 +188,7 @@
   if (res != OrthancPluginErrorCode_Success && IsHybridModeEnabled())
   {
     res = StorageReadRange(secondaryStorage.get(),
-                           OrthancPlugins::LogError, // log errors as errors on second try
+                           LogErrorAsError, // log errors as errors on second try
                            target,
                            uuid,
                            type,
@@ -181,7 +207,9 @@
 {
   try
   {
-    OrthancPlugins::LogInfo(storage->GetNameForLogs() + ": reading whole attachment " + std::string(uuid) + " of type " + boost::lexical_cast<std::string>(type));
+    Orthanc::Toolbox::ElapsedTimer timer;
+    LOG(INFO) << storage->GetNameForLogs() << ": reading whole attachment " << uuid
+              << " of type " << boost::lexical_cast<std::string>(type);
     std::unique_ptr<IStorage::IReader> reader(storage->GetReaderForObject(uuid, type, cryptoEnabled));
 
     size_t fileSize = reader->GetSize();
@@ -227,6 +255,9 @@
     {
       reader->ReadWhole(reinterpret_cast<char*>(target->data), fileSize);
     }
+
+    LOG(INFO) << storage->GetNameForLogs() << ": read whole attachment " << uuid
+              << " (" << timer.GetHumanTransferSpeed(true, fileSize) << ")";
   }
   catch (StoragePluginException& ex)
   {
@@ -242,7 +273,7 @@
                                                OrthancPluginContentType type)
 {
   OrthancPluginErrorCode res = StorageReadWhole(primaryStorage.get(),
-                                                (IsHybridModeEnabled() ? OrthancPlugins::LogWarning : OrthancPlugins::LogError), // log errors as warning on first try
+                                                (IsHybridModeEnabled() ? LogErrorAsWarning : LogErrorAsError), // log errors as warning on first try
                                                 target,
                                                 uuid,
                                                 type);
@@ -250,7 +281,7 @@
   if (res != OrthancPluginErrorCode_Success && IsHybridModeEnabled())
   {
     res = StorageReadWhole(secondaryStorage.get(),
-                           OrthancPlugins::LogError, // log errors as errors on second try
+                           LogErrorAsError, // log errors as errors on second try
                            target,
                            uuid,
                            type);
@@ -283,7 +314,8 @@
 {
   try
   {
-    OrthancPlugins::LogInfo(storage->GetNameForLogs() + ": deleting attachment " + std::string(uuid) + " of type " + boost::lexical_cast<std::string>(type));
+    LOG(INFO) << storage->GetNameForLogs() << ": deleting attachment " << uuid
+              << " of type " << boost::lexical_cast<std::string>(type);
     storage->DeleteObject(uuid, type, cryptoEnabled);
     if ((storage == primaryStorage.get()) && IsHybridModeEnabled())
     {
@@ -304,14 +336,14 @@
                                             OrthancPluginContentType type)
 {
   OrthancPluginErrorCode res = StorageRemove(primaryStorage.get(),
-                                             (IsHybridModeEnabled() ? OrthancPlugins::LogWarning : OrthancPlugins::LogError), // log errors as warning on first try
+                                             (IsHybridModeEnabled() ? LogErrorAsWarning : LogErrorAsError), // log errors as warning on first try
                                              uuid,
                                              type);
 
   if (res != OrthancPluginErrorCode_Success && IsHybridModeEnabled())
   {
     res = StorageRemove(secondaryStorage.get(),
-                        OrthancPlugins::LogError, // log errors as errors on second try
+                        LogErrorAsError, // log errors as errors on second try
                         uuid,
                         type);
   }
@@ -443,7 +475,7 @@
     }   
   }
 
-  OrthancPlugins::LogInfo("Moving " + boost::lexical_cast<std::string>(instances.size()) + " instances to " + targetStorage);
+  LOG(INFO) << "Moving " << instances.size() << " instances to " << targetStorage;
 
   std::unique_ptr<MoveStorageJob> job(CreateMoveStorageJob(targetStorage, instances, resourcesForJobContent));
 
@@ -521,13 +553,13 @@
   {
     OrthancPlugins::SetGlobalContext(context);
 
-    Orthanc::InitializeFramework("", false);
+#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 12, 4)
+    Orthanc::Logging::InitializePluginContext(context, StoragePluginFactory::GetStoragePluginName());
+#elif ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2)
     Orthanc::Logging::InitializePluginContext(context);
-
-    OrthancPlugins::OrthancConfiguration orthancConfig;
-
-    OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + " plugin is initializing");
-    OrthancPluginSetDescription(context, StoragePluginFactory::GetStorageDescription());
+#else
+    Orthanc::Logging::Initialize(context);
+#endif
 
     /* Check the version of the Orthanc core */
     if (OrthancPluginCheckVersion(context) == 0)
@@ -538,10 +570,17 @@
               ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
               ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
               ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
-      OrthancPlugins::LogError(info);
+      LOG(ERROR) << info;
       return -1;
     }
 
+    Orthanc::InitializeFramework("", false);
+
+    OrthancPlugins::OrthancConfiguration orthancConfig;
+
+    LOG(WARNING) << StoragePluginFactory::GetStoragePluginName() << " plugin is initializing";
+    OrthancPlugins::SetDescription(StoragePluginFactory::GetStoragePluginName(), StoragePluginFactory::GetStorageDescription());
+
     try
     {
       const char* pluginSectionName = StoragePluginFactory::GetConfigurationSectionName();
@@ -549,7 +588,8 @@
 
       if (!orthancConfig.IsSection(pluginSectionName))
       {
-        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": no \"" + pluginSectionName +  "\" section found in configuration, plugin is disabled");
+        LOG(WARNING) << StoragePluginFactory::GetStoragePluginName() << ": no \"" << pluginSectionName
+                     << "\" section found in configuration, plugin is disabled";
         return 0;
       }
 
@@ -562,34 +602,34 @@
       if (migrationFromFileSystemEnabled && hybridModeString == "Disabled")
       {
         hybridMode = HybridMode_WriteToObjectStorage;
-        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": 'MigrationFromFileSystemEnabled' configuration is deprecated, use 'HybridMode': 'WriteToObjectStorage' instead");
+        LOG(WARNING) << StoragePluginFactory::GetStoragePluginName() << ": 'MigrationFromFileSystemEnabled' configuration is deprecated, use 'HybridMode': 'WriteToObjectStorage' instead";
       }
       else if (hybridModeString == "WriteToObjectStorage")
       {
         hybridMode = HybridMode_WriteToObjectStorage;
-        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": WriteToObjectStorage HybridMode is enabled: writing to object-storage, try reading first from object-storage and, then, from file system");
+        LOG(WARNING) << StoragePluginFactory::GetStoragePluginName() << ": WriteToObjectStorage HybridMode is enabled: writing to object-storage, try reading first from object-storage and, then, from file system";
       }
       else if (hybridModeString == "WriteToFileSystem")
       {
         hybridMode = HybridMode_WriteToFileSystem;
-        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": WriteToFileSystem HybridMode is enabled: writing to file system, try reading first from file system and, then, from object-storage");
+        LOG(WARNING) << StoragePluginFactory::GetStoragePluginName() << ": WriteToFileSystem HybridMode is enabled: writing to file system, try reading first from file system and, then, from object-storage";
       }
       else
       {
-        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": HybridMode is disabled: writing to object-storage and reading only from object-storage");
+        LOG(WARNING) << StoragePluginFactory::GetStoragePluginName() << ": HybridMode is disabled: writing to object-storage and reading only from object-storage";
       }
 
       if (IsReadFromDisk())
       {
         fileSystemRootPath = orthancConfig.GetStringValue("StorageDirectory", "OrthancStorageNotDefined");
-        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": HybridMode: reading from file system is enabled, source: " + fileSystemRootPath);
+        LOG(WARNING) << StoragePluginFactory::GetStoragePluginName() << ": HybridMode: reading from file system is enabled, source: " << fileSystemRootPath;
       }
 
       objectsRootPath = pluginSection.GetStringValue("RootPath", std::string());
 
       if (objectsRootPath.size() >= 1 && objectsRootPath[0] == '/')
       {
-        OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": The RootPath shall not start with a '/': " + objectsRootPath);
+        LOG(ERROR) << StoragePluginFactory::GetStoragePluginName() << ": The RootPath shall not start with a '/': " << objectsRootPath;
         return -1;
       }
 
@@ -656,11 +696,11 @@
 
       if (cryptoEnabled)
       {
-        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": client-side encryption is enabled");
+        LOG(WARNING) << StoragePluginFactory::GetStoragePluginName() << ": client-side encryption is enabled";
       }
       else
       {
-        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": client-side encryption is disabled");
+        LOG(WARNING) << StoragePluginFactory::GetStoragePluginName() << ": client-side encryption is disabled";
       }
 
 
@@ -692,7 +732,7 @@
 
   ORTHANC_PLUGINS_API void OrthancPluginFinalize()
   {
-    OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + " plugin is finalizing");
+    LOG(WARNING) << StoragePluginFactory::GetStoragePluginName() << " plugin is finalizing";
     primaryStorage.reset();
     secondaryStorage.reset();
     Orthanc::FinalizeFramework();
--- a/Common/StoragePlugin.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Common/StoragePlugin.h	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Transfers accelerator plugin for Orthanc
- * Copyright (C) 2018-2021 Osimis S.A., Belgium
+ * Copyright (C) 2018-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 Affero General Public License
--- a/Dockerfile	Tue Nov 21 09:50:24 2023 +0100
+++ b/Dockerfile	Tue Jun 25 12:38:31 2024 +0200
@@ -11,7 +11,7 @@
 WORKDIR /sources
 
 # (framework version used to build the cloud storage plugins)
-RUN hg clone https://hg.orthanc-server.com/orthanc/ -r "Orthanc-1.10.1" 
+RUN hg clone https://orthanc.uclouvain.be/hg/orthanc/ -r "Orthanc-1.10.1" 
 
 RUN mkdir orthanc-object-storage
 
--- a/Google/CMakeLists.txt	Tue Nov 21 09:50:24 2023 +0100
+++ b/Google/CMakeLists.txt	Tue Jun 25 12:38:31 2024 +0200
@@ -1,5 +1,7 @@
 # Cloud storage plugins for Orthanc
-# Copyright (C) 2020-2021 Osimis S.A., Belgium
+# Copyright (C) 2020-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 Affero General Public License
@@ -19,14 +21,25 @@
 
 project(OrthancGoogleCloudStorage)
 
-set(PLUGIN_VERSION "2.3.0")
+set(PLUGIN_VERSION "mainline")
+
+if (PLUGIN_VERSION STREQUAL "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+else()
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.12.4")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+endif()
+
+set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc framework (can be \"system\", \"hg\", \"archive\", \"web\" or \"path\")")
+set(ORTHANC_FRAMEWORK_VERSION "${ORTHANC_FRAMEWORK_DEFAULT_VERSION}" CACHE STRING "Version of the Orthanc framework")
+set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"")
+set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"")
+set(ALLOW_DOWNLOADS ON)
+
 
 include(CheckIncludeFileCXX)
 
-set(ORTHANC_FRAMEWORK_SOURCE "web" CACHE STRING "orthanc source")
-set(ORTHANC_FRAMEWORK_VERSION "daf4807631c5" CACHE STRING "orthanc framework version")  # TODO: update to 1.12.2 when available
-set(ALLOW_DOWNLOADS ON)
-
 # Download and setup the Orthanc framework
 
 include(${CMAKE_SOURCE_DIR}/../Common/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake)
@@ -86,7 +99,10 @@
     ${COMMON_SOURCES}
   )
 
-DefineSourceBasenameForTarget(OrthancGoogleCloudStorage)
+if (COMMAND DefineSourceBasenameForTarget)
+  DefineSourceBasenameForTarget(OrthancGoogleCloudStorage)
+endif()
+
 
 set_target_properties(OrthancGoogleCloudStorage PROPERTIES
   VERSION ${PLUGIN_VERSION}
@@ -101,6 +117,12 @@
   cryptopp::cryptopp
   )
 
+install(
+  TARGETS OrthancGoogleCloudStorage
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+)
+
 add_executable(UnitTests
     ${GOOGLE_TEST_SOURCES}
     ${COMMON_SOURCES}
--- a/Google/GoogleStoragePlugin.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/Google/GoogleStoragePlugin.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -21,6 +23,8 @@
 
 #include "google/cloud/storage/client.h"
 
+#include <Logging.h>
+
 // Create aliases to make the code easier to read.
 namespace gcs = google::cloud::storage;
 
@@ -42,10 +46,10 @@
                       bool storageContainsUnknownFiles
                       );
 
-  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);
-  virtual bool HasFileExists() {return false;};
+  virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+  virtual IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+  virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE;
+  virtual bool HasFileExists() ORTHANC_OVERRIDE {return false;};
 };
 
 
@@ -67,7 +71,7 @@
   {
   }
 
-  virtual void Write(const char* data, size_t size)
+  virtual void Write(const char* data, size_t size) ORTHANC_OVERRIDE
   {
     stream_ = client_.WriteObject(bucketName_, path_);
 
@@ -108,7 +112,7 @@
 
   }
 
-  virtual size_t GetSize()
+  virtual size_t GetSize() ORTHANC_OVERRIDE
   {
     std::string firstExceptionMessage;
 
@@ -130,7 +134,7 @@
     throw StoragePluginException(firstExceptionMessage);
   }
 
-  void ReadWhole(char* data, size_t size)
+  virtual void ReadWhole(char* data, size_t size) ORTHANC_OVERRIDE
   {
     std::string firstExceptionMessage;
 
@@ -152,7 +156,7 @@
     throw StoragePluginException(firstExceptionMessage);
   }
 
-  void ReadRange(char* data, size_t size, size_t fromOffset)
+  virtual void ReadRange(char* data, size_t size, size_t fromOffset) ORTHANC_OVERRIDE
   {
     std::string firstExceptionMessage;
 
@@ -175,7 +179,7 @@
   }
 
 private:
-  virtual void _ReadWhole(const std::string& path, char* data, size_t size)
+  void _ReadWhole(const std::string& path, char* data, size_t size)
   {
     auto reader = client_.ReadObject(bucketName_, path);
 
@@ -192,7 +196,7 @@
     }
   }
 
-  virtual void _ReadRange(const std::string& path, char* data, size_t size, size_t fromOffset)
+  void _ReadRange(const std::string& path, char* data, size_t size, size_t fromOffset)
   {
     auto reader = client_.ReadObject(bucketName_, path, gcs::ReadRange(fromOffset, fromOffset + size));
 
@@ -221,7 +225,7 @@
     }
     else
     {
-        throw StoragePluginException("error while getting the size of " + std::string(path) + ": " + objectMetadata.status().message());
+      throw StoragePluginException("error while getting the size of " + std::string(path) + ": " + objectMetadata.status().message());
     }
   }
 
@@ -246,7 +250,7 @@
 
   if (!orthancConfig.IsSection(GetConfigurationSectionName()))
   {
-    OrthancPlugins::LogWarning(std::string(GetStoragePluginName()) + " plugin, section missing.  Plugin is not enabled.");
+    LOG(WARNING) << GetStoragePluginName() << " plugin, section missing.  Plugin is not enabled.";
     return nullptr;
   }
 
@@ -262,14 +266,14 @@
 
   if (!pluginSection.LookupStringValue(pathToGoogleCredentials, "ServiceAccountFile"))
   {
-    OrthancPlugins::LogError("GoogleCloudStorage/ServiceAccountFile configuration missing.  Unable to initialize plugin");
+    LOG(ERROR) << "GoogleCloudStorage/ServiceAccountFile configuration missing.  Unable to initialize plugin";
     return nullptr;
   }
 
   std::string googleBucketName;
   if (!pluginSection.LookupStringValue(googleBucketName, "BucketName"))
   {
-    OrthancPlugins::LogError("GoogleCloudStorage/BucketName configuration missing.  Unable to initialize plugin");
+    LOG(ERROR) << "GoogleCloudStorage/BucketName configuration missing.  Unable to initialize plugin";
     return nullptr;
   }
 
@@ -277,7 +281,7 @@
   auto creds = gcs::oauth2::CreateServiceAccountCredentialsFromJsonFilePath(pathToGoogleCredentials);
   if (!creds)
   {
-    OrthancPlugins::LogError("GoogleCloudStorage plugin: unable to validate credentials.  Check the ServiceAccountFile: " + creds.status().message());
+    LOG(ERROR) << "GoogleCloudStorage plugin: unable to validate credentials.  Check the ServiceAccountFile: " << creds.status().message();
     return nullptr;
   }
 
@@ -286,7 +290,7 @@
 
   if (!mainClient)
   {
-    OrthancPlugins::LogError("GoogleCloudStorage plugin: unable to create client: " + mainClient.status().message());
+    LOG(ERROR) << "GoogleCloudStorage plugin: unable to create client: " << mainClient.status().message();
     return nullptr;
   }
 
--- a/Google/GoogleStoragePlugin.h	Tue Nov 21 09:50:24 2023 +0100
+++ b/Google/GoogleStoragePlugin.h	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
--- a/NEWS	Tue Nov 21 09:50:24 2023 +0100
+++ b/NEWS	Tue Jun 25 12:38:31 2024 +0200
@@ -1,3 +1,14 @@
+Pending changes in the mainline
+===============================
+
+
+2023-12-20 - v 2.3.1
+====================
+
+* AWS plugin:
+  * New configuration "EnableAwsSdkLogs" to include AWS SDK logs in Orthanc logs.
+* Now displaying size, duration and transfer speed for each read/write operation.
+
 2023-10-17 - v 2.3.0
 ====================
 
--- a/README.md	Tue Nov 21 09:50:24 2023 +0100
+++ b/README.md	Tue Jun 25 12:38:31 2024 +0200
@@ -2,7 +2,7 @@
 
 Orthanc object-storages plugin for main cloud providers (Google/Azure/AWS)
 
-Check the [Orthanc book](https://book.orthanc-server.com/plugins/object-storage.html) for complete documentation.
+Check the [Orthanc book](https://orthanc.uclouvain.be/book/plugins/object-storage.html) for complete documentation.
 
 ## info for developers ##
 
--- a/TODO	Tue Nov 21 09:50:24 2023 +0100
+++ b/TODO	Tue Jun 25 12:38:31 2024 +0200
@@ -5,3 +5,6 @@
 
 - Test against AWS S3 Glacier:
   https://groups.google.com/g/orthanc-users/c/CT1lkzHLAUg/m/h75HuXYpBwAJ
+
+- AWS: use the latest aws S3-crt client ?
+  https://github.com/aws/aws-sdk-cpp/wiki/Improving-S3-Throughput-with-AWS-SDK-for-CPP-v1.9
--- a/UnitTestsSources/EncryptionTests.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/UnitTestsSources/EncryptionTests.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
@@ -232,11 +234,12 @@
 
 void MeasurePerformance(size_t sizeInMB, EncryptionHelpers& crypto)
 {
-  std::string largePlainText(sizeInMB * 1024 * 1024, 'A');
   std::string encryptedMessage;
   std::string decryptedMessage;
 
   {
+    const std::string largePlainText(sizeInMB * 1024 * 1024, 'A');
+
     auto start = boost::posix_time::microsec_clock::local_time();
     crypto.Encrypt(encryptedMessage, largePlainText);
 
--- a/UnitTestsSources/UnitTestsGcsClient.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/UnitTestsSources/UnitTestsGcsClient.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License
--- a/UnitTestsSources/UnitTestsMain.cpp	Tue Nov 21 09:50:24 2023 +0100
+++ b/UnitTestsSources/UnitTestsMain.cpp	Tue Jun 25 12:38:31 2024 +0200
@@ -1,6 +1,8 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ * Copyright (C) 2020-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 Affero General Public License