changeset 6473:4d8248653a14 pixel-anon

integration mainline->pixel-anon
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 22 Nov 2025 10:18:45 +0100
parents 2dce738c496f (current diff) f5cbafc741dd (diff)
children 95652230a6b7
files OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp OrthancFramework/Sources/Enumerations.h OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h TODO
diffstat 70 files changed, 1669 insertions(+), 1336 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Nov 20 17:49:15 2025 +0100
+++ b/NEWS	Sat Nov 22 10:18:45 2025 +0100
@@ -19,6 +19,15 @@
 * C-Move and C-Get jobs and HTTP responses now include a new "Details" field with the "DimseErrorStatus"
   of each operation and the list of "RetrievedInstancesIds".
 
+Plugin SDK
+----------
+
+* New primitives for handling queues to ensure that no messages are lost if
+  Orthanc is stopped while a plugin is processing a message:
+  - OrthancPluginReserveQueueValue() is a replacement for OrthancPluginDequeueValue(). This flavour
+    does not directly remove the value from the queue but reserves it for a certain duration.
+  - OrthancPluginAcknowledgeQueueValue() removes a reserved value from a queue.
+
 Maintenance
 -----------
 
@@ -34,6 +43,7 @@
   - Reading a DCMTK dictionary ("ExternalDictionaries" configuration)
   - Using a "TemporaryDirectory" to save zip file or to export DICOMDIR
   - The "SslCertificate" and other related configurations
+* Optimized indexes for /tools/lookup in the built-in SQLite database
 * Fix: DicomGetScu jobs are now saved in DB.
 * Fix: When the configuration option "MaximumStorageCacheSize" was set to 0, the default value (128)
        was actually applied. From now on, a value of 0 really means that the storage cache is disabled.
@@ -50,29 +60,8 @@
 * Upgraded dependencies for static builds:
   - civetweb 1.16, including patch for CVE-2025-55763
   - SQLite 3.50.4
-
-Plugins
--------
-
-* Worklists plugin:
-  - The Worklists plugin now provides a REST API to:
-    - create worklists through POST at /worklists/create
-    - list the worklists through GET at /worklists
-    - view a single worklist through GET at /worklists/{uuid}
-    - modify a worklist through PUT at /worklists/{uuid}
-    - delete a worklist through DELETE at /worklists/{uuid}
-    All details are available in the Orthanc book:
-    https://orthanc.uclouvain.be/book/plugins/worklists-plugin.html
-  - New configuration options:
-    - "SaveInOrthancDatabase" to store the worklists in the Orthanc DB (provided that you are using SQLite or PostgreSQL).
-    - "DeleteWorklistsOnStableStudy" to delete the worklist once its related study has been received and is stable.
-    - "SetStudyInstanceUidIfMissing" to add a StudyInstanceUID if the REST API request does not include one.
-    - "DeleteWorklistsDelay" to delete a worklist N hours after it has been created
-      (only available if using the "SaveInOrthancDatabase" mode).
-    - "HousekeepingInterval" to define the delay between 2 executions of the Worklist Housekeeping thread
-      that deletes the worklists when required.
-  - Note: the previous "Database" configuration has now been renamed in "Directory" to better differentiate
-    the "File" or "DB modes.
+  - libpng 1.6.50
+  - curl 8.17.0
 
 
 Version 1.12.9 (2025-08-11)
@@ -366,7 +355,7 @@
   an outgoing SCU connection if "DicomTlsRemoteCertificateRequired" is set to "false"
 * Fix C-Find queries not returning computed tags such as ModalitiesInStudy,
   NumberOfStudyRelatedSeries,... in very specific use cases
-* Fix C-Find queries not returning private tags in the modality worklist plugin
+* Fix C-Find queries not returning private tags in the sample modality worklist plugin
 * Fix an extremely rare error when 2 threads are trying to create the same folder
   in the File Storage at the same time
 * Fix crashes if handling very large images
@@ -625,7 +614,7 @@
 * Fix decoding of YBR_FULL RLE images for which the "Planar Configuration" 
   tag (0028,0006) equals 1
 * Made Orthanc more resilient to common spelling errors in SpecificCharacterSet
-* Modality worklists plugin: Allow searching on private tags (exact match only)
+* Sample Modality worklists plugin: Allow searching on private tags (exact match only)
 * Fix orphan files remaining in storage when working with MaximumStorageSize
   (https://discourse.orthanc-server.org/t/issue-with-deleting-incoming-dicoms-when-maximumstoragesize-is-reached/3510)
 * When deleting a resource, the "LastUpdate" metadata of its parents are now updated
--- a/OrthancFramework/Resources/CMake/BoostConfiguration.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/BoostConfiguration.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -282,14 +282,14 @@
       -DBOOST_HAS_FILESYSTEM_V3=1
       )
     list(APPEND BOOST_SOURCES
-      ${BOOST_NAME}/libs/filesystem/src/codecvt_error_category.cpp
-      ${BOOST_NAME}/libs/filesystem/src/directory.cpp
-      ${BOOST_NAME}/libs/filesystem/src/exception.cpp
-      ${BOOST_NAME}/libs/filesystem/src/operations.cpp
-      ${BOOST_NAME}/libs/filesystem/src/path.cpp
-      ${BOOST_NAME}/libs/filesystem/src/path_traits.cpp
-      ${BOOST_NAME}/libs/filesystem/src/portability.cpp
-      ${BOOST_NAME}/libs/filesystem/src/unique_path.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/codecvt_error_category.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/directory.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/exception.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/operations.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/path.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/path_traits.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/portability.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/unique_path.cpp
       )
 
     if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR
@@ -301,13 +301,13 @@
 
     elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
       list(APPEND BOOST_SOURCES
-        ${BOOST_NAME}/libs/filesystem/src/windows_file_codecvt.cpp
+        ${BOOST_SOURCES_DIR}/libs/filesystem/src/windows_file_codecvt.cpp
         )
     endif()
   endif()
 
   list(APPEND BOOST_SOURCES
-    ${BOOST_NAME}/libs/iostreams/src/file_descriptor.cpp
+    ${BOOST_SOURCES_DIR}/libs/iostreams/src/file_descriptor.cpp
     )
   
 
--- a/OrthancFramework/Resources/CMake/BoostConfigurationStatic-1.69.0.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/BoostConfigurationStatic-1.69.0.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -45,15 +45,17 @@
   ## Patching boost
   ## 
 
-  execute_process(
-    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-    ${CMAKE_CURRENT_LIST_DIR}/../Patches/boost-${BOOST_VERSION}-linux-standard-base.patch
-    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-    RESULT_VARIABLE Failure
-    )
+  if (FirstRun)
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+      ${CMAKE_CURRENT_LIST_DIR}/../Patches/boost-${BOOST_VERSION}-linux-standard-base.patch
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
 
-  if (FirstRun AND Failure)
-    message(FATAL_ERROR "Error while patching a file")
+    if (Failure)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
   endif()
 
 
@@ -199,10 +201,12 @@
       -DBOOST_HAS_FILESYSTEM_V3=1
       )
     list(APPEND BOOST_SOURCES
-      ${BOOST_NAME}/libs/filesystem/src/codecvt_error_category.cpp
-      ${BOOST_NAME}/libs/filesystem/src/operations.cpp
-      ${BOOST_NAME}/libs/filesystem/src/path.cpp
-      ${BOOST_NAME}/libs/filesystem/src/path_traits.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/codecvt_error_category.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/operations.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/path.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/path_traits.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/portability.cpp
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/unique_path.cpp
       )
 
     if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR
@@ -214,13 +218,13 @@
 
     elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
       list(APPEND BOOST_SOURCES
-        ${BOOST_NAME}/libs/filesystem/src/windows_file_codecvt.cpp
+        ${BOOST_SOURCES_DIR}/libs/filesystem/src/windows_file_codecvt.cpp
         )
     endif()
   endif()
 
   list(APPEND BOOST_SOURCES
-    ${BOOST_NAME}/libs/iostreams/src/file_descriptor.cpp
+    ${BOOST_SOURCES_DIR}/libs/iostreams/src/file_descriptor.cpp
     )
   
 
--- a/OrthancFramework/Resources/CMake/CivetwebConfiguration.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/CivetwebConfiguration.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -46,15 +46,17 @@
 
   DownloadPackage(${CIVETWEB_MD5} ${CIVETWEB_URL} "${CIVETWEB_SOURCES_DIR}")
 
-  execute_process(
-    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-    ${CMAKE_CURRENT_LIST_DIR}/../Patches/civetweb-1.16.patch
-    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-    RESULT_VARIABLE Failure
-    )
+  if (FirstRun)
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+      ${CMAKE_CURRENT_LIST_DIR}/../Patches/civetweb-1.16.patch
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
 
-  if (FirstRun AND Failure)
-    message(FATAL_ERROR "Error while patching a file")
+    if (Failure)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
   endif()
 
   include_directories(
--- a/OrthancFramework/Resources/CMake/Compiler.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/Compiler.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -246,6 +246,9 @@
   # fix this error that appears with recent compilers on MacOS: boost/mpl/aux_/integral_wrapper.hpp:73:31: error: integer value -1 is outside the valid range of values [0, 3] for this enumeration type [-Wenum-constexpr-conversion]
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-enum-constexpr-conversion")
 
+  # it seems that some recent MacOS compilers don't set these flags correctly which prevents zlib from building correctly
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64")
+
   add_definitions(
     -D_XOPEN_SOURCE=1
     )
--- a/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.0.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.0.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -109,15 +109,17 @@
   SET(DCMTK_ENABLE_CHARSET_CONVERSION "iconv" CACHE STRING "")
   SET(HAVE_SYS_GETTID 0 CACHE INTERNAL "")
 
-  execute_process(
-    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-    ${CMAKE_CURRENT_LIST_DIR}/../Patches/dcmtk-3.6.2-linux-standard-base.patch
-    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-    RESULT_VARIABLE Failure
-    )
+  if (FirstRun)
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+      ${CMAKE_CURRENT_LIST_DIR}/../Patches/dcmtk-3.6.2-linux-standard-base.patch
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
 
-  if (FirstRun AND Failure)
-    message(FATAL_ERROR "Error while patching a file")
+    if (Failure)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
   endif()
 endif()
 
@@ -182,16 +184,18 @@
     )
 
   if (CMAKE_COMPILER_IS_GNUCXX)
-    # This is a patch for DCMTK 3.6.0 and MinGW64
-    execute_process(
-      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-      ${CMAKE_CURRENT_LIST_DIR}/../Patches/dcmtk-3.6.0-mingw64.patch
-      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-      RESULT_VARIABLE Failure
-      )
+    if (FirstRun)
+      # This is a patch for DCMTK 3.6.0 and MinGW64
+      execute_process(
+        COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+        ${CMAKE_CURRENT_LIST_DIR}/../Patches/dcmtk-3.6.0-mingw64.patch
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        )
 
-    if (Failure AND FirstRun)
-      message(FATAL_ERROR "Error while patching a file")
+      if (Failure)
+        message(FATAL_ERROR "Error while patching a file")
+      endif()
     endif()
   endif()
 endif()
--- a/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -215,6 +215,10 @@
         # Advanced storage 0.2.2 (framework pre-1.12.10)
         set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
         set(ORTHANC_FRAMEWORK_MD5 "bd5ba2cec329010b912209345acbdeaf")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "0ebe8cfd9bf7")
+        # Worklists plugin 0.9.0 (framework pre-1.12.10)
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
+        set(ORTHANC_FRAMEWORK_MD5 "17a5ca9254e881ab89c93d052d4655cb")
       endif()
     endif()
   endif()
--- a/OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -21,9 +21,9 @@
 
 
 if (STATIC_BUILD OR NOT USE_SYSTEM_CURL)
-  SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-8.9.0)
-  SET(CURL_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/curl-8.9.0.tar.gz")
-  SET(CURL_MD5 "f9bca5d4d5bac1f04e6c5eb4d0418618")
+  SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-8.17.0)
+  SET(CURL_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/curl-8.17.0.tar.gz")
+  SET(CURL_MD5 "71e24b00f40a7503c1d07886e42d6305")
 
   if (IS_DIRECTORY "${CURL_SOURCES_DIR}")
     set(FirstRun OFF)
@@ -36,7 +36,7 @@
   if (FirstRun)
     execute_process(
       COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-      ${CMAKE_CURRENT_LIST_DIR}/../Patches/curl-8.9.0.patch
+      ${CMAKE_CURRENT_LIST_DIR}/../Patches/curl-8.17.0.patch
       WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
       RESULT_VARIABLE Failure
       )
@@ -55,6 +55,7 @@
   AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vssh CURL_SOURCES)
   AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vtls CURL_SOURCES)
   AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vquic CURL_SOURCES)
+  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/curlx CURL_SOURCES)
   source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*)
 
   add_definitions(
@@ -116,79 +117,57 @@
       SOURCE ${CURL_SOURCES} APPEND
       PROPERTY COMPILE_DEFINITIONS "HAVE_CONFIG_H=1;OS=\"${TMP_OS}\""
       )
-   
+
+    if(CMAKE_C_COMPILER_TARGET)
+      set(CURL_OS "\"${CMAKE_C_COMPILER_TARGET}\"")
+    else()
+      set(CURL_OS "\"${CMAKE_SYSTEM_NAME}\"")
+    endif()
+
     include(${CURL_SOURCES_DIR}/CMake/Macros.cmake)
 
-    # WARNING: Do *not* reorder the "check_include_file_concat()" below!
-    check_include_file_concat("stdio.h"          HAVE_STDIO_H)
-    check_include_file_concat("inttypes.h"       HAVE_INTTYPES_H)
-    check_include_file_concat("sys/filio.h"      HAVE_SYS_FILIO_H)
-    check_include_file_concat("sys/ioctl.h"      HAVE_SYS_IOCTL_H)
-    check_include_file_concat("sys/param.h"      HAVE_SYS_PARAM_H)
-    check_include_file_concat("sys/poll.h"       HAVE_SYS_POLL_H)
-    check_include_file_concat("sys/resource.h"   HAVE_SYS_RESOURCE_H)
-    check_include_file_concat("sys/select.h"     HAVE_SYS_SELECT_H)
-    check_include_file_concat("sys/socket.h"     HAVE_SYS_SOCKET_H)
-    check_include_file_concat("sys/sockio.h"     HAVE_SYS_SOCKIO_H)
-    check_include_file_concat("sys/stat.h"       HAVE_SYS_STAT_H)
-    check_include_file_concat("sys/time.h"       HAVE_SYS_TIME_H)
-    check_include_file_concat("sys/types.h"      HAVE_SYS_TYPES_H)
-    check_include_file_concat("sys/uio.h"        HAVE_SYS_UIO_H)
-    check_include_file_concat("sys/un.h"         HAVE_SYS_UN_H)
-    check_include_file_concat("sys/utime.h"      HAVE_SYS_UTIME_H)
-    check_include_file_concat("sys/xattr.h"      HAVE_SYS_XATTR_H)
-    check_include_file_concat("alloca.h"         HAVE_ALLOCA_H)
-    check_include_file_concat("arpa/inet.h"      HAVE_ARPA_INET_H)
-    check_include_file_concat("arpa/tftp.h"      HAVE_ARPA_TFTP_H)
-    check_include_file_concat("assert.h"         HAVE_ASSERT_H)
-    check_include_file_concat("crypto.h"         HAVE_CRYPTO_H)
-    check_include_file_concat("des.h"            HAVE_DES_H)
-    check_include_file_concat("err.h"            HAVE_ERR_H)
-    check_include_file_concat("errno.h"          HAVE_ERRNO_H)
-    check_include_file_concat("fcntl.h"          HAVE_FCNTL_H)
-    check_include_file_concat("idn2.h"           HAVE_IDN2_H)
-    check_include_file_concat("ifaddrs.h"        HAVE_IFADDRS_H)
-    check_include_file_concat("io.h"             HAVE_IO_H)
-    check_include_file_concat("krb.h"            HAVE_KRB_H)
-    check_include_file_concat("libgen.h"         HAVE_LIBGEN_H)
-    check_include_file_concat("limits.h"         HAVE_LIMITS_H)
-    check_include_file_concat("locale.h"         HAVE_LOCALE_H)
-    check_include_file_concat("net/if.h"         HAVE_NET_IF_H)
-    check_include_file_concat("netdb.h"          HAVE_NETDB_H)
-    check_include_file_concat("netinet/in.h"     HAVE_NETINET_IN_H)
-    check_include_file_concat("netinet/tcp.h"    HAVE_NETINET_TCP_H)
+    # Detect headers
+
+    # Use check_include_file_concat_curl() for headers required by subsequent
+    # check_include_file_concat_curl() or check_symbol_exists() detections.
+    # Order for these is significant.
+    check_include_file("sys/eventfd.h"    HAVE_SYS_EVENTFD_H)
+    check_include_file("sys/filio.h"      HAVE_SYS_FILIO_H)
+    check_include_file("sys/ioctl.h"      HAVE_SYS_IOCTL_H)
+    check_include_file("sys/param.h"      HAVE_SYS_PARAM_H)
+    check_include_file("sys/poll.h"       HAVE_SYS_POLL_H)
+    check_include_file("sys/resource.h"   HAVE_SYS_RESOURCE_H)
+    check_include_file_concat_curl("sys/select.h"     HAVE_SYS_SELECT_H)
+    check_include_file("sys/sockio.h"     HAVE_SYS_SOCKIO_H)
+    check_include_file_concat_curl("sys/types.h"      HAVE_SYS_TYPES_H)
+    check_include_file("sys/un.h"         HAVE_SYS_UN_H)
+    check_include_file_concat_curl("sys/utime.h"      HAVE_SYS_UTIME_H)  # sys/types.h (AmigaOS)
 
-    check_include_file_concat("pem.h"            HAVE_PEM_H)
-    check_include_file_concat("poll.h"           HAVE_POLL_H)
-    check_include_file_concat("pwd.h"            HAVE_PWD_H)
-    check_include_file_concat("rsa.h"            HAVE_RSA_H)
-    check_include_file_concat("setjmp.h"         HAVE_SETJMP_H)
-    check_include_file_concat("sgtty.h"          HAVE_SGTTY_H)
-    check_include_file_concat("signal.h"         HAVE_SIGNAL_H)
-    check_include_file_concat("ssl.h"            HAVE_SSL_H)
-    check_include_file_concat("stdbool.h"        HAVE_STDBOOL_H)
-    check_include_file_concat("stdint.h"         HAVE_STDINT_H)
-    check_include_file_concat("stdio.h"          HAVE_STDIO_H)
-    check_include_file_concat("stdlib.h"         HAVE_STDLIB_H)
-    check_include_file_concat("string.h"         HAVE_STRING_H)
-    check_include_file_concat("strings.h"        HAVE_STRINGS_H)
-    check_include_file_concat("stropts.h"        HAVE_STROPTS_H)
-    check_include_file_concat("termio.h"         HAVE_TERMIO_H)
-    check_include_file_concat("termios.h"        HAVE_TERMIOS_H)
-    check_include_file_concat("time.h"           HAVE_TIME_H)
-    check_include_file_concat("unistd.h"         HAVE_UNISTD_H)
-    check_include_file_concat("utime.h"          HAVE_UTIME_H)
-    check_include_file_concat("x509.h"           HAVE_X509_H)
-
-    check_include_file_concat("process.h"        HAVE_PROCESS_H)
-    check_include_file_concat("stddef.h"         HAVE_STDDEF_H)
-    check_include_file_concat("dlfcn.h"          HAVE_DLFCN_H)
-    check_include_file_concat("malloc.h"         HAVE_MALLOC_H)
-    check_include_file_concat("memory.h"         HAVE_MEMORY_H)
-    check_include_file_concat("netinet/if_ether.h" HAVE_NETINET_IF_ETHER_H)
-    check_include_file_concat("stdint.h"        HAVE_STDINT_H)
-    check_include_file_concat("sockio.h"        HAVE_SOCKIO_H)
-    check_include_file_concat("sys/utsname.h"   HAVE_SYS_UTSNAME_H)
+    check_include_file_concat_curl("arpa/inet.h"      HAVE_ARPA_INET_H)
+    check_include_file("dirent.h"         HAVE_DIRENT_H)
+    check_include_file("fcntl.h"          HAVE_FCNTL_H)
+    check_include_file_concat_curl("ifaddrs.h"        HAVE_IFADDRS_H)
+    check_include_file("io.h"             HAVE_IO_H)
+    check_include_file_concat_curl("libgen.h"         HAVE_LIBGEN_H)
+    check_include_file("linux/tcp.h"      HAVE_LINUX_TCP_H)
+    check_include_file("locale.h"         HAVE_LOCALE_H)
+    check_include_file_concat_curl("net/if.h"         HAVE_NET_IF_H)  # sys/select.h (e.g. MS-DOS/Watt-32)
+    check_include_file_concat_curl("netdb.h"          HAVE_NETDB_H)
+    check_include_file_concat_curl("netinet/in.h"     HAVE_NETINET_IN_H)
+    check_include_file("netinet/in6.h"    HAVE_NETINET_IN6_H)
+    check_include_file_concat_curl("netinet/tcp.h"    HAVE_NETINET_TCP_H)  # sys/types.h (e.g. Cygwin) netinet/in.h
+    check_include_file_concat_curl("netinet/udp.h"    HAVE_NETINET_UDP_H)  # sys/types.h (e.g. Cygwin)
+    check_include_file("poll.h"           HAVE_POLL_H)
+    check_include_file("pwd.h"            HAVE_PWD_H)
+    check_include_file("stdatomic.h"      HAVE_STDATOMIC_H)
+    check_include_file("stdbool.h"        HAVE_STDBOOL_H)
+    check_include_file("stdint.h"         HAVE_STDINT_H)
+    check_include_file("strings.h"        HAVE_STRINGS_H)
+    check_include_file("stropts.h"        HAVE_STROPTS_H)
+    check_include_file("termio.h"         HAVE_TERMIO_H)
+    check_include_file("termios.h"        HAVE_TERMIOS_H)
+    check_include_file_concat_curl("unistd.h"         HAVE_UNISTD_H)
+    check_include_file("utime.h"          HAVE_UTIME_H)
 
     check_type_size("size_t"  SIZEOF_SIZE_T)
     check_type_size("ssize_t"  SIZEOF_SSIZE_T)
@@ -202,97 +181,72 @@
     check_type_size("off_t"  SIZEOF_OFF_T)
     check_type_size("socklen_t" CURL_SIZEOF_CURL_SOCKLEN_T)
 
-    check_symbol_exists(basename      "${CURL_INCLUDES}" HAVE_BASENAME)
-    check_symbol_exists(socket        "${CURL_INCLUDES}" HAVE_SOCKET)
-    # poll on macOS is unreliable, it first did not exist, then was broken until
-    # fixed in 10.9 only to break again in 10.12.
-    if(NOT APPLE)
-      check_symbol_exists(poll        "${CURL_INCLUDES}" HAVE_POLL)
-    endif()
-    check_symbol_exists(select        "${CURL_INCLUDES}" HAVE_SELECT)
-    check_symbol_exists(strdup        "${CURL_INCLUDES}" HAVE_STRDUP)
-    check_symbol_exists(strstr        "${CURL_INCLUDES}" HAVE_STRSTR)
-    check_symbol_exists(strtok_r      "${CURL_INCLUDES}" HAVE_STRTOK_R)
-    check_symbol_exists(strftime      "${CURL_INCLUDES}" HAVE_STRFTIME)
-    check_symbol_exists(uname         "${CURL_INCLUDES}" HAVE_UNAME)
-    check_symbol_exists(strcasecmp    "${CURL_INCLUDES}" HAVE_STRCASECMP)
-    check_symbol_exists(stricmp       "${CURL_INCLUDES}" HAVE_STRICMP)
-    check_symbol_exists(strcmpi       "${CURL_INCLUDES}" HAVE_STRCMPI)
-    check_symbol_exists(strncmpi      "${CURL_INCLUDES}" HAVE_STRNCMPI)
-    check_symbol_exists(alarm         "${CURL_INCLUDES}" HAVE_ALARM)
-    if(NOT HAVE_STRNCMPI)
-      set(HAVE_STRCMPI)
-    endif(NOT HAVE_STRNCMPI)
+    check_function_exists("accept4"       HAVE_ACCEPT4)
+    check_function_exists("fnmatch"       HAVE_FNMATCH)
+    check_symbol_exists("basename"        "${CURL_INCLUDES};string.h" HAVE_BASENAME)  # libgen.h unistd.h
+    check_symbol_exists("opendir"         "dirent.h" HAVE_OPENDIR)
+    check_function_exists("poll"          HAVE_POLL)  # poll.h
+    check_symbol_exists("socket"          "${CURL_INCLUDES}" HAVE_SOCKET)  # winsock2.h sys/socket.h
+    check_symbol_exists("socketpair"      "${CURL_INCLUDES}" HAVE_SOCKETPAIR)  # sys/socket.h
+    check_symbol_exists("recv"            "${CURL_INCLUDES}" HAVE_RECV)  # proto/bsdsocket.h sys/types.h sys/socket.h
+    check_symbol_exists("send"            "${CURL_INCLUDES}" HAVE_SEND)  # proto/bsdsocket.h sys/types.h sys/socket.h
+    check_function_exists("sendmsg"       HAVE_SENDMSG)
+    check_function_exists("sendmmsg"      HAVE_SENDMMSG)
+    check_symbol_exists("select"          "${CURL_INCLUDES}" HAVE_SELECT)  # proto/bsdsocket.h sys/select.h sys/socket.h
+    check_symbol_exists("strdup"          "string.h" HAVE_STRDUP)
+    check_symbol_exists("memrchr"         "string.h" HAVE_MEMRCHR)
+    check_symbol_exists("alarm"           "unistd.h" HAVE_ALARM)
+    check_symbol_exists("fcntl"           "fcntl.h" HAVE_FCNTL)
+    check_function_exists("getppid"       HAVE_GETPPID)
+    check_function_exists("utimes"        HAVE_UTIMES)
 
-    check_symbol_exists(gethostbyaddr "${CURL_INCLUDES}" HAVE_GETHOSTBYADDR)
-    check_symbol_exists(gethostbyaddr_r "${CURL_INCLUDES}" HAVE_GETHOSTBYADDR_R)
-    check_symbol_exists(gettimeofday  "${CURL_INCLUDES}" HAVE_GETTIMEOFDAY)
-    check_symbol_exists(inet_addr     "${CURL_INCLUDES}" HAVE_INET_ADDR)
-    check_symbol_exists(inet_ntoa     "${CURL_INCLUDES}" HAVE_INET_NTOA)
-    check_symbol_exists(inet_ntoa_r   "${CURL_INCLUDES}" HAVE_INET_NTOA_R)
-    check_symbol_exists(tcsetattr     "${CURL_INCLUDES}" HAVE_TCSETATTR)
-    check_symbol_exists(tcgetattr     "${CURL_INCLUDES}" HAVE_TCGETATTR)
-    check_symbol_exists(perror        "${CURL_INCLUDES}" HAVE_PERROR)
-    check_symbol_exists(closesocket   "${CURL_INCLUDES}" HAVE_CLOSESOCKET)
-    check_symbol_exists(setvbuf       "${CURL_INCLUDES}" HAVE_SETVBUF)
-    check_symbol_exists(sigsetjmp     "${CURL_INCLUDES}" HAVE_SIGSETJMP)
-    check_symbol_exists(getpass_r     "${CURL_INCLUDES}" HAVE_GETPASS_R)
-    check_symbol_exists(strlcat       "${CURL_INCLUDES}" HAVE_STRLCAT)
-    check_symbol_exists(getpwuid      "${CURL_INCLUDES}" HAVE_GETPWUID)
-    check_symbol_exists(geteuid       "${CURL_INCLUDES}" HAVE_GETEUID)
-    check_symbol_exists(utime         "${CURL_INCLUDES}" HAVE_UTIME)
-    check_symbol_exists(gmtime_r      "${CURL_INCLUDES}" HAVE_GMTIME_R)
-    check_symbol_exists(localtime_r   "${CURL_INCLUDES}" HAVE_LOCALTIME_R)
+    check_function_exists("gettimeofday"  HAVE_GETTIMEOFDAY)  # sys/time.h
+    check_symbol_exists("closesocket"     "${CURL_INCLUDES}" HAVE_CLOSESOCKET)  # winsock2.h
+    check_symbol_exists("sigsetjmp"       "setjmp.h" HAVE_SIGSETJMP)
+    check_function_exists("getpass_r"     HAVE_GETPASS_R)
+    check_function_exists("getpwuid"      HAVE_GETPWUID)
+    check_function_exists("getpwuid_r"    HAVE_GETPWUID_R)
+    check_function_exists("geteuid"       HAVE_GETEUID)
+    check_function_exists("utime"         HAVE_UTIME)
+    check_symbol_exists("gmtime_r"        "stdlib.h;time.h" HAVE_GMTIME_R)
 
-    check_symbol_exists(gethostbyname   "${CURL_INCLUDES}" HAVE_GETHOSTBYNAME)
-    check_symbol_exists(gethostbyname_r "${CURL_INCLUDES}" HAVE_GETHOSTBYNAME_R)
+    check_symbol_exists("gethostbyname_r" "netdb.h" HAVE_GETHOSTBYNAME_R)
+    check_symbol_exists("gethostname"     "${CURL_INCLUDES}" HAVE_GETHOSTNAME)  # winsock2.h unistd.h proto/bsdsocket.h
 
-    check_symbol_exists(signal        "${CURL_INCLUDES}" HAVE_SIGNAL_FUNC)
-    check_symbol_exists(SIGALRM       "${CURL_INCLUDES}" HAVE_SIGNAL_MACRO)
-    if(HAVE_SIGNAL_FUNC AND HAVE_SIGNAL_MACRO)
-      set(HAVE_SIGNAL 1)
-    endif(HAVE_SIGNAL_FUNC AND HAVE_SIGNAL_MACRO)
-    check_symbol_exists(uname          "${CURL_INCLUDES}" HAVE_UNAME)
-    check_symbol_exists(strtoll        "${CURL_INCLUDES}" HAVE_STRTOLL)
-    check_symbol_exists(_strtoi64      "${CURL_INCLUDES}" HAVE__STRTOI64)
-    check_symbol_exists(strerror_r     "${CURL_INCLUDES}" HAVE_STRERROR_R)
-    check_symbol_exists(siginterrupt   "${CURL_INCLUDES}" HAVE_SIGINTERRUPT)
-    check_symbol_exists(perror         "${CURL_INCLUDES}" HAVE_PERROR)
-    check_symbol_exists(fork           "${CURL_INCLUDES}" HAVE_FORK)
-    check_symbol_exists(getaddrinfo    "${CURL_INCLUDES}" HAVE_GETADDRINFO)
-    check_symbol_exists(freeaddrinfo   "${CURL_INCLUDES}" HAVE_FREEADDRINFO)
-    check_symbol_exists(freeifaddrs    "${CURL_INCLUDES}" HAVE_FREEIFADDRS)
-    check_symbol_exists(pipe           "${CURL_INCLUDES}" HAVE_PIPE)
-    check_symbol_exists(ftruncate      "${CURL_INCLUDES}" HAVE_FTRUNCATE)
-    check_symbol_exists(getprotobyname "${CURL_INCLUDES}" HAVE_GETPROTOBYNAME)
-    check_symbol_exists(getrlimit      "${CURL_INCLUDES}" HAVE_GETRLIMIT)
-    check_symbol_exists(setlocale      "${CURL_INCLUDES}" HAVE_SETLOCALE)
-    check_symbol_exists(setmode        "${CURL_INCLUDES}" HAVE_SETMODE)
-    check_symbol_exists(setrlimit      "${CURL_INCLUDES}" HAVE_SETRLIMIT)
-    check_symbol_exists(fcntl          "${CURL_INCLUDES}" HAVE_FCNTL)
-    check_symbol_exists(ioctl          "${CURL_INCLUDES}" HAVE_IOCTL)
-    check_symbol_exists(setsockopt     "${CURL_INCLUDES}" HAVE_SETSOCKOPT)
+    check_symbol_exists("signal"          "signal.h" HAVE_SIGNAL)
+    check_symbol_exists("strerror_r"      "stdlib.h;string.h" HAVE_STRERROR_R)
+    check_symbol_exists("sigaction"       "signal.h" HAVE_SIGACTION)
+    check_symbol_exists("siginterrupt"    "signal.h" HAVE_SIGINTERRUPT)
+    check_symbol_exists("getaddrinfo"     "${CURL_INCLUDES};stdlib.h;string.h" HAVE_GETADDRINFO)  # ws2tcpip.h sys/socket.h netdb.h
+    check_symbol_exists("getifaddrs"      "${CURL_INCLUDES};stdlib.h" HAVE_GETIFADDRS)  # ifaddrs.h
+    check_symbol_exists("freeaddrinfo"    "${CURL_INCLUDES}" HAVE_FREEADDRINFO)  # ws2tcpip.h sys/socket.h netdb.h
+    check_function_exists("pipe"          HAVE_PIPE)
+    check_function_exists("pipe2"         HAVE_PIPE2)
+    check_function_exists("eventfd"       HAVE_EVENTFD)
+    check_symbol_exists("ftruncate"       "unistd.h" HAVE_FTRUNCATE)
+    check_symbol_exists("getpeername"     "${CURL_INCLUDES}" HAVE_GETPEERNAME)  # winsock2.h unistd.h proto/bsdsocket.h
+    check_symbol_exists("getsockname"     "${CURL_INCLUDES}" HAVE_GETSOCKNAME)  # winsock2.h unistd.h proto/bsdsocket.h
+    check_function_exists("getrlimit"       HAVE_GETRLIMIT)
+    check_function_exists("setlocale"       HAVE_SETLOCALE)
+    check_function_exists("setrlimit"       HAVE_SETRLIMIT)
 
-    if(HAVE_SIZEOF_LONG_LONG)
-      set(HAVE_LONGLONG 1)
-      set(HAVE_LL 1)
-    endif(HAVE_SIZEOF_LONG_LONG)
-
-    check_function_exists(mach_absolute_time HAVE_MACH_ABSOLUTE_TIME)
-    check_function_exists(gethostname HAVE_GETHOSTNAME)
-
-    check_include_file_concat("pthread.h" HAVE_PTHREAD_H)
-    check_symbol_exists(recv "sys/socket.h" HAVE_RECV)
-    check_symbol_exists(send "sys/socket.h" HAVE_SEND)
-
+    if(WIN32)
+      # include wincrypt.h as a workaround for mingw-w64 __MINGW64_VERSION_MAJOR <= 5 header bug */
+      check_symbol_exists("if_nametoindex"  "winsock2.h;wincrypt.h;iphlpapi.h" HAVE_IF_NAMETOINDEX)  # Windows Vista+ non-UWP */
+    else()
+      check_function_exists("if_nametoindex"  HAVE_IF_NAMETOINDEX)  # net/if.h
+      check_function_exists("realpath"        HAVE_REALPATH)
+      check_function_exists("sched_yield"     HAVE_SCHED_YIELD)
+      check_symbol_exists("strcasecmp"      "string.h" HAVE_STRCASECMP)
+      check_symbol_exists("stricmp"         "string.h" HAVE_STRICMP)
+      check_symbol_exists("strcmpi"         "string.h" HAVE_STRCMPI)
+    endif()
     check_struct_has_member("struct sockaddr_un" sun_path "sys/un.h" USE_UNIX_SOCKETS)
 
     list(APPEND CMAKE_REQUIRED_INCLUDES "${CURL_SOURCES_DIR}/include")
     set(CMAKE_EXTRA_INCLUDE_FILES "curl/system.h")
     check_type_size("curl_off_t"  SIZEOF_CURL_OFF_T)
 
-    add_definitions(-DHAVE_GLIBC_STRERROR_R=1)
-
     include(${CURL_SOURCES_DIR}/CMake/OtherTests.cmake)
 
     foreach(CURL_TEST
@@ -304,30 +258,20 @@
         HAVE_IOCTL_FIONBIO
         HAVE_IOCTL_SIOCGIFADDR
         HAVE_SETSOCKOPT_SO_NONBLOCK
-        HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
-        TIME_WITH_SYS_TIME
-        HAVE_O_NONBLOCK
-        HAVE_GETHOSTBYADDR_R_5
-        HAVE_GETHOSTBYADDR_R_7
-        HAVE_GETHOSTBYADDR_R_8
-        HAVE_GETHOSTBYADDR_R_5_REENTRANT
-        HAVE_GETHOSTBYADDR_R_7_REENTRANT
-        HAVE_GETHOSTBYADDR_R_8_REENTRANT
         HAVE_GETHOSTBYNAME_R_3
         HAVE_GETHOSTBYNAME_R_5
         HAVE_GETHOSTBYNAME_R_6
+        HAVE_BOOL_T
+        STDC_HEADERS
+        HAVE_ATOMIC
+        HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
         HAVE_GETHOSTBYNAME_R_3_REENTRANT
         HAVE_GETHOSTBYNAME_R_5_REENTRANT
         HAVE_GETHOSTBYNAME_R_6_REENTRANT
-        HAVE_SOCKLEN_T
-        HAVE_IN_ADDR_T
-        HAVE_BOOL_T
-        STDC_HEADERS
-        RETSIGTYPE_TEST
-        HAVE_INET_NTOA_R_DECL
-        HAVE_INET_NTOA_R_DECL_REENTRANT
         HAVE_GETADDRINFO
         HAVE_FILE_OFFSET_BITS
+        HAVE_GLIBC_STRERROR_R
+        HAVE_POSIX_STRERROR_R
         )
       curl_internal_test(${CURL_TEST})
     endforeach(CURL_TEST)
--- a/OrthancFramework/Resources/CMake/LibP11Configuration.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/LibP11Configuration.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -37,15 +37,17 @@
 
   DownloadPackage(${LIBP11_MD5} ${LIBP11_URL} "${LIBP11_SOURCES_DIR}")
 
-  # Apply the patches
-  execute_process(
-    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${CMAKE_CURRENT_LIST_DIR}/../Patches/libp11-0.4.0.patch
-    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-    RESULT_VARIABLE Failure
-    )
+  if (FirstRun)
+    # Apply the patches
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${CMAKE_CURRENT_LIST_DIR}/../Patches/libp11-0.4.0.patch
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
 
-  if (Failure AND FirstRun)
-    message(FATAL_ERROR "Error while patching libp11")
+    if (Failure)
+      message(FATAL_ERROR "Error while patching libp11")
+    endif()
   endif()
 
   # This command MUST be after applying the patch
--- a/OrthancFramework/Resources/CMake/LibPngConfiguration.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/LibPngConfiguration.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -21,9 +21,9 @@
 
 
 if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG)
-  SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.6.40)
-  SET(LIBPNG_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/libpng-1.6.40.tar.gz")
-  SET(LIBPNG_MD5 "ec4b597c3a9b1f8d2826575f530367b7")
+  SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.6.50)
+  SET(LIBPNG_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/libpng-1.6.50.tar.gz")
+  SET(LIBPNG_MD5 "eef2d3da281ae83ac8a8f5fd9fa9d325")
 
   DownloadPackage(${LIBPNG_MD5} ${LIBPNG_URL} "${LIBPNG_SOURCES_DIR}")
 
--- a/OrthancFramework/Resources/CMake/MongooseConfiguration.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/MongooseConfiguration.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -50,16 +50,18 @@
     set(MONGOOSE_PATCH ${CMAKE_CURRENT_LIST_DIR}/../Patches/mongoose-3.8-patch.diff)
   endif()
 
-  # Patch mongoose
-  execute_process(
-    COMMAND ${PATCH_EXECUTABLE} -N mongoose.c 
-    INPUT_FILE ${MONGOOSE_PATCH}
-    WORKING_DIRECTORY ${MONGOOSE_SOURCES_DIR}
-    RESULT_VARIABLE Failure
-    )
+  if (FirstRun)
+    # Patch mongoose
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -N mongoose.c
+      INPUT_FILE ${MONGOOSE_PATCH}
+      WORKING_DIRECTORY ${MONGOOSE_SOURCES_DIR}
+      RESULT_VARIABLE Failure
+      )
 
-  if (Failure AND FirstRun)
-    message(FATAL_ERROR "Error while patching a file")
+    if (Failure)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
   endif()
 
   include_directories(
--- a/OrthancFramework/Resources/CMake/UuidConfiguration.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/UuidConfiguration.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -41,15 +41,17 @@
     ## WebAssembly (used in Stone)
     ## 
 
-    execute_process(
-      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-      ${CMAKE_CURRENT_LIST_DIR}/../Patches/e2fsprogs-1.44.5.patch
-      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-      RESULT_VARIABLE Failure
-      )
+    if (FirstRun)
+      execute_process(
+        COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+        ${CMAKE_CURRENT_LIST_DIR}/../Patches/e2fsprogs-1.44.5.patch
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        )
 
-    if (FirstRun AND Failure)
-      message(FATAL_ERROR "Error while patching a file")
+      if (Failure)
+        message(FATAL_ERROR "Error while patching a file")
+      endif()
     endif()
 
 
--- a/OrthancFramework/Resources/CMake/ZlibConfiguration.cmake	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/CMake/ZlibConfiguration.cmake	Sat Nov 22 10:18:45 2025 +0100
@@ -25,8 +25,28 @@
   SET(ZLIB_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/zlib-1.3.1.tar.gz")
   SET(ZLIB_MD5 "9855b6d802d7fe5b7bd5b196a2271655")
 
+  if (IS_DIRECTORY "${ZLIB_SOURCES_DIR}")
+    set(FirstRun OFF)
+  else()
+    set(FirstRun ON)
+  endif()
+
   DownloadPackage(${ZLIB_MD5} ${ZLIB_URL} "${ZLIB_SOURCES_DIR}")
 
+  if (FirstRun)
+    # fix https://github.com/madler/zlib/issues/1044
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+      ${CMAKE_CURRENT_LIST_DIR}/../Patches/zlib-1.3.1-zconf.patch
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
+
+    if (Failure)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
+  endif()
+
   include_directories(
     ${ZLIB_SOURCES_DIR}
     )
--- a/OrthancFramework/Resources/Patches/OpenSSL-ConfigureHeaders.py	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Resources/Patches/OpenSSL-ConfigureHeaders.py	Sat Nov 22 10:18:45 2025 +0100
@@ -92,43 +92,43 @@
 def Parse(match):
     s = ''
     
-    for t in re.findall('generate_stack_macros\("(.+?)"\)', match.group(1)):
+    for t in re.findall(r'generate_stack_macros\("(.+?)"\)', match.group(1)):
         s += (GENERATE_STACK_MACROS
               .replace('${nametype}', t)
               .replace('${realtype}', t)
               .replace('${plaintype}', t))
         
-    for t in re.findall('generate_const_stack_macros\("(.+?)"\)', match.group(1)):
+    for t in re.findall(r'generate_const_stack_macros\("(.+?)"\)', match.group(1)):
         s += (GENERATE_STACK_MACROS
               .replace('${nametype}', t)
               .replace('${realtype}', 'const %s' % t)
               .replace('${plaintype}', t))
 
-    for t in re.findall('generate_stack_string_macros\(\)', match.group(1)):
+    for t in re.findall(r'generate_stack_string_macros\(\)', match.group(1)):
         s += (GENERATE_STACK_MACROS
               .replace('${nametype}', 'OPENSSL_STRING')
               .replace('${realtype}', 'char')
               .replace('${plaintype}', 'char'))
 
-    for t in re.findall('generate_stack_const_string_macros\(\)', match.group(1)):
+    for t in re.findall(r'generate_stack_const_string_macros\(\)', match.group(1)):
         s += (GENERATE_STACK_MACROS
               .replace('${nametype}', 'OPENSSL_CSTRING')
               .replace('${realtype}', 'const char')
               .replace('${plaintype}', 'char'))
 
-    for t in re.findall('generate_stack_block_macros\(\)', match.group(1)):
+    for t in re.findall(r'generate_stack_block_macros\(\)', match.group(1)):
         s += (GENERATE_STACK_MACROS
               .replace('${nametype}', 'OPENSSL_BLOCK')
               .replace('${realtype}', 'void')
               .replace('${plaintype}', 'void'))
         
-    for t in re.findall('generate_lhash_macros\("(.+?)"\)', match.group(1)):
+    for t in re.findall(r'generate_lhash_macros\("(.+?)"\)', match.group(1)):
         s += GENERATE_LHASH_MACROS.replace('${type}', t)
 
-    for t in re.findall('\$config{rc4_int}', match.group(1)):
+    for t in re.findall(r'\$config{rc4_int}', match.group(1)):
         s += 'unsigned int'
 
-    for t in re.findall('oids_to_c::process_leaves\(.+?\)', match.group(1), re.MULTILINE | re.DOTALL):
+    for t in re.findall(r'oids_to_c::process_leaves\(.+?\)', match.group(1), re.MULTILINE | re.DOTALL):
         if not CURRENT_HEADER in OIDS:
             raise Exception('Unknown header: %s' % CURRENT_HEADER)
 
@@ -145,18 +145,18 @@
     directory = os.path.join(sys.argv[1], base)
     for source in os.listdir(directory):
         if source.endswith('.h.in'):
-            target = re.sub('\.h\.in$', '.h', source)
+            target = re.sub(r'\.h\.in$', '.h', source)
                             
             with open(os.path.join(directory, source), 'r') as f:
                 with open(os.path.join(directory, target), 'w') as g:
                     CURRENT_HEADER = source
-                    g.write(re.sub('{-(.*?)-}.*?$', Parse, f.read(),
+                    g.write(re.sub(r'{-(.*?)-}.*?$', Parse, f.read(),
                                    flags = re.MULTILINE | re.DOTALL))
 
 
 with open(os.path.join(sys.argv[1], 'providers/common/der/orthanc_oids_gen.c'), 'w') as f:
     for (header, content) in OIDS.items():
-        f.write('#include "prov/%s"\n' % re.sub('\.h\.in$', '.h', header))
+        f.write('#include "prov/%s"\n' % re.sub(r'\.h\.in$', '.h', header))
 
     f.write('\n')
         
--- a/OrthancFramework/Resources/Patches/curl-8.12.1.patch	Thu Nov 20 17:49:15 2025 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-diff -urEb curl-8.12.1-orig/CMake/Macros.cmake curl-8.12.1/CMake/Macros.cmake
---- curl-8.12.1-orig/CMake/Macros.cmake 2025-02-13 08:15:00.000000000 +0100
-+++ curl-8.12.1/CMake/Macros.cmake      2025-03-27 10:25:42.119275658 +0100
-@@ -50,7 +50,7 @@
-     message(STATUS "Performing Test ${_curl_test}")
-     try_compile(${_curl_test}
-       ${PROJECT_BINARY_DIR}
--      "${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c"
-+      "${CURL_SOURCES_DIR}/CMake/CurlTests.c"
-       CMAKE_FLAGS
-         "-DCOMPILE_DEFINITIONS:STRING=-D${_curl_test} ${CURL_TEST_DEFINES} ${_cmake_required_definitions}"
-         "${_curl_test_add_libraries}"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Resources/Patches/curl-8.17.0.patch	Sat Nov 22 10:18:45 2025 +0100
@@ -0,0 +1,12 @@
+diff -urEb curl-8.17.0-orig/CMake/Macros.cmake curl-8.17.0/CMake/Macros.cmake
+--- curl-8.17.0-orig/CMake/Macros.cmake	2025-11-19 10:41:00.404885652 +0100
++++ curl-8.17.0/CMake/Macros.cmake	2025-11-19 10:42:01.556360886 +0100
+@@ -50,7 +50,7 @@
+     message(STATUS "Performing Test ${_curl_test}")
+     try_compile(${_curl_test}
+       ${PROJECT_BINARY_DIR}
+-      "${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c"
++      "${CURL_SOURCES_DIR}/CMake/CurlTests.c"
+       CMAKE_FLAGS
+         "-DCOMPILE_DEFINITIONS:STRING=-D${_curl_test} ${CURL_TEST_DEFINES} ${CMAKE_REQUIRED_FLAGS} ${_cmake_required_definitions}"
+         "${_curl_test_add_libraries}"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Resources/Patches/zlib-1.3.1-zconf.patch	Sat Nov 22 10:18:45 2025 +0100
@@ -0,0 +1,12 @@
+diff -ruN zlib-1.3.1-orig/zlib.h zlib-1.3.1/zlib.h
+--- zlib-1.3.1-orig/zlib.h	2025-11-18 10:09:54.402921485 +0100
++++ zlib-1.3.1/zlib.h	2025-11-18 10:10:33.743470112 +0100
+@@ -31,7 +31,7 @@
+ #ifndef ZLIB_H
+ #define ZLIB_H
+ 
+-#include "zconf.h"
++#include <zconf.h>
+ 
+ #ifdef __cplusplus
+ extern "C" {
--- a/OrthancFramework/SharedLibrary/CMakeLists.txt	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/SharedLibrary/CMakeLists.txt	Sat Nov 22 10:18:45 2025 +0100
@@ -194,7 +194,9 @@
   # Control the visibility of Boost
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND
       ORTHANC_STATIC_BOOST)
-    set_source_files_properties(${ORTHANC_CORE_SOURCES_INTERNAL}
+    set_source_files_properties(
+      ${ORTHANC_CORE_SOURCES_INTERNAL}
+      ${ORTHANC_DICOM_SOURCES_INTERNAL}
       PROPERTIES COMPILE_DEFINITIONS "BOOST_DATE_TIME_SOURCE;BOOST_FILESYSTEM_SOURCE;BOOST_LOCALE_SOURCE;BOOST_REGEX_SOURCE"
       )
   endif()
--- a/OrthancFramework/Sources/Cache/MemoryCache.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/Cache/MemoryCache.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -88,9 +88,19 @@
       while (!index_.IsEmpty())
       {
         Page* element = NULL;
-        index_.RemoveOldest(element);
-        assert(element != NULL);
-        delete element;
+
+        try
+        {
+          index_.RemoveOldest(element);
+
+          assert(element != NULL);
+          delete element;
+        }
+        catch (OrthancException& e)
+        {
+          // Don't throw exceptions in destructors
+          LOG(ERROR) << "Exception in destructor: " << e.What();
+        }
       }
     }
 
--- a/OrthancFramework/Sources/Cache/MemoryObjectCache.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/Cache/MemoryObjectCache.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -26,6 +26,7 @@
 #include "MemoryObjectCache.h"
 
 #include "../Compatibility.h"
+#include "../Logging.h"
 
 namespace Orthanc
 {
@@ -96,7 +97,16 @@
 
   MemoryObjectCache::~MemoryObjectCache()
   {
-    Recycle(0);
+    try
+    {
+      Recycle(0);
+    }
+    catch (OrthancException& e)
+    {
+      // Don't throw exceptions in destructors
+      LOG(ERROR) << "Exception in destructor: " << e.What();
+    }
+
     assert(content_.IsEmpty());
   }
 
--- a/OrthancFramework/Sources/Cache/MemoryStringCache.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/Cache/MemoryStringCache.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -25,6 +25,8 @@
 #include "../PrecompiledHeaders.h"
 #include "MemoryStringCache.h"
 
+#include "../Logging.h"
+
 namespace Orthanc
 {
   class MemoryStringCache::StringValue : public ICacheable
@@ -118,7 +120,16 @@
 
   MemoryStringCache::~MemoryStringCache()
   {
-    Recycle(0);
+    try
+    {
+      Recycle(0);
+    }
+    catch (OrthancException& e)
+    {
+      // Don't throw exceptions in destructors
+      LOG(ERROR) << "Exception in destructor: " << e.What();
+    }
+
     assert(content_.IsEmpty());
   }
 
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -1696,15 +1696,20 @@
   {
     DcmDataset* dcmDataset = GetDcmtkObjectConst().getDataset();
 
-    if (!this->HasTag(DICOM_TAG_PIXEL_DATA) && !DicomImageDecoder::IsPsmctRle1(*dcmDataset))
+    if (dcmDataset == NULL)
     {
-      throw OrthancException(ErrorCode_BadRequest, "Cannot extract a frame from a DIOCM file that does not have pixel data.");
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (!this->HasTag(DICOM_TAG_PIXEL_DATA) &&
+        !DicomImageDecoder::IsPsmctRle1(*dcmDataset))
+    {
+      throw OrthancException(ErrorCode_BadRequest, "Cannot extract a frame from a DICOM file that does not have pixel data.");
     }
 
     if (pimpl_->frameIndex_.get() == NULL)
     {
-      assert(pimpl_->file_ != NULL && dcmDataset != NULL);
-
+      assert(pimpl_->file_ != NULL);
       pimpl_->frameIndex_.reset(new DicomFrameIndex(*dcmDataset));
     }
 
--- a/OrthancFramework/Sources/Enumerations.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/Enumerations.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -1199,6 +1199,9 @@
       case ErrorPayloadType_Dimse:
         return "Dimse";
 
+      case ErrorPayloadType_RetrieveJob:
+        return "RetrieveJob";
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -1932,6 +1935,10 @@
     {
       return ErrorPayloadType_Dimse;
     }
+    else if (type == "RetrieveJob")
+    {
+      return ErrorPayloadType_RetrieveJob;
+    }
     else
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
--- a/OrthancFramework/Sources/Enumerations.h	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/Enumerations.h	Sat Nov 22 10:18:45 2025 +0100
@@ -805,7 +805,8 @@
   enum ErrorPayloadType
   {
     ErrorPayloadType_None = 1,
-    ErrorPayloadType_Dimse = 2
+    ErrorPayloadType_Dimse = 2,
+    ErrorPayloadType_RetrieveJob = 3,
   };
 
 
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -369,7 +369,7 @@
           serialized.isMember(ERROR_PAYLOAD))
       {
         const std::string type = SerializationToolbox::ReadString(serialized, ERROR_PAYLOAD_TYPE);
-        lastStatus_.GetErrorPayload().SetContent(StringToErrorPayloadType(type.c_str()), serialized[ERROR_PAYLOAD]);
+        lastStatus_.GetErrorPayload().SetContent(StringToErrorPayloadType(type), serialized[ERROR_PAYLOAD]);
       }
 
       job_->Start();
@@ -1300,27 +1300,35 @@
     {
       boost::mutex::scoped_lock lock(registry_.mutex_);
 
-      switch (targetState_)
+      try
       {
-        case JobState_Failure:
-          registry_.MarkRunningAsCompleted
-            (*handler_, canceled_ ? CompletedReason_Canceled : CompletedReason_Failure);
-          break;
+        switch (targetState_)
+        {
+          case JobState_Failure:
+            registry_.MarkRunningAsCompleted
+              (*handler_, canceled_ ? CompletedReason_Canceled : CompletedReason_Failure);
+            break;
 
-        case JobState_Success:
-          registry_.MarkRunningAsCompleted(*handler_, CompletedReason_Success);
-          break;
+          case JobState_Success:
+            registry_.MarkRunningAsCompleted(*handler_, CompletedReason_Success);
+            break;
 
-        case JobState_Paused:
-          registry_.MarkRunningAsPaused(*handler_);
-          break;
+          case JobState_Paused:
+            registry_.MarkRunningAsPaused(*handler_);
+            break;
+
+          case JobState_Retry:
+            registry_.MarkRunningAsRetry(*handler_, targetRetryTimeout_);
+            break;
 
-        case JobState_Retry:
-          registry_.MarkRunningAsRetry(*handler_, targetRetryTimeout_);
-          break;
-
-        default:
-          assert(0);
+          default:
+            assert(0);
+        }
+      }
+      catch (OrthancException& e)
+      {
+        // Don't throw exceptions in destructors
+        LOG(ERROR) << "Exception in destructor: " << e.What();
       }
     }
   }
--- a/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -191,12 +191,6 @@
   }
 
 
-  void SetOfInstancesJob::Start()
-  {
-    SetOfCommandsJob::Start();
-  }
-
-
   void SetOfInstancesJob::Reset()
   {
     SetOfCommandsJob::Reset();
--- a/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h	Sat Nov 22 10:18:45 2025 +0100
@@ -73,8 +73,6 @@
 
     bool IsFailedInstance(const std::string& instance) const;
 
-    virtual void Start() ORTHANC_OVERRIDE;
-
     virtual void Reset() ORTHANC_OVERRIDE;
 
     virtual void GetPublicContent(Json::Value& target) const ORTHANC_OVERRIDE;
--- a/OrthancFramework/Sources/MallocMemoryBuffer.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/MallocMemoryBuffer.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -25,6 +25,7 @@
 #include "PrecompiledHeaders.h"
 #include "MallocMemoryBuffer.h"
 
+#include "Logging.h"
 #include "OrthancException.h"
 
 #include <string.h>
@@ -40,6 +41,20 @@
   }
 
 
+  MallocMemoryBuffer::~MallocMemoryBuffer()
+  {
+    try
+    {
+      Clear();
+    }
+    catch (OrthancException& e)
+    {
+      // Don't throw exceptions in destructors
+      LOG(ERROR) << "Exception in destructor: " << e.What();
+    }
+  }
+
+
   void MallocMemoryBuffer::Clear()
   {
     if (size_ != 0)
--- a/OrthancFramework/Sources/MallocMemoryBuffer.h	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/MallocMemoryBuffer.h	Sat Nov 22 10:18:45 2025 +0100
@@ -45,10 +45,7 @@
   public:
     MallocMemoryBuffer();
 
-    virtual ~MallocMemoryBuffer() ORTHANC_OVERRIDE
-    {
-      Clear();
-    }
+    virtual ~MallocMemoryBuffer() ORTHANC_OVERRIDE;
 
     void Clear();
 
--- a/OrthancFramework/Sources/MetricsRegistry.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/MetricsRegistry.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -27,6 +27,7 @@
 
 #include "ChunkedBuffer.h"
 #include "Compatibility.h"
+#include "Logging.h"
 #include "OrthancException.h"
 
 #include <boost/math/special_functions/round.hpp>
@@ -617,7 +618,15 @@
 
   MetricsRegistry::ActiveCounter::~ActiveCounter()
   {
-    metrics_.Add(-1);
+    try
+    {
+      metrics_.Add(-1);
+    }
+    catch (OrthancException& e)
+    {
+      // Don't throw exceptions in destructors
+      LOG(ERROR) << "Exception in destructor: " << e.What();
+    }
   }
 
 
@@ -629,7 +638,15 @@
 
   MetricsRegistry::AvailableResourcesDecounter::~AvailableResourcesDecounter()
   {
-    metrics_.Add(1);
+    try
+    {
+      metrics_.Add(1);
+    }
+    catch (OrthancException& e)
+    {
+      // Don't throw exceptions in destructors
+      LOG(ERROR) << "Exception in destructor: " << e.What();
+    }
   }
 
 
@@ -673,7 +690,16 @@
     if (active_)
     {
       boost::posix_time::time_duration diff = GetNow() - start_;
-      registry_.SetIntegerValue(name_, static_cast<int64_t>(diff.total_milliseconds()), policy_);
+
+      try
+      {
+        registry_.SetIntegerValue(name_, static_cast<int64_t>(diff.total_milliseconds()), policy_);
+      }
+      catch (OrthancException& e)
+      {
+        // Don't throw exceptions in destructors
+        LOG(ERROR) << "Exception in destructor: " << e.What();
+      }
     }
   }
 }
--- a/OrthancFramework/Sources/MultiThreading/IRunnableBySteps.h	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/Sources/MultiThreading/IRunnableBySteps.h	Sat Nov 22 10:18:45 2025 +0100
@@ -32,10 +32,6 @@
   class IRunnableBySteps : public IDynamicObject
   {
   public:
-    virtual ~IRunnableBySteps() ORTHANC_OVERRIDE
-    {
-    }
-
     // Must return "true" if the runnable wishes to continue. Must
     // return "false" if the runnable has not finished its job.
     virtual bool Step() = 0;
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -1562,7 +1562,7 @@
 
     if (components.size() != 2)
     {
-      throw;
+      throw OrthancException(ErrorCode_InternalError);
     }
 
     int a = boost::lexical_cast<int>(components[0]);
@@ -1571,7 +1571,7 @@
         b < 0 || b > 15 ||
         (a == 0 && b == 0))
     {
-      throw;
+      throw OrthancException(ErrorCode_InternalError);
     }
 
     result[i] = static_cast<uint8_t>(a * 16 + b);
--- a/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -38,7 +38,6 @@
 #include "../Sources/OrthancException.h"
 #include "../Sources/RestApi/RestApiHierarchy.h"
 #include "../Sources/WebServiceParameters.h"
-#include "../Sources/MetricsRegistry.h"
 
 #include <ctype.h>
 #include <boost/lexical_cast.hpp>
@@ -1316,7 +1315,7 @@
       {
         if (b[i] != ('0' + i % 7))
         {
-          throw;
+          throw OrthancException(ErrorCode_InternalError);
         }
       }
       
@@ -1388,6 +1387,7 @@
 #endif
 
 
+#if ORTHANC_SANDBOXED != 1
 TEST(HttpServer, GetRelativePathToRoot)
 {
   ASSERT_THROW(HttpServer::GetRelativePathToRoot(""), OrthancException);
@@ -1397,3 +1397,4 @@
   ASSERT_EQ("./../../", HttpServer::GetRelativePathToRoot("/a/b/system"));
   ASSERT_EQ("../../../", HttpServer::GetRelativePathToRoot("/a/b/system/"));
 }
+#endif
--- a/OrthancServer/CMakeLists.txt	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/CMakeLists.txt	Sat Nov 22 10:18:45 2025 +0100
@@ -266,6 +266,8 @@
   INSTALL_REVISION_AND_CUSTOM_DATA  ${CMAKE_SOURCE_DIR}/Sources/Database/InstallRevisionAndCustomData.sql  
   INSTALL_DELETED_FILES             ${CMAKE_SOURCE_DIR}/Sources/Database/InstallDeletedFiles.sql
   INSTALL_KEY_VALUE_STORES_AND_QUEUES ${CMAKE_SOURCE_DIR}/Sources/Database/InstallKeyValueStoresAndQueues.sql
+  ADD_TIMEOUT_TO_QUEUES             ${CMAKE_SOURCE_DIR}/Sources/Database/AddTimeoutToQueues.sql
+  INSTALL_DICOM_IDENTIFIERS_INDEX_3 ${CMAKE_SOURCE_DIR}/Sources/Database/InstallDicomIdentifiersIndex3.sql
   )
 
 if (STANDALONE_BUILD)
@@ -636,14 +638,6 @@
     list(APPEND MODALITY_WORKLISTS_RESOURCES ${AUTOGENERATED_DIR}/ModalityWorklists.rc)
   endif()
 
-  EmbedResources(
-    --target=ModalityWorklistsResources
-    --namespace=Orthanc.FrameworkResources
-    --framework-path=${CMAKE_SOURCE_DIR}/../OrthancFramework/Sources
-    ${LIBICU_RESOURCES}
-    ${DCMTK_DICTIONARIES}
-    )
-
   set_source_files_properties(
     ${CMAKE_SOURCE_DIR}/Plugins/Samples/ModalityWorklists/Plugin.cpp
     PROPERTIES COMPILE_DEFINITIONS "MODALITY_WORKLISTS_VERSION=\"${ORTHANC_VERSION}\""
@@ -651,14 +645,12 @@
 
   add_library(ModalityWorklists SHARED 
     ${CMAKE_SOURCE_DIR}/Plugins/Samples/ModalityWorklists/Plugin.cpp
-    ${CMAKE_SOURCE_DIR}/Plugins/Samples/ModalityWorklists/OrthancFrameworkDependencies.cpp
-    ${AUTOGENERATED_DIR}/ModalityWorklistsResources.cpp
     ${MODALITY_WORKLISTS_RESOURCES}
     )
 
   DefineSourceBasenameForTarget(ModalityWorklists)
 
-  target_link_libraries(ModalityWorklists PluginsDependencies ${DCMTK_LIBRARIES})
+  target_link_libraries(ModalityWorklists PluginsDependencies)
 
   set_target_properties(
     ModalityWorklists PROPERTIES 
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -1499,6 +1499,21 @@
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
 
+    virtual bool ReserveQueueValue(std::string& value,
+                                   uint64_t& valueId, 
+                                   const std::string& queueId,
+                                   QueueOrigin origin,
+                                   uint32_t releaseTimeout) ORTHANC_OVERRIDE
+    {
+      throw OrthancException(ErrorCode_InternalError);  // Not supported
+    }
+      
+    virtual void AcknowledgeQueueValue(const std::string& queueId,
+                                       uint64_t valueId) ORTHANC_OVERRIDE
+    {
+      throw OrthancException(ErrorCode_InternalError);  // Not supported
+    }
+
     virtual void GetAttachmentCustomData(std::string& customData,
                                          const std::string& attachmentUuid) ORTHANC_OVERRIDE
     {
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -1111,6 +1111,21 @@
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
 
+    virtual bool ReserveQueueValue(std::string& value,
+                                   uint64_t& valueId, 
+                                   const std::string& queueId,
+                                   QueueOrigin origin,
+                                   uint32_t releaseTimeout) ORTHANC_OVERRIDE
+    {
+      throw OrthancException(ErrorCode_InternalError);  // Not supported
+    }
+      
+    virtual void AcknowledgeQueueValue(const std::string& queueId,
+                                       uint64_t valueId) ORTHANC_OVERRIDE
+    {
+      throw OrthancException(ErrorCode_InternalError);  // Not supported
+    }
+
     virtual void GetAttachmentCustomData(std::string& customData,
                                          const std::string& attachmentUuid) ORTHANC_OVERRIDE
     {
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -2051,6 +2051,73 @@
         throw OrthancException(ErrorCode_InternalError);
       }
     }
+
+    virtual bool ReserveQueueValue(std::string& value,
+                                   uint64_t& valueId, 
+                                   const std::string& queueId,
+                                   QueueOrigin origin,
+                                   uint32_t releaseTimeout) ORTHANC_OVERRIDE
+    {
+      if (database_.GetDatabaseCapabilities().HasReserveQueueValueSupport())
+      {
+        DatabasePluginMessages::TransactionRequest request;
+        request.mutable_reserve_queue_value()->set_queue_id(queueId);
+        request.mutable_reserve_queue_value()->set_release_timeout(releaseTimeout);
+
+        switch (origin)
+        {
+          case QueueOrigin_Back:
+            request.mutable_dequeue_value()->set_origin(DatabasePluginMessages::QUEUE_ORIGIN_BACK);
+            break;
+
+          case QueueOrigin_Front:
+            request.mutable_dequeue_value()->set_origin(DatabasePluginMessages::QUEUE_ORIGIN_FRONT);
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+
+        DatabasePluginMessages::TransactionResponse response;
+        ExecuteTransaction(response, DatabasePluginMessages::OPERATION_RESERVE_QUEUE_VALUE, request);
+
+        if (response.reserve_queue_value().found())
+        {
+          value = response.reserve_queue_value().value();
+          valueId = response.reserve_queue_value().value_id();
+          return true;
+        }
+        else
+        {
+          return false;
+        }
+      }
+      else
+      {
+        // This method shouldn't have been called
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+      
+    virtual void AcknowledgeQueueValue(const std::string& queueId,
+                                       uint64_t valueId) ORTHANC_OVERRIDE
+    {
+
+      if (database_.GetDatabaseCapabilities().HasReserveQueueValueSupport())
+      {
+        DatabasePluginMessages::TransactionRequest request;
+        request.mutable_acknowledge_queue_value()->set_queue_id(queueId);
+        request.mutable_acknowledge_queue_value()->set_value_id(valueId);
+
+        ExecuteTransaction(DatabasePluginMessages::OPERATION_ACKNOWLEDGE_QUEUE_VALUE, request);
+      }
+      else
+      {
+        // This method shouldn't have been called
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
   };
 
 
@@ -2143,6 +2210,7 @@
       dbCapabilities_.SetHasFindSupport(systemInfo.supports_find());
       dbCapabilities_.SetKeyValueStoresSupport(systemInfo.supports_key_value_stores());
       dbCapabilities_.SetQueuesSupport(systemInfo.supports_queues());
+      dbCapabilities_.SetReserveQueueValueSupport(systemInfo.supports_reserve_queue_value());
       dbCapabilities_.SetAttachmentCustomDataSupport(systemInfo.has_attachment_custom_data());
     }
 
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -4723,6 +4723,14 @@
     }
   }
 
+  static void CheckReserveQueueValueSupport(ServerContext& context)
+  {
+    if (!context.GetIndex().HasReserveQueueValueSupport())
+    {
+      throw OrthancException(ErrorCode_NotImplemented, "The database engine does not support queues (extended)");
+    }
+  }
+
   void OrthancPlugins::ApplyEnqueueValue(const _OrthancPluginEnqueueValue& parameters)
   {
     PImpl::ServerContextReference lock(*pimpl_);
@@ -4760,6 +4768,37 @@
     *parameters.size = lock.GetContext().GetIndex().GetQueueSize(parameters.queueId);
   }
 
+  void OrthancPlugins::ApplyReserveQueueValue(const _OrthancPluginReserveQueueValue& parameters)
+  {
+    PImpl::ServerContextReference lock(*pimpl_);
+
+    CheckReserveQueueValueSupport(lock.GetContext());
+
+    std::string value;
+    uint64_t valueId;
+
+    if (lock.GetContext().GetIndex().ReserveQueueValue(value, valueId, parameters.queueId, Plugins::Convert(parameters.origin), parameters.releaseTimeout))
+    {
+      CopyToMemoryBuffer(parameters.target, value);
+      *parameters.found = true;
+      *parameters.valueId = valueId;
+    }
+    else
+    {
+      *parameters.found = false;
+    }
+  }
+
+  void OrthancPlugins::ApplyAknowledgeQueueValue(const _OrthancPluginAcknowledgeQueueValue& parameters)
+  {
+    PImpl::ServerContextReference lock(*pimpl_);
+
+    CheckReserveQueueValueSupport(lock.GetContext());
+
+    lock.GetContext().GetIndex().AcknowledgeQueueValue(parameters.queueId, parameters.valueId);
+  }
+
+
   void OrthancPlugins::ApplySetStableStatus(const _OrthancPluginSetStableStatus& parameters)
   {
     PImpl::ServerContextReference lock(*pimpl_);
@@ -5920,6 +5959,20 @@
         return true;
       }
 
+      case _OrthancPluginService_ReserveQueueValue:
+      {
+        const _OrthancPluginReserveQueueValue& p = *reinterpret_cast<const _OrthancPluginReserveQueueValue*>(parameters);
+        ApplyReserveQueueValue(p);
+        return true;
+      }
+
+      case _OrthancPluginService_AcknowledgeQueueValue:
+      {
+        const _OrthancPluginAcknowledgeQueueValue& p = *reinterpret_cast<const _OrthancPluginAcknowledgeQueueValue*>(parameters);
+        ApplyAknowledgeQueueValue(p);
+        return true;
+      }
+
       case _OrthancPluginService_SetStableStatus:
       {
         const _OrthancPluginSetStableStatus& p = *reinterpret_cast<const _OrthancPluginSetStableStatus*>(parameters);
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h	Sat Nov 22 10:18:45 2025 +0100
@@ -249,6 +249,10 @@
 
     void ApplyGetQueueSize(const _OrthancPluginGetQueueSize& parameters);
 
+    void ApplyReserveQueueValue(const _OrthancPluginReserveQueueValue& parameters);
+
+    void ApplyAknowledgeQueueValue(const _OrthancPluginAcknowledgeQueueValue& parameters);
+
     void ApplySetStableStatus(const _OrthancPluginSetStableStatus& parameters);
 
     void ApplyEmitAuditLog(const _OrthancPluginEmitAuditLog& parameters);
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Sat Nov 22 10:18:45 2025 +0100
@@ -123,7 +123,7 @@
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     12
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  9
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  10
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -519,6 +519,8 @@
     _OrthancPluginService_GetQueueSize = 59,                        /* New in Orthanc 1.12.8 */
     _OrthancPluginService_SetStableStatus = 60,                     /* New in Orthanc 1.12.9 */
     _OrthancPluginService_EmitAuditLog = 61,                        /* New in Orthanc 1.12.9 */
+    _OrthancPluginService_ReserveQueueValue = 62,                   /* New in Orthanc 1.12.10 */
+    _OrthancPluginService_AcknowledgeQueueValue = 63,               /* New in Orthanc 1.12.10 */
 
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
@@ -2178,7 +2180,7 @@
    **/
   ORTHANC_PLUGIN_SINCE_SDK("1.4.0")
   ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginCheckVersionAdvanced(
-    OrthancPluginContext* context,
+    const OrthancPluginContext* context,
     int32_t expectedMajor,
     int32_t expectedMinor,
     int32_t expectedRevision)
@@ -2293,7 +2295,7 @@
    * @ingroup Callbacks
    **/
   ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginCheckVersion(
-    OrthancPluginContext* context)
+    const OrthancPluginContext* context)
   {
     return OrthancPluginCheckVersionAdvanced(
       context,
@@ -10412,9 +10414,12 @@
    * @param queueId A unique identifier identifying both the plugin and the queue.
    * @param origin The position from where the value is dequeued (back for LIFO, front for FIFO).
    * @return 0 if success, other value if error.
+   * @deprecated This function should not be used anymore because there is a risk of loosing
+   * a value if the consumer plugin crashes before it has processed the value. Use
+   * OrthancPluginReserveQueueValue() and OrthancPluginAcknowledgeQueueValue() if possible.
    **/
   ORTHANC_PLUGIN_SINCE_SDK("1.12.8")
-  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDequeueValue(
+  ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDequeueValue(
     OrthancPluginContext*         context,
     uint8_t*                      found,    /* out */
     OrthancPluginMemoryBuffer*    target,   /* out */
@@ -10438,7 +10443,7 @@
   } _OrthancPluginGetQueueSize;
 
   /**
-   * @brief Get the number of elements in a queue.
+   * @brief Get the number of elements that are currently stored in a queue.
    *
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param queueId A unique identifier identifying both the plugin and the queue.
@@ -10720,6 +10725,86 @@
   // )
   
 
+  typedef struct
+  {
+    uint8_t*                      found;
+    OrthancPluginMemoryBuffer*    target;
+    const char*                   queueId;
+    OrthancPluginQueueOrigin      origin;
+    uint32_t                      releaseTimeout;
+    uint64_t*                     valueId;
+  } _OrthancPluginReserveQueueValue;
+
+  /**
+   * @brief Reserve a value from a queue, which means that the message is not
+   *        available to other consumers.
+   *        The value is either:
+   *        - removed from the queue when one calls OrthancPluginAcknowledgeQueueValue()
+   *        - or made available again for consumers after the releaseTimeout has expired.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param found Pointer to a Boolean that is set to "true" iff. a value has been dequeued.
+   * @param target Memory buffer where to store the value that has been retrieved from the queue.
+   * It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param queueId A unique identifier identifying both the plugin and the queue.
+   * @param origin The position from where the value is dequeued (back for LIFO, front for FIFO).
+   * @param releaseTimeout Timeout in seconds. If the value is not acknowledged before this delay expires,
+   *                       the value is automatically released and made available for further calls to
+   *                       OrthancPluginDequeueValue() or OrthancPluginReserveQueueValue().
+   *                       This value cannot be equal to 0 second.
+   * @param valueId An opaque identifier for this value, to be subsequently provided to
+   * OrthancPluginAcknowledgeQueueValue().
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_SINCE_SDK("1.12.10")
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReserveQueueValue(
+    OrthancPluginContext*         context,
+    uint8_t*                      found,    /* out */
+    OrthancPluginMemoryBuffer*    target,   /* out */
+    uint64_t*                     valueId,  /* out */
+    const char*                   queueId,  /* in */
+    OrthancPluginQueueOrigin      origin,   /* in */
+    uint32_t                      releaseTimeout /* in */)
+  {
+    _OrthancPluginReserveQueueValue params;
+    params.found = found;
+    params.target = target;
+    params.queueId = queueId;
+    params.origin = origin;
+    params.valueId = valueId;
+    params.releaseTimeout = releaseTimeout;
+
+    return context->InvokeService(context, _OrthancPluginService_ReserveQueueValue, &params);
+  }
+
+  typedef struct
+  {
+    const char*                   queueId;
+    uint64_t                      valueId;
+  } _OrthancPluginAcknowledgeQueueValue;
+
+  /**
+   * @brief Acknowledge that a queue value has been properly consumed by the plugin
+   * and can be permanently removed from the queue.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param queueId A unique identifier identifying both the plugin and the queue.
+   * @param valueId The opaque identifier for the value provided by OrthancPluginReserveQueueValue().
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_SINCE_SDK("1.12.10")
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginAcknowledgeQueueValue(
+    OrthancPluginContext*         context,
+    const char*                   queueId,  /* in */
+    uint64_t                      valueId  /* in */)
+  {
+    _OrthancPluginAcknowledgeQueueValue params;
+    params.queueId = queueId;
+    params.valueId = valueId;
+
+    return context->InvokeService(context, _OrthancPluginService_AcknowledgeQueueValue, &params);
+  }
+
 #ifdef  __cplusplus
 }
 #endif
--- a/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Sat Nov 22 10:18:45 2025 +0100
@@ -175,6 +175,7 @@
     bool supports_key_value_stores = 10;  // New in Orthanc 1.12.8
     bool supports_queues = 11;            // New in Orthanc 1.12.8
     bool has_attachment_custom_data = 12; // New in Orthanc 1.12.8
+    bool supports_reserve_queue_value = 13;   // New in Orthanc 1.12.10
   }
 }
 
@@ -339,7 +340,8 @@
   OPERATION_GET_QUEUE_SIZE = 59;              // New in Orthanc 1.12.8
   OPERATION_GET_ATTACHMENT_CUSTOM_DATA = 60;  // New in Orthanc 1.12.8
   OPERATION_SET_ATTACHMENT_CUSTOM_DATA = 61;  // New in Orthanc 1.12.8
-
+  OPERATION_RESERVE_QUEUE_VALUE = 62;         // New in Orthanc 1.12.10
+  OPERATION_ACKNOWLEDGE_QUEUE_VALUE = 63;     // New in Orthanc 1.12.10
 }
 
 message Rollback {
@@ -1095,6 +1097,30 @@
   }
 }
 
+message ReserveQueueValue {
+  message Request {
+    string queue_id = 1;
+    QueueOrigin origin = 2;
+    uint32 release_timeout = 3;
+  }
+
+  message Response {
+    bool found = 1;
+    bytes value = 2;
+    uint64 value_id = 3;
+  }
+}
+
+message AcknowledgeQueueValue {
+  message Request {
+    string queue_id = 1;
+    uint64 value_id = 2;
+  }
+
+  message Response {
+  }
+}
+
 
 message TransactionRequest {
   sfixed64              transaction = 1;
@@ -1162,6 +1188,8 @@
   GetQueueSize.Request                    get_queue_size = 159;
   GetAttachmentCustomData.Request         get_attachment_custom_data = 160;
   SetAttachmentCustomData.Request         set_attachment_custom_data = 161;
+  ReserveQueueValue.Request               reserve_queue_value = 162;
+  AcknowledgeQueueValue.Request           acknowledge_queue_value = 163;
 }
 
 message TransactionResponse {
@@ -1227,6 +1255,8 @@
   GetQueueSize.Response                    get_queue_size = 159;
   GetAttachmentCustomData.Response         get_attachment_custom_data = 160;
   SetAttachmentCustomData.Response         set_attachment_custom_data = 161;
+  ReserveQueueValue.Response               reserve_queue_value = 162;
+  AcknowledgeQueueValue.Response           acknowledge_queue_value = 163;
 }
 
 enum RequestType {
--- a/OrthancServer/Plugins/Include/orthanc/OrthancPluginCodeModel.json	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancPluginCodeModel.json	Sat Nov 22 10:18:45 2025 +0100
@@ -2234,7 +2234,7 @@
                     "value": 2002
                 },
                 {
-                    "documentation": "The TCP port of the HTTP server is privileged or already in use",
+                    "documentation": "The TCP port of the HTTP server is privileged or already in use or one of the HTTP bind addresses does not exist",
                     "key": "HttpPortInUse",
                     "value": 2003
                 },
@@ -5486,6 +5486,37 @@
                 12,
                 9
             ]
+        },
+        {
+            "args": [
+                {
+                    "name": "arg0",
+                    "sdk_name": "queueId",
+                    "sdk_type": "const char *"
+                },
+                {
+                    "name": "arg1",
+                    "sdk_name": "valueId",
+                    "sdk_type": "uint64_t"
+                }
+            ],
+            "c_function": "OrthancPluginAcknowledgeQueueValue",
+            "documentation": {
+                "args": {
+                    "queueId": "A unique identifier identifying both the plugin and the queue.",
+                    "valueId": "The opaque identifier for the value provided by OrthancPluginReserveQueueValue()."
+                },
+                "description": [],
+                "return": "0 if success, other value if error.",
+                "summary": "Acknowledge that a queue value has been properly consumed by the plugin and can be permanently removed from the queue."
+            },
+            "return_sdk_enumeration": "OrthancPluginErrorCode",
+            "return_sdk_type": "enumeration",
+            "since_sdk": [
+                1,
+                12,
+                10
+            ]
         }
     ],
     "unwrapped_functions": [
@@ -5538,6 +5569,7 @@
         "OrthancPluginGetQueueSize",
         "OrthancPluginSetStableStatus",
         "OrthancPluginRegisterHttpAuthentication",
-        "OrthancPluginRegisterAuditLogHandler"
+        "OrthancPluginRegisterAuditLogHandler",
+        "OrthancPluginReserveQueueValue"
     ]
 }
\ No newline at end of file
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -221,6 +221,19 @@
   }
 
 
+  MemoryBuffer::~MemoryBuffer()
+  {
+    try
+    {
+      Clear();
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
+    {
+      // Don't throw exceptions in destructors
+    }
+  }
+
+
   void MemoryBuffer::Clear()
   {
     if (buffer_.data != NULL)
@@ -351,6 +364,8 @@
     }
   }
 
+
+#if (HAS_ORTHANC_PLUGIN_PEERS == 1) || (HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1) || (HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1)
   static void DecodeHttpHeaders(HttpHeaders& target,
                                 const MemoryBuffer& source)
   {
@@ -378,6 +393,8 @@
       }
     }
   }
+#endif
+
 
   // helper class to convert std::map of headers to the plugin SDK C structure
   class PluginHttpHeaders
@@ -652,6 +669,19 @@
   }
 
 
+  OrthancString::~OrthancString()
+  {
+    try
+    {
+      Clear();
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
+    {
+      // Don't throw exceptions in destructors
+    }
+  }
+
+
   void OrthancString::Assign(char* str)
   {
     Clear();
@@ -1301,6 +1331,20 @@
     }
   }
 
+
+  OrthancImage::~OrthancImage()
+  {
+    try
+    {
+      Clear();
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
+    {
+      // Don't throw exceptions in destructors
+    }
+  }
+
+
   void OrthancImage::UncompressPngImage(const void* data,
                                         size_t size)
   {
@@ -4607,8 +4651,11 @@
     uint8_t found = false;
     OrthancPlugins::MemoryBuffer valueBuffer;
 
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
     OrthancPluginErrorCode code = OrthancPluginDequeueValue(OrthancPlugins::GetGlobalContext(), &found,
                                                             *valueBuffer, queueId_.c_str(), origin);
+#pragma GCC diagnostic pop
 
     if (code != OrthancPluginErrorCode_Success)
     {
@@ -4643,4 +4690,54 @@
     }
   }
 #endif
+
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+  bool Queue::ReserveInternal(std::string& value, uint64_t& valueId, OrthancPluginQueueOrigin origin, uint32_t releaseTimeout)
+  {
+    uint8_t found = false;
+    OrthancPlugins::MemoryBuffer valueBuffer;
+
+    OrthancPluginErrorCode code = OrthancPluginReserveQueueValue(OrthancPlugins::GetGlobalContext(), &found,
+                                                                 *valueBuffer, &valueId, queueId_.c_str(), origin, releaseTimeout);
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+    else if (found)
+    {
+      valueBuffer.ToString(value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+  bool Queue::ReserveBack(std::string& value, uint64_t& valueId, uint32_t releaseTimeout)
+  {
+    return ReserveInternal(value, valueId, OrthancPluginQueueOrigin_Back, releaseTimeout);
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+  bool Queue::ReserveFront(std::string& value, uint64_t& valueId, uint32_t releaseTimeout)
+  {
+    return ReserveInternal(value, valueId, OrthancPluginQueueOrigin_Front, releaseTimeout);
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+  void Queue::Acknowledge(uint64_t valueId)
+  {
+    OrthancPluginAcknowledgeQueueValue(OrthancPlugins::GetGlobalContext(), queueId_.c_str(), valueId);
+  }
+#endif
 }
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Sat Nov 22 10:18:45 2025 +0100
@@ -142,6 +142,12 @@
 #  define HAS_ORTHANC_PLUGIN_QUEUES            0
 #endif
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 10)
+#  define HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE   1
+#else
+#  define HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE   0
+#endif
+
 
 // Macro to tag a function as having been deprecated
 #if (__cplusplus >= 201402L)  // C++14
@@ -213,10 +219,7 @@
   public:
     MemoryBuffer();
 
-    ~MemoryBuffer()
-    {
-      Clear();
-    }
+    ~MemoryBuffer();
 
     OrthancPluginMemoryBuffer* operator*()
     {
@@ -371,10 +374,7 @@
     {
     }
 
-    ~OrthancString()
-    {
-      Clear();
-    }
+    ~OrthancString();
 
     // This transfers ownership, warning: The string must have been
     // allocated by the Orthanc core
@@ -491,10 +491,7 @@
                  uint32_t                  pitch,
                  void*                     buffer);
 
-    ~OrthancImage()
-    {
-      Clear();
-    }
+    ~OrthancImage();
 
     void UncompressPngImage(const void* data,
                             size_t size);
@@ -1726,6 +1723,10 @@
 
     bool DequeueInternal(std::string& value, OrthancPluginQueueOrigin origin);
 
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    bool ReserveInternal(std::string& value, uint64_t& valueId, OrthancPluginQueueOrigin origin, uint32_t releaseTimeout);
+#endif
+
   public:
     explicit Queue(const std::string& queueId) :
       queueId_(queueId)
@@ -1745,17 +1746,37 @@
       Enqueue(value.empty() ? NULL : value.c_str(), value.size());
     }
 
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    // Use ReserveBack() instead
+    ORTHANC_PLUGIN_DEPRECATED
+#endif
     bool DequeueBack(std::string& value)
     {
       return DequeueInternal(value, OrthancPluginQueueOrigin_Back);
     }
 
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    // Use ReserveFront() instead
+    ORTHANC_PLUGIN_DEPRECATED
+#endif
     bool DequeueFront(std::string& value)
     {
       return DequeueInternal(value, OrthancPluginQueueOrigin_Front);
     }
 
     uint64_t GetSize();
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    bool ReserveBack(std::string& value, uint64_t& valueId, uint32_t releaseTimeout);
+#endif
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    bool ReserveFront(std::string& value, uint64_t& valueId, uint32_t releaseTimeout);
+#endif
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    void Acknowledge(uint64_t valueId);
+#endif
   };
 #endif
 }
--- a/OrthancServer/Plugins/Samples/ModalityWorklists/CMakeLists.txt	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Samples/ModalityWorklists/CMakeLists.txt	Sat Nov 22 10:18:45 2025 +0100
@@ -27,31 +27,22 @@
 SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
 SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
 
-include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake)
-include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake)
+SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
+SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost")
 
-set(ENABLE_JPEG ON CACHE INTERNAL "")
-set(ENABLE_PNG ON CACHE INTERNAL "")
-set(ENABLE_ZLIB ON CACHE INTERNAL "")
-set(ENABLE_LOCALE ON CACHE INTERNAL "")
-set(ENABLE_DCMTK ON CACHE INTERNAL "")
-set(ENABLE_MODULE_DICOM ON CACHE INTERNAL "")
-set(ENABLE_MODULE_JOBS OFF CACHE INTERNAL "")
-
-include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake)
+include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/JsonCppConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/BoostConfiguration.cmake)
 
 add_library(ModalityWorklists SHARED 
   Plugin.cpp
   ${CMAKE_SOURCE_DIR}/../Common/OrthancPluginCppWrapper.cpp
-  ${ORTHANC_CORE_SOURCES}
-  ${ORTHANC_DICOM_SOURCES}
-  ${AUTOGENERATED_SOURCES}
+  ${JSONCPP_SOURCES}
+  ${BOOST_SOURCES}
   )
 
 DefineSourceBasenameForTarget(ModalityWorklists)
 
-target_link_libraries(ModalityWorklists ${DCMTK_LIBRARIES})
-
 message("Setting the version of the plugin to ${MODALITY_WORKLISTS_VERSION}")
 add_definitions(
   -DMODALITY_WORKLISTS_VERSION="${MODALITY_WORKLISTS_VERSION}"
--- a/OrthancServer/Plugins/Samples/ModalityWorklists/OrthancFrameworkDependencies.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-/**
- * Remove the dependency upon ICU in plugins, as this greatly increase
- * the size of the resulting binaries, since they must embed the ICU
- * dictionary.
- **/
-
-#if BOOST_LOCALE_WITH_ICU == 1
-#  undef BOOST_LOCALE_WITH_ICU
-#  if ORTHANC_STATIC_ICU == 1
-#    include <unicode/udata.h>
-
-// Define an empty ICU dictionary for static builds
-extern "C"
-{
-  struct
-  {
-    double bogus;
-    uint8_t *bytes;
-  } U_ICUDATA_ENTRY_POINT = { 0.0, NULL };
-}
-
-#  endif
-#endif
-
-#include "../../../../OrthancFramework/Sources/ChunkedBuffer.cpp"
-#include "../../../../OrthancFramework/Sources/Compression/DeflateBaseCompressor.cpp"
-#include "../../../../OrthancFramework/Sources/Compression/GzipCompressor.cpp"
-#include "../../../../OrthancFramework/Sources/Compression/ZlibCompressor.cpp"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomArray.cpp"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomElement.cpp"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomInstanceHasher.cpp"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomPath.cpp"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomTag.cpp"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomValue.cpp"
-#include "../../../../OrthancFramework/Sources/DicomFormat/Window.cpp"
-#include "../../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp"
-#include "../../../../OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.cpp"
-#include "../../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp"
-#include "../../../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp"
-#include "../../../../OrthancFramework/Sources/DicomParsing/ToDcmtkBridge.cpp"
-#include "../../../../OrthancFramework/Sources/Enumerations.cpp"
-#include "../../../../OrthancFramework/Sources/HttpServer/HttpOutput.cpp"
-#include "../../../../OrthancFramework/Sources/Images/IImageWriter.cpp"
-#include "../../../../OrthancFramework/Sources/Images/Image.cpp"
-#include "../../../../OrthancFramework/Sources/Images/ImageAccessor.cpp"
-#include "../../../../OrthancFramework/Sources/Images/ImageBuffer.cpp"
-#include "../../../../OrthancFramework/Sources/Images/ImageProcessing.cpp"
-#include "../../../../OrthancFramework/Sources/Images/JpegErrorManager.cpp"
-#include "../../../../OrthancFramework/Sources/Images/JpegReader.cpp"
-#include "../../../../OrthancFramework/Sources/Images/JpegWriter.cpp"
-#include "../../../../OrthancFramework/Sources/Images/PamReader.cpp"
-#include "../../../../OrthancFramework/Sources/Images/PamWriter.cpp"
-#include "../../../../OrthancFramework/Sources/Images/PngReader.cpp"
-#include "../../../../OrthancFramework/Sources/Images/PngWriter.cpp"
-#include "../../../../OrthancFramework/Sources/Logging.cpp"
-#include "../../../../OrthancFramework/Sources/MetricsRegistry.cpp"
-#include "../../../../OrthancFramework/Sources/MultiThreading/RunnableWorkersPool.cpp"
-#include "../../../../OrthancFramework/Sources/MultiThreading/SharedMessageQueue.cpp"
-#include "../../../../OrthancFramework/Sources/OrthancException.cpp"
-#include "../../../../OrthancFramework/Sources/OrthancFramework.cpp"
-#include "../../../../OrthancFramework/Sources/RestApi/RestApiOutput.cpp"
-#include "../../../../OrthancFramework/Sources/SerializationToolbox.cpp"
-#include "../../../../OrthancFramework/Sources/SystemToolbox.cpp"
-#include "../../../../OrthancFramework/Sources/TemporaryFile.cpp"
-#include "../../../../OrthancFramework/Sources/Toolbox.cpp"
-
-namespace Orthanc
-{
-  void HttpClient::GlobalInitialize()
-  {
-  }
-
-  void HttpClient::GlobalFinalize()
-  {
-  }
-}
--- a/OrthancServer/Plugins/Samples/ModalityWorklists/Plugin.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Samples/ModalityWorklists/Plugin.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -24,84 +24,17 @@
 #define MODALITY_WORKLISTS_NAME "worklists"
 
 #include "../../../../OrthancFramework/Sources/Compatibility.h"
-#include "../../../../OrthancFramework/Sources/OrthancException.h"
-#include "../../../../OrthancFramework/Sources/Logging.h"
-#include "../../../../OrthancFramework/Sources/Toolbox.h"
-#include "../../../../OrthancFramework/Sources/SystemToolbox.h"
-#include "../../../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomPath.h"
-#include "../../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomInstanceHasher.h"
 #include "../Common/OrthancPluginCppWrapper.h"
 
-#include <boost/thread.hpp>
 #include <boost/filesystem.hpp>
 #include <json/value.h>
 #include <string.h>
 #include <iostream>
 #include <algorithm>
 
-static boost::filesystem::path worklistDirectory_;
+static std::string folder_;
 static bool filterIssuerAet_ = false;
 static unsigned int limitAnswers_ = 0;
-static std::unique_ptr<boost::thread> worklistHousekeeperThread_;
-static bool worklistHousekeeperThreadShouldStop_ = false;
-static bool deleteWorklistsOnStableStudy_ = true;
-static unsigned int hkIntervalInSeconds_ = 60;
-static unsigned int deleteDelayInHours_ = 24;
-static std::unique_ptr<OrthancPlugins::KeyValueStore> worklistsStore_;
-static bool setStudyInstanceUidIfMissing_ = true;
-
-enum WorklistStorageType
-{
-  WorklistStorageType_Folder = 1,
-  WorklistStorageType_OrthancDb = 2
-};
-
-static WorklistStorageType worklistStorage_ = WorklistStorageType_Folder;
-
-struct Worklist
-{
-  std::string id_;
-  std::string createdAt_;
-  std::string dicomContent_;
-
-  Worklist(const std::string& id, 
-           const std::string& dicomContent) :
-    id_(id),
-    createdAt_(Orthanc::SystemToolbox::GetNowIsoString(true)),
-    dicomContent_(dicomContent)
-  {
-  }
-
-
-  Worklist(const std::string& id, const Json::Value& jsonWl) :
-    id_(id),
-    createdAt_(jsonWl["CreatedAt"].asString())
-  {
-    std::string b64DicomContent = jsonWl["Dicom"].asString();
-    Orthanc::Toolbox::DecodeBase64(dicomContent_, b64DicomContent);
-  }
-
-  void Serialize(std::string& target) const
-  {
-    Json::Value t;
-    t["CreatedAt"] = createdAt_;
-    std::string b64DicomContent;
-    Orthanc::Toolbox::EncodeBase64(b64DicomContent, dicomContent_);
-    t["Dicom"] = b64DicomContent;
-    
-    Orthanc::Toolbox::WriteFastJson(target, t);
-  }
-
-  bool IsOlderThan(unsigned int delayInHours) const
-  {
-    boost::posix_time::ptime now(boost::posix_time::from_iso_string(Orthanc::SystemToolbox::GetNowIsoString(true)));
-    boost::posix_time::ptime creationDate(boost::posix_time::from_iso_string(createdAt_));
-
-    return (now - creationDate).total_seconds() > (3600 * deleteDelayInHours_);
-  }
-};
 
 /**
  * This is the main function for matching a DICOM worklist against a query.
@@ -109,10 +42,10 @@
 static bool MatchWorklist(OrthancPluginWorklistAnswers*      answers,
                            const OrthancPluginWorklistQuery*  query,
                            const OrthancPlugins::FindMatcher& matcher,
-                           const std::string& dicomContent)
+                           const std::string& path)
 {
   OrthancPlugins::MemoryBuffer dicom;
-  dicom.Assign(dicomContent);
+  dicom.ReadFile(path);
 
   if (matcher.IsMatch(dicom))
   {
@@ -204,116 +137,70 @@
   }
 }
 
-static void ListWorklistsFromDb(std::vector<Worklist>& target)
-{
-  assert(worklistStorage_ == WorklistStorageType_OrthancDb);
-  assert(worklistsStore_);
-  
-  std::unique_ptr<OrthancPlugins::KeyValueStore::Iterator> it(worklistsStore_->CreateIterator());
-  
-  while (it->Next())
-  {
-    std::string serialized;
-    it->GetValue(serialized);
-    
-    Json::Value jsonWl;
-    if (Orthanc::Toolbox::ReadJson(jsonWl, serialized))
-    {
-      Worklist wl(it->GetKey(), jsonWl);
-      target.push_back(wl);
-    }
-  };
-  
-}
 
 
-static void ListWorklistsFromFolder(std::vector<Worklist>& target)
-{
-  assert(worklistStorage_ == WorklistStorageType_Folder);
-
-  // Loop over the regular files in the database folder
-  namespace fs = boost::filesystem;
-
-  fs::path source = worklistDirectory_;
-  fs::directory_iterator end;
-
-  try
-  {
-    for (fs::directory_iterator it(source); it != end; ++it)
-    {
-      fs::file_type type(it->status().type());
-
-      if (type == fs::regular_file ||
-          type == fs::reparse_file)   // cf. BitBucket issue #11
-      {
-        std::string extension = it->path().extension().string();
-        std::transform(extension.begin(), extension.end(), extension.begin(), tolower);  // Convert to lowercase
-
-        if (extension == ".wl")
-        {
-          std::string worklistId = Orthanc::SystemToolbox::PathToUtf8(it->path().filename().replace_extension(""));
-          std::string dicomContent;
-          Orthanc::SystemToolbox::ReadFile(dicomContent, it->path());
-
-          Worklist wl(worklistId, dicomContent);
-          target.push_back(wl);
-        }
-      }
-    }
-  }
-  catch (fs::filesystem_error&)
-  {
-    LOG(ERROR) << "Inexistent folder while scanning for worklists: " + source.string();
-  }
-}
-
-
-OrthancPluginErrorCode WorklistCallback(OrthancPluginWorklistAnswers*     answers,
-                                        const OrthancPluginWorklistQuery* query,
-                                        const char*                       issuerAet,
-                                        const char*                       calledAet)
+OrthancPluginErrorCode Callback(OrthancPluginWorklistAnswers*     answers,
+                                const OrthancPluginWorklistQuery* query,
+                                const char*                       issuerAet,
+                                const char*                       calledAet)
 {
   try
   {
-    unsigned int parsedFilesCount = 0;
-    unsigned int matchedWorklistCount = 0;
-
     // Construct an object to match the worklists in the database against the C-Find query
     std::unique_ptr<OrthancPlugins::FindMatcher> matcher(CreateMatcher(query, issuerAet));
 
-    std::vector<Worklist> worklists;
+    // Loop over the regular files in the database folder
+    namespace fs = boost::filesystem;
 
-    if (worklistStorage_ == WorklistStorageType_Folder)
+    fs::path source(folder_);
+    fs::directory_iterator end;
+
+    try
     {
-      ListWorklistsFromFolder(worklists);
-    }
-    else if (worklistStorage_ == WorklistStorageType_OrthancDb)
-    {
-      ListWorklistsFromDb(worklists);
-    }
-
-    for (std::vector<Worklist>::const_iterator it = worklists.begin(); it != worklists.end(); ++it)
-    {
-      if (MatchWorklist(answers, query, *matcher, it->dicomContent_))
+      unsigned int parsedFilesCount = 0;
+      unsigned int matchedWorklistCount = 0;
+      
+      for (fs::directory_iterator it(source); it != end; ++it)
       {
-        if (limitAnswers_ != 0 &&
-            matchedWorklistCount >= limitAnswers_)
+        fs::file_type type(it->status().type());
+
+        if (type == fs::regular_file ||
+            type == fs::reparse_file)   // cf. BitBucket issue #11
         {
-          // Too many answers are to be returned wrt. the
-          // "LimitAnswers" configuration parameter. Mark the
-          // C-FIND result as incomplete.
-          OrthancPluginWorklistMarkIncomplete(OrthancPlugins::GetGlobalContext(), answers);
-          return OrthancPluginErrorCode_Success;
+          std::string extension = it->path().extension().string();
+          std::transform(extension.begin(), extension.end(), extension.begin(), tolower);  // Convert to lowercase
+
+          if (extension == ".wl")
+          {
+            parsedFilesCount++;
+            // We found a worklist (i.e. a DICOM find with extension ".wl"), match it against the query
+            if (MatchWorklist(answers, query, *matcher, it->path().string()))
+            {
+              if (limitAnswers_ != 0 &&
+                  matchedWorklistCount >= limitAnswers_)
+              {
+                // Too many answers are to be returned wrt. the
+                // "LimitAnswers" configuration parameter. Mark the
+                // C-FIND result as incomplete.
+                OrthancPluginWorklistMarkIncomplete(OrthancPlugins::GetGlobalContext(), answers);
+                return OrthancPluginErrorCode_Success;
+              }
+              
+              ORTHANC_PLUGINS_LOG_INFO("Worklist matched: " + it->path().string());
+              matchedWorklistCount++;
+            }
+          }
         }
-        
-        LOG(INFO) << "Worklist matched: " << it->id_;
-        matchedWorklistCount++;
       }
+
+      ORTHANC_PLUGINS_LOG_INFO("Worklist C-Find: parsed " + boost::lexical_cast<std::string>(parsedFilesCount) +
+                               " files, found " + boost::lexical_cast<std::string>(matchedWorklistCount) + " match(es)");
     }
-
-    LOG(INFO) << "Worklist C-Find: parsed " << boost::lexical_cast<std::string>(parsedFilesCount) <<
-                 " worklists, found " << boost::lexical_cast<std::string>(matchedWorklistCount) << " match(es)";
-
+    catch (fs::filesystem_error&)
+    {
+      ORTHANC_PLUGINS_LOG_ERROR("Inexistent folder while scanning for worklists: " + source.string());
+      return OrthancPluginErrorCode_DirectoryExpected;
+    }
 
     return OrthancPluginErrorCode_Success;
   }
@@ -323,411 +210,13 @@
   }
 }
 
-static void DeleteWorklist(const std::string& worklistId)
-{
-  switch (worklistStorage_)
-  {
-    case WorklistStorageType_Folder:
-    {
-      boost::filesystem::path path = worklistDirectory_ / (worklistId + ".wl");
-      if (!Orthanc::SystemToolbox::IsRegularFile(path))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-      }
-      Orthanc::SystemToolbox::RemoveFile(path);
-      break;
-    }
-
-    case WorklistStorageType_OrthancDb:
-      if (worklistsStore_.get() != NULL)
-      {
-        std::string notUsed;
-        if (!worklistsStore_->GetValue(notUsed, worklistId))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-        }
-        worklistsStore_->DeleteKey(worklistId);
-      }
-      break;
-
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-  }
-
-}
-
-static void WorklistHkWorkerThread()
-{
-  OrthancPluginSetCurrentThreadName(OrthancPlugins::GetGlobalContext(), "WL HOUSEKEEPER");
-
-  OrthancPluginLogWarning(OrthancPlugins::GetGlobalContext(), "Starting Worklist Housekeeper worker thread");
-  Orthanc::Toolbox::ElapsedTimer timer;
-
-  while (!worklistHousekeeperThreadShouldStop_)
-  {
-    boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
-
-    if (timer.GetElapsedMilliseconds() > (hkIntervalInSeconds_ * 1000))
-    {
-      timer.Restart();
-      LOG(INFO) << "Performing Worklist Housekeeping";
-
-      std::vector<Worklist> worklists;
-
-      if (worklistStorage_ == WorklistStorageType_OrthancDb)
-      {
-        ListWorklistsFromDb(worklists);
-      }
-      else if (worklistStorage_ == WorklistStorageType_Folder)
-      {
-        ListWorklistsFromFolder(worklists);
-      }
-
-      for (std::vector<Worklist>::const_iterator it = worklists.begin(); it != worklists.end(); ++it)
-      {
-        if (deleteDelayInHours_ > 0 && it->IsOlderThan(deleteDelayInHours_))
-        {
-          LOG(INFO) << "Deleting worklist " << it->id_ << " " << deleteDelayInHours_ << " hours after its creation";
-          DeleteWorklist(it->id_);
-        }
-        else if (deleteWorklistsOnStableStudy_)
-        {
-          std::string studyInstanceUid;
-          std::string patientId;
-
-          Orthanc::ParsedDicomFile parsed(it->dicomContent_);
-          
-          if (parsed.GetTagValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID) &&
-              parsed.GetTagValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID))
-          {
-            Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, "fake-id", "fake-id");
-            const std::string& studyOrthancId = hasher.HashStudy();
-
-            Json::Value studyInfo;
-            if (OrthancPlugins::RestApiGet(studyInfo, "/studies/" + studyOrthancId, false))
-            {
-              if (studyInfo["IsStable"].asBool())
-              {
-                LOG(INFO) << "Deleting worklist " << it->id_ << " because its study is now stable";
-                DeleteWorklist(it->id_);
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-}
-
-
-static Orthanc::DicomToJsonFormat GetFormat(const OrthancPluginHttpRequest* request)
-{
-  std::map<std::string, std::string> getArguments;
-  OrthancPlugins::GetGetArguments(getArguments, request);
-
-  Orthanc::DicomToJsonFormat format = Orthanc::DicomToJsonFormat_Human;
-
-  if (getArguments.find("format") != getArguments.end())
-  {
-    format = Orthanc::StringToDicomToJsonFormat(getArguments["format"]);
-  }
-
-  return format;
-}
-
-static Orthanc::ParsedDicomFile* GetWorklist(const std::string& id)
-{
-  std::string fileContent;
-
-  switch (worklistStorage_)
-  {
-    case WorklistStorageType_Folder:
-    {
-      boost::filesystem::path path = worklistDirectory_ / Orthanc::SystemToolbox::PathFromUtf8(id + ".wl");  // the id might be a filename from a file that was pushed by an external program (therefore, it can contain fancy characters)
-      if (!Orthanc::SystemToolbox::IsRegularFile(path))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource, "Worklist not found");
-      }
-
-      Orthanc::SystemToolbox::ReadFile(fileContent, path);
-      break;
-    }
-
-    case WorklistStorageType_OrthancDb:
-    {
-      std::string serializedWl;
-      if (!worklistsStore_->GetValue(serializedWl, id))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource, "Worklist not found");
-      }
-
-      Json::Value jsonWl;
-      if (Orthanc::Toolbox::ReadJson(jsonWl, serializedWl))
-      {
-        Worklist wl(id, jsonWl);
-        fileContent = wl.dicomContent_;
-      }
-      break;
-    }
-
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-  }
-
-  return new Orthanc::ParsedDicomFile(fileContent);  
-}
-
-static void SerializeWorklistForApi(Json::Value& target, const std::string& id, const Orthanc::ParsedDicomFile& parsed, Orthanc::DicomToJsonFormat format)
-{
-  target["ID"] = id;
-  target["Tags"] = Json::objectValue;
-  parsed.DatasetToJson(target["Tags"], format, Orthanc::DicomToJsonFlags_None, 0);
-}
 
 extern "C"
 {
-
-  OrthancPluginErrorCode ListWorklists(OrthancPluginRestOutput* output,
-                                       const char* url,
-                                       const OrthancPluginHttpRequest* request)
-  {
-    if (request->method != OrthancPluginHttpMethod_Get)
-    {
-      OrthancPlugins::AnswerMethodNotAllowed(output, "GET");
-    }
-    else
-    {
-      Json::Value response = Json::arrayValue;
-      Orthanc::DicomToJsonFormat format = GetFormat(request);
-
-      std::vector<Worklist> worklists;
-
-      if (worklistStorage_ == WorklistStorageType_Folder)
-      {
-        ListWorklistsFromFolder(worklists);
-      }
-      else if (worklistStorage_ == WorklistStorageType_OrthancDb)
-      {
-        ListWorklistsFromDb(worklists);
-      }
-
-      for (std::vector<Worklist>::const_iterator it = worklists.begin(); it != worklists.end(); ++it)
-      {
-        Orthanc::ParsedDicomFile parsed(it->dicomContent_);
-        Json::Value jsonWl;
-        SerializeWorklistForApi(jsonWl, it->id_, parsed, format);
-
-        if (worklistStorage_ == WorklistStorageType_OrthancDb)
-        {
-          jsonWl["CreationDate"] = it->createdAt_;
-        }
-
-        response.append(jsonWl);
-      }
-
-      OrthancPlugins::AnswerJson(response, output);
-    }
-
-    return OrthancPluginErrorCode_Success;
-  }
-
-
-  void CreateOrUpdateWorklist(std::string& worklistId, 
-                              bool defaultForceValue,
-                              OrthancPluginRestOutput* output,
-                              const char* url,
-                              const OrthancPluginHttpRequest* request)
-  {
-    Json::Value body;
-
-    if (!OrthancPlugins::ReadJson(body, request->body, request->bodySize))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "A JSON payload was expected");
-    }
-
-    if (!body.isMember("Tags") || !body["Tags"].isObject())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "'Tags' field is missing or not a JSON object");
-    }
-
-    bool force = defaultForceValue;
-    if (body.isMember("Force")) {
-      force = body["Force"].asBool();
-    }
-
-    Json::Value& jsonWorklist = body["Tags"];
-
-    if (!jsonWorklist.isMember("SpecificCharacterSet"))
-    {
-      jsonWorklist["SpecificCharacterSet"] = Orthanc::GetDicomSpecificCharacterSet(Orthanc::Encoding_Utf8);
-    }
-
-    std::unique_ptr<Orthanc::ParsedDicomFile> dicom(Orthanc::ParsedDicomFile::CreateFromJson(jsonWorklist, Orthanc::DicomFromJsonFlags_None, ""));      
-
-    if (!force) 
-    {
-      if (!dicom->HasTag(Orthanc::DICOM_TAG_SCHEDULED_PROCEDURE_STEP_SEQUENCE))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "'Tags' is missing a 'ScheduledProcedureStepSequence'.  Use 'Force': true to bypass this check.");
-      }
-      Orthanc::DicomMap step;
-      if (!dicom->LookupSequenceItem(step, Orthanc::DicomPath::Parse("ScheduledProcedureStepSequence"), 0) || !step.HasTag(Orthanc::DICOM_TAG_MODALITY))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "'ScheduledProcedureStepSequence' is missing a 'Modality'  Use 'Force': true to bypass this check.");
-      }
-      if (!dicom->LookupSequenceItem(step, Orthanc::DicomPath::Parse("ScheduledProcedureStepSequence"), 0) || !step.HasTag(Orthanc::DICOM_TAG_SCHEDULED_PROCEDURE_STEP_START_DATE))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "'ScheduledProcedureStepSequence' is missing a 'ScheduledProcedureStepStartDate'  Use 'Force': true to bypass this check.");
-      }
-    }
-
-    dicom->SetIfAbsent(Orthanc::DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, "1.2.276.0.7230010.3.1.0.1");
-      
-    if (setStudyInstanceUidIfMissing_)
-    {
-      dicom->SetIfAbsent(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, Orthanc::FromDcmtkBridge::GenerateUniqueIdentifier(Orthanc::ResourceType_Study));
-    }
-
-    if (worklistId.empty())
-    {
-      worklistId = Orthanc::Toolbox::GenerateUuid();
-    }
-
-    std::string dicomContent;
-    dicom->SaveToMemoryBuffer(dicomContent);
-      
-    switch (worklistStorage_)
-    {
-      case WorklistStorageType_Folder:
-        Orthanc::SystemToolbox::WriteFile(dicomContent.empty() ? NULL : dicomContent.c_str(), dicomContent.size(),
-                                          worklistDirectory_ / Orthanc::SystemToolbox::PathFromUtf8(worklistId + ".wl"), true);
-        break;
-
-      case WorklistStorageType_OrthancDb:
-      {
-        Worklist wl(worklistId, dicomContent);
-        std::string serializedWl;
-        wl.Serialize(serializedWl);
-
-        worklistsStore_->Store(worklistId, serializedWl);
-        break;
-      }
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-
-    Json::Value response;
-
-    response["ID"] = worklistId;
-    response["Path"] = "/worklists/" + worklistId;
-
-    OrthancPlugins::AnswerJson(response, output);
-  }
-
-
-  OrthancPluginErrorCode GetPutDeleteWorklist(OrthancPluginRestOutput* output,
-                                              const char* url,
-                                              const OrthancPluginHttpRequest* request)
-  {
-    std::string worklistId = std::string(request->groups[0]);
-
-    if (request->method == OrthancPluginHttpMethod_Delete)
-    {
-      DeleteWorklist(worklistId);
-
-      OrthancPlugins::AnswerString("{}", "application/json", output);
-    }
-    else if (request->method == OrthancPluginHttpMethod_Get)
-    {
-      Orthanc::DicomToJsonFormat format = GetFormat(request);
-      std::unique_ptr<Orthanc::ParsedDicomFile> parsed(GetWorklist(worklistId));
-
-      Json::Value jsonWl;
-      SerializeWorklistForApi(jsonWl, worklistId, *parsed, format);
-      
-      OrthancPlugins::AnswerJson(jsonWl, output);
-    }
-    else if (request->method == OrthancPluginHttpMethod_Put)
-    {
-      CreateOrUpdateWorklist(worklistId, true, output, url, request);
-    }
-    else
-    {
-      OrthancPlugins::AnswerMethodNotAllowed(output, "DELETE,GET");
-    }
-
-    return OrthancPluginErrorCode_Success;
-  }
-
-
-  OrthancPluginErrorCode PostCreateWorklist(OrthancPluginRestOutput* output,
-                                            const char* url,
-                                            const OrthancPluginHttpRequest* request)
-  {
-    if (request->method != OrthancPluginHttpMethod_Post)
-    {
-      OrthancPlugins::AnswerMethodNotAllowed(output, "POST");
-    }
-    else
-    {
-      std::string worklistId;
-      CreateOrUpdateWorklist(worklistId, false, output, url, request);
-    }
-    return OrthancPluginErrorCode_Success;
-  }
-
-  static OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, 
-                                                 OrthancPluginResourceType resourceType,
-                                                 const char *resourceId)
-  {
-    try
-    {
-      if (changeType == OrthancPluginChangeType_OrthancStarted)
-      {
-        Json::Value system;
-        bool hasKeyValueStores = (OrthancPlugins::RestApiGet(system, "/system", false) && system.isMember("Capabilities") &&
-                                  system["Capabilities"].isMember("HasKeyValueStores") && system["Capabilities"]["HasKeyValueStores"].asBool());
-
-        if (worklistStorage_ == WorklistStorageType_OrthancDb && !hasKeyValueStores)
-        {
-          LOG(ERROR) << "The Orthanc DB plugin does not support Key Value Stores.  It is therefore impossible to store the worklists in Orthanc Database";
-          return OrthancPluginErrorCode_IncompatibleConfigurations;
-        }
-
-        if (deleteDelayInHours_ > 0 && !hasKeyValueStores)
-        {
-          LOG(ERROR) << "The Orthanc DB plugin does not support Key Value Stores.  It is therefore impossible to use the \"DeleteWorklistsDelay\" option";
-          return OrthancPluginErrorCode_IncompatibleConfigurations;
-        }
-
-        if (worklistStorage_ == WorklistStorageType_OrthancDb)
-        {
-          worklistsStore_.reset(new OrthancPlugins::KeyValueStore("worklists"));
-        }
-
-        if (deleteDelayInHours_ > 0 || deleteWorklistsOnStableStudy_)
-        {
-          worklistHousekeeperThread_.reset(new boost::thread(WorklistHkWorkerThread));
-        }
-      }
-    }
-    catch (Orthanc::OrthancException& e)
-    {
-      LOG(ERROR) << "Exception: " << e.What();
-    }
-    catch (...)
-    {
-      LOG(ERROR) << "Uncatched native exception";
-    }      
-    return OrthancPluginErrorCode_Success;
-  }
-
   ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
   {
     OrthancPlugins::SetGlobalContext(c, MODALITY_WORKLISTS_NAME);
-    Orthanc::Logging::InitializePluginContext(c, MODALITY_WORKLISTS_NAME);
-  
+
     /* Check the version of the Orthanc core */
     if (OrthancPluginCheckVersion(c) == 0)
     {
@@ -737,29 +226,6 @@
       return -1;
     }
 
-    { // init the OrthancFramework
-      static const char* const LOCALE = "Locale";
-      static const char* const DEFAULT_ENCODING = "DefaultEncoding";
-
-      /**
-       * This function is a simplified version of function
-       * "Orthanc::OrthancInitialize()" that is executed when starting the
-       * Orthanc server.
-       **/
-      OrthancPlugins::OrthancConfiguration globalConfig;
-      Orthanc::InitializeFramework(globalConfig.GetStringValue(LOCALE, ""), false /* loadPrivateDictionary */);
-
-      std::string encoding;
-      if (globalConfig.LookupStringValue(encoding, DEFAULT_ENCODING))
-      {
-        Orthanc::SetDefaultDicomEncoding(Orthanc::StringToEncoding(encoding.c_str()));
-      }
-      else
-      {
-        Orthanc::SetDefaultDicomEncoding(Orthanc::ORTHANC_DEFAULT_DICOM_ENCODING);
-      }      
-    }
-
     ORTHANC_PLUGINS_LOG_WARNING("Sample worklist plugin is initializing");
     OrthancPluginSetDescription2(c, MODALITY_WORKLISTS_NAME, "Serve DICOM modality worklists from a folder with Orthanc.");
 
@@ -771,52 +237,19 @@
     bool enabled = worklists.GetBooleanValue("Enable", false);
     if (enabled)
     {
-      std::string folder;
-      if (worklists.LookupStringValue(folder, "Database") || worklists.LookupStringValue(folder, "Directory"))
+      if (worklists.LookupStringValue(folder_, "Database"))
       {
-        if (worklists.GetBooleanValue("SaveInOrthancDatabase", false))
-        {
-          LOG(ERROR) << "Worklists plugin: you can not set the \"SaveInOrthancDatabase\" configuration to \"true\" once you have configured the \"Directory\" (or former \"Database\") configuration.";
-          return -1;
-        }
-        
-        worklistStorage_ = WorklistStorageType_Folder;
-        worklistDirectory_ = folder; //Orthanc::SystemToolbox::PathFromUtf8(folder);
-        LOG(WARNING) << "The database of worklists will be read from folder: " << folder;
-      }
-      else if (worklists.GetBooleanValue("SaveInOrthancDatabase", false))
-      {
-
-        worklistStorage_ = WorklistStorageType_OrthancDb;
-        ORTHANC_PLUGINS_LOG_WARNING("The database of worklists will be read from Orthanc Database");
+        ORTHANC_PLUGINS_LOG_WARNING("The database of worklists will be read from folder: " + folder_);
+        OrthancPluginRegisterWorklistCallback(OrthancPlugins::GetGlobalContext(), Callback);
       }
       else
       {
-        LOG(ERROR) << "The configuration option \"Worklists.Directory\" must contain a path";
+        ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"Worklists.Database\" must contain a path");
         return -1;
       }
 
-      OrthancPluginRegisterWorklistCallback(OrthancPlugins::GetGlobalContext(), WorklistCallback);
-
       filterIssuerAet_ = worklists.GetBooleanValue("FilterIssuerAet", false);
       limitAnswers_ = worklists.GetUnsignedIntegerValue("LimitAnswers", 0);
-
-      deleteWorklistsOnStableStudy_ = worklists.GetBooleanValue("DeleteWorklistsOnStableStudy", true);
-      hkIntervalInSeconds_ = worklists.GetUnsignedIntegerValue("HousekeepingInterval", 60);
-      deleteDelayInHours_ = worklists.GetUnsignedIntegerValue("DeleteWorklistsDelay", 0);
-      setStudyInstanceUidIfMissing_ = worklists.GetBooleanValue("SetStudyInstanceUidIfMissing", true);
-
-      if (deleteDelayInHours_ > 0 && worklistStorage_ == WorklistStorageType_Folder)
-      {
-        LOG(ERROR) << "Worklists plugin: you can not set the \"DeleteWorklistsDelay\" configuration once you have configured the \"Directory\" (or former \"Database\") configuration.  This feature only works once \"SaveInOrthancDatabase\" is set to true.";
-        return -1;
-      }
-
-      OrthancPluginRegisterOnChangeCallback(OrthancPlugins::GetGlobalContext(), OnChangeCallback);
-
-      OrthancPluginRegisterRestCallback(OrthancPlugins::GetGlobalContext(), "/worklists/create", PostCreateWorklist);
-      OrthancPluginRegisterRestCallback(OrthancPlugins::GetGlobalContext(), "/worklists/([^/]+)", GetPutDeleteWorklist);
-      OrthancPluginRegisterRestCallback(OrthancPlugins::GetGlobalContext(), "/worklists", ListWorklists);
     }
     else
     {
@@ -830,13 +263,6 @@
   ORTHANC_PLUGINS_API void OrthancPluginFinalize()
   {
     ORTHANC_PLUGINS_LOG_WARNING("Sample worklist plugin is finalizing");
-
-    worklistHousekeeperThreadShouldStop_ = true;
-    if (worklistHousekeeperThread_.get() != NULL && worklistHousekeeperThread_->joinable())
-    {
-      worklistHousekeeperThread_->join();
-    }
-    worklistHousekeeperThread_.reset(NULL);
   }
 
 
--- a/OrthancServer/Plugins/Samples/WebDavFilesystem/Plugin.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Samples/WebDavFilesystem/Plugin.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -23,6 +23,8 @@
 
 #include "../Common/OrthancPluginCppWrapper.h"
 
+#include "../../../../OrthancFramework/Sources/Compatibility.h"
+
 #include <boost/thread/mutex.hpp>
 
 
@@ -69,12 +71,12 @@
     return content_;
   }
 
-  virtual bool IsFolder() const
+  virtual bool IsFolder() const ORTHANC_OVERRIDE
   {
     return false;
   }
     
-  virtual Resource* LookupPath(const std::vector<std::string>& path)
+  virtual Resource* LookupPath(const std::vector<std::string>& path) ORTHANC_OVERRIDE
   {
     if (path.empty())
     {
@@ -96,7 +98,7 @@
   Content content_;
 
 public:
-  virtual ~Folder()
+  virtual ~Folder() ORTHANC_OVERRIDE
   {
     for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
     {
@@ -105,12 +107,12 @@
     }
   }
 
-  virtual bool IsFolder() const
+  virtual bool IsFolder() const ORTHANC_OVERRIDE
   {
     return true;
   }
 
-  virtual Resource* LookupPath(const std::vector<std::string>& path)
+  virtual Resource* LookupPath(const std::vector<std::string>& path) ORTHANC_OVERRIDE
   {
     if (path.empty())
     {
@@ -242,7 +244,7 @@
   {
     boost::mutex::scoped_lock lock(mutex_);
     
-    Resource* resource = root_->LookupPath(path);
+    const Resource* resource = root_->LookupPath(path);
     return (resource != NULL &&
             resource->IsFolder());
   }
--- a/OrthancServer/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -194,8 +194,8 @@
   }
   else
   {
-    std::string s = "Unknown static resource in plugin: " + std::string(request->groups[0]);
-    OrthancPluginLogError(context, s.c_str());
+    const std::string t = "Unknown static resource in plugin: " + std::string(request->groups[0]);
+    OrthancPluginLogError(context, t.c_str());
     OrthancPluginSendHttpStatusCode(context, output, 404);
   }
 
--- a/OrthancServer/Resources/CodeModel/GenerateCodeModel.py	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Resources/CodeModel/GenerateCodeModel.py	Sat Nov 22 10:18:45 2025 +0100
@@ -475,7 +475,7 @@
         # Check that the first argument is the Orthanc context
         if (len(args) == 0 or
             args[0].type.kind != clang.cindex.TypeKind.POINTER or
-            args[0].type.get_pointee().spelling != 'OrthancPluginContext'):
+            not args[0].type.get_pointee().spelling in [ 'OrthancPluginContext', 'const OrthancPluginContext' ]):
             print('[INFO] Not in the Orthanc SDK: %s()' % node.spelling)
             continue
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Resources/RunCppCheck-2.1.sh	Sat Nov 22 10:18:45 2025 +0100
@@ -0,0 +1,111 @@
+#!/bin/bash
+# Note: This script is tuned to run with cppcheck v2.1
+
+set -ex
+
+CPPCHECK=cppcheck
+
+if [ $# -ge 1 ]; then
+    CPPCHECK=$1
+fi
+
+cat <<EOF > /tmp/cppcheck-suppressions.txt
+constParameter:../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp
+knownArgument:../../OrthancFramework/UnitTestsSources/ImageTests.cpp
+knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp
+nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:322
+stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1535
+stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:166
+stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:74
+stlFindInsert:../../OrthancServer/Sources/Database/MainDicomTagsRegistry.cpp:65
+stlFindInsert:../../OrthancServer/Sources/OrthancWebDav.cpp:328
+stlFindInsert:../../OrthancServer/Sources/ServerJobs/MergeStudyJob.cpp:41
+stlFindInsert:../../OrthancServer/Sources/ServerJobs/SplitStudyJob.cpp:191
+stlFindInsert:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:361
+syntaxError:../../OrthancFramework/Sources/SQLite/FunctionContext.h:53
+syntaxError:../../OrthancFramework/UnitTestsSources/DicomMapTests.cpp:74
+syntaxError:../../OrthancFramework/UnitTestsSources/ZipTests.cpp:133
+syntaxError:../../OrthancServer/UnitTestsSources/UnitTestsMain.cpp:325
+unreadVariable:../../OrthancFramework/Sources/FileStorage/StorageAccessor.cpp
+unreadVariable:../../OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp:1173
+useInitializationList:../../OrthancFramework/Sources/Images/PngReader.cpp:91
+useInitializationList:../../OrthancFramework/Sources/Images/PngWriter.cpp:99
+useInitializationList:../../OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp:275
+assertWithSideEffect:../../OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp:277
+assertWithSideEffect:../../OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp:1026
+assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:292
+assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:391
+assertWithSideEffect:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:3068
+assertWithSideEffect:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:286
+assertWithSideEffect:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:454
+EOF
+
+CPPCHECK_BUILD_DIR=/tmp/cppcheck-build-dir-2.1/
+mkdir -p ${CPPCHECK_BUILD_DIR}
+
+${CPPCHECK} -j8 --enable=all --quiet --std=c++11 \
+            --cppcheck-build-dir=${CPPCHECK_BUILD_DIR} \
+            --suppressions-list=/tmp/cppcheck-suppressions.txt \
+            --suppress=unusedFunction \
+            -DBOOST_HAS_DATE_TIME=1 \
+            -DBOOST_HAS_FILESYSTEM_V3=1 \
+            -DBOOST_HAS_REGEX=1 \
+            -DCIVETWEB_HAS_DISABLE_KEEP_ALIVE=1 \
+            -DCIVETWEB_HAS_WEBDAV_WRITING=1 \
+            -DDCMTK_VERSION_NUMBER=365 \
+            -DHAVE_MALLOPT=1 \
+            -DHAVE_MALLOC_TRIM=1 \
+            -DMONGOOSE_USE_CALLBACKS=1 \
+            -DJSONCPP_VERSION_MAJOR=1 \
+            -DJSONCPP_VERSION_MINOR=0 \
+            -DORTHANC_BUILDING_FRAMEWORK_LIBRARY=0 \
+            -DORTHANC_BUILD_UNIT_TESTS=1 \
+            -DORTHANC_ENABLE_BASE64=1 \
+            -DORTHANC_ENABLE_CIVETWEB=1 \
+            -DORTHANC_ENABLE_CURL=1 \
+            -DORTHANC_ENABLE_DCMTK=1 \
+            -DORTHANC_ENABLE_DCMTK_JPEG=1 \
+            -DORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS=1 \
+            -DORTHANC_ENABLE_DCMTK_NETWORKING=1 \
+            -DORTHANC_ENABLE_DCMTK_TRANSCODING=1 \
+            -DORTHANC_ENABLE_JPEG=1 \
+            -DORTHANC_ENABLE_LOCALE=1 \
+            -DORTHANC_ENABLE_LOGGING=1 \
+            -DORTHANC_ENABLE_LOGGING_STDIO=1 \
+            -DORTHANC_ENABLE_LUA=1 \
+            -DORTHANC_ENABLE_MD5=1 \
+            -DORTHANC_ENABLE_MONGOOSE=0 \
+            -DORTHANC_ENABLE_PKCS11=1 \
+            -DORTHANC_ENABLE_PLUGINS=1 \
+            -DORTHANC_ENABLE_PNG=1 \
+            -DORTHANC_ENABLE_PUGIXML=1 \
+            -DORTHANC_ENABLE_SQLITE=1 \
+            -DORTHANC_ENABLE_SSL=1 \
+            -DORTHANC_ENABLE_ZLIB=1 \
+            -DORTHANC_SANDBOXED=0 \
+            -DORTHANC_SQLITE_VERSION=3027001 \
+            -DORTHANC_UNIT_TESTS_LINK_FRAMEWORK=1 \
+            -DPUGIXML_VERSION=150 \
+            -DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1 \
+            -D__BYTE_ORDER=__LITTLE_ENDIAN \
+            -D__GNUC__ \
+            -D__cplusplus=201103 \
+            -D__linux__ \
+            -UNDEBUG \
+            -DHAS_ORTHANC_EXCEPTION=1 \
+            \
+            ../../OrthancFramework/Sources \
+            ../../OrthancFramework/UnitTestsSources \
+            ../../OrthancServer/Plugins/Engine \
+            ../../OrthancServer/Plugins/Include \
+            ../../OrthancServer/Sources \
+            ../../OrthancServer/UnitTestsSources \
+            ../../OrthancServer/Plugins/Samples/Common \
+            ../../OrthancServer/Plugins/Samples/ConnectivityChecks \
+            ../../OrthancServer/Plugins/Samples/DelayedDeletion \
+            ../../OrthancServer/Plugins/Samples/Housekeeper \
+            ../../OrthancServer/Plugins/Samples/ModalityWorklists \
+            ../../OrthancServer/Plugins/Samples/MultitenantDicom \
+            ../../OrthancServer/Plugins/Samples/AdoptDicomInstance \
+            \
+            2>&1
--- a/OrthancServer/Resources/RunCppCheck-2.17.0.sh	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Resources/RunCppCheck-2.17.0.sh	Sat Nov 22 10:18:45 2025 +0100
@@ -1,4 +1,5 @@
 #!/bin/bash
+# Note: This script is tuned to run with cppcheck v2.17.0
 
 set -ex
 
@@ -36,7 +37,11 @@
 assertWithSideEffect:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:454
 EOF
 
-${CPPCHECK} --enable=all --std=gnu++11 --library=boost \
+CPPCHECK_BUILD_DIR=/tmp/cppcheck-build-dir-2.7.0/
+mkdir -p ${CPPCHECK_BUILD_DIR}
+
+${CPPCHECK} -j8 --enable=all --std=gnu++11 --library=boost \
+            --cppcheck-build-dir=${CPPCHECK_BUILD_DIR} \
             --suppressions-list=/tmp/cppcheck-suppressions.txt \
             -I/usr/include/ \
             -I/usr/include/jsoncpp/ \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Resources/RunCppCheck-2.17.1.sh	Sat Nov 22 10:18:45 2025 +0100
@@ -0,0 +1,147 @@
+#!/bin/bash
+# Note: This script is tuned to run with cppcheck v2.17.1
+
+set -ex
+
+CPPCHECK=cppcheck
+
+if [ $# -ge 1 ]; then
+    CPPCHECK=$1
+fi
+
+cat <<EOF > /tmp/cppcheck-suppressions.txt
+assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:292
+assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:391
+assertWithSideEffect:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:286
+constParameterCallback:../../OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp:50
+constParameterCallback:../../OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp:112
+constParameterCallback:../../OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp:113
+constParameterCallback:../../OrthancFramework/Sources/Pkcs11.cpp:125
+constParameterCallback:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp:3489
+constParameterCallback:../../OrthancServer/Sources/OrthancGetRequestHandler.cpp:47
+constParameterPointer:../../OrthancFramework/Sources/Logging.cpp:447
+constParameterPointer:../../OrthancFramework/Sources/Logging.cpp:451
+constParameterPointer:../../OrthancFramework/Sources/Toolbox.cpp:3046
+cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:108
+cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:124
+cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:140
+cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:85
+knownConditionTrueFalse:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:114
+knownConditionTrueFalse:../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp:425
+knownConditionTrueFalse:../../OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp:345
+knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2291
+knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2292
+knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2293
+knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2294
+knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2295
+knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2296
+knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2297
+knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2298
+knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2299
+knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2300
+nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:322
+stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:166
+stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:74
+syntaxError:../../OrthancFramework/Sources/SQLite/FunctionContext.h:53
+syntaxError:../../OrthancFramework/UnitTestsSources/DicomMapTests.cpp:74
+syntaxError:../../OrthancFramework/UnitTestsSources/ZipTests.cpp:133
+syntaxError:../../OrthancServer/UnitTestsSources/UnitTestsMain.cpp:325
+useInitializationList:../../OrthancFramework/Sources/Images/PngReader.cpp:91
+useInitializationList:../../OrthancFramework/Sources/Images/PngWriter.cpp:99
+useInitializationList:../../OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp:275
+variableScope:../../OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp:228
+variableScope:../../OrthancServer/Sources/ServerJobs/OrthancPeerStoreJob.cpp:94
+EOF
+
+CPPCHECK_BUILD_DIR=/tmp/cppcheck-build-dir-2.7.1/
+mkdir -p ${CPPCHECK_BUILD_DIR}
+
+${CPPCHECK} -j8 --enable=all --quiet --std=c++11 \
+            --cppcheck-build-dir=${CPPCHECK_BUILD_DIR} \
+            --suppress=unusedFunction \
+            --suppress=missingIncludeSystem \
+            --suppress=missingInclude \
+            --suppress=useStlAlgorithm \
+            --check-level=exhaustive \
+            --suppressions-list=/tmp/cppcheck-suppressions.txt \
+            -DBOOST_HAS_DATE_TIME=1 \
+            -DBOOST_HAS_FILESYSTEM_V3=1 \
+            -DBOOST_HAS_REGEX=1 \
+            -DCIVETWEB_HAS_DISABLE_KEEP_ALIVE=1 \
+            -DCIVETWEB_HAS_WEBDAV_WRITING=1 \
+            -DDCMTK_VERSION_NUMBER=365 \
+            -DHAVE_MALLOPT=1 \
+            -DHAVE_MALLOC_TRIM=1 \
+            -DMONGOOSE_USE_CALLBACKS=1 \
+            -DJSONCPP_VERSION_MAJOR=1 \
+            -DJSONCPP_VERSION_MINOR=0 \
+            -DORTHANC_BUILDING_FRAMEWORK_LIBRARY=0 \
+            -DORTHANC_BUILD_UNIT_TESTS=1 \
+            -DORTHANC_ENABLE_BASE64=1 \
+            -DORTHANC_ENABLE_CIVETWEB=1 \
+            -DORTHANC_ENABLE_CURL=1 \
+            -DORTHANC_ENABLE_DCMTK=1 \
+            -DORTHANC_ENABLE_DCMTK_JPEG=1 \
+            -DORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS=1 \
+            -DORTHANC_ENABLE_DCMTK_NETWORKING=1 \
+            -DORTHANC_ENABLE_DCMTK_TRANSCODING=1 \
+            -DORTHANC_ENABLE_JPEG=1 \
+            -DORTHANC_ENABLE_LOCALE=1 \
+            -DORTHANC_ENABLE_LOGGING=1 \
+            -DORTHANC_ENABLE_LOGGING_STDIO=1 \
+            -DORTHANC_ENABLE_LUA=1 \
+            -DORTHANC_ENABLE_MD5=1 \
+            -DORTHANC_ENABLE_MONGOOSE=0 \
+            -DORTHANC_ENABLE_PKCS11=1 \
+            -DORTHANC_ENABLE_PLUGINS=1 \
+            -DORTHANC_ENABLE_PNG=1 \
+            -DORTHANC_ENABLE_PUGIXML=1 \
+            -DORTHANC_ENABLE_SQLITE=1 \
+            -DORTHANC_ENABLE_SSL=1 \
+            -DORTHANC_ENABLE_ZLIB=1 \
+            -DORTHANC_SANDBOXED=0 \
+            -DORTHANC_SQLITE_VERSION=3027001 \
+            -DORTHANC_UNIT_TESTS_LINK_FRAMEWORK=1 \
+            -DORTHANC_VERSION="\"mainline\"" \
+            -DPUGIXML_VERSION=150 \
+            -DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1 \
+            -D__BYTE_ORDER=__LITTLE_ENDIAN \
+            -D__GNUC__ \
+            -D__cplusplus=201103 \
+            -D__linux__ \
+            -UNDEBUG \
+            -DHAS_ORTHANC_EXCEPTION=1 \
+            \
+            ../../OrthancFramework/Sources \
+            ../../OrthancFramework/UnitTestsSources \
+            ../../OrthancServer/Plugins/Engine \
+            ../../OrthancServer/Plugins/Include \
+            ../../OrthancServer/Sources \
+            ../../OrthancServer/UnitTestsSources \
+            ../../OrthancServer/Plugins/Samples/Common \
+            \
+            ../../OrthancServer/Plugins/Samples/AdoptDicomInstance/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/AutomatedJpeg2kCompression/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp \
+            ../../OrthancServer/Plugins/Samples/ConnectivityChecks/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/CppSkeleton/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/CustomImageDecoder/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/DelayedDeletion/LargeDeleteJob.cpp \
+            ../../OrthancServer/Plugins/Samples/DelayedDeletion/PendingDeletionsDatabase.cpp \
+            ../../OrthancServer/Plugins/Samples/DelayedDeletion/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/ModalityWorklists/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.cpp \
+            ../../OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.cpp \
+            ../../OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp \
+            ../../OrthancServer/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.cpp \
+            ../../OrthancServer/Plugins/Samples/MultitenantDicom/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/MultitenantDicom/PluginToolbox.cpp \
+            ../../OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.cpp \
+            ../../OrthancServer/Plugins/Samples/Sanitizer/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/StorageArea/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/StorageCommitmentScp/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/WebDavFilesystem/Plugin.cpp \
+            ../../OrthancServer/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp \
+            \
+            2>&1
--- a/OrthancServer/Resources/RunCppCheck.sh	Thu Nov 20 17:49:15 2025 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-#!/bin/bash
-# note: this script was last tuned to run with cppcheck v2.17.1
-set -ex
-
-CPPCHECK=cppcheck
-
-if [ $# -ge 1 ]; then
-    CPPCHECK=$1
-fi
-
-cat <<EOF > /tmp/cppcheck-suppressions.txt
-knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp
-nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:322
-stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1525
-stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:166
-stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:74
-syntaxError:../../OrthancFramework/Sources/SQLite/FunctionContext.h:53
-syntaxError:../../OrthancFramework/UnitTestsSources/DicomMapTests.cpp:74
-syntaxError:../../OrthancFramework/UnitTestsSources/ZipTests.cpp:133
-syntaxError:../../OrthancServer/UnitTestsSources/UnitTestsMain.cpp:325
-uninitMemberVar:../../OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp:419
-useInitializationList:../../OrthancFramework/Sources/Images/PngReader.cpp:91
-useInitializationList:../../OrthancFramework/Sources/Images/PngWriter.cpp:99
-useInitializationList:../../OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp:275
-assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:292
-assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:391
-assertWithSideEffect:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:286
-variableScope:../../OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp:228
-variableScope:../../OrthancServer/Sources/ServerJobs/OrthancPeerStoreJob.cpp:94
-uselessOverride:../../OrthancFramework/Sources/MultiThreading/IRunnableBySteps.h:35
-uselessOverride:../../OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h:76
-cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:85
-cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:108
-cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:124
-cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:140
-constParameterPointer:../../OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h
-constParameterPointer:../../OrthancFramework/Sources/Logging.cpp:447
-constParameterPointer:../../OrthancFramework/Sources/Logging.cpp:451
-constParameterPointer:../../OrthancFramework/Sources/Toolbox.cpp:3053
-knownConditionTrueFalse:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:114
-knownConditionTrueFalse:../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp:425
-knownConditionTrueFalse:../../OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp:345
-throwInNoexceptFunction:../../OrthancFramework/Sources/Cache/MemoryStringCache.cpp:121
-throwInNoexceptFunction:../../OrthancFramework/Sources/Cache/MemoryCache.cpp:91
-throwInNoexceptFunction:../../OrthancFramework/Sources/Cache/MemoryObjectCache.cpp:99
-throwInNoexceptFunction:../../OrthancFramework/Sources/MallocMemoryBuffer.h:50
-throwInNoexceptFunction:../../OrthancFramework/Sources/MetricsRegistry.cpp:620
-throwInNoexceptFunction:../../OrthancFramework/Sources/MetricsRegistry.cpp:632
-throwInNoexceptFunction:../../OrthancFramework/Sources/MetricsRegistry.cpp:676
-throwInNoexceptFunction:../../OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp:1296
-throwInNoexceptFunction:../../OrthancServer/Sources/LuaScripting.cpp:830
-throwInNoexceptFunction:../../OrthancServer/Sources/StorageCommitmentReports.cpp:187
-throwInNoexceptFunction:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h:218
-throwInNoexceptFunction:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h:376
-throwInNoexceptFunction:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h:496
-rethrowNoCurrentException:../../OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp
-rethrowNoCurrentException:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp
-constParameterPointer:../../OrthancFramework/Sources/Toolbox.cpp:3046
-constParameterCallback:../../OrthancServer/Sources/OrthancGetRequestHandler.cpp
-constParameterCallback:../../OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp
-constParameterCallback:../../OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp
-constParameterCallback:../../OrthancFramework/Sources/Pkcs11.cpp
-constParameterCallback:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp:3449
-unknownMacro:../../OrthancFramework/Sources/DicomParsing/DicomModification.cpp:39
-EOF
-
-
-${CPPCHECK} -j 8 --enable=all --quiet --std=c++11 \
-            --suppress=missingIncludeSystem \
-            --suppress=missingInclude \
-            --suppress=useStlAlgorithm \
-            --check-level=exhaustive \
-            --suppressions-list=/tmp/cppcheck-suppressions.txt \
-            -DBOOST_HAS_DATE_TIME=1 \
-            -DBOOST_HAS_FILESYSTEM_V3=1 \
-            -DBOOST_HAS_REGEX=1 \
-            -DCIVETWEB_HAS_DISABLE_KEEP_ALIVE=1 \
-            -DCIVETWEB_HAS_WEBDAV_WRITING=1 \
-            -DDCMTK_VERSION_NUMBER=365 \
-            -DHAVE_MALLOPT=1 \
-            -DHAVE_MALLOC_TRIM=1 \
-            -DMONGOOSE_USE_CALLBACKS=1 \
-            -DJSONCPP_VERSION_MAJOR=1 \
-            -DJSONCPP_VERSION_MINOR=0 \
-            -DORTHANC_BUILDING_FRAMEWORK_LIBRARY=0 \
-            -DORTHANC_BUILD_UNIT_TESTS=1 \
-            -DORTHANC_ENABLE_BASE64=1 \
-            -DORTHANC_ENABLE_CIVETWEB=1 \
-            -DORTHANC_ENABLE_CURL=1 \
-            -DORTHANC_ENABLE_DCMTK=1 \
-            -DORTHANC_ENABLE_DCMTK_JPEG=1 \
-            -DORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS=1 \
-            -DORTHANC_ENABLE_DCMTK_NETWORKING=1 \
-            -DORTHANC_ENABLE_DCMTK_TRANSCODING=1 \
-            -DORTHANC_ENABLE_JPEG=1 \
-            -DORTHANC_ENABLE_LOCALE=1 \
-            -DORTHANC_ENABLE_LOGGING=1 \
-            -DORTHANC_ENABLE_LOGGING_STDIO=1 \
-            -DORTHANC_ENABLE_LUA=1 \
-            -DORTHANC_ENABLE_MD5=1 \
-            -DORTHANC_ENABLE_MONGOOSE=0 \
-            -DORTHANC_ENABLE_PKCS11=1 \
-            -DORTHANC_ENABLE_PLUGINS=1 \
-            -DORTHANC_ENABLE_PNG=1 \
-            -DORTHANC_ENABLE_PUGIXML=1 \
-            -DORTHANC_ENABLE_SQLITE=1 \
-            -DORTHANC_ENABLE_SSL=1 \
-            -DORTHANC_ENABLE_ZLIB=1 \
-            -DORTHANC_SANDBOXED=0 \
-            -DORTHANC_SQLITE_VERSION=3027001 \
-            -DORTHANC_UNIT_TESTS_LINK_FRAMEWORK=1 \
-            -DPUGIXML_VERSION=150 \
-            -DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1 \
-            -D__BYTE_ORDER=__LITTLE_ENDIAN \
-            -D__GNUC__ \
-            -D__cplusplus=201103 \
-            -D__linux__ \
-            -UNDEBUG \
-            -DHAS_ORTHANC_EXCEPTION=1 \
-            \
-            ../../OrthancFramework/Sources \
-            ../../OrthancFramework/UnitTestsSources \
-            ../../OrthancServer/Plugins/Engine \
-            ../../OrthancServer/Plugins/Include \
-            ../../OrthancServer/Sources \
-            ../../OrthancServer/UnitTestsSources \
-            ../../OrthancServer/Plugins/Samples/Common \
-            ../../OrthancServer/Plugins/Samples/ConnectivityChecks \
-            ../../OrthancServer/Plugins/Samples/DelayedDeletion \
-            ../../OrthancServer/Plugins/Samples/Housekeeper \
-            ../../OrthancServer/Plugins/Samples/ModalityWorklists \
-            ../../OrthancServer/Plugins/Samples/MultitenantDicom \
-            ../../OrthancServer/Plugins/Samples/AdoptDicomInstance \
-            \
-            2>&1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Database/AddTimeoutToQueues.sql	Sat Nov 22 10:18:45 2025 +0100
@@ -0,0 +1,26 @@
+-- Orthanc - A Lightweight, RESTful DICOM Store
+-- Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+-- Department, University Hospital of Liege, Belgium
+-- Copyright (C) 2017-2023 Osimis S.A., Belgium
+-- Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+-- Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+--
+-- This program is free software: you can redistribute it and/or
+-- modify it under the terms of the GNU General Public License as
+-- published by the Free Software Foundation, either version 3 of the
+-- License, or (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful, but
+-- WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-- General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+-- "reservedUntil" is interpreted exclusively, as the number of seconds
+-- since the Epoch. The reservation is valid as long as "now() < reservedUntil",
+-- and it expires as soon as "reservedUntil <= now()".
+ALTER TABLE Queues
+ADD COLUMN reservedUntil INTEGER DEFAULT NULL;
--- a/OrthancServer/Sources/Database/IDatabaseWrapper.h	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h	Sat Nov 22 10:18:45 2025 +0100
@@ -59,6 +59,7 @@
       bool hasAttachmentCustomDataSupport_;
       bool hasKeyValueStoresSupport_;
       bool hasQueuesSupport_;
+      bool hasReserveQueueValueSupport_;
 
     public:
       Capabilities() :
@@ -72,7 +73,8 @@
         hasExtendedChanges_(false),
         hasAttachmentCustomDataSupport_(false),
         hasKeyValueStoresSupport_(false),
-        hasQueuesSupport_(false)
+        hasQueuesSupport_(false),
+        hasReserveQueueValueSupport_(false)
       {
       }
 
@@ -186,6 +188,16 @@
         return hasQueuesSupport_;
       }
 
+      void SetReserveQueueValueSupport(bool value)
+      {
+        hasReserveQueueValueSupport_ = value;
+      }
+
+      bool HasReserveQueueValueSupport() const
+      {
+        return hasReserveQueueValueSupport_;
+      }
+
     };
 
 
@@ -471,6 +483,17 @@
       // New in Orthanc 1.12.8, for statistics only
       virtual uint64_t GetQueueSize(const std::string& queueId) = 0;
 
+      // New in Orthanc 1.12.10
+      virtual bool ReserveQueueValue(std::string& value /* out */,
+                                     uint64_t& valueId /* out */, 
+                                     const std::string& queueId,
+                                     QueueOrigin origin,
+                                     uint32_t releaseTimeout) = 0;
+      
+      // New in Orthanc 1.12.10
+      virtual void AcknowledgeQueueValue(const std::string& queueId,
+                                         uint64_t valueId) = 0;
+
     };
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Database/InstallDicomIdentifiersIndex3.sql	Sat Nov 22 10:18:45 2025 +0100
@@ -0,0 +1,26 @@
+-- Orthanc - A Lightweight, RESTful DICOM Store
+-- Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+-- Department, University Hospital of Liege, Belgium
+-- Copyright (C) 2017-2023 Osimis S.A., Belgium
+-- Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+-- Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+--
+-- This program is free software: you can redistribute it and/or
+-- modify it under the terms of the GNU General Public License as
+-- published by the Free Software Foundation, either version 3 of the
+-- License, or (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful, but
+-- WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-- General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+CREATE INDEX DicomIdentifiersIndex3 ON DicomIdentifiers(tagGroup, tagElement, value);
+
+-- remove these 2 old indexes that are now redundant.
+DROP INDEX IF EXISTS DicomIdentifiersIndex2;
+DROP INDEX IF EXISTS DicomIdentifiersIndexValues;
\ No newline at end of file
--- a/OrthancServer/Sources/Database/InstallKeyValueStoresAndQueues.sql	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Sources/Database/InstallKeyValueStoresAndQueues.sql	Sat Nov 22 10:18:45 2025 +0100
@@ -30,6 +30,7 @@
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        queueId TEXT NOT NULL,
        value BLOB NOT NULL
+       --- reservedUntil INTEGER DEFAULT NULL  -- added in AddTimeoutToQueues.sql
 );
 
 CREATE INDEX QueuesIndex ON Queues (queueId, id);
--- a/OrthancServer/Sources/Database/PrepareDatabase.sql	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Sources/Database/PrepareDatabase.sql	Sat Nov 22 10:18:45 2025 +0100
@@ -107,8 +107,8 @@
 
 -- The 3 following indexes were added in Orthanc 0.8.5 (database v5)
 CREATE INDEX DicomIdentifiersIndex1 ON DicomIdentifiers(id);
-CREATE INDEX DicomIdentifiersIndex2 ON DicomIdentifiers(tagGroup, tagElement);
-CREATE INDEX DicomIdentifiersIndexValues ON DicomIdentifiers(value COLLATE BINARY);
+-- CREATE INDEX DicomIdentifiersIndex2 ON DicomIdentifiers(tagGroup, tagElement);  -- removed in 1.12.10 (in InstallDicomIdentifiersIndex3.sql)
+-- CREATE INDEX DicomIdentifiersIndexValues ON DicomIdentifiers(value COLLATE BINARY);  -- removed in 1.12.10 (in InstallDicomIdentifiersIndex3.sql)
 
 CREATE INDEX ChangesIndex ON Changes(internalId);
 
@@ -152,6 +152,10 @@
 -- new in Orthanc 1.12.8 ------------------------- equivalent to InstallKeyValueStoresAndQueues.sql
 ${INSTALL_KEY_VALUE_STORES_AND_QUEUES}
 
+-- new in Orthanc 1.12.10 ------------------------ reservation in queues + speedup the database
+${ADD_TIMEOUT_TO_QUEUES}              -- equivalent to AddTimeoutToQueues.sql
+${INSTALL_DICOM_IDENTIFIERS_INDEX_3}  -- equivalent to InstallDicomIdentifiersIndex3.sql
+
 
 -- Track the fact that the "revision" column exists in the "Metadata" and "AttachedFiles"
 -- tables, and that the "customData" column exists in the "AttachedFiles" table
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -43,6 +43,16 @@
 #include <boost/lexical_cast.hpp>
 
 
+/**
+ * WARNING: Do not use the RETURNING clause in the SQLite statements
+ * of this file. Indeed, "the RETURNING syntax has been supported by
+ * SQLite since version 3.35.0 (2021-03-12)". However, versions of
+ * SQLite < 3.35.0 are still widely used (e.g. Ubuntu 22.04 LTS, which
+ * is supported until 2030, comes with SQLite 3.31.1).
+ * https://sqlite.org/lang_returning.html
+ **/
+
+
 namespace Orthanc
 {  
   static std::string JoinRequestedMetadata(const FindRequest::ChildrenSpecification& childrenSpec)
@@ -2280,58 +2290,106 @@
       }
 
       SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                          "INSERT INTO Queues (queueId, value) VALUES (?, ?)");
+                          "INSERT INTO Queues (queueId, value, reservedUntil) VALUES (?, ?, NULL)");
       s.BindString(0, queueId);
       s.BindBlob(1, value, valueSize);
       s.Run();
     }
 
-    // New in Orthanc 1.12.8
-    virtual bool DequeueValue(std::string& value,
-                              const std::string& queueId,
-                              QueueOrigin origin) ORTHANC_OVERRIDE
+
+    static int64_t GetSecondsSinceEpoch()
     {
-      int64_t rowId;
-      std::unique_ptr<SQLite::Statement> s;
-
+      // https://www.boost.org/doc/libs/1_69_0/doc/html/date_time/examples.html#date_time.examples.seconds_since_epoch
+      static const boost::posix_time::ptime EPOCH(boost::gregorian::date(1970, 1, 1));
+      const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
+      return (now - EPOCH).total_seconds();
+    }
+
+
+    static bool SelectValueFromQueue(uint64_t& valueId /* out */,
+                                     std::string& value /* out */,
+                                     SQLite::Connection& db,
+                                     const std::string& queueId,
+                                     QueueOrigin origin)
+    {
+      std::string order;
       switch (origin)
       {
-        case QueueOrigin_Front:
-          s.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT id, value FROM Queues WHERE queueId=? ORDER BY id ASC LIMIT 1"));
+        case QueueOrigin_Back:
+          order = "DESC";
           break;
 
-        case QueueOrigin_Back:
-          s.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT id, value FROM Queues WHERE queueId=? ORDER BY id DESC LIMIT 1"));
+        case QueueOrigin_Front:
+          order = "ASC";
           break;
 
         default:
           throw OrthancException(ErrorCode_InternalError);
       }
 
-      s->BindString(0, queueId);
-      if (!s->Step())
+      // "reservedUntil <= ?" indicates that the reservation has expired
+      const std::string sql = ("SELECT id, value FROM Queues WHERE queueId=? AND "
+                               "(reservedUntil IS NULL OR reservedUntil <= ?) ORDER BY id " + order + " LIMIT 1");
+
+      SQLite::Statement s(db, SQLITE_FROM_HERE_DYNAMIC(sql), sql);
+
+      s.BindString(0, queueId);
+      s.BindInt64(1, GetSecondsSinceEpoch() /* now */);
+
+      if (!s.Step())
       {
         // No value found
         return false;
       }
       else
       {
-        rowId = s->ColumnInt64(0);
-
-        if (!s->ColumnBlobAsString(1, &value))
+        int64_t id = s.ColumnInt64(0);
+        if (id < 0)
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        valueId = static_cast<uint64_t>(id);
+
+        if (!s.ColumnBlobAsString(1, &value))
         {
           throw OrthancException(ErrorCode_NotEnoughMemory);
         }
 
-        SQLite::Statement s2(db_, SQLITE_FROM_HERE,
-                            "DELETE FROM Queues WHERE id = ?");
-        s2.BindInt64(0, rowId);
-        s2.Run();
+        return true;
+      }
+    }
+
+
+    // New in Orthanc 1.12.8 (but deprecated, you should use ReserveQueueValue() instead)
+    virtual bool DequeueValue(std::string& value,
+                              const std::string& queueId,
+                              QueueOrigin origin) ORTHANC_OVERRIDE
+    {
+      uint64_t valueId;
+
+      if (SelectValueFromQueue(valueId, value, db_, queueId, origin))
+      {
+        SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Queues WHERE queueId=? AND id=?");
+        s.BindString(0, queueId);  // Providing "queueId" is just a safeguard
+        s.BindInt64(1, valueId);
+        s.Run();
+
+        if (db_.GetLastChangeCount() != 1)
+        {
+          // This occurs if the acknowledgment occurs after the reservation has expired
+          throw OrthancException(ErrorCode_UnknownResource);
+        }
 
         return true;
-      }    
+      }
+      else
+      {
+        return false;
+      }
     }
 
+
     // New in Orthanc 1.12.8
     virtual uint64_t GetQueueSize(const std::string& queueId) ORTHANC_OVERRIDE
     {
@@ -2340,6 +2398,67 @@
       s.Step();
       return s.ColumnInt64(0);
     }
+
+
+    // New in Orthanc 1.12.10
+    virtual bool ReserveQueueValue(std::string& value,
+                                   uint64_t& valueId,
+                                   const std::string& queueId,
+                                   QueueOrigin origin,
+                                   uint32_t releaseTimeout) ORTHANC_OVERRIDE
+    {
+      if (SelectValueFromQueue(valueId, value, db_, queueId, origin))
+      {
+        SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Queues SET reservedUntil=? WHERE queueId=? AND id=?");
+
+        const int64_t reservedUntil = GetSecondsSinceEpoch() /* now */ + releaseTimeout;
+        s.BindInt64(0, reservedUntil);
+        s.BindString(1, queueId);  // Providing "queueId" is just a safeguard
+        s.BindInt64(2, valueId);
+        s.Run();
+
+        if (db_.GetLastChangeCount() == 0)
+        {
+          // This occurs if the acknowledgment occurs after the reservation has expired
+          throw OrthancException(ErrorCode_UnknownResource);
+        }
+        else if (db_.GetLastChangeCount() > 1)
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+
+    // New in Orthanc 1.12.10
+    virtual void AcknowledgeQueueValue(const std::string& queueId,
+                                       uint64_t valueId) ORTHANC_OVERRIDE
+    {
+      // "? < reservedUntil" indicates that the reservation is still valid
+      SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                          "DELETE FROM Queues WHERE queueId=? AND id=? AND "
+                          "reservedUntil IS NOT NULL AND ? < reservedUntil");
+      s.BindString(0, queueId);  // Providing "queueId" is just a safeguard
+      s.BindInt64(1, static_cast<int64_t>(valueId));
+      s.BindInt64(2, GetSecondsSinceEpoch() /* now */);
+      s.Run();
+
+      if (db_.GetLastChangeCount() == 0)
+      {
+        // This occurs if the acknowledgment occurs after the reservation has expired
+        throw OrthancException(ErrorCode_UnknownResource);
+      }
+      else if (db_.GetLastChangeCount() > 1)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
   };
 
 
@@ -2587,6 +2706,7 @@
     dbCapabilities_.SetKeyValueStoresSupport(true);
     dbCapabilities_.SetQueuesSupport(true);
     dbCapabilities_.SetAttachmentCustomDataSupport(true);
+    dbCapabilities_.SetReserveQueueValueSupport(true);
     db_.Open(path);
   }
 
@@ -2604,6 +2724,7 @@
     dbCapabilities_.SetKeyValueStoresSupport(true);
     dbCapabilities_.SetQueuesSupport(true);
     dbCapabilities_.SetAttachmentCustomDataSupport(true);
+    dbCapabilities_.SetReserveQueueValueSupport(true);
     db_.OpenInMemory();
   }
 
@@ -2678,6 +2799,8 @@
         InjectEmbeddedScript(query, "${INSTALL_LABELS_TABLE}", ServerResources::INSTALL_LABELS_TABLE);
         InjectEmbeddedScript(query, "${INSTALL_DELETED_FILES}", ServerResources::INSTALL_DELETED_FILES);
         InjectEmbeddedScript(query, "${INSTALL_KEY_VALUE_STORES_AND_QUEUES}", ServerResources::INSTALL_KEY_VALUE_STORES_AND_QUEUES);
+        InjectEmbeddedScript(query, "${ADD_TIMEOUT_TO_QUEUES}", ServerResources::ADD_TIMEOUT_TO_QUEUES);
+        InjectEmbeddedScript(query, "${INSTALL_DICOM_IDENTIFIERS_INDEX_3}", ServerResources::INSTALL_DICOM_IDENTIFIERS_INDEX_3);
 
         db_.Execute(query);
       }
@@ -2743,6 +2866,20 @@
           LOG(INFO) << "Installing the \"KeyValueStores\" and \"Queues\" tables";
           ExecuteEmbeddedScript(db_, ServerResources::INSTALL_KEY_VALUE_STORES_AND_QUEUES);
         }
+
+        // New in Orthanc 1.12.10
+        if (!db_.DoesIndexExist("DicomIdentifiersIndex3"))
+        {
+          LOG(INFO) << "Installing the \"DicomIdentifiersIndex3\" index";
+          ExecuteEmbeddedScript(db_, ServerResources::INSTALL_DICOM_IDENTIFIERS_INDEX_3);
+        }
+
+        // New in Orthanc 1.12.10
+        if (!db_.DoesColumnExist("Queues", "reservedUntil"))
+        {
+          LOG(INFO) << "Adding timeout column to the \"Queues\" table";
+          ExecuteEmbeddedScript(db_, ServerResources::ADD_TIMEOUT_TO_QUEUES);
+        }
       }
 
       transaction->Commit(0);
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -3228,6 +3228,12 @@
     return db_.GetDatabaseCapabilities().HasQueuesSupport();
   }
 
+  bool StatelessDatabaseOperations::HasReserveQueueValueSupport()
+  {
+    boost::shared_lock<boost::shared_mutex> lock(mutex_);
+    return db_.GetDatabaseCapabilities().HasReserveQueueValueSupport();
+  }
+
   void StatelessDatabaseOperations::ExecuteCount(uint64_t& count,
                                                  const FindRequest& request)
   {
@@ -3574,6 +3580,93 @@
     return size;
   }
 
+  bool StatelessDatabaseOperations::ReserveQueueValue(std::string& value,
+                                                      uint64_t& valueId,
+                                                      const std::string& queueId,
+                                                      QueueOrigin origin,
+                                                      uint32_t releaseTimeout)
+  {
+    if (queueId.empty() ||
+        releaseTimeout == 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    class Operations : public IReadWriteOperations
+    {
+    private:
+      const std::string& queueId_;
+      uint64_t& valueId_;
+      std::string& value_;
+      QueueOrigin origin_;
+      uint32_t releaseTimeout_;
+      bool found_;
+
+    public:
+      Operations(std::string& value,
+                 uint64_t& valueId,
+                 const std::string& queueId,
+                 QueueOrigin origin,
+                 uint32_t releaseTimeout) :
+        queueId_(queueId),
+        valueId_(valueId),
+        value_(value),
+        origin_(origin),
+        releaseTimeout_(releaseTimeout),
+        found_(false)
+      {
+      }
+
+      bool HasFound()
+      {
+        return found_;
+      }
+
+      virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE
+      {
+        found_ = transaction.ReserveQueueValue(value_, valueId_, queueId_, origin_, releaseTimeout_);
+      }
+    };
+
+    Operations operations(value, valueId, queueId, origin, releaseTimeout);
+    Apply(operations);
+
+    return operations.HasFound();
+  }
+
+
+  void StatelessDatabaseOperations::AcknowledgeQueueValue(const std::string& queueId,
+                                                          uint64_t valueId)
+  {
+    if (queueId.empty())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    class Operations : public IReadWriteOperations
+    {
+    private:
+      const std::string& queueId_;
+      uint64_t valueId_;
+
+    public:
+      Operations(const std::string& queueId,
+                 uint64_t valueId) :
+        queueId_(queueId),
+        valueId_(valueId)
+      {
+      }
+
+      virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE
+      {
+        transaction.AcknowledgeQueueValue(queueId_, valueId_);
+      }
+    };
+
+    Operations operations(queueId, valueId);
+    Apply(operations);
+  }
+
 
   void StatelessDatabaseOperations::GetAttachmentCustomData(std::string& customData,
                                                             const std::string& attachmentUuid)
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Sat Nov 22 10:18:45 2025 +0100
@@ -485,6 +485,21 @@
         return transaction_.DequeueValue(value, queueId, origin);
       }
 
+      bool ReserveQueueValue(std::string& value,
+                             uint64_t& valueId,
+                             const std::string& queueId,
+                             QueueOrigin origin,
+                             uint32_t releaseTimeout)
+      {
+        return transaction_.ReserveQueueValue(value, valueId, queueId, origin, releaseTimeout);
+      }
+
+      void AcknowledgeQueueValue(const std::string& queueId,
+                                 uint64_t valueId)
+      {
+        return transaction_.AcknowledgeQueueValue(queueId, valueId);
+      }
+
       void SetAttachmentCustomData(const std::string& attachmentUuid,
                                       const void* customData,
                                       size_t customDataSize)
@@ -622,6 +637,8 @@
 
     bool HasQueuesSupport();
 
+    bool HasReserveQueueValueSupport();
+
     void GetExportedResources(Json::Value& target,
                               int64_t since,
                               uint32_t limit);
@@ -841,6 +858,15 @@
     
     uint64_t GetQueueSize(const std::string& queueId);
 
+    bool ReserveQueueValue(std::string& value,
+                           uint64_t& valueId,
+                           const std::string& queueId,
+                           QueueOrigin origin,
+                           uint32_t releaseTimeout);
+
+    void AcknowledgeQueueValue(const std::string& queueId,
+                               uint64_t valueId);
+
     class KeysValuesIterator : public boost::noncopyable
     {
     private:
--- a/OrthancServer/Sources/LuaScripting.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Sources/LuaScripting.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -827,7 +827,16 @@
     if (state_ == State_Running)
     {
       LOG(ERROR) << "INTERNAL ERROR: LuaScripting::Stop() should be invoked manually to avoid mess in the destruction order!";
-      Stop();
+
+      try
+      {
+        Stop();
+      }
+      catch (OrthancException& e)
+      {
+        // Don't throw exceptions in destructors
+        LOG(ERROR) << "Exception in destructor: " << e.What();
+      }
     }
   }
 
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -99,6 +99,7 @@
     static const char* const HAS_QUEUES = "HasQueues";
     static const char* const HAS_EXTENDED_FIND = "HasExtendedFind";
     static const char* const READ_ONLY = "ReadOnly";
+    static const char* const HAS_RESERVE_QUEUE_VALUE = "HasReserveQueueValue";
 
     if (call.IsDocumentation())
     {
@@ -147,7 +148,9 @@
         .SetAnswerField(HAS_LABELS, RestApiCallDocumentation::Type_Boolean,
                         "Whether the database back-end supports labels (new in Orthanc 1.12.0)")
         .SetAnswerField(CAPABILITIES, RestApiCallDocumentation::Type_JsonObject,
-                        "Whether the back-end supports optional features like 'HasExtendedChanges', 'HasExtendedFind' (new in Orthanc 1.12.5) ")
+                        "Whether the database back-end supports optional features like 'HasExtendedChanges', 'HasExtendedFind' "
+                        "(new in Orthanc 1.12.5), 'HasKeyValueStores', 'HasQueues' (new in Orthanc 1.12.8), "
+                        "and 'HasReserveQueueValue' (new in Orthanc 1.12.10)")
         .SetAnswerField(READ_ONLY, RestApiCallDocumentation::Type_Boolean,
                         "Whether Orthanc is running in read only mode (new in Orthanc 1.12.5)")
         .SetHttpGetSample("https://orthanc.uclouvain.be/demo/system", true);
@@ -213,6 +216,7 @@
     result[CAPABILITIES][HAS_EXTENDED_FIND] = OrthancRestApi::GetIndex(call).HasFindSupport();
     result[CAPABILITIES][HAS_KEY_VALUE_STORES] = OrthancRestApi::GetIndex(call).HasKeyValueStoresSupport();
     result[CAPABILITIES][HAS_QUEUES] = OrthancRestApi::GetIndex(call).HasQueuesSupport();
+    result[CAPABILITIES][HAS_RESERVE_QUEUE_VALUE] = OrthancRestApi::GetIndex(call).HasReserveQueueValueSupport();
     
     call.GetOutput().AnswerJson(result);
   }
--- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -380,6 +380,6 @@
     Json::Value publicContent;
     GetPublicContent(publicContent);
 
-    payload.SetContent(ErrorPayloadType_Dimse, publicContent["Details"]);
+    payload.SetContent(ErrorPayloadType_RetrieveJob, publicContent["Details"]);
   }
 }
--- a/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -187,7 +187,6 @@
     explicit Unserializer(StorageCommitmentScpJob& that) :
       that_(that)
     {
-      that_.ready_ = false;
     }
 
     virtual ICommand* Unserialize(const Json::Value& source) const ORTHANC_OVERRIDE
@@ -420,9 +419,9 @@
                                                    const Json::Value& serialized) :
     SetOfCommandsJob(new Unserializer(*this), serialized),
     context_(context),
+    ready_(false),
     transactionUid_(SerializationToolbox::ReadString(serialized, TRANSACTION_UID)),
     calledAet_(SerializationToolbox::ReadString(serialized, CALLED_AET))
-    // "ready_" is initialized by the unserializer
   {
     if (serialized.type() != Json::objectValue ||
         !serialized.isMember(REMOTE_MODALITY))
--- a/OrthancServer/Sources/StorageCommitmentReports.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/Sources/StorageCommitmentReports.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -24,6 +24,7 @@
 #include "PrecompiledHeadersServer.h"
 #include "StorageCommitmentReports.h"
 
+#include "../../OrthancFramework/Sources/Logging.h"
 #include "../../OrthancFramework/Sources/OrthancException.h"
 
 namespace Orthanc
@@ -184,10 +185,18 @@
     while (!content_.IsEmpty())
     {
       Report* report = NULL;
-      content_.RemoveOldest(report);
 
-      assert(report != NULL);
-      delete report;
+      try
+      {
+        content_.RemoveOldest(report);
+        assert(report != NULL);
+        delete report;
+      }
+      catch (OrthancException& e)
+      {
+        // Don't throw exceptions in destructors
+        LOG(ERROR) << "Exception in destructor: " << e.What();
+      }
     }
   }
 
--- a/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -1319,6 +1319,82 @@
   db.Close();
 }
 
+TEST(SQLiteDatabaseWrapper, ReserveQueueValue)
+{
+  SQLiteDatabaseWrapper db;   // The SQLite DB is in memory
+  db.Open();
+
+  {
+    StatelessDatabaseOperations op(db, false);
+    op.SetTransactionContextFactory(new DummyTransactionContextFactory);
+
+    ASSERT_EQ(0u, op.GetQueueSize("test"));
+    op.EnqueueValue("test", "a");
+    ASSERT_EQ(1u, op.GetQueueSize("test"));
+    op.EnqueueValue("test", "b");
+    ASSERT_EQ(2u, op.GetQueueSize("test"));
+    op.EnqueueValue("test", "c");
+    ASSERT_EQ(3u, op.GetQueueSize("test"));
+
+    std::string s;
+    uint64_t valueId0, valueId1, valueId2, valueId3;
+    ASSERT_TRUE(op.ReserveQueueValue(s, valueId0, "test", QueueOrigin_Back, 100));  
+    ASSERT_EQ("c", s);
+    ASSERT_EQ(3u, op.GetQueueSize("test"));  // the reserved values are still counted !
+    
+    ASSERT_TRUE(op.ReserveQueueValue(s, valueId1, "test", QueueOrigin_Back, 100));  
+    ASSERT_EQ("b", s);
+    
+    ASSERT_TRUE(op.ReserveQueueValue(s, valueId2, "test", QueueOrigin_Back, 100));  
+    ASSERT_EQ("a", s);
+    ASSERT_EQ(3u, op.GetQueueSize("test"));
+
+    ASSERT_FALSE(op.ReserveQueueValue(s, valueId3, "test", QueueOrigin_Back, 100));
+
+    op.AcknowledgeQueueValue("test", valueId1);
+    ASSERT_EQ(2u, op.GetQueueSize("test"));
+    ASSERT_THROW(op.AcknowledgeQueueValue("test", valueId1), OrthancException);  // Cannot acknowledge twice
+    op.AcknowledgeQueueValue("test", valueId2);
+    op.AcknowledgeQueueValue("test", valueId0);
+    ASSERT_EQ(0u, op.GetQueueSize("test"));
+
+    op.EnqueueValue("test", "d");
+    op.EnqueueValue("test", "e");
+    op.EnqueueValue("test", "f");
+    op.EnqueueValue("test", "g");
+    op.EnqueueValue("test", "h");
+
+    // reserve 2 values front and back and acknowledge only "e" & "f"
+    ASSERT_TRUE(op.ReserveQueueValue(s, valueId0, "test", QueueOrigin_Front, 1));  
+    ASSERT_EQ("d", s);
+    ASSERT_TRUE(op.ReserveQueueValue(s, valueId0, "test", QueueOrigin_Front, 1));  
+    ASSERT_EQ("e", s);
+    op.AcknowledgeQueueValue("test", valueId0);
+    ASSERT_TRUE(op.ReserveQueueValue(s, valueId0, "test", QueueOrigin_Back, 1));  
+    ASSERT_EQ("h", s);
+    ASSERT_TRUE(op.ReserveQueueValue(s, valueId0, "test", QueueOrigin_Back, 1));  
+    ASSERT_EQ("g", s);
+    op.AcknowledgeQueueValue("test", valueId0);
+
+    // "DequeueValue()" must ignore the reserved values
+    ASSERT_TRUE(op.DequeueValue(s, "test", QueueOrigin_Front));
+    ASSERT_EQ("f", s);
+
+    SystemToolbox::USleep(2000000); // the granularity being the second, we might have to wait up to 2 seconds
+
+    // "d" and "h" remain
+    ASSERT_TRUE(op.ReserveQueueValue(s, valueId0, "test", QueueOrigin_Front, 1));  
+    ASSERT_EQ("d", s);
+    ASSERT_TRUE(op.ReserveQueueValue(s, valueId0, "test", QueueOrigin_Back, 1));  
+    ASSERT_EQ("h", s);
+
+    // the queue is empty at this point since "f" has been reserved already
+    ASSERT_FALSE(op.ReserveQueueValue(s, valueId0, "test", QueueOrigin_Back, 1));  
+  }
+
+  db.Close();
+}
+
 
 TEST_F(DatabaseWrapperTest, BinaryCustomData)
 {
@@ -1356,4 +1432,4 @@
   }
 
   transaction_->DeleteResource(patient);
-}
\ No newline at end of file
+}
--- a/OrthancServer/UnitTestsSources/VersionsTests.cpp	Thu Nov 20 17:49:15 2025 +0100
+++ b/OrthancServer/UnitTestsSources/VersionsTests.cpp	Sat Nov 22 10:18:45 2025 +0100
@@ -120,13 +120,13 @@
 TEST(Versions, CurlStatic)
 {
   curl_version_info_data* v = curl_version_info(CURLVERSION_NOW);
-  ASSERT_STREQ("8.9.0", v->version);
+  ASSERT_STREQ("8.17.0", v->version);
 }
 
 TEST(Versions, PngStatic)
 {
-  ASSERT_EQ(10640u, png_access_version_number());
-  ASSERT_STREQ("1.6.40", PNG_LIBPNG_VER_STRING);
+  ASSERT_EQ(10650u, png_access_version_number());
+  ASSERT_STREQ("1.6.50", PNG_LIBPNG_VER_STRING);
 }
 
 TEST(Versions, JpegStatic)
--- a/TODO	Thu Nov 20 17:49:15 2025 +0100
+++ b/TODO	Sat Nov 22 10:18:45 2025 +0100
@@ -329,32 +329,6 @@
   can not cross the C/C++ frontier safely -> we need a OrthancPluginRegisterStorageArea3 with a return value.
   Ex: install DelayedDeletion + S3 storage.  Right now, the second plugin to load is just ignored with an error
   message in the logs.
-* Queues: There is currently a risk of loosing messages from the Queues:
-    message = orthanc.DequeueValue("instances-to-process", orthanc.QueueOrigin.FRONT)
-    # consider Orthanc is interrupted here (hard shutdown)
-    proccess(message)
-  The message will never be processed ...
-  We should have an acknowledge/commit mechanism e.g:
-    message, messageId = orthanc.DequeueValue2("instances-to-process", orthanc.QueueOrigin.FRONT, 5) # where 5 is a "timeout"
-    # At this point, the message is still in the queue but will not be dequeued by other consumers.
-    # If the message is not acknowledged within 5 seconds, it will get back into the queue.
-    process(message)
-    orthanc.AcknowledgeQueue("instances-to-process", messageId)
-    # This requires adding a new "timeout" column in the DB with the reservation_expiration timestamp.
-
-  Note by SJ: Introducing an acknowledgement would greatly complexify
-  the SDK and the core of Orthanc. I would favor another approach by
-  introducing 2 functions in the SDK:
-  - OrthancPluginQueuePeekFront(context, found, target, queueId): Same behavior as
-    OrthancPluginDequeueValue(), but limited to the back of the queue, and doesn't modify the queue
-  - OrthancPluginQueuePopFront(context, target): Remove the front element from the queue
-  As long as OrthancPluginQueuePopFront() is not called, no message would be lost.
-  Note that we cannot propose "OrthancPluginQueuePeekBack()" or
-  "OrthancPluginQueuePeek(..., OrthancPluginQueueOrigin_Back)" (i.e., LIFO stacks), as a
-  concurrent call to "OrthancPluginEnqueueValue()" would alter the back of the queue.
-
-  Note by AM: With PeekFront: 2 consumers on 2 Orthanc instances might consume the same message no ?
-
 * Add OrthancPluginSetStableStatus2() which would take an additional OrthancPluginResourceType
   argument to avoid possible ambiguity about the resource of interest