changeset 131:c55b0583084b

integration fix-leak->mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 30 Aug 2023 11:52:45 +0200
parents a3f77cf16396 (diff) 5643e97d9367 (current diff)
children 4cf3c2bc119f
files CMakeLists.txt CodeAnalysis/Class.mustache CodeAnalysis/Enumeration.mustache CodeAnalysis/ParseOrthancSDK.py CodeAnalysis/sdk.cpp.mustache CodeAnalysis/sdk.h.mustache NEWS Sources/Autogenerated/sdk.cpp Sources/Autogenerated/sdk.h Sources/Autogenerated/sdk_OrthancPluginChangeType.impl.h Sources/Autogenerated/sdk_OrthancPluginCompressionType.impl.h Sources/Autogenerated/sdk_OrthancPluginConstraintType.impl.h Sources/Autogenerated/sdk_OrthancPluginContentType.impl.h Sources/Autogenerated/sdk_OrthancPluginCreateDicomFlags.impl.h Sources/Autogenerated/sdk_OrthancPluginDicomInstance.impl.h Sources/Autogenerated/sdk_OrthancPluginDicomToJsonFlags.impl.h Sources/Autogenerated/sdk_OrthancPluginDicomToJsonFormat.impl.h Sources/Autogenerated/sdk_OrthancPluginDicomWebBinaryMode.impl.h Sources/Autogenerated/sdk_OrthancPluginDicomWebNode.impl.h Sources/Autogenerated/sdk_OrthancPluginErrorCode.impl.h Sources/Autogenerated/sdk_OrthancPluginFindAnswers.impl.h Sources/Autogenerated/sdk_OrthancPluginFindMatcher.impl.h Sources/Autogenerated/sdk_OrthancPluginFindQuery.impl.h Sources/Autogenerated/sdk_OrthancPluginHttpMethod.impl.h Sources/Autogenerated/sdk_OrthancPluginIdentifierConstraint.impl.h Sources/Autogenerated/sdk_OrthancPluginImage.impl.h Sources/Autogenerated/sdk_OrthancPluginImageFormat.impl.h Sources/Autogenerated/sdk_OrthancPluginInstanceOrigin.impl.h Sources/Autogenerated/sdk_OrthancPluginJob.impl.h Sources/Autogenerated/sdk_OrthancPluginJobStepStatus.impl.h Sources/Autogenerated/sdk_OrthancPluginJobStopReason.impl.h Sources/Autogenerated/sdk_OrthancPluginMetricsType.impl.h Sources/Autogenerated/sdk_OrthancPluginPeers.impl.h Sources/Autogenerated/sdk_OrthancPluginPixelFormat.impl.h Sources/Autogenerated/sdk_OrthancPluginReceivedInstanceAction.impl.h Sources/Autogenerated/sdk_OrthancPluginResourceType.impl.h Sources/Autogenerated/sdk_OrthancPluginRestOutput.impl.h Sources/Autogenerated/sdk_OrthancPluginServerChunkedRequestReader.impl.h Sources/Autogenerated/sdk_OrthancPluginStorageArea.impl.h Sources/Autogenerated/sdk_OrthancPluginStorageCommitmentFailureReason.impl.h Sources/Autogenerated/sdk_OrthancPluginValueRepresentation.impl.h Sources/Autogenerated/sdk_OrthancPluginWorklistAnswers.impl.h Sources/Autogenerated/sdk_OrthancPluginWorklistQuery.impl.h Sources/DicomScpCallbacks.cpp Sources/IncomingHttpRequestFilter.cpp Sources/Plugin.cpp Sources/RestCallbacks.cpp
diffstat 90 files changed, 1190 insertions(+), 247 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Aug 30 11:40:22 2023 +0200
+++ b/CMakeLists.txt	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 2.8)
 project(OrthancPython)
 
-set(PLUGIN_VERSION "4.0")
+set(PLUGIN_VERSION "mainline")
 
 if (PLUGIN_VERSION STREQUAL "mainline")
   set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
@@ -187,6 +187,7 @@
   Sources/ReceivedInstanceCallback.cpp
   Sources/RestCallbacks.cpp
   Sources/StorageArea.cpp
+  Sources/StorageCommitmentScpCallback.cpp
 
   # Third-party sources
   ${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp
--- a/CodeAnalysis/Class.mustache	Wed Aug 30 11:40:22 2023 +0200
+++ b/CodeAnalysis/Class.mustache	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/CodeAnalysis/Enumeration.mustache	Wed Aug 30 11:40:22 2023 +0200
+++ b/CodeAnalysis/Enumeration.mustache	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/CodeAnalysis/GlobalFunctions.mustache	Wed Aug 30 11:40:22 2023 +0200
+++ b/CodeAnalysis/GlobalFunctions.mustache	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/CodeAnalysis/ParseOrthancSDK.py	Wed Aug 30 11:40:22 2023 +0200
+++ b/CodeAnalysis/ParseOrthancSDK.py	Wed Aug 30 11:52:45 2023 +0200
@@ -2,8 +2,8 @@
 
 ##
 ## Python plugin for Orthanc
-## Copyright (C) 2020-2022 Osimis S.A., Belgium
-## Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+## Copyright (C) 2020-2023 Osimis S.A., Belgium
+## Copyright (C) 2021-2023 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/CodeAnalysis/sdk.cpp.mustache	Wed Aug 30 11:40:22 2023 +0200
+++ b/CodeAnalysis/sdk.cpp.mustache	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/CodeAnalysis/sdk.h.mustache	Wed Aug 30 11:40:22 2023 +0200
+++ b/CodeAnalysis/sdk.h.mustache	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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	Wed Aug 30 11:40:22 2023 +0200
+++ b/NEWS	Wed Aug 30 11:52:45 2023 +0200
@@ -1,12 +1,21 @@
 Pending changes in the mainline
 ===============================
 
+* New functions from the SDK wrapped in Python:
+  - orthanc.RegisterStorageCommitmentScpCallback()
 * New configuration section "Python" to group settings related to the plugin:
   - "Python.Path" is an alias for global option "PythonScript"
   - "Python.Verbose" is an alias for global option "PythonVerbose"
 * New configuration option "Python.DisplayMemoryUsage" to periodically
   display memory allocations that stem from Python plugins
 
+Maintenance
+-----------
+
+* Fix memory leaks when a python script calls orthanc.RestApiPost() and sibling methods,
+  in IncomingHttpRequestFilter and in the CMove callback.
+* New builders for Windows: Supporting 32 / 64bit with Python 3.9 / 3.10 / 3.11
+
 
 Version 4.0 (2022-02-23)
 ========================
--- a/README	Wed Aug 30 11:40:22 2023 +0200
+++ b/README	Wed Aug 30 11:52:45 2023 +0200
@@ -18,6 +18,14 @@
 http://book.orthanc-server.com/plugins/python.html
 
 
+Contributing
+------------
+
+Instructions for contributing to the Orthanc project are included in
+the Orthanc Book:
+https://book.orthanc-server.com/developers/repositories.html
+
+
 Licensing
 ---------
 
--- a/Resources/Orthanc/CMake/AutoGeneratedCode.cmake	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/CMake/AutoGeneratedCode.cmake	Wed Aug 30 11:52:45 2023 +0200
@@ -1,8 +1,8 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2022 Osimis S.A., Belgium
-# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 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/Resources/Orthanc/CMake/Compiler.cmake	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/CMake/Compiler.cmake	Wed Aug 30 11:52:45 2023 +0200
@@ -1,8 +1,8 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2022 Osimis S.A., Belgium
-# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 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
@@ -237,7 +237,8 @@
 
 
 if (DEFINED ENABLE_PROFILING AND ENABLE_PROFILING)
-  if (CMAKE_COMPILER_IS_GNUCXX)
+  if (CMAKE_COMPILER_IS_GNUCXX OR
+      CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
     set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg")
     set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
--- a/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake	Wed Aug 30 11:52:45 2023 +0200
@@ -1,8 +1,8 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2022 Osimis S.A., Belgium
-# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 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
@@ -144,6 +144,16 @@
         set(ORTHANC_FRAMEWORK_MD5 "c912bbb860d640d3ae3003b5c9698205")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.10.0")
         set(ORTHANC_FRAMEWORK_MD5 "8610c82d9153f22e929f2110f8f60279")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.10.1")
+        set(ORTHANC_FRAMEWORK_MD5 "caf667fc5ea452b3d0c2f70bfd02599c")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.0")
+        set(ORTHANC_FRAMEWORK_MD5 "962c4a4a706a2ef28b390d8515dd7091")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.1")
+        set(ORTHANC_FRAMEWORK_MD5 "a39661c406adf22cf574fde290cf4bbf")
+      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 "5c1b11009d782f248739919db6bf7f7a")
 
       # Below this point are development snapshots that were used to
       # release some plugin, before an official release of the Orthanc
--- a/Resources/Orthanc/CMake/DownloadPackage.cmake	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/CMake/DownloadPackage.cmake	Wed Aug 30 11:52:45 2023 +0200
@@ -1,8 +1,8 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2022 Osimis S.A., Belgium
-# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 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/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -2,8 +2,8 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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
@@ -250,26 +250,52 @@
     }
   }
 
+  // helper class to convert std::map of headers to the plugin SDK C structure
+  class PluginHttpHeaders
+  {
+    std::vector<const char*> headersKeys_;
+    std::vector<const char*> headersValues_;
+  public:
+
+    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++)
+      {
+        headersKeys_.push_back(it->first.c_str());
+        headersValues_.push_back(it->second.c_str());
+      }      
+    }
+
+    const char* const* GetKeys()
+    {
+      return (headersKeys_.empty() ? NULL : &headersKeys_[0]);
+    }
+
+    const char* const* GetValues()
+    {
+      return (headersValues_.empty() ? NULL : &headersValues_[0]);
+    }
+
+    uint32_t GetSize()
+    {
+      return static_cast<uint32_t>(headersKeys_.size());
+    }
+  };
+
   bool MemoryBuffer::RestApiGet(const std::string& uri,
                                 const std::map<std::string, std::string>& httpHeaders,
                                 bool applyPlugins)
   {
     Clear();
 
-    std::vector<const char*> headersKeys;
-    std::vector<const char*> headersValues;
-    
-    for (std::map<std::string, std::string>::const_iterator
-           it = httpHeaders.begin(); it != httpHeaders.end(); it++)
-    {
-      headersKeys.push_back(it->first.c_str());
-      headersValues.push_back(it->second.c_str());
-    }
+    PluginHttpHeaders headers(httpHeaders);
 
     return CheckHttp(OrthancPluginRestApiGet2(
-                       GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(),
-                       (headersKeys.empty() ? NULL : &headersKeys[0]),
-                       (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins));
+                       GetGlobalContext(), &buffer_, uri.c_str(), 
+                       headers.GetSize(),
+                       headers.GetKeys(),
+                       headers.GetValues(), applyPlugins));
   }
 
   bool MemoryBuffer::RestApiPost(const std::string& uri,
@@ -292,6 +318,41 @@
     }
   }
 
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const void* body,
+                                 size_t bodySize,
+                                 const std::map<std::string, std::string>& httpHeaders,
+                                 bool applyPlugins)
+  {
+    MemoryBuffer answerHeaders;
+    uint16_t httpStatus;
+
+    PluginHttpHeaders headers(httpHeaders);
+
+    return CheckHttp(OrthancPluginCallRestApi(GetGlobalContext(), 
+                                              &buffer_,
+                                              *answerHeaders,
+                                              &httpStatus,
+                                              OrthancPluginHttpMethod_Post,
+                                              uri.c_str(),
+                                              headers.GetSize(), headers.GetKeys(), headers.GetValues(),
+                                              body, bodySize,
+                                              applyPlugins));
+  }
+
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const Json::Value& body,
+                                 const std::map<std::string, std::string>& httpHeaders,
+                                 bool applyPlugins)
+  {
+    std::string s;
+    WriteFastJson(s, body);
+    return RestApiPost(uri, s.c_str(), s.size(), httpHeaders, applyPlugins);
+  }
+#endif
 
   bool MemoryBuffer::RestApiPut(const std::string& uri,
                                 const void* body,
@@ -511,6 +572,22 @@
   }
 
 
+  void OrthancString::ToJsonWithoutComments(Json::Value& target) const
+  {
+    if (str_ == NULL)
+    {
+      LogError("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_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
   void MemoryBuffer::DicomToJson(Json::Value& target,
                                  OrthancPluginDicomToJsonFormat format,
                                  OrthancPluginDicomToJsonFlags flags,
@@ -645,7 +722,7 @@
       ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
-    str.ToJson(configuration_);
+    str.ToJsonWithoutComments(configuration_);
 
     if (configuration_.type() != Json::objectValue)
     {
@@ -673,6 +750,12 @@
     }
   }
 
+  OrthancConfiguration::OrthancConfiguration(const Json::Value& configuration, const std::string& path) :
+    configuration_(configuration),
+    path_(path)
+  {
+  }
+
 
   std::string OrthancConfiguration::GetPath(const std::string& key) const
   {
@@ -1028,7 +1111,7 @@
     if (configuration_[key].type() != Json::objectValue)
     {
       LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a string as expected");
+               "\" is not an object as expected");
 
       ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
@@ -1374,6 +1457,27 @@
   }
 
 
+  bool RestApiGet(Json::Value& result,
+                  const std::string& uri,
+                  const std::map<std::string, std::string>& httpHeaders,
+                  bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+
 
   bool RestApiGet(Json::Value& result,
                   const std::string& uri,
@@ -1441,6 +1545,30 @@
     }
   }
 
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   const std::map<std::string, std::string>& httpHeaders,
+                   bool applyPlugins)
+  {
+    MemoryBuffer answer;
+
+    if (!answer.RestApiPost(uri, body, httpHeaders, applyPlugins))
+    {
+      return false;
+    }
+    else
+    {
+      if (!answer.IsEmpty())
+      {
+        answer.ToJson(result);
+      }
+      return true;
+    }
+  }
+#endif
+
 
   bool RestApiPost(Json::Value& result,
                    const std::string& uri,
@@ -1530,24 +1658,18 @@
              " is required)");
   }
 
-
-  bool CheckMinimalOrthancVersion(unsigned int major,
-                                  unsigned int minor,
-                                  unsigned int revision)
+  bool CheckMinimalVersion(const char* version,
+                           unsigned int major,
+                           unsigned int minor,
+                           unsigned int revision)
   {
-    if (!HasGlobalContext())
-    {
-      LogError("Bad Orthanc context in the plugin");
-      return false;
-    }
-
-    if (!strcmp(GetGlobalContext()->orthancVersion, "mainline"))
+    if (!strcmp(version, "mainline"))
     {
       // Assume compatibility with the mainline
       return true;
     }
 
-    // Parse the version of the Orthanc core
+    // Parse the version
     int aa, bb, cc;
     if (
 #ifdef _MSC_VER
@@ -1555,7 +1677,7 @@
 #else
       sscanf
 #endif
-      (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
+      (version, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
       aa < 0 ||
       bb < 0 ||
       cc < 0)
@@ -1579,7 +1701,6 @@
       return false;
     }
 
-
     // Check the minor version number
     assert(a == major);
 
@@ -1607,6 +1728,21 @@
   }
 
 
+  bool CheckMinimalOrthancVersion(unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision)
+  {
+    if (!HasGlobalContext())
+    {
+      LogError("Bad Orthanc context in the plugin");
+      return false;
+    }
+
+    return CheckMinimalVersion(GetGlobalContext()->orthancVersion,
+                               major, minor, revision);
+  }
+
+
 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
   const char* AutodetectMimeType(const std::string& path)
   {
@@ -1776,7 +1912,8 @@
 
   bool OrthancPeers::DoGet(MemoryBuffer& target,
                            size_t index,
-                           const std::string& uri) const
+                           const std::string& uri,
+                           const std::map<std::string, std::string>& headers) const
   {
     if (index >= index_.size())
     {
@@ -1785,10 +1922,12 @@
 
     OrthancPlugins::MemoryBuffer answer;
     uint16_t status;
+    PluginHttpHeaders pluginHeaders(headers);
+
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
       (GetGlobalContext(), *answer, NULL, &status, peers_,
        static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
-       0, NULL, NULL, NULL, 0, timeout_);
+       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_);
 
     if (code == OrthancPluginErrorCode_Success)
     {
@@ -1804,21 +1943,23 @@
 
   bool OrthancPeers::DoGet(MemoryBuffer& target,
                            const std::string& name,
-                           const std::string& uri) const
+                           const std::string& uri,
+                           const std::map<std::string, std::string>& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
-            DoGet(target, index, uri));
+            DoGet(target, index, uri, headers));
   }
 
 
   bool OrthancPeers::DoGet(Json::Value& target,
                            size_t index,
-                           const std::string& uri) const
+                           const std::string& uri,
+                           const std::map<std::string, std::string>& headers) const
   {
     MemoryBuffer buffer;
 
-    if (DoGet(buffer, index, uri))
+    if (DoGet(buffer, index, uri, headers))
     {
       buffer.ToJson(target);
       return true;
@@ -1832,11 +1973,12 @@
 
   bool OrthancPeers::DoGet(Json::Value& target,
                            const std::string& name,
-                           const std::string& uri) const
+                           const std::string& uri,
+                           const std::map<std::string, std::string>& headers) const
   {
     MemoryBuffer buffer;
 
-    if (DoGet(buffer, name, uri))
+    if (DoGet(buffer, name, uri, headers))
     {
       buffer.ToJson(target);
       return true;
@@ -1851,22 +1993,24 @@
   bool OrthancPeers::DoPost(MemoryBuffer& target,
                             const std::string& name,
                             const std::string& uri,
-                            const std::string& body) const
+                            const std::string& body,
+                            const std::map<std::string, std::string>& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
-            DoPost(target, index, uri, body));
+            DoPost(target, index, uri, body, headers));
   }
 
 
   bool OrthancPeers::DoPost(Json::Value& target,
                             size_t index,
                             const std::string& uri,
-                            const std::string& body) const
+                            const std::string& body,
+                            const std::map<std::string, std::string>& headers) const
   {
     MemoryBuffer buffer;
 
-    if (DoPost(buffer, index, uri, body))
+    if (DoPost(buffer, index, uri, body, headers))
     {
       buffer.ToJson(target);
       return true;
@@ -1881,11 +2025,12 @@
   bool OrthancPeers::DoPost(Json::Value& target,
                             const std::string& name,
                             const std::string& uri,
-                            const std::string& body) const
+                            const std::string& body,
+                            const std::map<std::string, std::string>& headers) const
   {
     MemoryBuffer buffer;
 
-    if (DoPost(buffer, name, uri, body))
+    if (DoPost(buffer, name, uri, body, headers))
     {
       buffer.ToJson(target);
       return true;
@@ -1900,7 +2045,8 @@
   bool OrthancPeers::DoPost(MemoryBuffer& target,
                             size_t index,
                             const std::string& uri,
-                            const std::string& body) const
+                            const std::string& body,
+                            const std::map<std::string, std::string>& headers) const
   {
     if (index >= index_.size())
     {
@@ -1915,10 +2061,12 @@
 
     OrthancPlugins::MemoryBuffer answer;
     uint16_t status;
+    PluginHttpHeaders pluginHeaders(headers);
+
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
       (GetGlobalContext(), *answer, NULL, &status, peers_,
        static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
-       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_);
 
     if (code == OrthancPluginErrorCode_Success)
     {
@@ -1934,7 +2082,8 @@
 
   bool OrthancPeers::DoPut(size_t index,
                            const std::string& uri,
-                           const std::string& body) const
+                           const std::string& body,
+                           const std::map<std::string, std::string>& headers) const
   {
     if (index >= index_.size())
     {
@@ -1949,10 +2098,12 @@
 
     OrthancPlugins::MemoryBuffer answer;
     uint16_t status;
+    PluginHttpHeaders pluginHeaders(headers);
+
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
       (GetGlobalContext(), *answer, NULL, &status, peers_,
        static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
-       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_);
 
     if (code == OrthancPluginErrorCode_Success)
     {
@@ -1967,16 +2118,18 @@
 
   bool OrthancPeers::DoPut(const std::string& name,
                            const std::string& uri,
-                           const std::string& body) const
+                           const std::string& body,
+                           const std::map<std::string, std::string>& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
-            DoPut(index, uri, body));
+            DoPut(index, uri, body, headers));
   }
 
 
   bool OrthancPeers::DoDelete(size_t index,
-                              const std::string& uri) const
+                              const std::string& uri,
+                              const std::map<std::string, std::string>& headers) const
   {
     if (index >= index_.size())
     {
@@ -1985,10 +2138,12 @@
 
     OrthancPlugins::MemoryBuffer answer;
     uint16_t status;
+    PluginHttpHeaders pluginHeaders(headers);
+
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
       (GetGlobalContext(), *answer, NULL, &status, peers_,
        static_cast<uint32_t>(index), OrthancPluginHttpMethod_Delete, uri.c_str(),
-       0, NULL, NULL, NULL, 0, timeout_);
+       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_);
 
     if (code == OrthancPluginErrorCode_Success)
     {
@@ -2002,11 +2157,12 @@
 
 
   bool OrthancPeers::DoDelete(const std::string& name,
-                              const std::string& uri) const
+                              const std::string& uri,
+                              const std::map<std::string, std::string>& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
-            DoDelete(index, uri));
+            DoDelete(index, uri, headers));
   }
 #endif
 
@@ -2043,6 +2199,36 @@
   }
 
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+  static OrthancPluginErrorCode CopyStringToMemoryBuffer(OrthancPluginMemoryBuffer* target,
+                                                         const std::string& source)
+  {
+    if (OrthancPluginCreateMemoryBuffer(globalContext_, target, source.size()) != OrthancPluginErrorCode_Success)
+    {
+      return OrthancPluginErrorCode_NotEnoughMemory;
+    }
+    else
+    {
+      if (!source.empty())
+      {
+        memcpy(target->data, source.c_str(), source.size());
+      }
+      
+      return OrthancPluginErrorCode_Success;
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+  OrthancPluginErrorCode OrthancJob::CallbackGetContent(OrthancPluginMemoryBuffer* target,
+                                                        void* job)
+  {
+    assert(job != NULL);
+    OrthancJob& that = *reinterpret_cast<OrthancJob*>(job);
+    return CopyStringToMemoryBuffer(target, that.content_);
+  }
+#else
   const char* OrthancJob::CallbackGetContent(void* job)
   {
     assert(job != NULL);
@@ -2056,8 +2242,33 @@
       return 0;
     }
   }
-
-
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+  int32_t OrthancJob::CallbackGetSerialized(OrthancPluginMemoryBuffer* target,
+                                            void* job)
+  {
+    assert(job != NULL);
+    OrthancJob& that = *reinterpret_cast<OrthancJob*>(job);
+    
+    if (that.hasSerialized_)
+    {
+      if (CopyStringToMemoryBuffer(target, that.serialized_) == OrthancPluginErrorCode_Success)
+      {
+        return 1;
+      }
+      else
+      {
+        return -1;
+      }
+    }
+    else
+    {
+      return 0;
+    }
+  }
+#else
   const char* OrthancJob::CallbackGetSerialized(void* job)
   {
     assert(job != NULL);
@@ -2080,6 +2291,7 @@
       return 0;
     }
   }
+#endif
 
 
   OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
@@ -2211,10 +2423,15 @@
       ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
     }
 
-    OrthancPluginJob* orthanc = OrthancPluginCreateJob(
-      GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
-      CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
-      CallbackStep, CallbackStop, CallbackReset);
+    OrthancPluginJob* orthanc =
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+      OrthancPluginCreateJob2
+#else
+      OrthancPluginCreateJob
+#endif
+      (GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
+       CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
+       CallbackStep, CallbackStop, CallbackReset);
 
     if (orthanc == NULL)
     {
@@ -2510,7 +2727,7 @@
         }
         catch (...)
         {
-          return OrthancPluginErrorCode_InternalError;
+          return OrthancPluginErrorCode_Plugin;
         }
       }
     }    
@@ -3532,4 +3749,254 @@
     }
   }
 #endif
+
+
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static std::vector<std::string> WebDavConvertPath(uint32_t pathSize,
+                                                    const char* const*  pathItems)
+  {
+    std::vector<std::string> result(pathSize);
+
+    for (uint32_t i = 0; i < pathSize; i++)
+    {
+      result[i] = pathItems[i];
+    }
+
+    return result;
+  }
+#endif
+  
+    
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavIsExistingFolder(uint8_t*            isExisting,
+                                                       uint32_t            pathSize,
+                                                       const char* const*  pathItems,
+                                                       void*               payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+
+    try
+    {
+      *isExisting = (that.IsExistingFolder(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+  
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavListFolder(uint8_t*                        isExisting,
+                                                 OrthancPluginWebDavCollection*  collection,
+                                                 OrthancPluginWebDavAddFile      addFile,
+                                                 OrthancPluginWebDavAddFolder    addFolder,
+                                                 uint32_t                        pathSize,
+                                                 const char* const*              pathItems,
+                                                 void*                           payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+      
+    try
+    {
+      std::list<IWebDavCollection::FileInfo> files;
+      std::list<IWebDavCollection::FolderInfo> subfolders;
+      
+      if (!that.ListFolder(files, subfolders, WebDavConvertPath(pathSize, pathItems)))
+      {
+        *isExisting = 0;
+      }
+      else
+      {
+        *isExisting = 1;
+      
+        for (std::list<IWebDavCollection::FileInfo>::const_iterator
+               it = files.begin(); it != files.end(); ++it)
+        {
+          OrthancPluginErrorCode code = addFile(
+            collection, it->GetName().c_str(), it->GetContentSize(),
+            it->GetMimeType().c_str(), it->GetDateTime().c_str());
+        
+          if (code != OrthancPluginErrorCode_Success)
+          {
+            return code;
+          }
+        }
+      
+        for (std::list<IWebDavCollection::FolderInfo>::const_iterator it =
+               subfolders.begin(); it != subfolders.end(); ++it)
+        {
+          OrthancPluginErrorCode code = addFolder(
+            collection, it->GetName().c_str(), it->GetDateTime().c_str());
+        
+          if (code != OrthancPluginErrorCode_Success)
+          {
+            return code;
+          }
+        }
+      }
+      
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif    
+
+
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavRetrieveFile(OrthancPluginWebDavCollection*   collection,
+                                                   OrthancPluginWebDavRetrieveFile  retrieveFile,
+                                                   uint32_t                         pathSize,
+                                                   const char* const*               pathItems,
+                                                   void*                            payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+
+    try
+    {
+      std::string content, mime, dateTime;
+        
+      if (that.GetFile(content, mime, dateTime, WebDavConvertPath(pathSize, pathItems)))
+      {
+        return retrieveFile(collection, content.empty() ? NULL : content.c_str(),
+                            content.size(), mime.c_str(), dateTime.c_str());
+      }
+      else
+      {
+        // Inexisting file
+        return OrthancPluginErrorCode_Success;
+      }
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_InternalError;
+    }
+  }  
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavStoreFileCallback(uint8_t*            isReadOnly, /* out */
+                                                        uint32_t            pathSize,
+                                                        const char* const*  pathItems,
+                                                        const void*         data,
+                                                        uint64_t            size,
+                                                        void*               payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+
+    try
+    {
+      if (static_cast<uint64_t>(static_cast<size_t>(size)) != size)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+      }
+      
+      *isReadOnly = (that.StoreFile(WebDavConvertPath(pathSize, pathItems), data,
+                                    static_cast<size_t>(size)) ? 1 : 0);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_InternalError;
+    }
+  }
+#endif
+
+  
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavCreateFolderCallback(uint8_t*            isReadOnly, /* out */
+                                                           uint32_t            pathSize,
+                                                           const char* const*  pathItems,
+                                                           void*               payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+
+    try
+    {
+      *isReadOnly = (that.CreateFolder(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_InternalError;
+    }
+  }
+#endif
+  
+  
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  static OrthancPluginErrorCode WebDavDeleteItemCallback(uint8_t*            isReadOnly, /* out */
+                                                         uint32_t            pathSize,
+                                                         const char* const*  pathItems,
+                                                         void*               payload)
+  {
+    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
+
+    try
+    {
+      *isReadOnly = (that.DeleteItem(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_InternalError;
+    }
+  }
+#endif
+
+  
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  void IWebDavCollection::Register(const std::string& uri,
+                                   IWebDavCollection& collection)
+  {
+    OrthancPluginErrorCode code = OrthancPluginRegisterWebDavCollection(
+      GetGlobalContext(), uri.c_str(), WebDavIsExistingFolder, WebDavListFolder, WebDavRetrieveFile,
+      WebDavStoreFileCallback, WebDavCreateFolderCallback, WebDavDeleteItemCallback, &collection);
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+
+  void GetHttpHeaders(std::map<std::string, std::string>& result, const OrthancPluginHttpRequest* request)
+  {
+    result.clear();
+
+    for (uint32_t i = 0; i < request->headersCount; ++i)
+    {
+      result[request->headersKeys[i]] = request->headersValues[i];
+    }    
+  }
 }
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Wed Aug 30 11:52:45 2023 +0200
@@ -2,8 +2,8 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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
@@ -115,6 +115,18 @@
 #  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  0
 #endif
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2)
+#  define HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API  1
+#else
+#  define HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API  0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 10, 1)
+#  define HAS_ORTHANC_PLUGIN_WEBDAV  1
+#else
+#  define HAS_ORTHANC_PLUGIN_WEBDAV  0
+#endif
+
 
 
 namespace OrthancPlugins
@@ -218,6 +230,19 @@
                      const Json::Value& body,
                      bool applyPlugins);
 
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+    bool RestApiPost(const std::string& uri,
+                     const Json::Value& body,
+                     const std::map<std::string, std::string>& httpHeaders,
+                     bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const void* body,
+                     size_t bodySize,
+                     const std::map<std::string, std::string>& httpHeaders,
+                     bool applyPlugins);
+#endif
+
     bool RestApiPut(const std::string& uri,
                     const Json::Value& body,
                     bool applyPlugins);
@@ -297,10 +322,17 @@
       return str_;
     }
 
+    bool IsNullOrEmpty() const
+    {
+      return str_ == NULL || str_[0] == 0;
+    }
+
     void ToString(std::string& target) const;
 
     void ToJson(Json::Value& target) const;
-  };
+  
+    void ToJsonWithoutComments(Json::Value& target) const;
+};
 
 
   class OrthancConfiguration : public boost::noncopyable
@@ -314,10 +346,12 @@
     void LoadConfiguration();
     
   public:
-    OrthancConfiguration();
+    OrthancConfiguration(); // loads the full Orthanc configuration
 
     explicit OrthancConfiguration(bool load);
 
+    explicit OrthancConfiguration(const Json::Value& configuration, const std::string& path);  // e.g. to load a section from a default json content
+
     const Json::Value& GetJson() const
     {
       return configuration_;
@@ -501,6 +535,11 @@
                   const std::string& uri,
                   bool applyPlugins);
 
+  bool RestApiGet(Json::Value& result,
+                  const std::string& uri,
+                  const std::map<std::string, std::string>& httpHeaders,
+                  bool applyPlugins);
+
   bool RestApiGetString(std::string& result,
                         const std::string& uri,
                         bool applyPlugins);
@@ -522,6 +561,14 @@
                    size_t bodySize,
                    bool applyPlugins);
 
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+  bool RestApiPost(Json::Value& result,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   const std::map<std::string, std::string>& httpHeaders,
+                   bool applyPlugins);
+#endif
+
   bool RestApiPost(Json::Value& result,
                    const std::string& uri,
                    const Json::Value& body,
@@ -602,6 +649,10 @@
                                   unsigned int minor,
                                   unsigned int revision);
 
+  bool CheckMinimalVersion(const char* version,
+                           unsigned int major,
+                           unsigned int minor,
+                           unsigned int revision);
 
   namespace Internals
   {
@@ -711,53 +762,65 @@
 
     bool DoGet(MemoryBuffer& target,
                size_t index,
-               const std::string& uri) const;
+               const std::string& uri,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoGet(MemoryBuffer& target,
                const std::string& name,
-               const std::string& uri) const;
+               const std::string& uri,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoGet(Json::Value& target,
                size_t index,
-               const std::string& uri) const;
+               const std::string& uri,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoGet(Json::Value& target,
                const std::string& name,
-               const std::string& uri) const;
+               const std::string& uri,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoPost(MemoryBuffer& target,
                 size_t index,
                 const std::string& uri,
-                const std::string& body) const;
+                const std::string& body,
+                const std::map<std::string, std::string>& headers) const;
 
     bool DoPost(MemoryBuffer& target,
                 const std::string& name,
                 const std::string& uri,
-                const std::string& body) const;
+                const std::string& body,
+                const std::map<std::string, std::string>& headers) const;
 
     bool DoPost(Json::Value& target,
                 size_t index,
                 const std::string& uri,
-                const std::string& body) const;
+                const std::string& body,
+                const std::map<std::string, std::string>& headers) const;
 
     bool DoPost(Json::Value& target,
                 const std::string& name,
                 const std::string& uri,
-                const std::string& body) const;
+                const std::string& body,
+                const std::map<std::string, std::string>& headers) const;
 
     bool DoPut(size_t index,
                const std::string& uri,
-               const std::string& body) const;
+               const std::string& body,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoPut(const std::string& name,
                const std::string& uri,
-               const std::string& body) const;
+               const std::string& body,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoDelete(size_t index,
-                  const std::string& uri) const;
+                  const std::string& uri,
+                  const std::map<std::string, std::string>& headers) const;
 
     bool DoDelete(const std::string& name,
-                  const std::string& uri) const;
+                  const std::string& uri,
+                  const std::map<std::string, std::string>& headers) const;
   };
 #endif
 
@@ -777,9 +840,19 @@
 
     static float CallbackGetProgress(void* job);
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+    static OrthancPluginErrorCode CallbackGetContent(OrthancPluginMemoryBuffer* target,
+                                                     void* job);
+#else
     static const char* CallbackGetContent(void* job);
+#endif
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+    static int32_t CallbackGetSerialized(OrthancPluginMemoryBuffer* target,
+                                         void* job);
+#else
     static const char* CallbackGetSerialized(void* job);
+#endif
 
     static OrthancPluginJobStepStatus CallbackStep(void* job);
 
@@ -1246,4 +1319,108 @@
                                     const std::string& transferSyntax);
 #endif
   };
+
+// helper method to convert Http headers from the plugin SDK to a std::map
+void GetHttpHeaders(std::map<std::string, std::string>& result, const OrthancPluginHttpRequest* request);
+
+#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
+  class IWebDavCollection : public boost::noncopyable
+  {
+  public:
+    class FileInfo
+    {
+    private:
+      std::string  name_;
+      uint64_t     contentSize_;
+      std::string  mime_;
+      std::string  dateTime_;
+
+    public:
+      FileInfo(const std::string& name,
+               uint64_t contentSize,
+               const std::string& dateTime) :
+        name_(name),
+        contentSize_(contentSize),
+        dateTime_(dateTime)
+      {
+      }
+
+      const std::string& GetName() const
+      {
+        return name_;
+      }
+
+      uint64_t GetContentSize() const
+      {
+        return contentSize_;
+      }
+
+      void SetMimeType(const std::string& mime)
+      {
+        mime_ = mime;
+      }
+
+      const std::string& GetMimeType() const
+      {
+        return mime_;
+      }
+
+      const std::string& GetDateTime() const
+      {
+        return dateTime_;
+      }
+    };
+  
+    class FolderInfo
+    {
+    private:
+      std::string  name_;
+      std::string  dateTime_;
+
+    public:
+      FolderInfo(const std::string& name,
+                 const std::string& dateTime) :
+        name_(name),
+        dateTime_(dateTime)
+      {
+      }
+
+      const std::string& GetName() const
+      {
+        return name_;
+      }
+
+      const std::string& GetDateTime() const
+      {
+        return dateTime_;
+      }
+    };
+  
+    virtual ~IWebDavCollection()
+    {
+    }
+
+    virtual bool IsExistingFolder(const std::vector<std::string>& path) = 0;
+
+    virtual bool ListFolder(std::list<FileInfo>& files,
+                            std::list<FolderInfo>& subfolders,
+                            const std::vector<std::string>& path) = 0;
+  
+    virtual bool GetFile(std::string& content /* out */,
+                         std::string& mime /* out */,
+                         std::string& dateTime /* out */,
+                         const std::vector<std::string>& path) = 0;
+
+    virtual bool StoreFile(const std::vector<std::string>& path,
+                           const void* data,
+                           size_t size) = 0;
+
+    virtual bool CreateFolder(const std::vector<std::string>& path) = 0;
+
+    virtual bool DeleteItem(const std::vector<std::string>& path) = 0;
+
+    static void Register(const std::string& uri,
+                         IWebDavCollection& collection);
+  };
+#endif
 }
--- a/Resources/Orthanc/Plugins/OrthancPluginException.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/Plugins/OrthancPluginException.h	Wed Aug 30 11:52:45 2023 +0200
@@ -2,8 +2,8 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Wed Aug 30 11:52:45 2023 +0200
@@ -1,8 +1,8 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2022 Osimis S.A., Belgium
-# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 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/Resources/Orthanc/Toolchains/LinuxStandardBaseToolchain.cmake	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/Toolchains/LinuxStandardBaseToolchain.cmake	Wed Aug 30 11:52:45 2023 +0200
@@ -1,8 +1,8 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2022 Osimis S.A., Belgium
-# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 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
@@ -22,11 +22,11 @@
 #
 # Full build, as used on the BuildBot CIS:
 #
-#   $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DBOOST_LOCALE_BACKEND=icu -DENABLE_PKCS11=ON -G Ninja
+#   $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DUSE_LEGACY_BOOST=ON -DBOOST_LOCALE_BACKEND=icu -DENABLE_PKCS11=ON -G Ninja
 #
 # Or, more lightweight version (without libp11 and ICU):
 #
-#   $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -G Ninja
+#   $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_BOOST=ON -G Ninja
 #
 
 INCLUDE(CMakeForceCompiler)
--- a/Resources/Orthanc/Toolchains/MinGW-W64-Toolchain32.cmake	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/Toolchains/MinGW-W64-Toolchain32.cmake	Wed Aug 30 11:52:45 2023 +0200
@@ -1,8 +1,8 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2022 Osimis S.A., Belgium
-# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 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/Resources/Orthanc/Toolchains/MinGW-W64-Toolchain64.cmake	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/Toolchains/MinGW-W64-Toolchain64.cmake	Wed Aug 30 11:52:45 2023 +0200
@@ -1,8 +1,8 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2022 Osimis S.A., Belgium
-# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 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/Resources/Orthanc/Toolchains/MinGWToolchain.cmake	Wed Aug 30 11:40:22 2023 +0200
+++ b/Resources/Orthanc/Toolchains/MinGWToolchain.cmake	Wed Aug 30 11:52:45 2023 +0200
@@ -1,8 +1,8 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2022 Osimis S.A., Belgium
-# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_GlobalFunctions.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_GlobalFunctions.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginChangeType.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginChangeType.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginCompressionType.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginCompressionType.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginConstraintType.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginConstraintType.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginContentType.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginContentType.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginCreateDicomFlags.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginCreateDicomFlags.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginDicomInstance.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginDicomInstance.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginDicomToJsonFlags.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginDicomToJsonFlags.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginDicomToJsonFormat.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginDicomToJsonFormat.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginDicomWebBinaryMode.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginDicomWebBinaryMode.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginDicomWebNode.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginDicomWebNode.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginErrorCode.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginErrorCode.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginFindAnswers.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginFindAnswers.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginFindMatcher.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginFindMatcher.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginFindQuery.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginFindQuery.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginHttpMethod.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginHttpMethod.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginIdentifierConstraint.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginIdentifierConstraint.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginImage.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginImage.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginImageFormat.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginImageFormat.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginInstanceOrigin.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginInstanceOrigin.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginJob.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginJob.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginJobStepStatus.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginJobStepStatus.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginJobStopReason.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginJobStopReason.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginMetricsType.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginMetricsType.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginPeers.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginPeers.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginPixelFormat.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginPixelFormat.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginReceivedInstanceAction.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginReceivedInstanceAction.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginResourceType.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginResourceType.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginRestOutput.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginRestOutput.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginServerChunkedRequestReader.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginServerChunkedRequestReader.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginStorageArea.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginStorageArea.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginStorageCommitmentFailureReason.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginStorageCommitmentFailureReason.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginValueRepresentation.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginValueRepresentation.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginWorklistAnswers.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginWorklistAnswers.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Autogenerated/sdk_OrthancPluginWorklistQuery.impl.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginWorklistQuery.impl.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/DicomScpCallbacks.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/DicomScpCallbacks.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/DicomScpCallbacks.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/DicomScpCallbacks.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/ICallbackRegistration.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/ICallbackRegistration.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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
@@ -58,6 +58,46 @@
 }
 
 
+PyObject *ICallbackRegistration::Apply2(ICallbackRegistration& registration,
+                                        PyObject* args,
+                                        PyObject*& singletonCallback1,
+                                        PyObject*& singletonCallback2,
+                                        const std::string& details)
+{
+  // https://docs.python.org/3/extending/extending.html#calling-python-functions-from-c
+  PyObject* callback1 = NULL;
+  PyObject* callback2 = NULL;
+
+  if (!PyArg_ParseTuple(args, "OO", &callback1, &callback2) ||
+      callback1 == NULL || callback2 == NULL)
+  {
+    const std::string message = "Expected two callback functions to register " + details;
+    PyErr_SetString(PyExc_ValueError, message.c_str());
+    return NULL;
+  }
+  else if (singletonCallback1 != NULL || singletonCallback2 != NULL)
+  {
+    const std::string message = "Can only register once for " + details;
+    PyErr_SetString(PyExc_RuntimeError, message.c_str());
+    return NULL;
+  }
+  else
+  {
+    OrthancPlugins::LogInfo("Registering callbacks " + details);
+    registration.Register();
+
+    singletonCallback1 = callback1;
+    Py_XINCREF(singletonCallback1);
+
+    singletonCallback2 = callback2;
+    Py_XINCREF(singletonCallback2);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+}
+
+
 void ICallbackRegistration::Unregister(PyObject*& singletonCallback)
 {
   PythonLock lock;
--- a/Sources/ICallbackRegistration.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/ICallbackRegistration.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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
@@ -40,5 +40,12 @@
                          PyObject*& singletonCallback,
                          const std::string& details);
 
+  // The GIL must be locked
+  static PyObject *Apply2(ICallbackRegistration& registration,
+                          PyObject* args,
+                          PyObject*& singletonCallback1,
+                          PyObject*& singletonCallback2,
+                          const std::string& details);
+
   static void Unregister(PyObject*& singletonCallback);
 };
--- a/Sources/IncomingHttpRequestFilter.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/IncomingHttpRequestFilter.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/IncomingHttpRequestFilter.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/IncomingHttpRequestFilter.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/IncomingInstanceFilter.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/IncomingInstanceFilter.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/IncomingInstanceFilter.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/IncomingInstanceFilter.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/OnChangeCallback.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/OnChangeCallback.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/OnChangeCallback.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/OnChangeCallback.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/OnStoredInstanceCallback.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/OnStoredInstanceCallback.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/OnStoredInstanceCallback.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/OnStoredInstanceCallback.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/Plugin.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/Plugin.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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
@@ -32,6 +32,7 @@
 #include "IncomingInstanceFilter.h"
 #include "ReceivedInstanceCallback.h"
 #include "StorageArea.h"
+#include "StorageCommitmentScpCallback.h"
 
 #include "RestCallbacks.h"
 #include "PythonModule.h"
@@ -414,6 +415,15 @@
   }
 
   /**
+   * New in release 4.1
+   **/
+
+  {
+    PyMethodDef f = { "RegisterStorageCommitmentScpCallback", RegisterStorageCommitmentScpCallback, METH_VARARGS, "" };
+    functions.push_back(f);
+  }
+
+  /**
    * Append all the global functions that were automatically generated
    **/
   
--- a/Sources/PythonFunction.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/PythonFunction.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/PythonFunction.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/PythonFunction.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/PythonHeaderWrapper.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/PythonHeaderWrapper.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/PythonLock.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/PythonLock.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/PythonLock.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/PythonLock.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/PythonModule.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/PythonModule.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/PythonModule.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/PythonModule.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
 2 * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/PythonObject.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/PythonObject.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/PythonObject.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/PythonObject.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/PythonString.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/PythonString.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/PythonString.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/PythonString.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/ReceivedInstanceCallback.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/ReceivedInstanceCallback.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/ReceivedInstanceCallback.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/ReceivedInstanceCallback.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/RestCallbacks.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/RestCallbacks.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/RestCallbacks.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/RestCallbacks.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/StorageArea.cpp	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/StorageArea.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/StorageArea.h	Wed Aug 30 11:40:22 2023 +0200
+++ b/Sources/StorageArea.h	Wed Aug 30 11:52:45 2023 +0200
@@ -1,7 +1,7 @@
 /**
  * Python plugin for Orthanc
- * Copyright (C) 2020-2022 Osimis S.A., Belgium
- * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/Sources/StorageCommitmentScpCallback.cpp	Wed Aug 30 11:52:45 2023 +0200
@@ -0,0 +1,186 @@
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/>.
+ **/
+
+
+#include "StorageCommitmentScpCallback.h"
+
+#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
+#include "ICallbackRegistration.h"
+#include "PythonString.h"
+
+
+static PyObject*   storageCommitmentScpCallback_ = NULL;
+static PyObject*   storageCommitmentLookupCallback_ = NULL;
+
+
+static OrthancPluginErrorCode StorageCommitmentSCPCallback(
+  void**              handler /* out */,
+  const char*         jobId,
+  const char*         transactionUid,
+  const char* const*  sopClassUids,
+  const char* const*  sopInstanceUids,
+  uint32_t            countInstances,
+  const char*         remoteAet,
+  const char*         calledAet)
+{
+  try
+  {
+    PythonLock lock;
+
+    PythonObject args(lock, PyTuple_New(6));
+    {
+      PythonString str(lock, jobId);
+      PyTuple_SetItem(args.GetPyObject(), 0, str.Release());
+    }
+    {
+      PythonString str(lock, transactionUid);
+      PyTuple_SetItem(args.GetPyObject(), 1, str.Release());
+    }
+    {
+      PythonObject sopClassUidList(lock, PyList_New(countInstances));
+      for (uint32_t i = 0; i < countInstances; i++)
+      {
+        PythonString str(lock, sopClassUids[i]);
+        PyList_SetItem(sopClassUidList.GetPyObject(), i, str.Release());
+      }
+      PyTuple_SetItem(args.GetPyObject(), 2, sopClassUidList.Release());
+      PythonObject sopInstanceUidList(lock, PyList_New(countInstances));
+      for (uint32_t i = 0; i < countInstances; i++)
+      {
+        PythonString str(lock, sopInstanceUids[i]);
+        PyList_SetItem(sopInstanceUidList.GetPyObject(), i, str.Release());
+      }
+      PyTuple_SetItem(args.GetPyObject(), 3, sopInstanceUidList.Release());
+    }
+    {
+      PythonString str(lock, remoteAet);
+      PyTuple_SetItem(args.GetPyObject(), 4, str.Release());
+    }
+    {
+      PythonString str(lock, calledAet);
+      PyTuple_SetItem(args.GetPyObject(), 5, str.Release());
+    }
+
+    PythonObject result(lock, PyObject_CallObject(storageCommitmentScpCallback_, args.GetPyObject()));
+    *handler = result.Release();
+
+    std::string traceback;
+    if (lock.HasErrorOccurred(traceback))
+    {
+      OrthancPlugins::LogError("Error in the Python storage commitment SCP callback, "
+                               "traceback:\n" + traceback);
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    OrthancPlugins::LogError("Error in the Python storage commitment SCP callback: " +
+                             std::string(e.What(OrthancPlugins::GetGlobalContext())));
+  }
+  return OrthancPluginErrorCode_Success;
+}
+
+static OrthancPluginErrorCode StorageCommitmentLookupCallback(
+  OrthancPluginStorageCommitmentFailureReason* target /* out */,
+  void*                                        handler,
+  const char*                                  sopClassUid,
+  const char*                                  sopInstanceUid)
+{
+  try
+  {
+    PythonLock lock;
+
+    PythonObject args(lock, PyTuple_New(3));
+    {
+      PythonString str(lock, sopClassUid);
+      PyTuple_SetItem(args.GetPyObject(), 0, str.Release());
+    }
+    {
+      PythonString str(lock, sopInstanceUid);
+      PyTuple_SetItem(args.GetPyObject(), 1, str.Release());
+    }
+    {
+      PyObject* data = (PyObject*) handler;
+      Py_INCREF(data);  // Keep a reference before it was stolen by PyTuple_SetItem.
+      PyTuple_SetItem(args.GetPyObject(), 2, data);
+    }
+
+    PythonObject result(lock, PyObject_CallObject(storageCommitmentLookupCallback_, args.GetPyObject()));
+
+    if (!PyLong_Check(result.GetPyObject()))
+    {
+      OrthancPlugins::LogError("The Python storage commitment Lookup callback has not returned an int as the return value");
+      return OrthancPluginErrorCode_Plugin;
+    }
+
+    *target = static_cast<OrthancPluginStorageCommitmentFailureReason>(PyLong_AsLong(result.GetPyObject()));
+
+    std::string traceback;
+    if (lock.HasErrorOccurred(traceback))
+    {
+      OrthancPlugins::LogError("Error in the Python storage commitment Lookup callback, "
+                               "traceback:\n" + traceback);
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    OrthancPlugins::LogError("Error in the Python storage commitment Lookup callback: " +
+                             std::string(e.What(OrthancPlugins::GetGlobalContext())));
+  }
+  return OrthancPluginErrorCode_Success;
+}
+
+static void StorageCommitmentDestructor(void *handler)
+{
+  PythonLock lock;
+  Py_DECREF((PyObject*)handler);  // Release the reference
+}
+
+PyObject* RegisterStorageCommitmentScpCallback(PyObject* module, PyObject* args)
+{
+  // The GIL is locked at this point (no need to create "PythonLock")
+
+  class Registration : public ICallbackRegistration
+  {
+  public:
+    virtual void Register() ORTHANC_OVERRIDE
+    {
+      OrthancPluginRegisterStorageCommitmentScpCallback(
+        OrthancPlugins::GetGlobalContext(),
+        StorageCommitmentSCPCallback,
+        StorageCommitmentDestructor,
+        StorageCommitmentLookupCallback);
+    }
+  };
+
+  {
+    Registration registration;
+    return ICallbackRegistration::Apply2(registration, args,
+      storageCommitmentScpCallback_,
+      storageCommitmentLookupCallback_,
+      "Python storage commitment SCP & Lookup callback");
+  }
+}
+
+void FinalizeStorageCommitmentScpCallback()
+{
+  ICallbackRegistration::Unregister(storageCommitmentScpCallback_);
+  ICallbackRegistration::Unregister(storageCommitmentLookupCallback_);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/StorageCommitmentScpCallback.h	Wed Aug 30 11:52:45 2023 +0200
@@ -0,0 +1,27 @@
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 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/>.
+ **/
+
+
+#pragma once
+
+#include "PythonHeaderWrapper.h"
+
+PyObject* RegisterStorageCommitmentScpCallback(PyObject* module, PyObject* args);
+
+void FinalizeStorageCommitmentScpCallback();
\ No newline at end of file