changeset 5994:bad103ab55c5 attach-custom-data

merged default -> attach-custom-data
author Alain Mazy <am@orthanc.team>
date Thu, 30 Jan 2025 17:41:33 +0100
parents 79a497908b04 (current diff) 9c20ede25ef8 (diff)
children 984f46eb8e04
files .hgignore NEWS OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake OrthancFramework/Sources/FileStorage/FileInfo.cpp OrthancFramework/Sources/FileStorage/FileInfo.h OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp OrthancFramework/Sources/FileStorage/FilesystemStorage.h OrthancFramework/Sources/FileStorage/IStorageArea.h OrthancFramework/Sources/FileStorage/MemoryStorageArea.h OrthancFramework/Sources/FileStorage/StorageAccessor.cpp OrthancFramework/Sources/FileStorage/StorageAccessor.h OrthancFramework/UnitTestsSources/FileStorageTests.cpp OrthancServer/CMakeLists.txt OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabase.h OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Engine/OrthancPlugins.h OrthancServer/Plugins/Include/orthanc/OrthancCDatabasePlugin.h OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto OrthancServer/Resources/Configuration.json OrthancServer/Resources/Graveyard/FindRefactoringForSQLite.cpp OrthancServer/Sources/Database/BaseDatabaseWrapper.cpp OrthancServer/Sources/Database/BaseDatabaseWrapper.h OrthancServer/Sources/Database/IDatabaseWrapper.h OrthancServer/Sources/Database/PrepareDatabase.sql OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h OrthancServer/Sources/OrthancInitialization.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/ServerContext.h OrthancServer/Sources/ServerEnumerations.h OrthancServer/Sources/ServerIndex.cpp OrthancServer/UnitTestsSources/ServerIndexTests.cpp TODO
diffstat 720 files changed, 9878 insertions(+), 11804 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.clang-format	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,57 @@
+---
+Language: Cpp
+BasedOnStyle: LLVM
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignOperands: true
+AlignTrailingComments: false
+AlwaysBreakTemplateDeclarations: Yes
+BraceWrapping: 
+  AfterCaseLabel: true
+  AfterClass: true
+  AfterControlStatement: true
+  AfterEnum: true
+  AfterFunction: true
+  AfterNamespace: true
+  AfterStruct: true
+  AfterUnion: true
+  AfterExternBlock: true
+  BeforeCatch: true
+  BeforeElse: true
+  BeforeLambdaBody: true
+  BeforeWhile: true
+  IndentBraces: false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializers: AfterColon
+BreakConstructorInitializersBeforeComma: false
+ColumnLimit: 200
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ContinuationIndentWidth: 2
+IncludeCategories: 
+  - Regex: '^<.*'
+    Priority: 1
+  - Regex: '^".*'
+    Priority: 2
+  - Regex: '.*'
+    Priority: 3
+IncludeIsMainRegex: '([-_](test|unittest))?$'
+IndentCaseLabels: true
+InsertNewlineAtEOF: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 2
+NamespaceIndentation: All
+SpaceAfterCStyleCast: true
+SpaceAfterTemplateKeyword: false
+SpaceBeforeRangeBasedForLoopColon: false
+SpaceInEmptyParentheses: false
+SpacesInAngles: false
+SpacesInConditionalStatement: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+TabWidth: 2
+...
--- a/.hgignore	Wed Oct 09 11:06:20 2024 +0200
+++ b/.hgignore	Thu Jan 30 17:41:33 2025 +0100
@@ -7,6 +7,7 @@
 .vscode/
 *~
 *.cmake.orig
+.idea/
 
 # when opening Orthanc in VSCode, it might find a java project and create files we wan't to ignore:
 .settings/
--- a/CITATION.cff	Wed Oct 09 11:06:20 2024 +0200
+++ b/CITATION.cff	Thu Jan 30 17:41:33 2025 +0100
@@ -10,5 +10,5 @@
 doi: "10.1007/s10278-018-0082-y"
 license: "GPL-3.0-or-later"
 repository-code: "https://orthanc.uclouvain.be/hg/orthanc/"
-version: 1.12.4
-date-released: 2024-06-05
+version: 1.12.6
+date-released: 2025-01-22
--- a/NEWS	Wed Oct 09 11:06:20 2024 +0200
+++ b/NEWS	Thu Jan 30 17:41:33 2025 +0100
@@ -10,82 +10,170 @@
   - using multiple disk for image storage
   - use more human friendly storage structure (experimental feature)
 
+REST API
+--------
+
+* API version upgraded to 28
+
+
 Plugins
 -------
 
-* New database plugin SDK (v4) to handle customData for attachments.
+* New database plugin SDK (vX) to handle customData for attachments.
 * New storage plugin SDK (v3) to handle customData for attachments,
 
-* TODO-FIND: complete the list of updated routes:
-  - /studies?expand and sibbling routes now also return "Metadata" (if the DB implements 'extended-api-v1')
-  - /studies?since=x&limit=0 and sibbling routes: limit=0 now means "no limit" instead of "no results"
+
+Maintenance
+-----------
+
+* In the "ExtendedFind" mode, optimized "tools/find" when "StorageAccessMode" is 
+  set to "Never".
+
+
+Version 1.12.6 (2025-01-22)
+===========================
+
+General
+-------
+
+* DICOM: Added support for C-GET SCU.
+* Added new configuration options:
+  - "AcceptedSopClasses" and "RejectedSopClasses" to limit the SOP classes
+    accepted by Orthanc when acting as C-STORE SCP.
+  - "DicomDefaultRetrieveMethod" to define whether Orthanc uses C-MOVE or
+    C-GET to retrieve a resource after a C-FIND (if calling "/queries/.../retrieve").
+    This configuration can be overridden for each DICOM modality by setting
+    the "RetrieveMethod" field in the "DicomModalities" section.
+    Default value: "C-MOVE" to preserve backward compatibility.
+  - "MaximumConcurrentDcmtkTranscoders" to reduce CPU and memory usage by limiting
+    the number of concurrent DCMTK transcoders that are simultaneously running
+    at any given time.
+
+REST API
+--------
+
+* API version upgraded to 27
+* C-GET SCU requests can be triggered through the new route "/modalities/{id}/get"
+
+Plugins
+-------
+
+* SDK: Added "OrthancPluginStartStreamAnswer()" and "OrthancPluginSendStreamChunk()"
+  to allow the sending of HTTP responses by chunks.
+
+Maintenance
+-----------
+
+* Fix: In the "ExtendedFind" mode, using "tools/find" while querying against
+  "ModalitiesInStudy" was not compatible with pagination. This notably prevented
+  the use of the modality filter in Orthanc Explorer 2.
+* If the "HttpsCACertificates" configuration is empty, Orthanc now uses the
+  operating system native CA store (if any). This is equivalent to the "--ca-native"
+  curl option.
+* Housekeeper plugin:
+  - Fix the "Force" configuration that was ineffective.
+  - Allow transcoding to lossy transfer syntax. Orthanc will leave
+    the "SOPInstanceUID" DICOM tag untouched in this case.
+* In the "/archive" routes:
+  - The numbers in the filenames now match the "InstanceNumber" tag whenever possible.
+    When not, the files are ordered in the same order as the instances in the series.
+  - Added optimization to use the "ExtendedFind" extension, hereby reducing the number
+    of SQL queries.
+* DICOM negotiation:
+  - When opening a DICOM SCU connection, Orthanc now only proposes the contexts that it is
+    going to use in the connection, and not all the contexts as in previous versions
+    (e.g., if performing a C-ECHO, Orthanc will not propose C-MOVE or C-FIND).
+* DICOM C-GET SCP: Orthanc will not refuse anymore to send, for instance, a
+  LittleEndianExplicit file, if the accepted transfer syntax is a compressed one.
+* By default, DCMTK now uses its own "oficonv" library for character set conversion.
+  This can be tuned using the new CMake option "-DDCMTK_LOCALE_BACKEND=oficonv".
+* Improved progress reporting for DicomMoveScu jobs.
+* Upgraded dependencies for static builds:
+  - dcmtk 3.6.9
+
+
+Version 1.12.5 (2024-12-17)
+===========================
 
 General
 -------
 
 * Database:
-  - Introduced database optimizations "ExtendedFind" to replace many small SQL queries
-    by a small number of large SQL queries to greatly reduce the cost of DB latency.
-    Furthermore, this "ExtendedFind" brings new sorting and filtering features to the 
-    Rest API (TODO).
-  - Introduced database optimizations "ExtendedChanges" to allow filtering of /changes.
+  - Introduced the database optimization "ExtendedFind" to replace many small SQL queries
+    by a single, large SQL query. This can greatly reduce the cost related to latency
+    when working with large databases (e.g., if using PostgreSQL).
+    Furthermore, "ExtendedFind" brings new sorting and filtering features to the
+    REST API, mainly in "/tools/find".
+  - Introduced the new database primitive "ExtendedChanges" to allow filtering on "/changes".
   - Reduced the number of SQL queries when ingesting DICOM files.
-* Introduced a new configuration "ReadOnly" to forbid an Orthanc instance to perform 
-  any modifications in the Index DB or in the storage.
+* Introduced a new configuration "ReadOnly" to forbid an Orthanc instance to perform
+  any modification to the index database or to the storage area.
   
+
 REST API
 --------
 
-* API version upgraded to 25
-* Improved parsing of multiple numerical values in DICOM tags.
+* API version upgraded to 26
+* Support HTTP "Range" request header on "{...}/attachments/{...}/data" and
+  "{...}/attachments/{...}/compressed-data"
+* Improved parsing of multiple numerical values in DICOM tags
   https://discourse.orthanc-server.org/t/qido-includefield-with-sequences/4746/6
-* in /system, added a new field "Capabilities" with new values:
-  - "HasExtendedChanges" for DB backend that provides this feature (the default SQLite DB
-    or PostgreSQL vX.X, MySQL vX.X, ODBC vX.X).
-  - "HasExtendedFind" for DB backend that provides this feature (the default SQLite DB
-    or PostgreSQL vX.X, MySQL vX.X, ODBC vX.X).
-* With DB backend with "HasExtendedChanges" support, /changes now supports 2 more options: 
-  - 'type' to filter the changes returned by the query 
-  - 'to' to potentially cycle through changes in reverse order.
-  example: /changes?type=StableStudy&to=7584&limit=100
-* With DB backend with "HasExtendedFind" support, /tools/find now supports new options:
-  - 'OrderBy' to order by DICOM Tag or metadata value
-  - 'ParentPatient', 'ParentStudy', 'ParentSeries' to retrieve only descendants of an
-    Orthanc resource.
-  - 'QueryMetadata' to filter results based on metadata values.
-  - 'ResponseContent' to define what shall be included in the response for each returned
-    resource (e.g: Metadata, Children, ...)
-
+* In "/system", added a new field "Capabilities" with new values:
+  - "HasExtendedChanges" if the index database provides this optimization
+  - "HasExtendedFind" if the index database provides this primitive
+* If the index database provides the "HasExtendedChanges" primitive, "/changes"
+  supports two additional arguments:
+  - "type" to filter the changes returned by the query
+  - "to" to possibly cycle through changes in reverse order
+  Example: "/changes?type=StableStudy&to=7584&limit=100"
+* If the index database provides the "HasExtendedFind" primitive, "/tools/find"
+  supports new options:
+  - "OrderBy" to order by DICOM tag or metadata value
+  - "ParentPatient", "ParentStudy", and "ParentSeries" to retrieve only descendants of a
+    given DICOM resource
+  - "MetadataQuery" to filter results based on metadata values
+  - "ResponseContent" to define what shall be included in the response for each returned
+    resource (e.g: Metadata, Children,...)
+* In "/tools/find", the "Limit" and "Since" arguments are not allowed anymore if the
+  query requests filtering on DICOM tags that are not stored in the index database
+* The new "/tools/count-resources" API route is similar to "tools/find" but only
+  returns the number of resources matching the criteria
+* "/studies?since=x&limit=0" and similar routes for patients, series, and instances:
+  "limit=0" now means "no limit" instead of "no results" as in previous versions of Orthanc
+* In DICOMweb JSON, the "DS - Decimal String" values were previously represented as float
+  numbers, but are now represented as strings to avoid introduction of long float representation
+  (e.g 0.1429999999999 vs "0.143") and to be compliant with the DICOMweb standard:
+  https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.3.html
+  This has no impact on the Stone Web viewer and OHIF:
+  https://discourse.orthanc-server.org/t/dicomwebplugin-does-not-return-series-metadata-properly/5195
 
 Maintenance
 -----------
 
 * DICOM TLS: "DicomTlsTrustedCertificates" is not required anymore when issuing
-  an outgoing SCU connexion when "DicomTlsRemoteCertificateRequired" is set to false.
-* Introduced a new thread to update the statistics at regular interval for the
-  DB plugins that are implementing the UpdateAndGetStatistics function (currently only
-  PostgreSQL).  This avoids very long update times in case you don't call /statistics
-  for a long period.
-* Fix C-Find queries not returning computed tags like ModalitiesInStudy, NumberOfStudyRelatedSeries, ...
-  in very specific use-cases.
-* Fix extremely rare error when 2 threads are trying to create the same folder in the File Storage 
-  at the same time.
+  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 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
+* Fix deadlock when parsing specific invalid DICOM files
+* Loading plugins: Orthanc will now fail to start when provided with a plugin path
+  that can not be found
 * Metrics:
-  - fix a few metrics that were not published
-  - added 2 metrics: orthanc_storage_cache_miss_count & orthanc_storage_cache_hit_count 
-* Upgraded dependencies for static builds:
-  - curl 8.9.0
-  - SQLite 3.46
+  - Fix a few metrics that were not published
+  - Added 2 metrics: "orthanc_storage_cache_miss_count" and "orthanc_storage_cache_hit_count"
 * Added a new fallback when trying to decode a frame: transcode the file using the plugin
-  before decoding the frame.  This solves some issues with JP2K Lossy compression:
+  before decoding the frame. This solves some issues with JP2K Lossy compression:
   https://discourse.orthanc-server.org/t/decoding-displaying-jpeg2000-lossy-images/5117
 * Added new warnings that can be disabled in the configuration: 
   - W003_DecoderFailure
   - W004_NoMainDicomTagsSignature
   - W005_RequestingTagFromLowerResourceLevel
-* New default MainDicomTags are now stored in DB.  Note that, in order to store these values
-  for resources that were ingested in Orthanc before this release, you would have to run
-  the Housekeeper plugin or call /reconstruct on every resources
+  - W006_RequestingTagFromMetaHeader
+  - W007_MissingRequestedTagsNotReadFromDisk
+* New default MainDicomTags are now stored in the DB:
   - At Study Level:
     - TimezoneOffsetFromUTC (used in QIDO-RS default queries)
   - At Series Level:
@@ -93,7 +181,13 @@
     - PerformedProcedureStepStartDate (used in QIDO-RS default queries)
     - PerformedProcedureStepStartTime (used in QIDO-RS default queries)
     - RequestAttributesSequence (used in QIDO-RS default queries)
-
+  - Note that, in order to access these values for resources that were ingested in Orthanc
+    before this release, you will have to run the Housekeeper plugin or to call
+    "/reconstruct" on every resource
+* Upgraded dependencies for static builds:
+  - boost 1.86.0
+  - curl 8.9.0
+  - SQLite 3.46
 
 
 Version 1.12.4 (2024-06-05)
@@ -118,7 +212,7 @@
 * Housekeeper plugin: 
   - Added an option "LimitMainDicomTagsReconstructLevel"
     (allowed values: "Patient", "Study", "Series", "Instance"). This can greatly speed
-    up the housekeeper process, e.g. if you have only update the Study level ExtraMainDicomTags.
+    up the housekeeper process, e.g. if you have only updated the Study level ExtraMainDicomTags.
   - Fixed broken /instances/../tags route after running the Housekeeper
     after having changed the "IngestTranscoding".
 * SDK: added OrthancPluginLogMessage() as a new primitive for plugins
--- a/OrthancFramework/Resources/CMake/AutoGeneratedCode.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/AutoGeneratedCode.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/BoostConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/BoostConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -91,10 +91,10 @@
   ## Parameters for static compilation of Boost 
   ##
   
-  set(BOOST_NAME boost_1_85_0)
-  set(BOOST_VERSION 1.85.0)
-  set(BOOST_BCP_SUFFIX bcpdigest-1.12.4)
-  set(BOOST_MD5 "1017e9c8383efdea01c059a8d3cc4dda")
+  set(BOOST_NAME boost_1_86_0)
+  set(BOOST_VERSION 1.86.0)
+  set(BOOST_BCP_SUFFIX bcpdigest-1.12.5)
+  set(BOOST_MD5 "20b9c325c0dde830889ee75a9e64ded8")
   set(BOOST_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz")
   set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
 
@@ -115,7 +115,7 @@
   if (FirstRun)
     execute_process(
       COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-      ${CMAKE_CURRENT_LIST_DIR}/../Patches/boost-1.85.0-emscripten.patch
+      ${CMAKE_CURRENT_LIST_DIR}/../Patches/boost-1.86.0-emscripten.patch
       WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
       RESULT_VARIABLE Failure
       )
--- a/OrthancFramework/Resources/CMake/BoostConfiguration.sh	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/BoostConfiguration.sh	Thu Jan 30 17:41:33 2025 +0100
@@ -27,9 +27,10 @@
 ##   - Orthanc 1.12.2: Boost 1.83.0
 ##   - Orthanc 1.12.3: Boost 1.84.0
 ##   - Orthanc > 1.12.3: Boost 1.85.0
+##   - Orthanc 1.12.5: Boost 1.86.0
 
-BOOST_VERSION=1_85_0
-ORTHANC_VERSION=1.12.4
+BOOST_VERSION=1_86_0
+ORTHANC_VERSION=1.12.5
 
 rm -rf /tmp/boost_${BOOST_VERSION}
 rm -rf /tmp/bcp/boost_${BOOST_VERSION}
--- a/OrthancFramework/Resources/CMake/BoostConfigurationStatic-1.69.0.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/BoostConfigurationStatic-1.69.0.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/CivetwebConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/CivetwebConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/Compiler.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/Compiler.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -239,7 +239,9 @@
   add_definitions(
     -D_XOPEN_SOURCE=1
     )
-  link_libraries(iconv)
+  
+  # Linking with iconv breaks the Universal builds on modern compilers
+  # link_libraries(iconv)
 
 elseif (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
   message("Building using Emscripten (for WebAssembly or asm.js targets)")
--- a/OrthancFramework/Resources/CMake/DcmtkConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/DcmtkConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -39,6 +39,8 @@
     include(${CMAKE_CURRENT_LIST_DIR}/DcmtkConfigurationStatic-3.6.7.cmake)
   elseif (DCMTK_STATIC_VERSION STREQUAL "3.6.8")
     include(${CMAKE_CURRENT_LIST_DIR}/DcmtkConfigurationStatic-3.6.8.cmake)
+  elseif (DCMTK_STATIC_VERSION STREQUAL "3.6.9")
+    include(${CMAKE_CURRENT_LIST_DIR}/DcmtkConfigurationStatic-3.6.9.cmake)
   else()
     message(FATAL_ERROR "Unsupported version of DCMTK: ${DCMTK_STATIC_VERSION}")
   endif()
@@ -58,9 +60,9 @@
     )
 
   if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
-    message(${DCMTK_SOURCES_DIR})
     list(REMOVE_ITEM DCMTK_SOURCES 
       ${DCMTK_SOURCES_DIR}/ofstd/libsrc/offilsys.cc
+      ${DCMTK_SOURCES_DIR}/ofstd/libsrc/ofwhere.c   # Needed since DCMTK 3.6.9
       )
   endif()
 
--- a/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.0.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.0.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.2.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.2.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.4.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.4.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.5.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.5.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.6.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.6.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.7.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.7.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.8.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.8.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.9.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,301 @@
+# 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 Lesser 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+SET(DCMTK_VERSION_NUMBER 369)
+SET(DCMTK_PACKAGE_VERSION "3.6.9")
+SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.9)
+SET(DCMTK_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/dcmtk-3.6.9.tar.gz")
+SET(DCMTK_MD5 "cb30587f8da760c832a4f19d159acda5")
+
+macro(DCMTK_UNSET)
+endmacro()
+
+macro(DCMTK_UNSET_CACHE)
+endmacro()
+
+set(DCMTK_BINARY_DIR ${DCMTK_SOURCES_DIR}/)
+set(DCMTK_CMAKE_INCLUDE ${DCMTK_SOURCES_DIR}/)
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  set(DCMTK_WITH_THREADS OFF)  # Disable thread support in wasm/asm.js
+else()
+  set(DCMTK_WITH_THREADS ON)
+endif()
+
+add_definitions(-DDCMTK_INSIDE_LOG4CPLUS=1)
+
+if (IS_DIRECTORY "${DCMTK_SOURCES_DIR}")
+  set(FirstRun OFF)
+else()
+  set(FirstRun ON)
+endif()
+
+DownloadPackage(${DCMTK_MD5} ${DCMTK_URL} "${DCMTK_SOURCES_DIR}")
+
+
+if (FirstRun)
+  # Apply the patches
+  execute_process(
+    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+    ${CMAKE_CURRENT_LIST_DIR}/../Patches/dcmtk-3.6.9.patch
+    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+    RESULT_VARIABLE Failure
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while patching a file")
+  endif()
+
+  if (MSVC)
+    # Older versions of Microsoft Visual Studio (notably MSVC2008)
+    # don't like void usage of function arguments in C source files,
+    # in order to avoid a warning about unused arguments. This patch
+    # removes such usages that were not present in DCMTK <= 3.6.6.
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+      ${CMAKE_CURRENT_LIST_DIR}/../Patches/dcmtk-3.6.9-visual-studio.patch
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
+
+    if (Failure)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
+  endif()
+
+  configure_file(
+    ${CMAKE_CURRENT_LIST_DIR}/../Patches/dcmtk-dcdict_orthanc.cc
+    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict_orthanc.cc
+    COPYONLY)
+else()
+  message("The patches for DCMTK have already been applied")
+endif()
+
+
+include_directories(
+  ${DCMTK_SOURCES_DIR}/dcmiod/include
+  ${DCMTK_SOURCES_DIR}/oficonv/include
+  )
+
+
+# C_CHAR_UNSIGNED *must* be set before calling "GenerateDCMTKConfigure.cmake"
+IF (CMAKE_CROSSCOMPILING)
+  if (CMAKE_COMPILER_IS_GNUCXX AND
+      CMAKE_SYSTEM_NAME STREQUAL "Windows")  # MinGW
+    SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.")
+
+  elseif(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")  # WebAssembly or asm.js
+
+    # Check out "../WebAssembly/ArithmeticTests/" to regenerate the
+    # "arith.h" file
+    configure_file(
+      ${CMAKE_CURRENT_LIST_DIR}/WebAssembly/arith.h
+      ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/arith.h
+      COPYONLY)
+
+    UNSET(C_CHAR_UNSIGNED CACHE)
+    SET(C_CHAR_UNSIGNED 0 CACHE INTERNAL "")
+
+  else()
+    message(FATAL_ERROR "Support your platform here")
+  endif()
+ENDIF()
+
+
+if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+  SET(DCMTK_ENABLE_CHARSET_CONVERSION "iconv" CACHE STRING "")
+  SET(HAVE_SYS_GETTID 0 CACHE INTERNAL "")
+endif()
+
+
+SET(DCMTK_SOURCE_DIR ${DCMTK_SOURCES_DIR})
+include(GNUInstallDirs)  # Needed since DCMTK 3.6.9
+include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake)
+include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake)
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")  # WebAssembly or
+  # asm.js The macros below are not properly discovered by DCMTK
+  # when using WebAssembly. Check out "../WebAssembly/arith.h" for
+  # how we produced these values. This step MUST be after
+  # "GenerateDCMTKConfigure" and before the generation of
+  # "osconfig.h".
+  UNSET(SIZEOF_VOID_P   CACHE)
+  UNSET(SIZEOF_CHAR     CACHE)
+  UNSET(SIZEOF_DOUBLE   CACHE)
+  UNSET(SIZEOF_FLOAT    CACHE)
+  UNSET(SIZEOF_INT      CACHE)
+  UNSET(SIZEOF_LONG     CACHE)
+  UNSET(SIZEOF_SHORT    CACHE)
+  UNSET(SIZEOF_VOID_P   CACHE)
+
+  SET(SIZEOF_VOID_P 4   CACHE INTERNAL "")
+  SET(SIZEOF_CHAR 1     CACHE INTERNAL "")
+  SET(SIZEOF_DOUBLE 8   CACHE INTERNAL "")
+  SET(SIZEOF_FLOAT 4    CACHE INTERNAL "")
+  SET(SIZEOF_INT 4      CACHE INTERNAL "")
+  SET(SIZEOF_LONG 4     CACHE INTERNAL "")
+  SET(SIZEOF_SHORT 2    CACHE INTERNAL "")
+  SET(SIZEOF_VOID_P 4   CACHE INTERNAL "")
+endif()
+
+
+set(DCMTK_PACKAGE_VERSION_SUFFIX "")
+set(DCMTK_PACKAGE_VERSION_NUMBER ${DCMTK_VERSION_NUMBER})
+
+
+# For the dcmtls module, necessary since DCMTK 3.6.7 (cf. file
+# "dcmtls/libsrc/tlslayer.cc"). This must be done before the
+# invokation of "configure_file()"!
+if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL)
+  # The "CHECK_FUNCTIONWITHHEADER_EXISTS()" provided by DCMTK only
+  # works with the system-wide version of OpenSSL. If statically
+  # linking against OpenSSL, we manually provide information about
+  # OpenSSL 3.0.x
+  set(HAVE_OPENSSL_PROTOTYPE_DH_BITS 1)
+  set(HAVE_OPENSSL_PROTOTYPE_EVP_PKEY_BASE_ID 1)
+  set(HAVE_OPENSSL_PROTOTYPE_SSL_CTX_GET0_PARAM 1)
+  set(HAVE_OPENSSL_PROTOTYPE_SSL_CTX_GET_CERT_STORE 1)
+  set(HAVE_OPENSSL_PROTOTYPE_SSL_CTX_GET_CIPHERS 1)
+  set(HAVE_OPENSSL_PROTOTYPE_X509_GET_SIGNATURE_NID 1)
+  set(HAVE_OPENSSL_PROTOTYPE_X509_STORE_GET0_PARAM 1)
+else()
+  CHECK_FUNCTIONWITHHEADER_EXISTS("DH_bits" "openssl/dh.h" HAVE_OPENSSL_PROTOTYPE_DH_BITS)
+  CHECK_FUNCTIONWITHHEADER_EXISTS("EVP_PKEY_base_id" "openssl/evp.h" HAVE_OPENSSL_PROTOTYPE_EVP_PKEY_BASE_ID)
+  CHECK_FUNCTIONWITHHEADER_EXISTS("SSL_CTX_get0_param" "openssl/ssl.h" HAVE_OPENSSL_PROTOTYPE_SSL_CTX_GET0_PARAM)
+  CHECK_FUNCTIONWITHHEADER_EXISTS("SSL_CTX_get_cert_store" "openssl/ssl.h" HAVE_OPENSSL_PROTOTYPE_SSL_CTX_GET_CERT_STORE)
+  CHECK_FUNCTIONWITHHEADER_EXISTS("SSL_CTX_get_ciphers" "openssl/ssl.h" HAVE_OPENSSL_PROTOTYPE_SSL_CTX_GET_CIPHERS)
+  CHECK_FUNCTIONWITHHEADER_EXISTS("X509_STORE_get0_param" "openssl/x509.h" HAVE_OPENSSL_PROTOTYPE_X509_STORE_GET0_PARAM)
+  CHECK_FUNCTIONWITHHEADER_EXISTS("X509_get_signature_nid" "openssl/x509.h" HAVE_OPENSSL_PROTOTYPE_X509_GET_SIGNATURE_NID)
+endif()
+
+
+# "DCMTK_ENABLE_CHARSET_CONVERSION" is defined by "osconfig.h.in"
+if (NOT DEFINED DCMTK_LOCALE_BACKEND OR   # This is the case if locale support is disabled (e.g. in Stone)
+    DCMTK_LOCALE_BACKEND STREQUAL "gcc")
+  set(DCMTK_ENABLE_CHARSET_CONVERSION "DCMTK_CHARSET_CONVERSION_STDLIBC_ICONV" CACHE STRING "" FORCE)
+elseif (DCMTK_LOCALE_BACKEND STREQUAL "libiconv")
+  set(DCMTK_ENABLE_CHARSET_CONVERSION "DCMTK_CHARSET_CONVERSION_ICONV" CACHE STRING "" FORCE)
+elseif (DCMTK_LOCALE_BACKEND STREQUAL "icu")
+  message(FATAL_ERROR "Support for ICU has been removed since DCMTK 3.6.9")
+elseif (DCMTK_LOCALE_BACKEND STREQUAL "oficonv")
+  set(DCMTK_ENABLE_CHARSET_CONVERSION "DCMTK_CHARSET_CONVERSION_OFICONV" CACHE STRING "" FORCE)
+else()
+  message(FATAL_ERROR "Invalid value for DCMTK_LOCALE_BACKEND: ${DCMTK_LOCALE_BACKEND}")
+endif()
+
+CONFIGURE_FILE(
+  ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in
+  ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h)
+
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  link_libraries(netapi32)  # For NetWkstaUserGetInfo@12
+  link_libraries(iphlpapi)  # For GetAdaptersInfo@8
+
+  # Configure Wine if cross-compiling for Windows
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    include(${DCMTK_SOURCES_DIR}/CMake/dcmtkUseWine.cmake)
+    FIND_PROGRAM(WINE_WINE_PROGRAM wine)
+    FIND_PROGRAM(WINE_WINEPATH_PROGRAM winepath)
+    list(APPEND DCMTK_TRY_COMPILE_REQUIRED_CMAKE_FLAGS "-DCMAKE_EXE_LINKER_FLAGS=-static")
+  endif()
+endif()
+
+# This step must be after the generation of "osconfig.h" => Removed since DCMTK 3.6.9
+#if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+#  INSPECT_FUNDAMENTAL_ARITHMETIC_TYPES()
+#endif()
+
+
+# Source for the logging facility of DCMTK
+AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES)
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
+  list(REMOVE_ITEM DCMTK_SOURCES
+    ${DCMTK_SOURCES_DIR}/oflog/libsrc/clfsap.cc
+    ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc
+    ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc
+    )
+
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  list(REMOVE_ITEM DCMTK_SOURCES
+    ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc
+    ${DCMTK_SOURCES_DIR}/oflog/libsrc/clfsap.cc
+    )
+endif()
+
+
+# Starting with DCMTK 3.6.2, the Nagle algorithm is not disabled by
+# default since this does not seem to be appropriate (anymore) for
+# most modern operating systems. In order to change this default, the
+# environment variable NO_TCPDELAY can be set to "1" (see envvars.txt
+# for details). Alternatively, the macro DISABLE_NAGLE_ALGORITHM can
+# be defined to change this setting at compilation time (see
+# macros.txt for details).
+# https://forum.dcmtk.org/viewtopic.php?t=4632
+add_definitions(
+  -DDISABLE_NAGLE_ALGORITHM=1
+  )
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  # For compatibility with Windows XP, avoid using fiber-local-storage
+  # in log4cplus, but use thread-local-storage instead. Otherwise,
+  # Windows XP complains about missing "FlsGetValue()" in KERNEL32.dll
+  add_definitions(
+    -DDCMTK_LOG4CPLUS_AVOID_WIN32_FLS
+    )
+
+  if (CMAKE_COMPILER_IS_GNUCXX OR             # MinGW
+      "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")  # MSVC for 32bit (*)
+
+    # (*) With multithreaded logging enabled, Visual Studio 2008 fails
+    # with error: ".\dcmtk-3.6.7\oflog\libsrc\globinit.cc(422) : error
+    # C2664: 'dcmtk::log4cplus::thread::impl::tls_init' : cannot
+    # convert parameter 1 from 'void (__stdcall *)(void *)' to
+    # 'dcmtk::log4cplus::thread::impl::tls_init_cleanup_func_type'"
+    #   None of the functions with this name in scope match the target type
+
+    add_definitions(
+      -DDCMTK_LOG4CPLUS_SINGLE_THREADED
+      )
+  endif()
+
+  if (CMAKE_COMPILER_IS_GNUCXX)  # MinGW
+    # Necessary since DCMTK 3.6.9
+    add_definitions(
+      -DENABLE_OLD_OFSTD_FTOA_IMPLEMENTATION
+      -DENABLE_OLD_OFSTD_ATOF_IMPLEMENTATION
+      )
+  endif()
+endif()
+
+
+if (DCMTK_LOCALE_BACKEND STREQUAL "oficonv")
+  AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oficonv/libsrc DCMTK_SOURCES)
+endif()
--- a/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -165,6 +165,10 @@
         set(ORTHANC_FRAMEWORK_MD5 "975f5bf2142c22cb1777b4f6a0a614c5")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.4")
         set(ORTHANC_FRAMEWORK_MD5 "1e61779ea4a7cd705720bdcfed8a6a73")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.5")
+        set(ORTHANC_FRAMEWORK_MD5 "5bb69f092981fdcfc11dec0a0f9a7db3")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.6")
+        set(ORTHANC_FRAMEWORK_MD5 "0e971f32f4f3e4951e0f3b5de49a3da6")
 
       # Below this point are development snapshots that were used to
       # release some plugin, before an official release of the Orthanc
--- a/OrthancFramework/Resources/CMake/DownloadPackage.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/DownloadPackage.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/EmscriptenParameters.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/EmscriptenParameters.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/GoogleTestConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/GoogleTestConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/JsonCppConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/JsonCppConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/LibIconvConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/LibIconvConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/LibIcuConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/LibIcuConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/LibJpegConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/LibJpegConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/LibP11Configuration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/LibP11Configuration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/LibPngConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/LibPngConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/LuaConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/LuaConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/MongooseConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/MongooseConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/OpenSslConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/OpenSslConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/OpenSslConfigurationStatic-1.1.1.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/OpenSslConfigurationStatic-1.1.1.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/OpenSslConfigurationStatic-3.0.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/OpenSslConfigurationStatic-3.0.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -456,7 +456,8 @@
     # is shipped with the stdlib
     unset(BOOST_LOCALE_BACKEND CACHE)
   else()
-    if (BOOST_LOCALE_BACKEND STREQUAL "gcc")
+    if (BOOST_LOCALE_BACKEND STREQUAL "gcc" OR
+        BOOST_LOCALE_BACKEND STREQUAL "oficonv")
     elseif (BOOST_LOCALE_BACKEND STREQUAL "libiconv")
       include(${CMAKE_CURRENT_LIST_DIR}/LibIconvConfiguration.cmake)
     elseif (BOOST_LOCALE_BACKEND STREQUAL "icu")
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -39,7 +39,7 @@
 # Version of the Orthanc API, can be retrieved from "/system" URI in
 # order to check whether new URI endpoints are available even if using
 # the mainline version of Orthanc
-set(ORTHANC_API_VERSION "25")
+set(ORTHANC_API_VERSION "28")
 
 
 #####################################################################
@@ -79,7 +79,7 @@
 
 # Parameters specific to DCMTK
 set(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (only when using system version of DCMTK)")
-set(DCMTK_STATIC_VERSION "3.6.8" CACHE STRING "Version of DCMTK to be used in static builds (can be \"3.6.0\", \"3.6.2\", \"3.6.4\", \"3.6.5\", \"3.6.6\", \"3.6.7\", or \"3.6.8\")")
+set(DCMTK_STATIC_VERSION "3.6.9" CACHE STRING "Version of DCMTK to be used in static builds (can be \"3.6.0\", \"3.6.2\", \"3.6.4\", \"3.6.5\", \"3.6.6\", \"3.6.7\", \"3.6.8\", or \"3.6.9\")")
 set(USE_DCMTK_362_PRIVATE_DIC ON CACHE BOOL "Use the dictionary of private tags from DCMTK 3.6.2 if using DCMTK 3.6.0")
 set(USE_SYSTEM_DCMTK ON CACHE BOOL "Use the system version of DCMTK")
 set(ENABLE_DCMTK_LOG ON CACHE BOOL "Enable logging internal to DCMTK")
@@ -90,6 +90,7 @@
 set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
 set(SYSTEM_MONGOOSE_USE_CALLBACKS ON CACHE BOOL "The system version of Mongoose uses callbacks (version >= 3.7)")
 set(BOOST_LOCALE_BACKEND "libiconv" CACHE STRING "Back-end for locales that is used by Boost (can be \"gcc\", \"libiconv\", \"icu\", or \"wconv\" on Windows)")
+set(DCMTK_LOCALE_BACKEND "oficonv" CACHE STRING "Back-end for locales that is used by DCMTK (can be \"gcc\", \"libiconv\", \"icu\" (only up to DCMTK 3.6.8), \"oficonv\")")
 set(USE_PUGIXML ON CACHE BOOL "Use the Pugixml parser (turn off only for debug)")
 set(USE_LEGACY_JSONCPP OFF CACHE BOOL "Use the old branch 0.x.y of JsonCpp, that does not require a C++11 compiler (for LSB and old versions of Visual Studio)")
 set(USE_LEGACY_LIBICU OFF CACHE BOOL "Use icu icu4c-58_2, latest version not requiring a C++11 compiler (for LSB and old versions of Visual Studio)")
--- a/OrthancFramework/Resources/CMake/ProtobufConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/ProtobufConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/PugixmlConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/PugixmlConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/SQLiteConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/SQLiteConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -20,16 +20,7 @@
 # <http://www.gnu.org/licenses/>.
 
 
-if (APPLE)
-  # Under OS X, the binaries must always be linked against the
-  # system-wide version of SQLite. Otherwise, if some Orthanc plugin
-  # also uses its own version of SQLite (such as orthanc-webviewer),
-  # this results in a crash in "sqlite3_mutex_enter(db->mutex);" (the
-  # mutex is not initialized), probably because the EXE and the DYNLIB
-  # share the same memory location for this mutex.
-  set(SQLITE_STATIC OFF)
-
-elseif (STATIC_BUILD OR NOT USE_SYSTEM_SQLITE)
+if (STATIC_BUILD OR NOT USE_SYSTEM_SQLITE)
   set(SQLITE_STATIC ON)
 else()
   set(SQLITE_STATIC OFF)
--- a/OrthancFramework/Resources/CMake/UuidConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/UuidConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/VisualStudioPrecompiledHeaders.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/VisualStudioPrecompiledHeaders.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/WebAssembly/ArithmeticTests/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/WebAssembly/ArithmeticTests/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CMake/ZlibConfiguration.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CMake/ZlibConfiguration.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CheckOrthancFrameworkSymbols.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CheckOrthancFrameworkSymbols.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CodeGeneration/CheckDcmtkTransferSyntaxes.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CodeGeneration/CheckDcmtkTransferSyntaxes.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CodeGeneration/ErrorCodes.json	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CodeGeneration/ErrorCodes.json	Thu Jan 30 17:41:33 2025 +0100
@@ -607,6 +607,11 @@
     "Name": "NoCGetHandler", 
     "Description": "No request handler factory for DICOM C-GET SCP"
   },
+  {
+    "Code": 2045, 
+    "Name": "DicomGetUnavailable", 
+    "Description": "DicomUserConnection: The C-GET command is not supported by the remote SCP"
+  },
 
 
 
--- a/OrthancFramework/Resources/CodeGeneration/GenerateErrorCodes.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CodeGeneration/GenerateErrorCodes.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxes.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxes.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxesDcmtk.mustache	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxesDcmtk.mustache	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxesEnumerations.mustache	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxesEnumerations.mustache	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/DcmtkTools/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/DcmtkTools/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/DcmtkTools/dummy.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/DcmtkTools/dummy.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/EmbedResources.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/EmbedResources.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/Graveyard/EclipseCodingStyle.xml	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,167 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<profiles version="1">
-<profile kind="CodeFormatterProfile" name="Orthanc" version="1">
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.lineSplit" value="80"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_member_access" value="0"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_base_types" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
-<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_constructor_initializer_list" value="0"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_exception_specification" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_base_types" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_access_specifier" value="true"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_exception_specification" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_arguments" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.comment.min_distance_between_code_and_line_comment" value="1"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_expressions_in_array_initializer" value="18"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_declarator_list" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_bracket" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.tabulation.size" value="2"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_enumerator_list" value="49"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_declarator_list" value="16"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.indent_empty_lines" value="false"/>
-<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
-<setting id="org.eclipse.cdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.brace_position_for_method_declaration" value="next_line"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_arguments" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_base_clause" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.join_wrapped_lines" value="true"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_declarator_list" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_arguments_in_method_invocation" value="18"/>
-<setting id="org.eclipse.cdt.core.formatter.comment.never_indent_line_comments_on_first_column" value="true"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_brackets" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_bracket" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_parameters_in_method_declaration" value="18"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block" value="next_line"/>
-<setting id="org.eclipse.cdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="true"/>
-<setting id="org.eclipse.cdt.core.formatter.brace_position_for_type_declaration" value="next_line"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_arguments" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_expression_list" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_parameters" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.continuation_indentation" value="2"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_expression_list" value="0"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_parameters" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression" value="34"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
-<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_extra_spaces" value="0"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_compare_to_type_header" value="false"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_namespace_header" value="true"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_compact_if" value="16"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_assignment" value="16"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression_chain" value="18"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_parameters" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_expression_list" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_exception_specification" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_identifier_in_function_declaration" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_base_clause_in_type_declaration" value="80"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_exception_specification" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.indent_declaration_compare_to_template_header" value="false"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_body" value="true"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_binary_expression" value="18"/>
-<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_block" value="true"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_arguments" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_parameters" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.tabulation.char" value="space"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_parameters" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_colon_in_constructor_initializer_list" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block_in_case" value="next_line"/>
-<setting id="org.eclipse.cdt.core.formatter.compact_else_if" value="true"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_template_declaration" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_base_clause" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
-<setting id="org.eclipse.cdt.core.formatter.brace_position_for_switch" value="next_line"/>
-<setting id="org.eclipse.cdt.core.formatter.alignment_for_overloaded_left_shift_chain" value="18"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.indentation.size" value="2"/>
-<setting id="org.eclipse.cdt.core.formatter.brace_position_for_namespace_declaration" value="next_line"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_arguments" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.brace_position_for_array_initializer" value="next_line"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_namespace_declaration" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_bracket" value="do not insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_while_in_do_statement" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_parameters" value="insert"/>
-<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_arguments" value="do not insert"/>
-</profile>
-</profiles>
--- a/OrthancFramework/Resources/Graveyard/FromDcmtkBridge.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,192 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-  DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag)
-  {
-    DcmTag key(tag.GetGroup(), tag.GetElement());
-
-    if (tag.IsPrivate())
-    {
-      // This raises BitBucket issue 140 (Modifying private tags with
-      // REST API changes VR from LO to UN)
-      // https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=140
-      LOG(WARNING) << "You are using DCMTK < 3.6.1: All the private tags "
-        "are considered as having a binary value representation";
-      return new DcmOtherByteOtherWord(key);
-    }
-    else if (IsBinaryTag(key))
-    {
-      return new DcmOtherByteOtherWord(key);
-    }
-
-    switch (key.getEVR())
-    {
-      // http://support.dcmtk.org/docs/dcvr_8h-source.html
-
-      /**
-       * Binary types, handled above
-       **/
-    
-#if DCMTK_VERSION_NUMBER >= 361
-      case EVR_OD:
-#endif            
-
-#if DCMTK_VERSION_NUMBER >= 362
-      case EVR_OL:
-#endif            
-
-      case EVR_OB:  // other byte
-      case EVR_OF:  // other float
-      case EVR_OW:  // other word
-      case EVR_UN:  // unknown value representation
-      case EVR_ox:  // OB or OW depending on context
-        throw OrthancException(ErrorCode_InternalError);
-
-
-      /**
-       * String types.
-       * http://support.dcmtk.org/docs/classDcmByteString.html
-       **/
-      
-      case EVR_AS:  // age string
-        return new DcmAgeString(key);
-
-      case EVR_AE:  // application entity title
-        return new DcmApplicationEntity(key);
-
-      case EVR_CS:  // code string
-        return new DcmCodeString(key);        
-
-      case EVR_DA:  // date string
-        return new DcmDate(key);
-        
-      case EVR_DT:  // date time string
-        return new DcmDateTime(key);
-
-      case EVR_DS:  // decimal string
-        return new DcmDecimalString(key);
-
-      case EVR_IS:  // integer string
-        return new DcmIntegerString(key);
-
-      case EVR_TM:  // time string
-        return new DcmTime(key);
-
-      case EVR_UI:  // unique identifier
-        return new DcmUniqueIdentifier(key);
-
-      case EVR_ST:  // short text
-        return new DcmShortText(key);
-
-      case EVR_LO:  // long string
-        return new DcmLongString(key);
-
-      case EVR_LT:  // long text
-        return new DcmLongText(key);
-
-      case EVR_UT:  // unlimited text
-        return new DcmUnlimitedText(key);
-
-      case EVR_SH:  // short string
-        return new DcmShortString(key);
-
-      case EVR_PN:  // person name
-        return new DcmPersonName(key);
-
-#if DCMTK_VERSION_NUMBER >= 361
-      case EVR_UC:  // unlimited characters
-        return new DcmUnlimitedCharacters(key);
-#endif
-
-#if DCMTK_VERSION_NUMBER >= 361
-      case EVR_UR:  // URI/URL
-        return new DcmUniversalResourceIdentifierOrLocator(key);
-#endif
-          
-        
-      /**
-       * Numerical types
-       **/ 
-      
-      case EVR_SL:  // signed long
-        return new DcmSignedLong(key);
-
-      case EVR_SS:  // signed short
-        return new DcmSignedShort(key);
-
-      case EVR_UL:  // unsigned long
-        return new DcmUnsignedLong(key);
-
-      case EVR_US:  // unsigned short
-        return new DcmUnsignedShort(key);
-
-      case EVR_FL:  // float single-precision
-        return new DcmFloatingPointSingle(key);
-
-      case EVR_FD:  // float double-precision
-        return new DcmFloatingPointDouble(key);
-
-
-      /**
-       * Sequence types, should never occur at this point.
-       **/
-
-      case EVR_SQ:  // sequence of items
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-
-
-      /**
-       * TODO
-       **/
-
-      case EVR_AT:  // attribute tag
-        throw OrthancException(ErrorCode_NotImplemented);
-
-
-      /**
-       * Internal to DCMTK.
-       **/ 
-
-      case EVR_xs:  // SS or US depending on context
-      case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
-      case EVR_na:  // na="not applicable", for data which has no VR
-      case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
-      case EVR_item:  // used internally for items
-      case EVR_metainfo:  // used internally for meta info datasets
-      case EVR_dataset:  // used internally for datasets
-      case EVR_fileFormat:  // used internally for DICOM files
-      case EVR_dicomDir:  // used internally for DICOMDIR objects
-      case EVR_dirRecord:  // used internally for DICOMDIR records
-      case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
-      case EVR_pixelItem:  // used internally for pixel items in a compressed image
-      case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
-      case EVR_PixelData:  // used internally for uncompressed pixeld data
-      case EVR_OverlayData:  // used internally for overlay data
-      case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
-      default:
-        break;
-    }
-
-    throw OrthancException(ErrorCode_InternalError);
-  }
--- a/OrthancFramework/Resources/Graveyard/Multithreading/BagOfTasks.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../ICommand.h"
-
-#include <list>
-#include <cstddef>
-
-namespace Orthanc
-{
-  class BagOfTasks : public boost::noncopyable
-  {
-  private:
-    typedef std::list<ICommand*>  Tasks;
-
-    Tasks  tasks_;
-
-  public:
-    ~BagOfTasks()
-    {
-      for (Tasks::iterator it = tasks_.begin(); it != tasks_.end(); ++it)
-      {
-        delete *it;
-      }
-    }
-
-    ICommand* Pop()
-    {
-      ICommand* task = tasks_.front();
-      tasks_.pop_front();
-      return task;
-    }
-
-    void Push(ICommand* task)   // Takes ownership
-    {
-      if (task != NULL)
-      {
-        tasks_.push_back(task);
-      }
-    }
-
-    size_t GetSize() const
-    {
-      return tasks_.size();
-    }
-
-    bool IsEmpty() const
-    {
-      return tasks_.empty();
-    }
-  };
-}
--- a/OrthancFramework/Resources/Graveyard/Multithreading/BagOfTasksProcessor.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,268 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "BagOfTasksProcessor.h"
-
-#include "../Logging.h"
-#include "../OrthancException.h"
-
-#include <stdio.h>
-
-namespace Orthanc
-{
-  class BagOfTasksProcessor::Task : public IDynamicObject
-  {
-  private:
-    uint64_t                 bag_;
-    std::auto_ptr<ICommand>  command_;
-
-  public:
-    Task(uint64_t  bag,
-         ICommand* command) :
-      bag_(bag),
-      command_(command)
-    {
-    }
-
-    bool Execute()
-    {
-      try
-      {
-        return command_->Execute();
-      }
-      catch (OrthancException& e)
-      {
-        LOG(ERROR) << "Exception while processing a bag of tasks: " << e.What();
-        return false;
-      }
-      catch (std::runtime_error& e)
-      {
-        LOG(ERROR) << "Runtime exception while processing a bag of tasks: " << e.what();
-        return false;
-      }
-      catch (...)
-      {
-        LOG(ERROR) << "Native exception while processing a bag of tasks";
-        return false;
-      }
-    }
-
-    uint64_t GetBag()
-    {
-      return bag_;
-    }
-  };
-
-
-  void BagOfTasksProcessor::SignalProgress(Task& task,
-                                           Bag& bag)
-  {
-    assert(bag.done_ < bag.size_);
-
-    bag.done_ += 1;
-
-    if (bag.done_ == bag.size_)
-    {
-      exitStatus_[task.GetBag()] = (bag.status_ == BagStatus_Running);
-      bagFinished_.notify_all();
-    }
-  }
-
-  void BagOfTasksProcessor::Worker(BagOfTasksProcessor* that)
-  {
-    while (that->continue_)
-    {
-      std::auto_ptr<IDynamicObject> obj(that->queue_.Dequeue(100));
-      if (obj.get() != NULL)
-      {
-        Task& task = *dynamic_cast<Task*>(obj.get());
-
-        {
-          boost::mutex::scoped_lock lock(that->mutex_);
-
-          Bags::iterator bag = that->bags_.find(task.GetBag());
-          assert(bag != that->bags_.end());
-          assert(bag->second.done_ < bag->second.size_);
-
-          if (bag->second.status_ != BagStatus_Running)
-          {
-            // Do not execute this task, as its parent bag of tasks
-            // has failed or is tagged as canceled
-            that->SignalProgress(task, bag->second);
-            continue;
-          }
-        }
-
-        bool success = task.Execute();
-
-        {
-          boost::mutex::scoped_lock lock(that->mutex_);
-
-          Bags::iterator bag = that->bags_.find(task.GetBag());
-          assert(bag != that->bags_.end());
-
-          if (!success)
-          {
-            bag->second.status_ = BagStatus_Failed;
-          }
-
-          that->SignalProgress(task, bag->second);
-        }
-      }
-    }
-  }
-
-
-  void BagOfTasksProcessor::Cancel(int64_t bag)
-  {
-    boost::mutex::scoped_lock  lock(mutex_);
-
-    Bags::iterator it = bags_.find(bag);
-    if (it != bags_.end())
-    {
-      it->second.status_ = BagStatus_Canceled;
-    }
-  }
-
-
-  bool BagOfTasksProcessor::Join(int64_t bag)
-  {
-    boost::mutex::scoped_lock  lock(mutex_);
-
-    while (continue_)
-    {
-      ExitStatus::iterator it = exitStatus_.find(bag);
-      if (it == exitStatus_.end())  // The bag is still running
-      {
-        bagFinished_.wait(lock);
-      }
-      else
-      {
-        bool status = it->second;
-        exitStatus_.erase(it);
-        return status;
-      }
-    }
-
-    return false;   // The processor is stopping
-  }
-
-
-  float BagOfTasksProcessor::GetProgress(int64_t bag)
-  {
-    boost::mutex::scoped_lock  lock(mutex_);
-
-    Bags::const_iterator it = bags_.find(bag);
-    if (it == bags_.end())
-    {
-      // The bag of tasks has finished
-      return 1.0f;
-    }
-    else
-    {
-      return (static_cast<float>(it->second.done_) / 
-              static_cast<float>(it->second.size_));
-    }
-  }
-
-
-  bool BagOfTasksProcessor::Handle::Join()
-  {
-    if (hasJoined_)
-    {
-      return status_;
-    }
-    else
-    {
-      status_ = that_.Join(bag_);
-      hasJoined_ = true;
-      return status_;
-    }
-  }
-
-
-  BagOfTasksProcessor::BagOfTasksProcessor(size_t countThreads) : 
-    countBags_(0),
-    continue_(true)
-  {
-    if (countThreads == 0)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    threads_.resize(countThreads);
-
-    for (size_t i = 0; i < threads_.size(); i++)
-    {
-      threads_[i] = new boost::thread(Worker, this);
-    }
-  }
-
-
-  BagOfTasksProcessor::~BagOfTasksProcessor()
-  {
-    continue_ = false;
-
-    bagFinished_.notify_all();   // Wakes up all the pending "Join()"
-
-    for (size_t i = 0; i < threads_.size(); i++)
-    {
-      if (threads_[i])
-      {
-        if (threads_[i]->joinable())
-        {
-          threads_[i]->join();
-        }
-
-        delete threads_[i];
-        threads_[i] = NULL;
-      }
-    }
-  }
-
-
-  BagOfTasksProcessor::Handle* BagOfTasksProcessor::Submit(BagOfTasks& tasks)
-  {
-    if (tasks.GetSize() == 0)
-    {
-      return new Handle(*this, 0, true);
-    }
-
-    boost::mutex::scoped_lock lock(mutex_);
-
-    uint64_t id = countBags_;
-    countBags_ += 1;
-
-    Bag bag(tasks.GetSize());
-    bags_[id] = bag;
-
-    while (!tasks.IsEmpty())
-    {
-      queue_.Enqueue(new Task(id, tasks.Pop()));
-    }
-
-    return new Handle(*this, id, false);
-  }
-}
--- a/OrthancFramework/Resources/Graveyard/Multithreading/BagOfTasksProcessor.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "BagOfTasks.h"
-#include "SharedMessageQueue.h"
-
-#include <stdint.h>
-#include <map>
-
-namespace Orthanc
-{
-  class BagOfTasksProcessor : public boost::noncopyable
-  {
-  private:
-    enum BagStatus
-    {
-      BagStatus_Running,
-      BagStatus_Canceled,
-      BagStatus_Failed
-    };
-
-
-    struct Bag
-    {
-      size_t    size_;
-      size_t    done_;
-      BagStatus status_;
-
-      Bag() :
-        size_(0),
-        done_(0),
-        status_(BagStatus_Failed)
-      {
-      }
-
-      explicit Bag(size_t size) : 
-        size_(size),
-        done_(0),
-        status_(BagStatus_Running)
-      {
-      }
-    };
-
-    class Task;
-
-
-    typedef std::map<uint64_t, Bag>   Bags;
-    typedef std::map<uint64_t, bool>  ExitStatus;
-
-    SharedMessageQueue  queue_;
-
-    boost::mutex  mutex_;
-    uint64_t  countBags_;
-    Bags bags_;
-    std::vector<boost::thread*>   threads_;
-    ExitStatus  exitStatus_;
-    bool continue_;
-
-    boost::condition_variable  bagFinished_;
-
-    static void Worker(BagOfTasksProcessor* that);
-
-    void Cancel(int64_t bag);
-
-    bool Join(int64_t bag);
-
-    float GetProgress(int64_t bag);
-
-    void SignalProgress(Task& task,
-                        Bag& bag);
-
-  public:
-    class Handle : public boost::noncopyable
-    {
-      friend class BagOfTasksProcessor;
-
-    private:
-      BagOfTasksProcessor&  that_;
-      uint64_t              bag_;
-      bool                  hasJoined_;
-      bool                  status_;
- 
-      Handle(BagOfTasksProcessor&  that,
-             uint64_t bag,
-             bool empty) : 
-        that_(that),
-        bag_(bag),
-        hasJoined_(empty)
-      {
-      }
-
-    public:
-      ~Handle()
-      {
-        Join();
-      }
-
-      void Cancel()
-      {
-        that_.Cancel(bag_);
-      }
-
-      bool Join();
-
-      float GetProgress()
-      {
-        return that_.GetProgress(bag_);
-      }
-    };
-  
-
-    explicit BagOfTasksProcessor(size_t countThreads);
-
-    ~BagOfTasksProcessor();
-
-    Handle* Submit(BagOfTasks& tasks);
-  };
-}
--- a/OrthancFramework/Resources/Graveyard/Multithreading/ICommand.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IDynamicObject.h"
-
-namespace Orthanc
-{
-  /**
-   * This class is the base class for the "Command" design pattern.
-   * http://en.wikipedia.org/wiki/Command_pattern
-   **/
-  class ICommand : public IDynamicObject
-  {
-  public:
-    virtual bool Execute() = 0;
-  };
-}
--- a/OrthancFramework/Resources/Graveyard/Multithreading/ILockable.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <boost/noncopyable.hpp>
-
-namespace Orthanc
-{
-  class ILockable : public boost::noncopyable
-  {
-    friend class Locker;
-
-  protected:
-    virtual void Lock() = 0;
-
-    virtual void Unlock() = 0;
-
-  public:
-    virtual ~ILockable()
-    {
-    }
-  };
-}
--- a/OrthancFramework/Resources/Graveyard/Multithreading/Locker.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ILockable.h"
-
-namespace Orthanc
-{
-  class Locker : public boost::noncopyable
-  {
-  private:
-    ILockable& lockable_;
-
-  public:
-    Locker(ILockable& lockable) : lockable_(lockable)
-    {
-      lockable_.Lock();
-    }
-
-    virtual ~Locker()
-    {
-      lockable_.Unlock();
-    }
-  };
-}
--- a/OrthancFramework/Resources/Graveyard/Multithreading/Mutex.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "Mutex.h"
-
-#include "../OrthancException.h"
-
-#if defined(_WIN32)
-#include <windows.h>
-#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
-#include <pthread.h>
-#else
-#error Support your platform here
-#endif
-
-namespace Orthanc
-{
-#if defined (_WIN32)
-
-  struct Mutex::PImpl
-  {
-    CRITICAL_SECTION criticalSection_;
-  };
-
-  Mutex::Mutex()
-  {
-    pimpl_ = new PImpl;
-    ::InitializeCriticalSection(&pimpl_->criticalSection_);
-  }
-
-  Mutex::~Mutex()
-  {
-    ::DeleteCriticalSection(&pimpl_->criticalSection_);
-    delete pimpl_;
-  }
-
-  void Mutex::Lock()
-  {
-    ::EnterCriticalSection(&pimpl_->criticalSection_);
-  }
-
-  void Mutex::Unlock()
-  {
-    ::LeaveCriticalSection(&pimpl_->criticalSection_);
-  }
-
-
-#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
-
-  struct Mutex::PImpl
-  {
-    pthread_mutex_t mutex_;
-  };
-
-  Mutex::Mutex()
-  {
-    pimpl_ = new PImpl;
-
-    if (pthread_mutex_init(&pimpl_->mutex_, NULL) != 0)
-    {
-      delete pimpl_;
-      throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-
-  Mutex::~Mutex()
-  {
-    pthread_mutex_destroy(&pimpl_->mutex_);
-    delete pimpl_;
-  }
-
-  void Mutex::Lock()
-  {
-    if (pthread_mutex_lock(&pimpl_->mutex_) != 0)
-    {
-      throw OrthancException(ErrorCode_InternalError);    
-    }
-  }
-
-  void Mutex::Unlock()
-  {
-    if (pthread_mutex_unlock(&pimpl_->mutex_) != 0)
-    {
-      throw OrthancException(ErrorCode_InternalError);    
-    }
-  }
-
-#else
-#error Support your plateform here
-#endif
-}
--- a/OrthancFramework/Resources/Graveyard/Multithreading/Mutex.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ILockable.h"
-
-namespace Orthanc
-{
-  class Mutex : public ILockable
-  {
-  private:
-    struct PImpl;
-
-    PImpl *pimpl_;
-
-  protected:
-    virtual void Lock();
-
-    virtual void Unlock();
-    
-  public:
-    Mutex();
-
-    ~Mutex();
-  };
-}
--- a/OrthancFramework/Resources/Graveyard/Multithreading/ReaderWriterLock.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "ReaderWriterLock.h"
-
-#include <boost/thread/shared_mutex.hpp>
-
-namespace Orthanc
-{
-  namespace
-  {
-    // Anonymous namespace to avoid clashes between compilation
-    // modules.
-
-    class ReaderLockable : public ILockable
-    {
-    private:
-      boost::shared_mutex& lock_;
-
-    protected:
-      virtual void Lock()
-      {
-        lock_.lock_shared();
-      }
-
-      virtual void Unlock()
-      {
-        lock_.unlock_shared();        
-      }
-
-    public:
-      explicit ReaderLockable(boost::shared_mutex& lock) : lock_(lock)
-      {
-      }
-    };
-
-
-    class WriterLockable : public ILockable
-    {
-    private:
-      boost::shared_mutex& lock_;
-
-    protected:
-      virtual void Lock()
-      {
-        lock_.lock();
-      }
-
-      virtual void Unlock()
-      {
-        lock_.unlock();        
-      }
-
-    public:
-      explicit WriterLockable(boost::shared_mutex& lock) : lock_(lock)
-      {
-      }
-    };
-  }
-
-  struct ReaderWriterLock::PImpl
-  {
-    boost::shared_mutex lock_;
-    ReaderLockable reader_;
-    WriterLockable writer_;
-
-    PImpl() : reader_(lock_), writer_(lock_)
-    {
-    }
-  };
-
-
-  ReaderWriterLock::ReaderWriterLock()
-  {
-    pimpl_ = new PImpl;
-  }
-
-
-  ReaderWriterLock::~ReaderWriterLock()
-  {
-    delete pimpl_;
-  }
-
-
-  ILockable&  ReaderWriterLock::ForReader()
-  {
-    return pimpl_->reader_;
-  }
-
-
-  ILockable&  ReaderWriterLock::ForWriter()
-  {
-    return pimpl_->writer_;
-  }
-}
--- a/OrthancFramework/Resources/Graveyard/Multithreading/ReaderWriterLock.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ILockable.h"
-
-#include <boost/noncopyable.hpp>
-
-namespace Orthanc
-{
-  class ReaderWriterLock : public boost::noncopyable
-  {
-  private:
-    struct PImpl;
-
-    PImpl *pimpl_;
-
-  public:
-    ReaderWriterLock();
-
-    virtual ~ReaderWriterLock();
-
-    ILockable& ForReader();
-
-    ILockable& ForWriter();
-  };
-}
--- a/OrthancFramework/Resources/Graveyard/TestTranscoding.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,991 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
-                                           DcmFileFormat& dicom,
-                                           DicomTransferSyntax syntax)
-  {
-    E_TransferSyntax xfer;
-    if (!LookupDcmtkTransferSyntax(xfer, syntax))
-    {
-      return false;
-    }
-    else if (!dicom.validateMetaInfo(xfer).good())
-    {
-      throw OrthancException(ErrorCode_InternalError,
-                             "Cannot setup the transfer syntax to write a DICOM instance");
-    }
-    else
-    {
-      return SaveToMemoryBufferInternal(buffer, dicom, xfer);
-    }
-  }
-
-
-  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
-                                           DcmFileFormat& dicom)
-  {
-    E_TransferSyntax xfer = dicom.getDataset()->getCurrentXfer();
-    if (xfer == EXS_Unknown)
-    {
-      throw OrthancException(ErrorCode_InternalError,
-                             "Cannot write a DICOM instance with unknown transfer syntax");
-    }
-    else if (!dicom.validateMetaInfo(xfer).good())
-    {
-      throw OrthancException(ErrorCode_InternalError,
-                             "Cannot setup the transfer syntax to write a DICOM instance");
-    }
-    else
-    {
-      return SaveToMemoryBufferInternal(buffer, dicom, xfer);
-    }
-  }
-
-
-
-
-
-#include <dcmtk/dcmdata/dcostrmb.h>
-#include <dcmtk/dcmdata/dcpixel.h>
-#include <dcmtk/dcmdata/dcpxitem.h>
-
-#include "../Core/DicomParsing/Internals/DicomFrameIndex.h"
-
-namespace Orthanc
-{
-  class IParsedDicomImage : public boost::noncopyable
-  {
-  public:
-    virtual ~IParsedDicomImage()
-    {
-    }
-
-    virtual DicomTransferSyntax GetTransferSyntax() = 0;
-
-    virtual std::string GetSopClassUid() = 0;
-
-    virtual std::string GetSopInstanceUid() = 0;
-
-    virtual unsigned int GetFramesCount() = 0;
-
-    // Can return NULL, for compressed transfer syntaxes
-    virtual ImageAccessor* GetUncompressedFrame(unsigned int frame) = 0;
-    
-    virtual void GetCompressedFrame(std::string& target,
-                                    unsigned int frame) = 0;
-    
-    virtual void WriteToMemoryBuffer(std::string& target) = 0;
-  };
-
-
-  class IDicomImageReader : public boost::noncopyable
-  {
-  public:
-    virtual ~IDicomImageReader()
-    {
-    }
-
-    virtual IParsedDicomImage* Read(const void* data,
-                                    size_t size) = 0;
-
-    virtual IParsedDicomImage* Transcode(const void* data,
-                                         size_t size,
-                                         DicomTransferSyntax syntax,
-                                         bool allowNewSopInstanceUid) = 0;
-  };
-
-
-  class DcmtkImageReader : public IDicomImageReader
-  {
-  private:
-    class Image : public IParsedDicomImage
-    {
-    private:
-      std::unique_ptr<DcmFileFormat>    dicom_;
-      std::unique_ptr<DicomFrameIndex>  index_;
-      DicomTransferSyntax               transferSyntax_;
-      std::string                       sopClassUid_;
-      std::string                       sopInstanceUid_;
-
-      static std::string GetStringTag(DcmDataset& dataset,
-                                      const DcmTagKey& tag)
-      {
-        const char* value = NULL;
-
-        if (!dataset.findAndGetString(tag, value).good() ||
-            value == NULL)
-        {
-          throw OrthancException(ErrorCode_BadFileFormat,
-                                 "Missing SOP class/instance UID in DICOM instance");
-        }
-        else
-        {
-          return std::string(value);
-        }
-      }
-
-    public:
-      Image(DcmFileFormat* dicom,
-            DicomTransferSyntax syntax) :
-        dicom_(dicom),
-        transferSyntax_(syntax)
-      {
-        if (dicom == NULL ||
-            dicom_->getDataset() == NULL)
-        {
-          throw OrthancException(ErrorCode_NullPointer);
-        }
-
-        DcmDataset& dataset = *dicom_->getDataset();
-        index_.reset(new DicomFrameIndex(dataset));
-
-        sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID);
-        sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID);
-      }
-
-      virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE
-      {
-        return transferSyntax_;
-      }
-
-      virtual std::string GetSopClassUid() ORTHANC_OVERRIDE
-      {
-        return sopClassUid_;
-      }
-    
-      virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE
-      {
-        return sopInstanceUid_;
-      }
-
-      virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE
-      {
-        return index_->GetFramesCount();
-      }
-
-      virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE
-      {
-        assert(dicom_.get() != NULL);
-        if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, transferSyntax_))
-        {
-          throw OrthancException(ErrorCode_InternalError,
-                                 "Cannot write the DICOM instance to a memory buffer");
-        }
-      }
-
-      virtual ImageAccessor* GetUncompressedFrame(unsigned int frame) ORTHANC_OVERRIDE
-      {
-        assert(dicom_.get() != NULL &&
-               dicom_->getDataset() != NULL);
-        return DicomImageDecoder::Decode(*dicom_->getDataset(), frame);
-      }
-
-      virtual void GetCompressedFrame(std::string& target,
-                                      unsigned int frame) ORTHANC_OVERRIDE
-      {
-        assert(index_.get() != NULL);
-        index_->GetRawFrame(target, frame);
-      }
-    };
-
-    unsigned int lossyQuality_;
-
-    static DicomTransferSyntax DetectTransferSyntax(DcmFileFormat& dicom)
-    {
-      if (dicom.getDataset() == NULL)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-        
-      DcmDataset& dataset = *dicom.getDataset();
-
-      E_TransferSyntax xfer = dataset.getCurrentXfer();
-      if (xfer == EXS_Unknown)
-      {
-        dataset.updateOriginalXfer();
-        xfer = dataset.getCurrentXfer();
-        if (xfer == EXS_Unknown)
-        {
-          throw OrthancException(ErrorCode_BadFileFormat,
-                                 "Cannot determine the transfer syntax of the DICOM instance");
-        }
-      }
-
-      DicomTransferSyntax syntax;
-      if (FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, xfer))
-      {
-        return syntax;
-      }
-      else
-      {
-        throw OrthancException(
-          ErrorCode_BadFileFormat,
-          "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer));
-      }
-    }
-
-
-    static uint16_t GetBitsStored(DcmFileFormat& dicom)
-    {
-      if (dicom.getDataset() == NULL)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-
-      uint16_t bitsStored;
-      if (dicom.getDataset()->findAndGetUint16(DCM_BitsStored, bitsStored).good())
-      {
-        return bitsStored;
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_BadFileFormat,
-                               "Missing \"Bits Stored\" tag in DICOM instance");
-      }      
-    }
-    
-      
-  public:
-    DcmtkImageReader() :
-      lossyQuality_(90)
-    {
-    }
-
-    void SetLossyQuality(unsigned int quality)
-    {
-      if (quality <= 0 ||
-          quality > 100)
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-      else
-      {
-        lossyQuality_ = quality;
-      }
-    }
-
-    unsigned int GetLossyQuality() const
-    {
-      return lossyQuality_;
-    }
-
-    virtual IParsedDicomImage* Read(const void* data,
-                                    size_t size)
-    {
-      std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(data, size));
-      if (dicom.get() == NULL)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-      DicomTransferSyntax transferSyntax = DetectTransferSyntax(*dicom);
-
-      return new Image(dicom.release(), transferSyntax);
-    }
-
-    virtual IParsedDicomImage* Transcode(const void* data,
-                                         size_t size,
-                                         DicomTransferSyntax syntax,
-                                         bool allowNewSopInstanceUid)
-    {
-      std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(data, size));
-      if (dicom.get() == NULL)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-      const uint16_t bitsStored = GetBitsStored(*dicom);
-
-      if (syntax == DetectTransferSyntax(*dicom))
-      {
-        // No transcoding is needed
-        return new Image(dicom.release(), syntax);
-      }
-      
-      if (syntax == DicomTransferSyntax_LittleEndianImplicit &&
-          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianImplicit, NULL))
-      {
-        return new Image(dicom.release(), syntax);
-      }
-
-      if (syntax == DicomTransferSyntax_LittleEndianExplicit &&
-          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianExplicit, NULL))
-      {
-        return new Image(dicom.release(), syntax);
-      }
-      
-      if (syntax == DicomTransferSyntax_BigEndianExplicit &&
-          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_BigEndianExplicit, NULL))
-      {
-        return new Image(dicom.release(), syntax);
-      }
-
-      if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit &&
-          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL))
-      {
-        return new Image(dicom.release(), syntax);
-      }
-
-#if ORTHANC_ENABLE_JPEG == 1
-      if (syntax == DicomTransferSyntax_JPEGProcess1 &&
-          allowNewSopInstanceUid &&
-          bitsStored == 8)
-      {
-        DJ_RPLossy rpLossy(lossyQuality_);
-        
-        if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess1, &rpLossy))
-        {
-          return new Image(dicom.release(), syntax);
-        }
-      }
-#endif
-      
-#if ORTHANC_ENABLE_JPEG == 1
-      if (syntax == DicomTransferSyntax_JPEGProcess2_4 &&
-          allowNewSopInstanceUid &&
-          bitsStored <= 12)
-      {
-        DJ_RPLossy rpLossy(lossyQuality_);
-        if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess2_4, &rpLossy))
-        {
-          return new Image(dicom.release(), syntax);
-        }
-      }
-#endif
-
-      //LOG(INFO) << "Unable to transcode DICOM image using the built-in reader";
-      return NULL;
-    }
-  };
-  
-
-  
-  class IDicomTranscoder1 : public boost::noncopyable
-  {
-  public:
-    virtual ~IDicomTranscoder1()
-    {
-    }
-
-    virtual DcmFileFormat& GetDicom() = 0;
-
-    virtual DicomTransferSyntax GetTransferSyntax() = 0;
-
-    virtual std::string GetSopClassUid() = 0;
-
-    virtual std::string GetSopInstanceUid() = 0;
-
-    virtual unsigned int GetFramesCount() = 0;
-
-    virtual ImageAccessor* DecodeFrame(unsigned int frame) = 0;
-
-    virtual void GetCompressedFrame(std::string& target,
-                                    unsigned int frame) = 0;
-
-    // NB: Transcoding can change the value of "GetSopInstanceUid()"
-    // and "GetTransferSyntax()" if lossy compression is applied
-    virtual bool Transcode(std::string& target,
-                           DicomTransferSyntax syntax,
-                           bool allowNewSopInstanceUid) = 0;
-
-    virtual void WriteToMemoryBuffer(std::string& target) = 0;
-  };
-
-
-  class DcmtkTranscoder2 : public IDicomTranscoder1
-  {
-  private:
-    std::unique_ptr<DcmFileFormat>    dicom_;
-    std::unique_ptr<DicomFrameIndex>  index_;
-    DicomTransferSyntax               transferSyntax_;
-    std::string                       sopClassUid_;
-    std::string                       sopInstanceUid_;
-    uint16_t                          bitsStored_;
-    unsigned int                      lossyQuality_;
-
-    static std::string GetStringTag(DcmDataset& dataset,
-                                    const DcmTagKey& tag)
-    {
-      const char* value = NULL;
-
-      if (!dataset.findAndGetString(tag, value).good() ||
-          value == NULL)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat,
-                               "Missing SOP class/instance UID in DICOM instance");
-      }
-      else
-      {
-        return std::string(value);
-      }
-    }
-
-    void Setup(DcmFileFormat* dicom)
-    {
-      lossyQuality_ = 90;
-      
-      dicom_.reset(dicom);
-      
-      if (dicom == NULL ||
-          dicom_->getDataset() == NULL)
-      {
-        throw OrthancException(ErrorCode_NullPointer);
-      }
-
-      DcmDataset& dataset = *dicom_->getDataset();
-      index_.reset(new DicomFrameIndex(dataset));
-
-      E_TransferSyntax xfer = dataset.getCurrentXfer();
-      if (xfer == EXS_Unknown)
-      {
-        dataset.updateOriginalXfer();
-        xfer = dataset.getCurrentXfer();
-        if (xfer == EXS_Unknown)
-        {
-          throw OrthancException(ErrorCode_BadFileFormat,
-                                 "Cannot determine the transfer syntax of the DICOM instance");
-        }
-      }
-
-      if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax_, xfer))
-      {
-        throw OrthancException(
-          ErrorCode_BadFileFormat,
-          "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer));
-      }
-
-      if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored_).good())
-      {
-        throw OrthancException(ErrorCode_BadFileFormat,
-                               "Missing \"Bits Stored\" tag in DICOM instance");
-      }      
-
-      sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID);
-      sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID);
-    }
-    
-  public:
-    DcmtkTranscoder2(DcmFileFormat* dicom)  // Takes ownership
-    {
-      Setup(dicom);
-    }
-
-    DcmtkTranscoder2(const void* dicom,
-                    size_t size)
-    {
-      Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size));
-    }
-
-    void SetLossyQuality(unsigned int quality)
-    {
-      if (quality <= 0 ||
-          quality > 100)
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-      else
-      {
-        lossyQuality_ = quality;
-      }
-    }
-
-    unsigned int GetLossyQuality() const
-    {
-      return lossyQuality_;
-    }
-
-    unsigned int GetBitsStored() const
-    {
-      return bitsStored_;
-    }
-
-    virtual DcmFileFormat& GetDicom()
-    {
-      assert(dicom_ != NULL);
-      return *dicom_;
-    }
-
-    virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE
-    {
-      return transferSyntax_;
-    }
-
-    virtual std::string GetSopClassUid() ORTHANC_OVERRIDE
-    {
-      return sopClassUid_;
-    }
-    
-    virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE
-    {
-      return sopInstanceUid_;
-    }
-
-    virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE
-    {
-      return index_->GetFramesCount();
-    }
-
-    virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE
-    {
-      if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_))
-      {
-        throw OrthancException(ErrorCode_InternalError,
-                               "Cannot write the DICOM instance to a memory buffer");
-      }
-    }
-
-    virtual ImageAccessor* DecodeFrame(unsigned int frame) ORTHANC_OVERRIDE
-    {
-      assert(dicom_->getDataset() != NULL);
-      return DicomImageDecoder::Decode(*dicom_->getDataset(), frame);
-    }
-
-    virtual void GetCompressedFrame(std::string& target,
-                                    unsigned int frame) ORTHANC_OVERRIDE
-    {
-      index_->GetRawFrame(target, frame);
-    }
-
-    virtual bool Transcode(std::string& target,
-                           DicomTransferSyntax syntax,
-                           bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
-    {
-      assert(dicom_ != NULL &&
-             dicom_->getDataset() != NULL);
-      
-      if (syntax == GetTransferSyntax())
-      {
-        printf("NO TRANSCODING\n");
-        
-        // No change in the transfer syntax => simply serialize the current dataset
-        WriteToMemoryBuffer(target);
-        return true;
-      }
-      
-      printf(">> %d\n", bitsStored_);
-
-      if (syntax == DicomTransferSyntax_LittleEndianImplicit &&
-          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
-          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
-      {
-        transferSyntax_ = DicomTransferSyntax_LittleEndianImplicit;
-        return true;
-      }
-
-      if (syntax == DicomTransferSyntax_LittleEndianExplicit &&
-          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
-          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
-      {
-        transferSyntax_ = DicomTransferSyntax_LittleEndianExplicit;
-        return true;
-      }
-      
-      if (syntax == DicomTransferSyntax_BigEndianExplicit &&
-          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
-          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
-      {
-        transferSyntax_ = DicomTransferSyntax_BigEndianExplicit;
-        return true;
-      }
-
-      if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit &&
-          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
-          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
-      {
-        transferSyntax_ = DicomTransferSyntax_DeflatedLittleEndianExplicit;
-        return true;
-      }
-
-#if ORTHANC_ENABLE_JPEG == 1
-      if (syntax == DicomTransferSyntax_JPEGProcess1 &&
-          allowNewSopInstanceUid &&
-          GetBitsStored() == 8)
-      {
-        DJ_RPLossy rpLossy(lossyQuality_);
-        
-        if (FromDcmtkBridge::Transcode(*dicom_, syntax, &rpLossy) &&
-            FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
-        {
-          transferSyntax_ = DicomTransferSyntax_JPEGProcess1;
-          sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID);
-          return true;
-        }
-      }
-#endif
-      
-#if ORTHANC_ENABLE_JPEG == 1
-      if (syntax == DicomTransferSyntax_JPEGProcess2_4 &&
-          allowNewSopInstanceUid &&
-          GetBitsStored() <= 12)
-      {
-        DJ_RPLossy rpLossy(lossyQuality_);
-        if (FromDcmtkBridge::Transcode(*dicom_, syntax, &rpLossy) &&
-            FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
-        {
-          transferSyntax_ = DicomTransferSyntax_JPEGProcess2_4;
-          sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID);
-          return true;
-        }
-      }
-#endif
-
-      return false;
-    }
-  };
-}
-
-
-
-
-#include <boost/filesystem.hpp>
-
-
-static void TestFile(const std::string& path)
-{
-  static unsigned int count = 0;
-  count++;
-  
-
-  printf("** %s\n", path.c_str());
-
-  std::string s;
-  SystemToolbox::ReadFile(s, path);
-
-  Orthanc::DcmtkTranscoder2 transcoder(s.c_str(), s.size());
-
-  /*if (transcoder.GetBitsStored() != 8)  // TODO
-    return; */
-
-  {
-    char buf[1024];
-    sprintf(buf, "/tmp/source-%06d.dcm", count);
-    printf(">> %s\n", buf);
-    Orthanc::SystemToolbox::WriteFile(s, buf);
-  }
-
-  printf("[%s] [%s] [%s] %d %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()),
-         transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(),
-         transcoder.GetFramesCount(), transcoder.GetTransferSyntax());
-
-  for (size_t i = 0; i < transcoder.GetFramesCount(); i++)
-  {
-    std::string f;
-    transcoder.GetCompressedFrame(f, i);
-
-    if (i == 0)
-    {
-      char buf[1024];
-      sprintf(buf, "/tmp/frame-%06d.raw", count);
-      printf(">> %s\n", buf);
-      Orthanc::SystemToolbox::WriteFile(f, buf);
-    }
-  }
-
-  {
-    std::string t;
-    transcoder.WriteToMemoryBuffer(t);
-
-    Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size());
-    printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size());
-  }
-
-  {
-    std::string a = transcoder.GetSopInstanceUid();
-    DicomTransferSyntax b = transcoder.GetTransferSyntax();
-    
-    DicomTransferSyntax syntax = DicomTransferSyntax_JPEGProcess2_4;
-    //DicomTransferSyntax syntax = DicomTransferSyntax_LittleEndianExplicit;
-
-    std::string t;
-    bool ok = transcoder.Transcode(t, syntax, true);
-    printf("Transcoding: %d\n", ok);
-
-    if (ok)
-    {
-      printf("[%s] => [%s]\n", a.c_str(), transcoder.GetSopInstanceUid().c_str());
-      printf("[%s] => [%s]\n", GetTransferSyntaxUid(b),
-             GetTransferSyntaxUid(transcoder.GetTransferSyntax()));
-      
-      {
-        char buf[1024];
-        sprintf(buf, "/tmp/transcoded-%06d.dcm", count);
-        printf(">> %s\n", buf);
-        Orthanc::SystemToolbox::WriteFile(t, buf);
-      }
-
-      Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size());
-      printf("  => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size());
-    }
-  }
-  
-  printf("\n");
-}
-
-TEST(Toto, DISABLED_Transcode)
-{
-  //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL);
-
-  if (1)
-  {
-    const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes";
-    
-    for (boost::filesystem::directory_iterator it(PATH);
-         it != boost::filesystem::directory_iterator(); ++it)
-    {
-      if (boost::filesystem::is_regular_file(it->status()))
-      {
-        TestFile(it->path().string());
-      }
-    }
-  }
-
-  if (0)
-  {
-    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm");
-    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm");
-  }
-
-  if (0)
-  {
-    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm");
-  }
-}
-
-
-TEST(Toto, DISABLED_Transcode2)
-{
-  for (int i = 0; i <= DicomTransferSyntax_XML; i++)
-  {
-    DicomTransferSyntax a = (DicomTransferSyntax) i;
-
-    std::string path = ("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
-                        std::string(GetTransferSyntaxUid(a)) + ".dcm");
-    if (Orthanc::SystemToolbox::IsRegularFile(path))
-    {
-      printf("\n======= %s\n", GetTransferSyntaxUid(a));
-
-      std::string source;
-      Orthanc::SystemToolbox::ReadFile(source, path);
-
-      DcmtkImageReader reader;
-
-      {
-        std::unique_ptr<IParsedDicomImage> image(
-          reader.Read(source.c_str(), source.size()));
-        ASSERT_TRUE(image.get() != NULL);
-        ASSERT_EQ(a, image->GetTransferSyntax());
-
-        std::string target;
-        image->WriteToMemoryBuffer(target);
-      }
-
-      for (int j = 0; j <= DicomTransferSyntax_XML; j++)
-      {
-        DicomTransferSyntax b = (DicomTransferSyntax) j;
-        //if (a == b) continue;
-
-        std::unique_ptr<IParsedDicomImage> image(
-          reader.Transcode(source.c_str(), source.size(), b, true));
-        if (image.get() != NULL)
-        {
-          printf("[%s] -> [%s]\n", GetTransferSyntaxUid(a), GetTransferSyntaxUid(b));
-
-          std::string target;
-          image->WriteToMemoryBuffer(target);
-
-          char buf[1024];
-          sprintf(buf, "/tmp/%s-%s.dcm", GetTransferSyntaxUid(a), GetTransferSyntaxUid(b));
-          
-          SystemToolbox::WriteFile(target, buf);
-        }
-        else if (a != DicomTransferSyntax_JPEG2000 &&
-                 a != DicomTransferSyntax_JPEG2000LosslessOnly)
-        {
-          ASSERT_TRUE(b != DicomTransferSyntax_LittleEndianImplicit &&
-                      b != DicomTransferSyntax_LittleEndianExplicit &&
-                      b != DicomTransferSyntax_BigEndianExplicit &&
-                      b != DicomTransferSyntax_DeflatedLittleEndianExplicit);
-        }
-      }
-    }
-  }
-}
-
-
-#include "../Core/DicomNetworking/DicomAssociation.h"
-#include "../Core/DicomNetworking/DicomControlUserConnection.h"
-#include "../Core/DicomNetworking/DicomStoreUserConnection.h"
-
-TEST(Toto, DISABLED_DicomAssociation)
-{
-  DicomAssociationParameters params;
-  params.SetLocalApplicationEntityTitle("ORTHANC");
-  params.SetRemoteApplicationEntityTitle("PACS");
-  params.SetRemotePort(2001);
-
-#if 0
-  DicomAssociation assoc;
-  assoc.ProposeGenericPresentationContext(UID_StorageCommitmentPushModelSOPClass);
-  assoc.ProposeGenericPresentationContext(UID_VerificationSOPClass);
-  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
-                                   DicomTransferSyntax_JPEGProcess1);
-  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
-                                   DicomTransferSyntax_JPEGProcess2_4);
-  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
-                                   DicomTransferSyntax_JPEG2000);
-  
-  assoc.Open(params);
-
-  int presID = ASC_findAcceptedPresentationContextID(&assoc.GetDcmtkAssociation(), UID_ComputedRadiographyImageStorage);
-  printf(">> %d\n", presID);
-    
-  std::map<DicomTransferSyntax, uint8_t> pc;
-  printf(">> %d\n", assoc.LookupAcceptedPresentationContext(pc, UID_ComputedRadiographyImageStorage));
-  
-  for (std::map<DicomTransferSyntax, uint8_t>::const_iterator
-         it = pc.begin(); it != pc.end(); ++it)
-  {
-    printf("[%s] => %d\n", GetTransferSyntaxUid(it->first), it->second);
-  }
-#else
-  {
-    DicomControlUserConnection assoc(params);
-
-    try
-    {
-      printf(">> %d\n", assoc.Echo());
-    }
-    catch (OrthancException&)
-    {
-    }
-  }
-    
-  params.SetRemoteApplicationEntityTitle("PACS");
-  params.SetRemotePort(2000);
-
-  {
-    DicomControlUserConnection assoc(params);
-    printf(">> %d\n", assoc.Echo());
-  }
-
-#endif
-}
-
-static void TestTranscode(DicomStoreUserConnection& scu,
-                          const std::string& sopClassUid,
-                          DicomTransferSyntax transferSyntax)
-{
-  std::set<DicomTransferSyntax> accepted;
-
-  scu.LookupTranscoding(accepted, sopClassUid, transferSyntax);
-  if (accepted.empty())
-  {
-    throw OrthancException(ErrorCode_NetworkProtocol,
-                           "The SOP class is not supported by the remote modality");
-  }
-
-  {
-    unsigned int count = 0;
-    for (std::set<DicomTransferSyntax>::const_iterator
-           it = accepted.begin(); it != accepted.end(); ++it)
-    {
-      LOG(INFO) << "available for transcoding " << (count++) << ": " << sopClassUid
-                << " / " << GetTransferSyntaxUid(*it);
-    }
-  }
-  
-  if (accepted.find(transferSyntax) != accepted.end())
-  {
-    printf("**** OK, without transcoding !! [%s]\n", GetTransferSyntaxUid(transferSyntax));
-  }
-  else
-  {
-    // Transcoding - only in Orthanc >= 1.7.0
-
-    const DicomTransferSyntax uncompressed[] = {
-      DicomTransferSyntax_LittleEndianImplicit,  // Default transfer syntax
-      DicomTransferSyntax_LittleEndianExplicit,
-      DicomTransferSyntax_BigEndianExplicit
-    };
-
-    bool found = false;
-    for (size_t i = 0; i < 3; i++)
-    {
-      if (accepted.find(uncompressed[i]) != accepted.end())
-      {
-        printf("**** TRANSCODING to %s\n", GetTransferSyntaxUid(uncompressed[i]));
-        found = true;
-        break;
-      }
-    }
-
-    if (!found)
-    {
-      printf("**** KO KO KO\n");
-    }
-  }
-}
-
-
-TEST(Toto, DISABLED_Store)
-{
-  DicomAssociationParameters params;
-  params.SetLocalApplicationEntityTitle("ORTHANC");
-  params.SetRemoteApplicationEntityTitle("STORESCP");
-  params.SetRemotePort(2000);
-
-  DicomStoreUserConnection assoc(params);
-  assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess1);
-  assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess2_4);
-  //assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);
-
-  //assoc.SetUncompressedSyntaxesProposed(false);  // Necessary for transcoding
-  assoc.SetCommonClassesProposed(false);
-  assoc.SetRetiredBigEndianProposed(true);
-  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);
-  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000);
-  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000);
-}
-
-
-TEST(Toto, DISABLED_Store2)
-{
-  DicomAssociationParameters params;
-  params.SetLocalApplicationEntityTitle("ORTHANC");
-  params.SetRemoteApplicationEntityTitle("STORESCP");
-  params.SetRemotePort(2000);
-
-  DicomStoreUserConnection assoc(params);
-  //assoc.SetCommonClassesProposed(false);
-  assoc.SetRetiredBigEndianProposed(true);
-
-  std::string s;
-  Orthanc::SystemToolbox::ReadFile(s, "/tmp/i/" + std::string(GetTransferSyntaxUid(DicomTransferSyntax_BigEndianExplicit)) +".dcm");
-
-  std::string c, i;
-  assoc.Store(c, i, s.c_str(), s.size());
-  printf("[%s] [%s]\n", c.c_str(), i.c_str());
-}
-
--- a/OrthancFramework/Resources/Patches/OpenSSL-ConfigureHeaders.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/Patches/OpenSSL-ConfigureHeaders.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/Patches/OpenSSL-ExtractProvidersOIDs.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/Patches/OpenSSL-ExtractProvidersOIDs.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Resources/Patches/boost-1.86.0-emscripten.patch	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,127 @@
+diff -urEb boost_1_86_0.orig/libs/locale/src/boost/locale/shared/date_time.cpp boost_1_86_0/libs/locale/src/boost/locale/shared/date_time.cpp
+--- boost_1_86_0.orig/libs/locale/src/boost/locale/shared/date_time.cpp	2024-09-25 15:46:01.000000000 +0200
++++ boost_1_86_0/libs/locale/src/boost/locale/shared/date_time.cpp	2024-09-25 15:58:51.306131987 +0200
+@@ -12,8 +12,10 @@
+ #include <boost/locale/date_time.hpp>
+ #include <boost/locale/formatting.hpp>
+ #include <boost/core/exchange.hpp>
+-#include <boost/thread/locks.hpp>
+-#include <boost/thread/mutex.hpp>
++#if !defined(__EMSCRIPTEN__)
++#  include <boost/thread/locks.hpp>
++#  include <boost/thread/mutex.hpp>
++#endif
+ #include <cmath>
+ 
+ namespace boost { namespace locale {
+@@ -400,6 +402,7 @@
+         return impl_->get_option(abstract_calendar::is_dst) != 0;
+     }
+ 
++#if !defined(__EMSCRIPTEN__)
+     namespace time_zone {
+         boost::mutex& tz_mutex()
+         {
+@@ -422,7 +425,7 @@
+             return boost::exchange(tz_id(), new_id);
+         }
+     } // namespace time_zone
+-
++#endif
+ }} // namespace boost::locale
+ 
+ // boostinspect:nominmax
+diff -urEb boost_1_86_0.orig/libs/locale/src/boost/locale/shared/generator.cpp boost_1_86_0/libs/locale/src/boost/locale/shared/generator.cpp
+--- boost_1_86_0.orig/libs/locale/src/boost/locale/shared/generator.cpp	2024-09-25 15:46:01.000000000 +0200
++++ boost_1_86_0/libs/locale/src/boost/locale/shared/generator.cpp	2024-09-25 16:00:07.756233916 +0200
+@@ -7,8 +7,10 @@
+ #include <boost/locale/encoding.hpp>
+ #include <boost/locale/generator.hpp>
+ #include <boost/locale/localization_backend.hpp>
+-#include <boost/thread/locks.hpp>
+-#include <boost/thread/mutex.hpp>
++#if !defined(__EMSCRIPTEN__)
++#  include <boost/thread/locks.hpp>
++#  include <boost/thread/mutex.hpp>
++#endif
+ #include <algorithm>
+ #include <map>
+ #include <vector>
+@@ -21,8 +23,9 @@
+         {}
+ 
+         mutable std::map<std::string, std::locale> cached;
++#if !defined(__EMSCRIPTEN__)
+         mutable boost::mutex cached_lock;
+-
++#endif
+         category_t cats;
+         char_facet_t chars;
+ 
+@@ -101,7 +104,9 @@
+     std::locale generator::generate(const std::locale& base, const std::string& id) const
+     {
+         if(d->caching_enabled) {
++#if !defined(__EMSCRIPTEN__)
+             boost::unique_lock<boost::mutex> guard(d->cached_lock);
++#endif
+             const auto p = d->cached.find(id);
+             if(p != d->cached.end())
+                 return p->second;
+@@ -126,7 +131,9 @@
+                 result = backend->install(result, facet, char_facet_t::nochar);
+         }
+         if(d->caching_enabled) {
++#if !defined(__EMSCRIPTEN__)
+             boost::unique_lock<boost::mutex> guard(d->cached_lock);
++#endif
+             const auto p = d->cached.find(id);
+             if(p == d->cached.end())
+                 d->cached[id] = result;
+diff -urEb boost_1_86_0.orig/libs/locale/src/boost/locale/shared/localization_backend.cpp boost_1_86_0/libs/locale/src/boost/locale/shared/localization_backend.cpp
+--- boost_1_86_0.orig/libs/locale/src/boost/locale/shared/localization_backend.cpp	2024-09-25 15:46:01.000000000 +0200
++++ boost_1_86_0/libs/locale/src/boost/locale/shared/localization_backend.cpp	2024-09-25 16:01:09.196820495 +0200
+@@ -5,8 +5,10 @@
+ // https://www.boost.org/LICENSE_1_0.txt
+ 
+ #include <boost/locale/localization_backend.hpp>
+-#include <boost/thread/locks.hpp>
+-#include <boost/thread/mutex.hpp>
++#if !defined(__EMSCRIPTEN__)
++#  include <boost/thread/locks.hpp>
++#  include <boost/thread/mutex.hpp>
++#endif
+ #include <functional>
+ #include <memory>
+ #include <vector>
+@@ -211,11 +213,13 @@
+             return mgr;
+         }
+ 
++#if !defined(__EMSCRIPTEN__)
+         boost::mutex& localization_backend_manager_mutex()
+         {
+             static boost::mutex the_mutex;
+             return the_mutex;
+         }
++#endif
+         localization_backend_manager& localization_backend_manager_global()
+         {
+             static localization_backend_manager the_manager = make_default_backend_mgr();
+@@ -225,12 +229,16 @@
+ 
+     localization_backend_manager localization_backend_manager::global()
+     {
++#if !defined(__EMSCRIPTEN__)
+         boost::unique_lock<boost::mutex> lock(localization_backend_manager_mutex());
++#endif
+         return localization_backend_manager_global();
+     }
+     localization_backend_manager localization_backend_manager::global(const localization_backend_manager& in)
+     {
++#if !defined(__EMSCRIPTEN__)
+         boost::unique_lock<boost::mutex> lock(localization_backend_manager_mutex());
++#endif
+         return exchange(localization_backend_manager_global(), in);
+     }
+ 
--- a/OrthancFramework/Resources/Patches/dcmtk-3.6.8.patch	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/Patches/dcmtk-3.6.8.patch	Thu Jan 30 17:41:33 2025 +0100
@@ -1,6 +1,6 @@
 diff -urEb dcmtk-DCMTK-3.6.8.orig/CMake/GenerateDCMTKConfigure.cmake dcmtk-DCMTK-3.6.8/CMake/GenerateDCMTKConfigure.cmake
---- dcmtk-DCMTK-3.6.8.orig/CMake/GenerateDCMTKConfigure.cmake	2024-01-09 17:13:10.329673608 +0100
-+++ dcmtk-DCMTK-3.6.8/CMake/GenerateDCMTKConfigure.cmake	2024-01-09 18:21:52.568142681 +0100
+--- dcmtk-DCMTK-3.6.8.orig/CMake/GenerateDCMTKConfigure.cmake	2023-12-19 11:12:57.000000000 +0100
++++ dcmtk-DCMTK-3.6.8/CMake/GenerateDCMTKConfigure.cmake	2024-11-25 16:54:59.036009112 +0100
 @@ -224,6 +224,8 @@
  
  # Check the sizes of various types
@@ -18,9 +18,11 @@
  
  # Check for include files, libraries, and functions
  include("${DCMTK_CMAKE_INCLUDE}CMake/dcmtkTryCompile.cmake")
+Only in dcmtk-DCMTK-3.6.8/config/include/dcmtk/config: arith.h
+Only in dcmtk-DCMTK-3.6.8/config/include/dcmtk/config: osconfig.h
 diff -urEb dcmtk-DCMTK-3.6.8.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h dcmtk-DCMTK-3.6.8/dcmdata/include/dcmtk/dcmdata/dcdict.h
---- dcmtk-DCMTK-3.6.8.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h	2024-01-09 17:13:10.337673529 +0100
-+++ dcmtk-DCMTK-3.6.8/dcmdata/include/dcmtk/dcmdata/dcdict.h	2024-01-09 18:21:52.568142681 +0100
+--- dcmtk-DCMTK-3.6.8.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h	2023-12-19 11:12:57.000000000 +0100
++++ dcmtk-DCMTK-3.6.8/dcmdata/include/dcmtk/dcmdata/dcdict.h	2024-11-25 16:54:59.036009112 +0100
 @@ -162,6 +162,12 @@
      /// returns an iterator to the end of the repeating tag dictionary
      DcmDictEntryListIterator repeatingEnd() { return repDict.end(); }
@@ -35,17 +37,18 @@
  
      /** private undefined assignment operator
 diff -urEb dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcdict.cc dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcdict.cc
---- dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcdict.cc	2024-01-09 17:13:10.337673529 +0100
-+++ dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcdict.cc	2024-01-09 18:21:52.568142681 +0100
+--- dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcdict.cc	2023-12-19 11:12:57.000000000 +0100
++++ dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcdict.cc	2024-11-25 16:54:59.036009112 +0100
 @@ -914,3 +914,5 @@
    wrlock().clear();
    wrunlock();
  }
 +
 +#include "dcdict_orthanc.cc"
+Only in dcmtk-DCMTK-3.6.8/dcmdata/libsrc: dcdict_orthanc.cc
 diff -urEb dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcpxitem.cc dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcpxitem.cc
---- dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcpxitem.cc	2024-01-09 17:13:10.337673529 +0100
-+++ dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcpxitem.cc	2024-01-09 18:21:52.568142681 +0100
+--- dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcpxitem.cc	2023-12-19 11:12:57.000000000 +0100
++++ dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcpxitem.cc	2024-11-25 16:54:59.036009112 +0100
 @@ -31,6 +31,9 @@
  #include "dcmtk/dcmdata/dcostrma.h"    /* for class DcmOutputStream */
  #include "dcmtk/dcmdata/dcwcache.h"    /* for class DcmWriteCache */
@@ -57,8 +60,8 @@
  // ********************************
  
 diff -urEb dcmtk-DCMTK-3.6.8.orig/dcmnet/libsrc/scu.cc dcmtk-DCMTK-3.6.8/dcmnet/libsrc/scu.cc
---- dcmtk-DCMTK-3.6.8.orig/dcmnet/libsrc/scu.cc	2024-01-09 17:13:10.349673411 +0100
-+++ dcmtk-DCMTK-3.6.8/dcmnet/libsrc/scu.cc	2024-01-09 18:23:08.723435667 +0100
+--- dcmtk-DCMTK-3.6.8.orig/dcmnet/libsrc/scu.cc	2023-12-19 11:12:57.000000000 +0100
++++ dcmtk-DCMTK-3.6.8/dcmnet/libsrc/scu.cc	2024-11-25 16:54:59.036009112 +0100
 @@ -19,6 +19,11 @@
   *
   */
@@ -72,8 +75,8 @@
  
  #include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */
 diff -urEb dcmtk-DCMTK-3.6.8.orig/oflog/include/dcmtk/oflog/thread/syncpub.h dcmtk-DCMTK-3.6.8/oflog/include/dcmtk/oflog/thread/syncpub.h
---- dcmtk-DCMTK-3.6.8.orig/oflog/include/dcmtk/oflog/thread/syncpub.h	2024-01-09 17:13:10.389673016 +0100
-+++ dcmtk-DCMTK-3.6.8/oflog/include/dcmtk/oflog/thread/syncpub.h	2024-01-09 18:21:52.568142681 +0100
+--- dcmtk-DCMTK-3.6.8.orig/oflog/include/dcmtk/oflog/thread/syncpub.h	2023-12-19 11:12:57.000000000 +0100
++++ dcmtk-DCMTK-3.6.8/oflog/include/dcmtk/oflog/thread/syncpub.h	2024-11-25 16:54:59.037009100 +0100
 @@ -63,7 +63,7 @@
  
  DCMTK_LOG4CPLUS_INLINE_EXPORT
@@ -111,8 +114,8 @@
  
  
 diff -urEb dcmtk-DCMTK-3.6.8.orig/oflog/libsrc/oflog.cc dcmtk-DCMTK-3.6.8/oflog/libsrc/oflog.cc
---- dcmtk-DCMTK-3.6.8.orig/oflog/libsrc/oflog.cc	2024-01-09 17:13:10.389673016 +0100
-+++ dcmtk-DCMTK-3.6.8/oflog/libsrc/oflog.cc	2024-01-09 18:21:52.568142681 +0100
+--- dcmtk-DCMTK-3.6.8.orig/oflog/libsrc/oflog.cc	2023-12-19 11:12:57.000000000 +0100
++++ dcmtk-DCMTK-3.6.8/oflog/libsrc/oflog.cc	2024-11-25 16:54:59.037009100 +0100
 @@ -19,6 +19,11 @@
   *
   */
@@ -126,8 +129,8 @@
  #include "dcmtk/oflog/oflog.h"
  
 diff -urEb dcmtk-DCMTK-3.6.8.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd/offile.h
---- dcmtk-DCMTK-3.6.8.orig/ofstd/include/dcmtk/ofstd/offile.h	2024-01-09 17:13:10.389673016 +0100
-+++ dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd/offile.h	2024-01-09 18:21:52.568142681 +0100
+--- dcmtk-DCMTK-3.6.8.orig/ofstd/include/dcmtk/ofstd/offile.h	2023-12-19 11:12:57.000000000 +0100
++++ dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd/offile.h	2024-11-25 16:54:59.037009100 +0100
 @@ -570,7 +570,7 @@
     */
    void setlinebuf()
@@ -137,3 +140,18 @@
      this->setvbuf(NULL, _IOLBF, 0);
  #else
      :: setlinebuf(file_);
+diff -urEb dcmtk-DCMTK-3.6.8.orig/ofstd/include/dcmtk/ofstd/ofutil.h dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd/ofutil.h
+--- dcmtk-DCMTK-3.6.8.orig/ofstd/include/dcmtk/ofstd/ofutil.h	2023-12-19 11:12:57.000000000 +0100
++++ dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd/ofutil.h	2024-11-25 17:00:27.525244000 +0100
+@@ -75,8 +75,8 @@
+         // copy constructor should be fine for primitive types.
+         inline type(const T& pt)
+         : t( pt ) {}
+-        inline type(const OFrvalue_storage& rhs)
+-        : t( rhs.pt ) {}
++        inline type(const type& rhs)
++        : t( rhs.t ) {}
+ 
+         // automatic conversion to the underlying type
+         inline operator T&() const { return OFconst_cast( T&, t ); }
+Only in dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd: ofutil.h~
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Resources/Patches/dcmtk-3.6.9-visual-studio.patch	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,1615 @@
+diff -urEb dcmtk-3.6.9.orig/config/math.cc dcmtk-3.6.9/config/math.cc
+--- dcmtk-3.6.9.orig/config/math.cc	2025-01-16 18:04:46.121846434 +0100
++++ dcmtk-3.6.9/config/math.cc	2025-01-17 07:45:46.252026376 +0100
+@@ -42,11 +42,17 @@
+ #include <windows.h>
+ #endif
+ 
++#if defined(_MSC_VER)
++#include <float.h>
++#endif
++
+ struct dcmtk_config_math
+ {
+   static inline OFBool isnan( float f )
+   {
+-#ifdef HAVE_PROTOTYPE_STD__ISNAN
++#if defined(_MSC_VER)
++    return _isnan(static_cast<double>(f)) != 0;
++#elif defined(HAVE_PROTOTYPE_STD__ISNAN)
+     return STD_NAMESPACE isnan(f);
+ #else
+     return ::isnan(f);
+@@ -55,7 +61,9 @@
+ 
+   static inline OFBool isnan( double d )
+   {
+-#ifdef HAVE_PROTOTYPE_STD__ISNAN
++#if defined(_MSC_VER)
++    return _isnan(d) != 0;
++#elif defined(HAVE_PROTOTYPE_STD__ISNAN)
+     return STD_NAMESPACE isnan(d);
+ #else
+     return ::isnan(d);
+@@ -64,7 +72,9 @@
+ 
+   static inline OFBool isinf( float f )
+   {
+-#ifdef HAVE_PROTOTYPE_STD__ISINF
++#if defined(_MSC_VER)
++    return _finite(static_cast<double>(f)) != 0;
++#elif defined(HAVE_PROTOTYPE_STD__ISINF)
+     return STD_NAMESPACE isinf( f );
+ #else
+     return ::isinf( f );
+@@ -73,7 +83,9 @@
+ 
+   static inline OFBool isinf( double d )
+   {
+-#ifdef HAVE_PROTOTYPE_STD__ISINF
++#if defined(_MSC_VER)
++    return _finite(d) != 0;
++#elif defined(HAVE_PROTOTYPE_STD__ISINF)
+     return STD_NAMESPACE isinf( d );
+ #else
+     return ::isinf( d );
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jccoefct.c dcmtk-3.6.9/dcmjpeg/libijg12/jccoefct.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jccoefct.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jccoefct.c	2025-01-16 20:11:42.016717178 +0100
+@@ -343,7 +343,7 @@
+ METHODDEF(boolean)
+ compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
+ {
+-  (void)input_buf;
++  //(void)input_buf;
+   j_lossy_c_ptr lossyc = (j_lossy_c_ptr) cinfo->codec;
+   c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private;
+   JDIMENSION MCU_col_num;   /* index of current MCU within row */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jcdiffct.c dcmtk-3.6.9/dcmjpeg/libijg12/jcdiffct.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jcdiffct.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jcdiffct.c	2025-01-16 20:11:42.016717178 +0100
+@@ -302,7 +302,7 @@
+ METHODDEF(boolean)
+ compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
+ {
+-  (void)input_buf;
++  //(void)input_buf;
+   j_lossless_c_ptr losslsc = (j_lossless_c_ptr) cinfo->codec;
+   c_diff_ptr diff = (c_diff_ptr) losslsc->diff_private;
+   /* JDIMENSION MCU_col_num; */ /* index of current MCU within row */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jcpred.c dcmtk-3.6.9/dcmjpeg/libijg12/jcpred.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jcpred.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jcpred.c	2025-01-16 20:11:42.017717169 +0100
+@@ -213,7 +213,7 @@
+          const JSAMPROW input_buf, JSAMPROW prev_row,
+          JDIFFROW diff_buf, JDIMENSION width)
+ {
+-  (void)prev_row;
++  //(void)prev_row;
+   DIFFERENCE_1D(INITIAL_PREDICTORx);
+ 
+   /*
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jctrans.c dcmtk-3.6.9/dcmjpeg/libijg12/jctrans.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jctrans.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jctrans.c	2025-01-16 20:11:42.017717169 +0100
+@@ -267,7 +267,7 @@
+ METHODDEF(boolean)
+ compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
+ {
+-  (void)input_buf;
++  //(void)input_buf;
+   j_lossy_c_ptr lossyc = (j_lossy_c_ptr) cinfo->codec;
+   c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private;
+   JDIMENSION MCU_col_num;   /* index of current MCU within row */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdmerge.c dcmtk-3.6.9/dcmjpeg/libijg12/jdmerge.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdmerge.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jdmerge.c	2025-01-16 20:11:42.017717169 +0100
+@@ -148,7 +148,7 @@
+             JDIMENSION out_rows_avail)
+ /* 2:1 vertical sampling case: may need a spare row. */
+ {
+-  (void) in_row_groups_avail;
++  //(void) in_row_groups_avail;
+   my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
+   JSAMPROW work_ptrs[2];
+   JDIMENSION num_rows;      /* number of rows returned to caller */
+@@ -198,8 +198,8 @@
+             JDIMENSION out_rows_avail)
+ /* 1:1 vertical sampling case: much easier, never need a spare row. */
+ {
+-  (void) in_row_groups_avail;
+-  (void) out_rows_avail;
++  //(void) in_row_groups_avail;
++  //(void) out_rows_avail;
+   my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
+ 
+   /* Just do the upsampling. */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdpostct.c dcmtk-3.6.9/dcmjpeg/libijg12/jdpostct.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdpostct.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jdpostct.c	2025-01-16 20:11:42.017717169 +0100
+@@ -161,8 +161,8 @@
+               JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+               JDIMENSION out_rows_avail)
+ {
+-  (void) output_buf;
+-  (void) out_rows_avail;
++  //(void) output_buf;
++  //(void) out_rows_avail;
+   my_post_ptr post = (my_post_ptr) cinfo->post;
+   JDIMENSION old_next_row, num_rows;
+ 
+@@ -207,9 +207,9 @@
+             JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+             JDIMENSION out_rows_avail)
+ {
+-  (void) input_buf;
+-  (void) in_row_group_ctr;
+-  (void) in_row_groups_avail;
++  //(void) input_buf;
++  //(void) in_row_group_ctr;
++  //(void) in_row_groups_avail;
+ 
+   my_post_ptr post = (my_post_ptr) cinfo->post;
+   JDIMENSION num_rows, max_rows;
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdpred.c dcmtk-3.6.9/dcmjpeg/libijg12/jdpred.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdpred.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jdpred.c	2025-01-16 20:11:42.017717169 +0100
+@@ -101,8 +101,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_1D(INITIAL_PREDICTOR2);
+ }
+ 
+@@ -111,8 +111,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_2D(PREDICTOR2);
+   JPEG_UNUSED(Rc);
+   JPEG_UNUSED(Rb);
+@@ -123,8 +123,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_2D(PREDICTOR3);
+   JPEG_UNUSED(Rc);
+   JPEG_UNUSED(Rb);
+@@ -135,8 +135,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_2D(PREDICTOR4);
+   JPEG_UNUSED(Rc);
+   JPEG_UNUSED(Rb);
+@@ -147,8 +147,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR5);
+   JPEG_UNUSED(Rc);
+@@ -160,8 +160,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR6);
+   JPEG_UNUSED(Rc);
+@@ -173,8 +173,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR7);
+   JPEG_UNUSED(Rc);
+@@ -195,7 +195,7 @@
+                 JDIFFROW undiff_buf, JDIMENSION width)
+ {
+ 
+-  (void)prev_row;
++  //(void)prev_row;
+   j_lossless_d_ptr losslsd = (j_lossless_d_ptr) cinfo->codec;
+ 
+   UNDIFFERENCE_1D(INITIAL_PREDICTORx);
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdsample.c dcmtk-3.6.9/dcmjpeg/libijg12/jdsample.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdsample.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jdsample.c	2025-01-16 20:11:42.018717160 +0100
+@@ -92,7 +92,7 @@
+           JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+           JDIMENSION out_rows_avail)
+ {
+-  (void)in_row_groups_avail;
++  //(void)in_row_groups_avail;
+   my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
+   int ci;
+   jpeg_component_info * compptr;
+@@ -158,8 +158,8 @@
+ fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)cinfo;
+-  (void)compptr;
++  //(void)cinfo;
++  //(void)compptr;
+   *output_data_ptr = input_data;
+ }
+ 
+@@ -173,9 +173,9 @@
+ noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)cinfo;
+-  (void)compptr;
+-  (void)input_data;
++  //(void)cinfo;
++  //(void)compptr;
++  //(void)input_data;
+   *output_data_ptr = NULL;  /* safety check */
+ }
+ 
+@@ -239,7 +239,7 @@
+ h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)compptr;
++  //(void)compptr;
+   JSAMPARRAY output_data = *output_data_ptr;
+   register JSAMPROW inptr, outptr;
+   register JSAMPLE invalue;
+@@ -268,7 +268,7 @@
+ h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)compptr;
++  //(void)compptr;
+   JSAMPARRAY output_data = *output_data_ptr;
+   register JSAMPROW inptr, outptr;
+   register JSAMPLE invalue;
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdscale.c dcmtk-3.6.9/dcmjpeg/libijg12/jdscale.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdscale.c	2025-01-16 18:04:46.157846124 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jdscale.c	2025-01-16 20:11:42.018717160 +0100
+@@ -67,7 +67,7 @@
+ 	const JDIFFROW diff_buf, JSAMPROW output_buf,
+ 	JDIMENSION width)
+ {
+-  (void)cinfo;
++  //(void)cinfo;
+   unsigned int xindex;
+ 
+   for (xindex = 0; xindex < width; xindex++)
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jerror.c dcmtk-3.6.9/dcmjpeg/libijg12/jerror.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jerror.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jerror.c	2025-01-17 11:14:11.975382089 +0100
+@@ -34,6 +34,10 @@
+ #define EXIT_FAILURE  1
+ #endif
+ 
++#if defined(_MSC_VER) && _MSC_VER < 1900
++#define snprintf _snprintf
++#endif
++
+ 
+ /*
+  * Create the message string table.
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jquant1.c dcmtk-3.6.9/dcmjpeg/libijg12/jquant1.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jquant1.c	2025-01-16 18:04:46.157846124 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jquant1.c	2025-01-16 20:11:42.018717160 +0100
+@@ -251,8 +251,8 @@
+    * (Forcing the upper and lower values to the limits ensures that
+    * dithering can't produce a color outside the selected gamut.)
+    */
+-  (void) cinfo;
+-  (void) ci;
++  //(void) cinfo;
++  //(void) ci;
+   return (int) (((IJG_INT32) j * MAXJSAMPLE + maxj/2) / maxj);
+ }
+ 
+@@ -262,8 +262,8 @@
+ /* Return largest input value that should map to j'th output value */
+ /* Must have largest(j=0) >= 0, and largest(j=maxj) >= MAXJSAMPLE */
+ {
+-  (void) cinfo;
+-  (void) ci;
++  //(void) cinfo;
++  //(void) ci;
+   /* Breakpoints are halfway between values returned by output_value */
+   return (int) (((IJG_INT32) (2*j + 1) * MAXJSAMPLE + maxj) / (2*maxj));
+ }
+@@ -744,7 +744,7 @@
+ METHODDEF(void)
+ start_pass_1_quant (j_decompress_ptr cinfo, boolean is_pre_scan)
+ {
+-  (void) is_pre_scan;
++  //(void) is_pre_scan;
+   my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+   size_t arraysize;
+   int i;
+@@ -802,7 +802,7 @@
+ METHODDEF(void)
+ finish_pass_1_quant (j_decompress_ptr cinfo)
+ {
+-  (void) cinfo;
++  //(void) cinfo;
+   /* no work in 1-pass case */
+ }
+ 
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jquant2.c dcmtk-3.6.9/dcmjpeg/libijg12/jquant2.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jquant2.c	2025-01-16 18:04:46.157846124 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg12/jquant2.c	2025-01-16 20:11:42.018717160 +0100
+@@ -224,7 +224,7 @@
+ prescan_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf,
+           JSAMPARRAY output_buf, int num_rows)
+ {
+-  (void) output_buf;
++  //(void) output_buf;
+   my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+   register JSAMPROW ptr;
+   register histptr histp;
+@@ -1156,7 +1156,7 @@
+ METHODDEF(void)
+ finish_pass2 (j_decompress_ptr cinfo)
+ {
+-  (void) cinfo;
++  //(void) cinfo;
+   /* no work */
+ }
+ 
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jccoefct.c dcmtk-3.6.9/dcmjpeg/libijg16/jccoefct.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jccoefct.c	2025-01-16 18:04:46.159846107 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jccoefct.c	2025-01-16 20:11:42.019717150 +0100
+@@ -343,7 +343,7 @@
+ METHODDEF(boolean)
+ compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
+ {
+-  (void)input_buf;
++  //(void)input_buf;
+   j_lossy_c_ptr lossyc = (j_lossy_c_ptr) cinfo->codec;
+   c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private;
+   JDIMENSION MCU_col_num;   /* index of current MCU within row */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jcdiffct.c dcmtk-3.6.9/dcmjpeg/libijg16/jcdiffct.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jcdiffct.c	2025-01-16 18:04:46.159846107 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jcdiffct.c	2025-01-16 20:11:42.019717150 +0100
+@@ -302,7 +302,7 @@
+ METHODDEF(boolean)
+ compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
+ {
+-  (void)input_buf;
++  //(void)input_buf;
+   j_lossless_c_ptr losslsc = (j_lossless_c_ptr) cinfo->codec;
+   c_diff_ptr diff = (c_diff_ptr) losslsc->diff_private;
+   /* JDIMENSION MCU_col_num; */ /* index of current MCU within row */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jcpred.c dcmtk-3.6.9/dcmjpeg/libijg16/jcpred.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jcpred.c	2025-01-16 18:04:46.159846107 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jcpred.c	2025-01-16 20:11:42.019717150 +0100
+@@ -213,7 +213,7 @@
+          const JSAMPROW input_buf, JSAMPROW prev_row,
+          JDIFFROW diff_buf, JDIMENSION width)
+ {
+-  (void)prev_row;
++  //(void)prev_row;
+   DIFFERENCE_1D(INITIAL_PREDICTORx);
+ 
+   /*
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jctrans.c dcmtk-3.6.9/dcmjpeg/libijg16/jctrans.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jctrans.c	2025-01-16 18:04:46.159846107 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jctrans.c	2025-01-16 20:11:42.019717150 +0100
+@@ -267,7 +267,7 @@
+ METHODDEF(boolean)
+ compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
+ {
+-  (void)input_buf;
++  //(void)input_buf;
+   j_lossy_c_ptr lossyc = (j_lossy_c_ptr) cinfo->codec;
+   c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private;
+   JDIMENSION MCU_col_num;   /* index of current MCU within row */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdmerge.c dcmtk-3.6.9/dcmjpeg/libijg16/jdmerge.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdmerge.c	2025-01-16 18:04:46.159846107 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jdmerge.c	2025-01-16 20:11:42.019717150 +0100
+@@ -169,7 +169,7 @@
+             JDIMENSION out_rows_avail)
+ /* 2:1 vertical sampling case: may need a spare row. */
+ {
+-  (void) in_row_groups_avail;
++  //(void) in_row_groups_avail;
+   my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
+   JSAMPROW work_ptrs[2];
+   JDIMENSION num_rows;      /* number of rows returned to caller */
+@@ -219,8 +219,8 @@
+             JDIMENSION out_rows_avail)
+ /* 1:1 vertical sampling case: much easier, never need a spare row. */
+ {
+-  (void) in_row_groups_avail;
+-  (void) out_rows_avail;
++  //(void) in_row_groups_avail;
++  //(void) out_rows_avail;
+   my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
+ 
+   /* Just do the upsampling. */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdpostct.c dcmtk-3.6.9/dcmjpeg/libijg16/jdpostct.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdpostct.c	2025-01-16 18:04:46.159846107 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jdpostct.c	2025-01-16 20:11:42.019717150 +0100
+@@ -161,8 +161,8 @@
+               JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+               JDIMENSION out_rows_avail)
+ {
+-  (void) output_buf;
+-  (void) out_rows_avail;
++  //(void) output_buf;
++  //(void) out_rows_avail;
+   my_post_ptr post = (my_post_ptr) cinfo->post;
+   JDIMENSION old_next_row, num_rows;
+ 
+@@ -207,9 +207,9 @@
+             JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+             JDIMENSION out_rows_avail)
+ {
+-  (void) input_buf;
+-  (void) in_row_group_ctr;
+-  (void) in_row_groups_avail;
++  //(void) input_buf;
++  //(void) in_row_group_ctr;
++  //(void) in_row_groups_avail;
+   my_post_ptr post = (my_post_ptr) cinfo->post;
+   JDIMENSION num_rows, max_rows;
+ 
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdpred.c dcmtk-3.6.9/dcmjpeg/libijg16/jdpred.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdpred.c	2025-01-16 18:04:46.159846107 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jdpred.c	2025-01-16 20:11:42.020717140 +0100
+@@ -101,8 +101,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_1D(INITIAL_PREDICTOR2);
+ }
+ 
+@@ -111,8 +111,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-   (void)cinfo;
+-  (void)comp_index;
++   //(void)cinfo;
++  //(void)comp_index;
+  UNDIFFERENCE_2D(PREDICTOR2);
+   JPEG_UNUSED(Rc);
+   JPEG_UNUSED(Rb);
+@@ -123,8 +123,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_2D(PREDICTOR3);
+   JPEG_UNUSED(Rc);
+   JPEG_UNUSED(Rb);
+@@ -135,8 +135,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_2D(PREDICTOR4A);
+   JPEG_UNUSED(Rc);
+   JPEG_UNUSED(Rb);
+@@ -147,8 +147,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_2D(PREDICTOR4);
+   JPEG_UNUSED(Rc);
+   JPEG_UNUSED(Rb);
+@@ -159,8 +159,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR5);
+   JPEG_UNUSED(Rc);
+@@ -172,8 +172,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR5A);
+   JPEG_UNUSED(Rc);
+@@ -185,8 +185,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR6);
+   JPEG_UNUSED(Rc);
+@@ -198,8 +198,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR6A);
+   JPEG_UNUSED(Rc);
+@@ -211,8 +211,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR7);
+   JPEG_UNUSED(Rc);
+@@ -224,8 +224,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR7A);
+   JPEG_UNUSED(Rc);
+@@ -245,7 +245,7 @@
+                 const JDIFFROW diff_buf, JDIFFROW prev_row,
+                 JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)prev_row;
++  //(void)prev_row;
+   j_lossless_d_ptr losslsd = (j_lossless_d_ptr) cinfo->codec;
+ 
+   UNDIFFERENCE_1D(INITIAL_PREDICTORx);
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdsample.c dcmtk-3.6.9/dcmjpeg/libijg16/jdsample.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdsample.c	2025-01-16 18:04:46.159846107 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jdsample.c	2025-01-16 20:11:42.020717140 +0100
+@@ -92,7 +92,7 @@
+           JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+           JDIMENSION out_rows_avail)
+ {
+-  (void)in_row_groups_avail;
++  //(void)in_row_groups_avail;
+   my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
+   int ci;
+   jpeg_component_info * compptr;
+@@ -158,8 +158,8 @@
+ fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)cinfo;
+-  (void)compptr;
++  //(void)cinfo;
++  //(void)compptr;
+   *output_data_ptr = input_data;
+ }
+ 
+@@ -173,9 +173,9 @@
+ noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)cinfo;
+-  (void)compptr;
+-  (void)input_data;
++  //(void)cinfo;
++  //(void)compptr;
++  //(void)input_data;
+   *output_data_ptr = NULL;  /* safety check */
+ }
+ 
+@@ -239,7 +239,7 @@
+ h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)compptr;
++  //(void)compptr;
+   JSAMPARRAY output_data = *output_data_ptr;
+   register JSAMPROW inptr, outptr;
+   register JSAMPLE invalue;
+@@ -268,7 +268,7 @@
+ h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)compptr;
++  //(void)compptr;
+   JSAMPARRAY output_data = *output_data_ptr;
+   register JSAMPROW inptr, outptr;
+   register JSAMPLE invalue;
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdscale.c dcmtk-3.6.9/dcmjpeg/libijg16/jdscale.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdscale.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jdscale.c	2025-01-16 20:11:42.020717140 +0100
+@@ -67,7 +67,7 @@
+ 	const JDIFFROW diff_buf, JSAMPROW output_buf,
+ 	JDIMENSION width)
+ {
+-  (void)cinfo;
++  //(void)cinfo;
+   unsigned int xindex;
+ 
+   for (xindex = 0; xindex < width; xindex++)
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jerror.c dcmtk-3.6.9/dcmjpeg/libijg16/jerror.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jerror.c	2025-01-16 18:04:46.159846107 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jerror.c	2025-01-17 11:14:19.002328399 +0100
+@@ -34,6 +34,10 @@
+ #define EXIT_FAILURE  1
+ #endif
+ 
++#if defined(_MSC_VER) && _MSC_VER < 1900
++#define snprintf _snprintf
++#endif
++
+ 
+ /*
+  * Create the message string table.
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jquant1.c dcmtk-3.6.9/dcmjpeg/libijg16/jquant1.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jquant1.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jquant1.c	2025-01-16 20:11:42.020717140 +0100
+@@ -251,8 +251,8 @@
+    * (Forcing the upper and lower values to the limits ensures that
+    * dithering can't produce a color outside the selected gamut.)
+    */
+-  (void) cinfo;
+-  (void) ci;
++  //(void) cinfo;
++  //(void) ci;
+   return (int) (((IJG_INT32) j * MAXJSAMPLE + maxj/2) / maxj);
+ }
+ 
+@@ -262,8 +262,8 @@
+ /* Return largest input value that should map to j'th output value */
+ /* Must have largest(j=0) >= 0, and largest(j=maxj) >= MAXJSAMPLE */
+ {
+-  (void) cinfo;
+-  (void) ci;
++  //(void) cinfo;
++  //(void) ci;
+   /* Breakpoints are halfway between values returned by output_value */
+   return (int) (((IJG_INT32) (2*j + 1) * MAXJSAMPLE + maxj) / (2*maxj));
+ }
+@@ -744,7 +744,7 @@
+ METHODDEF(void)
+ start_pass_1_quant (j_decompress_ptr cinfo, boolean is_pre_scan)
+ {
+-  (void) is_pre_scan;
++  //(void) is_pre_scan;
+   my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+   size_t arraysize;
+   int i;
+@@ -802,7 +802,7 @@
+ METHODDEF(void)
+ finish_pass_1_quant (j_decompress_ptr cinfo)
+ {
+-  (void) cinfo;
++  //(void) cinfo;
+   /* no work in 1-pass case */
+ }
+ 
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jquant2.c dcmtk-3.6.9/dcmjpeg/libijg16/jquant2.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jquant2.c	2025-01-16 18:04:46.158846116 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg16/jquant2.c	2025-01-16 20:11:42.021717131 +0100
+@@ -224,7 +224,7 @@
+ prescan_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf,
+           JSAMPARRAY output_buf, int num_rows)
+ {
+-  (void) output_buf;
++  //(void) output_buf;
+   my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+   register JSAMPROW ptr;
+   register histptr histp;
+@@ -1156,7 +1156,7 @@
+ METHODDEF(void)
+ finish_pass2 (j_decompress_ptr cinfo)
+ {
+-  (void) cinfo;
++  //(void) cinfo;
+   /* no work */
+ }
+ 
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jccoefct.c dcmtk-3.6.9/dcmjpeg/libijg8/jccoefct.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jccoefct.c	2025-01-16 18:04:46.161846090 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jccoefct.c	2025-01-16 20:11:42.021717131 +0100
+@@ -343,7 +343,7 @@
+ METHODDEF(boolean)
+ compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
+ {
+-  (void)input_buf;
++  //(void)input_buf;
+   j_lossy_c_ptr lossyc = (j_lossy_c_ptr) cinfo->codec;
+   c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private;
+   JDIMENSION MCU_col_num;   /* index of current MCU within row */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jcdiffct.c dcmtk-3.6.9/dcmjpeg/libijg8/jcdiffct.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jcdiffct.c	2025-01-16 18:04:46.161846090 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jcdiffct.c	2025-01-16 20:11:42.021717131 +0100
+@@ -302,7 +302,7 @@
+ METHODDEF(boolean)
+ compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
+ {
+-  (void)input_buf;
++  //(void)input_buf;
+   j_lossless_c_ptr losslsc = (j_lossless_c_ptr) cinfo->codec;
+   c_diff_ptr diff = (c_diff_ptr) losslsc->diff_private;
+   /* JDIMENSION MCU_col_num; */ /* index of current MCU within row */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jcpred.c dcmtk-3.6.9/dcmjpeg/libijg8/jcpred.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jcpred.c	2025-01-16 18:04:46.161846090 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jcpred.c	2025-01-16 20:11:42.021717131 +0100
+@@ -213,7 +213,7 @@
+          const JSAMPROW input_buf, JSAMPROW prev_row,
+          JDIFFROW diff_buf, JDIMENSION width)
+ {
+-  (void)prev_row;
++  //(void)prev_row;
+   DIFFERENCE_1D(INITIAL_PREDICTORx);
+ 
+   /*
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jctrans.c dcmtk-3.6.9/dcmjpeg/libijg8/jctrans.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jctrans.c	2025-01-16 18:04:46.161846090 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jctrans.c	2025-01-16 20:11:42.021717131 +0100
+@@ -267,7 +267,7 @@
+ METHODDEF(boolean)
+ compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf)
+ {
+-  (void)input_buf;
++  //(void)input_buf;
+   j_lossy_c_ptr lossyc = (j_lossy_c_ptr) cinfo->codec;
+   c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private;
+   JDIMENSION MCU_col_num;   /* index of current MCU within row */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdmerge.c dcmtk-3.6.9/dcmjpeg/libijg8/jdmerge.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdmerge.c	2025-01-16 18:04:46.161846090 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jdmerge.c	2025-01-16 20:11:42.021717131 +0100
+@@ -148,7 +148,7 @@
+             JDIMENSION out_rows_avail)
+ /* 2:1 vertical sampling case: may need a spare row. */
+ {
+-  (void) in_row_groups_avail;
++  //(void) in_row_groups_avail;
+   my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
+   JSAMPROW work_ptrs[2];
+   JDIMENSION num_rows;      /* number of rows returned to caller */
+@@ -198,8 +198,8 @@
+             JDIMENSION out_rows_avail)
+ /* 1:1 vertical sampling case: much easier, never need a spare row. */
+ {
+-  (void) in_row_groups_avail;
+-  (void) out_rows_avail;
++  //(void) in_row_groups_avail;
++  //(void) out_rows_avail;
+   my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
+ 
+   /* Just do the upsampling. */
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdpostct.c dcmtk-3.6.9/dcmjpeg/libijg8/jdpostct.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdpostct.c	2025-01-16 18:04:46.160846099 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jdpostct.c	2025-01-16 20:11:42.022717122 +0100
+@@ -161,8 +161,8 @@
+               JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+               JDIMENSION out_rows_avail)
+ {
+-  (void) output_buf;
+-  (void) out_rows_avail;
++  //(void) output_buf;
++  //(void) out_rows_avail;
+   my_post_ptr post = (my_post_ptr) cinfo->post;
+   JDIMENSION old_next_row, num_rows;
+ 
+@@ -207,9 +207,9 @@
+             JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+             JDIMENSION out_rows_avail)
+ {
+-  (void) input_buf;
+-  (void) in_row_group_ctr;
+-  (void) in_row_groups_avail;
++  //(void) input_buf;
++  //(void) in_row_group_ctr;
++  //(void) in_row_groups_avail;
+   my_post_ptr post = (my_post_ptr) cinfo->post;
+   JDIMENSION num_rows, max_rows;
+ 
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdpred.c dcmtk-3.6.9/dcmjpeg/libijg8/jdpred.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdpred.c	2025-01-16 18:04:46.161846090 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jdpred.c	2025-01-16 20:11:42.022717122 +0100
+@@ -101,8 +101,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_1D(INITIAL_PREDICTOR2);
+ }
+ 
+@@ -111,8 +111,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_2D(PREDICTOR2);
+   JPEG_UNUSED(Rc);
+   JPEG_UNUSED(Rb);
+@@ -123,8 +123,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_2D(PREDICTOR3);
+   JPEG_UNUSED(Rc);
+   JPEG_UNUSED(Rb);
+@@ -135,8 +135,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   UNDIFFERENCE_2D(PREDICTOR4);
+   JPEG_UNUSED(Rc);
+   JPEG_UNUSED(Rb);
+@@ -147,8 +147,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR5);
+   JPEG_UNUSED(Rc);
+@@ -160,8 +160,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR6);
+   JPEG_UNUSED(Rc);
+@@ -173,8 +173,8 @@
+            const JDIFFROW diff_buf, const JDIFFROW prev_row,
+            JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)cinfo;
+-  (void)comp_index;
++  //(void)cinfo;
++  //(void)comp_index;
+   SHIFT_TEMPS
+   UNDIFFERENCE_2D(PREDICTOR7);
+   JPEG_UNUSED(Rc);
+@@ -194,7 +194,7 @@
+                 const JDIFFROW diff_buf, JDIFFROW prev_row,
+                 JDIFFROW undiff_buf, JDIMENSION width)
+ {
+-  (void)prev_row;
++  //(void)prev_row;
+   j_lossless_d_ptr losslsd = (j_lossless_d_ptr) cinfo->codec;
+ 
+   UNDIFFERENCE_1D(INITIAL_PREDICTORx);
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdsample.c dcmtk-3.6.9/dcmjpeg/libijg8/jdsample.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdsample.c	2025-01-16 18:04:46.161846090 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jdsample.c	2025-01-16 20:11:42.022717122 +0100
+@@ -92,7 +92,7 @@
+           JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+           JDIMENSION out_rows_avail)
+ {
+-  (void)in_row_groups_avail;
++  //(void)in_row_groups_avail;
+   my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample;
+   int ci;
+   jpeg_component_info * compptr;
+@@ -158,8 +158,8 @@
+ fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)cinfo;
+-  (void)compptr;
++  //(void)cinfo;
++  //(void)compptr;
+   *output_data_ptr = input_data;
+ }
+ 
+@@ -173,9 +173,9 @@
+ noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)cinfo;
+-  (void)compptr;
+-  (void)input_data;
++  //(void)cinfo;
++  //(void)compptr;
++  //(void)input_data;
+   *output_data_ptr = NULL;  /* safety check */
+ }
+ 
+@@ -239,7 +239,7 @@
+ h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)compptr;
++  //(void)compptr;
+   JSAMPARRAY output_data = *output_data_ptr;
+   register JSAMPROW inptr, outptr;
+   register JSAMPLE invalue;
+@@ -268,7 +268,7 @@
+ h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+            JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
+ {
+-  (void)compptr;
++  //(void)compptr;
+   JSAMPARRAY output_data = *output_data_ptr;
+   register JSAMPROW inptr, outptr;
+   register JSAMPLE invalue;
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdscale.c dcmtk-3.6.9/dcmjpeg/libijg8/jdscale.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdscale.c	2025-01-16 18:04:46.160846099 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jdscale.c	2025-01-16 20:11:42.022717122 +0100
+@@ -67,7 +67,7 @@
+ 	const JDIFFROW diff_buf, JSAMPROW output_buf,
+ 	JDIMENSION width)
+ {
+-  (void)cinfo;
++  //(void)cinfo;
+   unsigned int xindex;
+ 
+   for (xindex = 0; xindex < width; xindex++)
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jerror.c dcmtk-3.6.9/dcmjpeg/libijg8/jerror.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jerror.c	2025-01-16 18:04:46.160846099 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jerror.c	2025-01-17 11:14:02.543454148 +0100
+@@ -34,6 +34,10 @@
+ #define EXIT_FAILURE  1
+ #endif
+ 
++#if defined(_MSC_VER) && _MSC_VER < 1900
++#define snprintf _snprintf
++#endif
++
+ 
+ /*
+  * Create the message string table.
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jquant1.c dcmtk-3.6.9/dcmjpeg/libijg8/jquant1.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jquant1.c	2025-01-16 18:04:46.160846099 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jquant1.c	2025-01-16 20:11:42.022717122 +0100
+@@ -251,8 +251,8 @@
+    * (Forcing the upper and lower values to the limits ensures that
+    * dithering can't produce a color outside the selected gamut.)
+    */
+-  (void) cinfo;
+-  (void) ci;
++  //(void) cinfo;
++  //(void) ci;
+   return (int) (((IJG_INT32) j * MAXJSAMPLE + maxj/2) / maxj);
+ }
+ 
+@@ -262,8 +262,8 @@
+ /* Return largest input value that should map to j'th output value */
+ /* Must have largest(j=0) >= 0, and largest(j=maxj) >= MAXJSAMPLE */
+ {
+-  (void) cinfo;
+-  (void) ci;
++  //(void) cinfo;
++  //(void) ci;
+   /* Breakpoints are halfway between values returned by output_value */
+   return (int) (((IJG_INT32) (2*j + 1) * MAXJSAMPLE + maxj) / (2*maxj));
+ }
+@@ -744,7 +744,7 @@
+ METHODDEF(void)
+ start_pass_1_quant (j_decompress_ptr cinfo, boolean is_pre_scan)
+ {
+-  (void) is_pre_scan;
++  //(void) is_pre_scan;
+   my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+   size_t arraysize;
+   int i;
+@@ -802,7 +802,7 @@
+ METHODDEF(void)
+ finish_pass_1_quant (j_decompress_ptr cinfo)
+ {
+-  (void) cinfo;
++  //(void) cinfo;
+   /* no work in 1-pass case */
+ }
+ 
+diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jquant2.c dcmtk-3.6.9/dcmjpeg/libijg8/jquant2.c
+--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jquant2.c	2025-01-16 18:04:46.160846099 +0100
++++ dcmtk-3.6.9/dcmjpeg/libijg8/jquant2.c	2025-01-16 20:11:42.023717112 +0100
+@@ -224,7 +224,7 @@
+ prescan_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf,
+           JSAMPARRAY output_buf, int num_rows)
+ {
+-  (void) output_buf;
++  //(void) output_buf;
+   my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+   register JSAMPROW ptr;
+   register histptr histp;
+@@ -1156,7 +1156,7 @@
+ METHODDEF(void)
+ finish_pass2 (j_decompress_ptr cinfo)
+ {
+-  (void) cinfo;
++  //(void) cinfo;
+   /* no work */
+ }
+ 
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_bcs.h dcmtk-3.6.9/oficonv/libsrc/citrus_bcs.h
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_bcs.h	2025-01-16 18:04:46.146846219 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_bcs.h	2025-01-17 11:14:44.746131630 +0100
+@@ -39,7 +39,21 @@
+ #include <errno.h>
+ #include <stdint.h>
+ #include <limits.h>
++
++#undef EOPNOTSUPP
++#define EOPNOTSUPP 130   // https://learn.microsoft.com/fr-fr/cpp/c-runtime-library/errno-constants
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
++#if defined(_MSC_VER) && _MSC_VER < 1900
++#define snprintf _snprintf
++#endif
+ 
+ #define CITRUS_DECONST(type, var)    ((type)(uintptr_t)(const void *)(var))
+ 
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_big5.c dcmtk-3.6.9/oficonv/libsrc/citrus_big5.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_big5.c	2025-01-16 18:04:46.137846297 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_big5.c	2025-01-17 09:59:15.249381150 +0100
+@@ -218,13 +218,6 @@
+     return (0);
+ }
+ 
+-static const _citrus_prop_hint_t root_hints[] = {
+-    _CITRUS_PROP_HINT_NUM("row", &_citrus_BIG5_fill_rowcol),
+-    _CITRUS_PROP_HINT_NUM("col", &_citrus_BIG5_fill_rowcol),
+-    _CITRUS_PROP_HINT_NUM("excludes", &_citrus_BIG5_fill_excludes),
+-    _CITRUS_PROP_HINT_END
+-};
+-
+ static void
+ /*ARGSUSED*/
+ _citrus_BIG5_encoding_module_uninit(_BIG5EncodingInfo *ei)
+@@ -245,6 +238,18 @@
+     const char *s;
+     int err;
+ 
++    _citrus_prop_hint_t root_hints[4];
++    root_hints[0].name = "row";
++    root_hints[0].type = _CITRUS_PROP_NUM;
++    root_hints[0].cb.num.func = _citrus_BIG5_fill_rowcol;
++    root_hints[1].name = "col";
++    root_hints[1].type = _CITRUS_PROP_NUM;
++    root_hints[1].cb.num.func = _citrus_BIG5_fill_rowcol;
++    root_hints[2].name = "excludes";
++    root_hints[2].type = _CITRUS_PROP_NUM;
++    root_hints[2].cb.num.func = _citrus_BIG5_fill_excludes;
++    root_hints[3].name = NULL;  // _CITRUS_PROP_HINT_END
++
+     memset((void *)ei, 0, sizeof(*ei));
+     TAILQ_INIT(&ei->excludes);
+ 
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_db_hash.h dcmtk-3.6.9/oficonv/libsrc/citrus_db_hash.h
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_db_hash.h	2025-01-16 18:04:46.135846314 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_db_hash.h	2025-01-17 09:36:14.692449741 +0100
+@@ -29,7 +29,8 @@
+ 
+ #include "dcmtk/config/osconfig.h"
+ #include "dcmtk/oficonv/oidefine.h"
+-#include <inttypes.h>
++
++#include <stdint.h>
+ 
+ struct _citrus_region;
+ 
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_esdb.c dcmtk-3.6.9/oficonv/libsrc/citrus_esdb.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_esdb.c	2025-01-16 18:04:46.137846297 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_esdb.c	2025-01-17 07:57:57.711032732 +0100
+@@ -34,7 +34,15 @@
+ 
+ #include <errno.h>
+ #include <limits.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_esdb.h dcmtk-3.6.9/oficonv/libsrc/citrus_esdb.h
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_esdb.h	2025-01-16 18:04:46.137846297 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_esdb.h	2025-01-17 07:58:02.525992089 +0100
+@@ -29,7 +29,14 @@
+ 
+ #include "dcmtk/config/osconfig.h"
+ #include "citrus_types.h"
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
+ 
+ struct _citrus_esdb_charset {
+     _citrus_csid_t           ec_csid;
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_gbk2k.c dcmtk-3.6.9/oficonv/libsrc/citrus_gbk2k.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_gbk2k.c	2025-01-16 18:04:46.146846219 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_gbk2k.c	2025-01-17 07:58:08.702939916 +0100
+@@ -34,7 +34,15 @@
+ 
+ #include <errno.h>
+ #include <limits.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stddef.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_hz.c dcmtk-3.6.9/oficonv/libsrc/citrus_hz.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_hz.c	2025-01-16 18:04:46.144846236 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_hz.c	2025-01-17 10:04:16.511792074 +0100
+@@ -571,13 +571,6 @@
+     return (0);
+ }
+ 
+-static const _citrus_prop_hint_t escape_hints[] = {
+-_CITRUS_PROP_HINT_STR("CH", &_citrus_HZ_parse_char),
+-_CITRUS_PROP_HINT_STR("GL", &_citrus_HZ_parse_graphic),
+-_CITRUS_PROP_HINT_STR("GR", &_citrus_HZ_parse_graphic),
+-_CITRUS_PROP_HINT_END
+-};
+-
+ static int
+ _citrus_HZ_parse_escape(void *context, const char *name, const char *s)
+ {
+@@ -585,6 +578,18 @@
+     escape_t *escape;
+     void *p[2];
+ 
++    _citrus_prop_hint_t escape_hints[4];
++    escape_hints[0].name = "CH";
++    escape_hints[0].type = _CITRUS_PROP_STR;
++    escape_hints[0].cb.str.func = _citrus_HZ_parse_char;
++    escape_hints[1].name = "GL";
++    escape_hints[1].type = _CITRUS_PROP_STR;
++    escape_hints[1].cb.str.func = _citrus_HZ_parse_graphic;
++    escape_hints[2].name = "GR";
++    escape_hints[2].type = _CITRUS_PROP_STR;
++    escape_hints[2].cb.str.func = _citrus_HZ_parse_graphic;
++    escape_hints[3].name = NULL;  // _CITRUS_PROP_HINT_END
++
+     ei = (_HZEncodingInfo *)context;
+     escape = calloc(1, sizeof(*escape));
+     if (escape == NULL)
+@@ -605,18 +610,21 @@
+         escape_hints, (void *)&p[0], s, strlen(s)));
+ }
+ 
+-static const _citrus_prop_hint_t root_hints[] = {
+-_CITRUS_PROP_HINT_STR("0", &_citrus_HZ_parse_escape),
+-_CITRUS_PROP_HINT_STR("1", &_citrus_HZ_parse_escape),
+-_CITRUS_PROP_HINT_END
+-};
+-
+ static int
+ _citrus_HZ_encoding_module_init(_HZEncodingInfo * ei,
+     const void * var, size_t lenvar)
+ {
+     int errnum;
+ 
++    _citrus_prop_hint_t root_hints[3];
++    root_hints[0].name = "0";
++    root_hints[0].type = _CITRUS_PROP_STR;
++    root_hints[0].cb.str.func = _citrus_HZ_parse_escape;
++    root_hints[1].name = "1";
++    root_hints[1].type = _CITRUS_PROP_STR;
++    root_hints[1].cb.str.func = _citrus_HZ_parse_escape;
++    root_hints[2].name = NULL;  // _CITRUS_PROP_HINT_END
++
+     memset(ei, 0, sizeof(*ei));
+     TAILQ_INIT(E0SET(ei));
+     TAILQ_INIT(E1SET(ei));
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv.c dcmtk-3.6.9/oficonv/libsrc/citrus_iconv.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv.c	2025-01-16 18:04:46.146846219 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv.c	2025-01-17 09:23:11.596327937 +0100
+@@ -49,7 +49,15 @@
+ #endif
+ 
+ #include <limits.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+@@ -299,14 +307,17 @@
+ _citrus_iconv_open(struct _citrus_iconv * * rcv,
+     const char * src, const char * dst)
+ {
+-struct _citrus_iconv *cv = NULL;
++#ifdef HAVE_WINDOWS_H
++    char current_codepage[20];
++#endif
++
++    struct _citrus_iconv *cv = NULL;
+     struct _citrus_iconv_shared *ci = NULL;
+     char realdst[OFICONV_PATH_MAX], realsrc[OFICONV_PATH_MAX];
+     int ret;
+ 
+     init_cache();
+ #ifdef HAVE_WINDOWS_H
+-    char current_codepage[20];
+     snprintf(current_codepage, sizeof(current_codepage), "%lu", (unsigned long) GetConsoleOutputCP());
+ #endif
+ 
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_local.h dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_local.h
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_local.h	2025-01-16 18:04:46.146846219 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_local.h	2025-01-17 07:58:20.317841727 +0100
+@@ -29,7 +29,15 @@
+ 
+ #include "dcmtk/config/osconfig.h"
+ #include "dcmtk/oficonv/iconv.h"
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stdint.h>
+ 
+ #ifdef HAVE_SYS_QUEUE_H
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_none.c dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_none.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_none.c	2025-01-16 18:04:46.141846262 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_none.c	2025-01-17 07:58:25.509797800 +0100
+@@ -35,7 +35,15 @@
+ 
+ 
+ #include <errno.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stdlib.h>
+ #include <string.h>
+ 
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_std.c dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_std.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_std.c	2025-01-16 18:04:46.141846262 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_std.c	2025-01-17 07:58:30.205758052 +0100
+@@ -36,7 +36,15 @@
+ 
+ #include <errno.h>
+ #include <limits.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iso2022.c dcmtk-3.6.9/oficonv/libsrc/citrus_iso2022.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iso2022.c	2025-01-16 18:04:46.142846254 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_iso2022.c	2025-01-17 07:58:35.485713336 +0100
+@@ -35,7 +35,15 @@
+ 
+ #include <errno.h>
+ #include <limits.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stddef.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_johab.c dcmtk-3.6.9/oficonv/libsrc/citrus_johab.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_johab.c	2025-01-16 18:04:46.144846236 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_johab.c	2025-01-17 07:58:40.390671776 +0100
+@@ -34,7 +34,15 @@
+ 
+ #include <errno.h>
+ #include <limits.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stddef.h>
+ #include <stdint.h>
+ #include <stdio.h>
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_module.c dcmtk-3.6.9/oficonv/libsrc/citrus_module.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_module.c	2025-01-16 18:04:46.146846219 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_module.c	2025-01-17 07:58:45.172631238 +0100
+@@ -98,7 +98,15 @@
+ #endif
+ #include <errno.h>
+ #include <limits.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stddef.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_mskanji.c dcmtk-3.6.9/oficonv/libsrc/citrus_mskanji.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_mskanji.c	2025-01-16 18:04:46.146846219 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_mskanji.c	2025-01-17 07:58:51.749575452 +0100
+@@ -67,7 +67,15 @@
+ 
+ #include <errno.h>
+ #include <limits.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stddef.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_prop.c dcmtk-3.6.9/oficonv/libsrc/citrus_prop.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_prop.c	2025-01-16 18:04:46.140846271 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_prop.c	2025-01-17 07:58:55.534543331 +0100
+@@ -30,7 +30,15 @@
+ 
+ #include <errno.h>
+ #include <limits.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_region.h dcmtk-3.6.9/oficonv/libsrc/citrus_region.h
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_region.h	2025-01-16 18:04:46.142846254 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_region.h	2025-01-17 07:59:02.917480641 +0100
+@@ -31,7 +31,14 @@
+ #include "dcmtk/config/osconfig.h"
+ #include <stdint.h>
+ #include <string.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
+ 
+ #ifdef HAVE_SYS_TYPES_H
+ #include <sys/types.h>
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_utf8.c dcmtk-3.6.9/oficonv/libsrc/citrus_utf8.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_utf8.c	2025-01-16 18:04:46.146846219 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_utf8.c	2025-01-17 07:57:46.519127132 +0100
+@@ -66,7 +66,15 @@
+ 
+ #include <errno.h>
+ #include <limits.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stddef.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/oficonv_iconv.c dcmtk-3.6.9/oficonv/libsrc/oficonv_iconv.c
+--- dcmtk-3.6.9.orig/oficonv/libsrc/oficonv_iconv.c	2025-01-16 18:04:46.137846297 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/oficonv_iconv.c	2025-01-17 09:16:42.622548106 +0100
+@@ -40,7 +40,15 @@
+ 
+ #include <errno.h>
+ #include <limits.h>
++
++#if (_MSC_VER >= 1900)
+ #include <stdbool.h>
++#else
++#define bool int
++#define false 0
++#define true 1
++#endif
++
+ #include <stdlib.h>
+ #include <string.h>
+ #include <stdio.h>
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/windows_mmap.h dcmtk-3.6.9/oficonv/libsrc/windows_mmap.h
+--- dcmtk-3.6.9.orig/oficonv/libsrc/windows_mmap.h	2025-01-16 18:04:46.137846297 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/windows_mmap.h	2025-01-17 09:21:53.565976060 +0100
+@@ -74,6 +74,12 @@
+ 
+ static void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
+ {
++  DWORD flProtect;
++  DWORD dwDesiredAccess;
++  HANDLE mmap_fd, h;
++  off_t end;
++  void *ret;
++
+     (void) start;
+ 
+ 	if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC))
+@@ -84,7 +90,6 @@
+ 	} else if (flags & MAP_ANON)
+ 		return MAP_FAILED;
+ 
+-	DWORD flProtect;
+     flProtect = 0;
+ 	if (prot & PROT_WRITE) {
+ 		if (prot & PROT_EXEC)
+@@ -99,8 +104,7 @@
+ 	} else
+ 		flProtect = PAGE_READONLY;
+ 
+-	off_t end = (off_t)(length + offset);
+-	HANDLE mmap_fd, h;
++	end = (off_t)(length + offset);
+ 	if (fd == -1)
+ 		mmap_fd = INVALID_HANDLE_VALUE;
+ 	else
+@@ -109,7 +113,6 @@
+ 	if (h == NULL)
+ 		return MAP_FAILED;
+ 
+-	DWORD dwDesiredAccess;
+ 	if (prot & PROT_WRITE)
+ 		dwDesiredAccess = FILE_MAP_WRITE;
+ 	else
+@@ -118,7 +121,7 @@
+ 		dwDesiredAccess |= FILE_MAP_EXECUTE;
+ 	if (flags & MAP_PRIVATE)
+ 		dwDesiredAccess |= FILE_MAP_COPY;
+-	void *ret = MapViewOfFile(h, dwDesiredAccess, MM_DWORD_HI(offset), MM_DWORD_LO(offset), length);
++	ret = MapViewOfFile(h, dwDesiredAccess, MM_DWORD_HI(offset), MM_DWORD_LO(offset), length);
+ 	if (ret == NULL) {
+ 		CloseHandle(h);
+ 		ret = MAP_FAILED;
+@@ -140,11 +143,13 @@
+ 
+ static void munmap(void *addr, size_t length)
+ {
++  mmap_cleanup_t **prevPtr;
++  mmap_cleanup_t *mc;
++
+     (void) length;
+ 	UnmapViewOfFile(addr);
+ 	// Look up through the tracking elements to close the handle
+-	mmap_cleanup_t **prevPtr = &mmap_cleanup;
+-	mmap_cleanup_t *mc;
++	prevPtr = &mmap_cleanup;
+ 	for (mc = *prevPtr; mc != NULL; prevPtr = &mc->next, mc = *prevPtr)
+ 	{
+ 		if (mc->addr == addr)
+diff -urEb dcmtk-3.6.9.orig/ofstd/include/dcmtk/ofstd/oftypes.h dcmtk-3.6.9/ofstd/include/dcmtk/ofstd/oftypes.h
+--- dcmtk-3.6.9.orig/ofstd/include/dcmtk/ofstd/oftypes.h	2025-01-16 18:04:46.148846202 +0100
++++ dcmtk-3.6.9/ofstd/include/dcmtk/ofstd/oftypes.h	2025-01-17 07:49:26.122182761 +0100
+@@ -79,10 +79,9 @@
+ 
+ #include <cstddef>
+ BEGIN_EXTERN_C
+-#ifdef HAVE_STDINT_H
++#if defined(HAVE_STDINT_H) || _MSC_VER >= 1600
+ #include <stdint.h>
+ #endif
+-#include <inttypes.h>
+ END_EXTERN_C
+ 
+ #include "dcmtk/ofstd/ofstream.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Resources/Patches/dcmtk-3.6.9.patch	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,179 @@
+diff -urEb dcmtk-3.6.9.orig/CMake/GenerateDCMTKConfigure.cmake dcmtk-3.6.9/CMake/GenerateDCMTKConfigure.cmake
+--- dcmtk-3.6.9.orig/CMake/GenerateDCMTKConfigure.cmake	2025-01-21 15:53:12.632715225 +0100
++++ dcmtk-3.6.9/CMake/GenerateDCMTKConfigure.cmake	2025-01-21 15:53:27.614627545 +0100
+@@ -227,12 +227,15 @@
+ 
+ # Check the sizes of various types
+ include (CheckTypeSize)
++if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
++  # This doesn't work for wasm, Orthanc defines the macros manually
+ CHECK_TYPE_SIZE("double" SIZEOF_DOUBLE)
+ CHECK_TYPE_SIZE("float" SIZEOF_FLOAT)
+ CHECK_TYPE_SIZE("int" SIZEOF_INT)
+ CHECK_TYPE_SIZE("long" SIZEOF_LONG)
+ CHECK_TYPE_SIZE("short" SIZEOF_SHORT)
+ CHECK_TYPE_SIZE("void*" SIZEOF_VOID_P)
++endif()
+ 
+ # Check for include files, libraries, and functions
+ include("${DCMTK_CMAKE_INCLUDE}CMake/dcmtkTryCompile.cmake")
+diff -urEb dcmtk-3.6.9.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h dcmtk-3.6.9/dcmdata/include/dcmtk/dcmdata/dcdict.h
+--- dcmtk-3.6.9.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h	2025-01-21 15:53:12.622715283 +0100
++++ dcmtk-3.6.9/dcmdata/include/dcmtk/dcmdata/dcdict.h	2025-01-21 15:53:27.614627545 +0100
+@@ -163,6 +163,12 @@
+     /// returns an iterator to the end of the repeating groups data dictionary
+     DcmDictEntryListIterator repeatingEnd() { return repDict.end(); }
+ 
++    // Function by the Orthanc project to load a dictionary from a
++    // memory buffer, which is necessary in sandboxed
++    // environments. This is an adapted version of
++    // DcmDataDictionary::loadDictionary().
++    OFBool loadFromMemory(const std::string& content, OFBool errorIfAbsent = OFTrue);
++    
+ private:
+ 
+     /** private undefined assignment operator
+diff -urEb dcmtk-3.6.9.orig/dcmdata/libsrc/dcdict.cc dcmtk-3.6.9/dcmdata/libsrc/dcdict.cc
+--- dcmtk-3.6.9.orig/dcmdata/libsrc/dcdict.cc	2025-01-21 15:53:12.625715265 +0100
++++ dcmtk-3.6.9/dcmdata/libsrc/dcdict.cc	2025-01-21 15:53:27.615627539 +0100
+@@ -904,3 +904,5 @@
+   wrlock().clear();
+   wrunlock();
+ }
++
++#include "dcdict_orthanc.cc"
+diff -urEb dcmtk-3.6.9.orig/dcmdata/libsrc/dcpxitem.cc dcmtk-3.6.9/dcmdata/libsrc/dcpxitem.cc
+--- dcmtk-3.6.9.orig/dcmdata/libsrc/dcpxitem.cc	2025-01-21 15:53:12.623715277 +0100
++++ dcmtk-3.6.9/dcmdata/libsrc/dcpxitem.cc	2025-01-21 15:53:27.615627539 +0100
+@@ -31,6 +31,8 @@
+ #include "dcmtk/dcmdata/dcostrma.h"    /* for class DcmOutputStream */
+ #include "dcmtk/dcmdata/dcwcache.h"    /* for class DcmWriteCache */
+ 
++#undef max
++#include "dcmtk/ofstd/oflimits.h"
+ 
+ // ********************************
+ 
+diff -urEb dcmtk-3.6.9.orig/dcmnet/libsrc/scu.cc dcmtk-3.6.9/dcmnet/libsrc/scu.cc
+--- dcmtk-3.6.9.orig/dcmnet/libsrc/scu.cc	2025-01-21 15:53:12.655715091 +0100
++++ dcmtk-3.6.9/dcmnet/libsrc/scu.cc	2025-01-21 15:53:27.616627533 +0100
+@@ -19,6 +19,11 @@
+  *
+  */
+ 
++#if defined(_WIN32)
++#  define __STDC_LIMIT_MACROS   // Get access to UINT16_MAX
++#  include <stdint.h>
++#endif
++
+ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
+ 
+ #include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */
+diff -urEb dcmtk-3.6.9.orig/oficonv/include/dcmtk/oficonv/iconv.h dcmtk-3.6.9/oficonv/include/dcmtk/oficonv/iconv.h
+--- dcmtk-3.6.9.orig/oficonv/include/dcmtk/oficonv/iconv.h	2025-01-21 15:53:12.637715196 +0100
++++ dcmtk-3.6.9/oficonv/include/dcmtk/oficonv/iconv.h	2025-01-21 15:53:27.617627527 +0100
+@@ -55,7 +55,12 @@
+ #endif
+ 
+ struct __tag_iconv_t;
++
++#if defined(__LSB_VERSION__)
++typedef void *iconv_t;
++#else
+ typedef struct __tag_iconv_t *iconv_t;
++#endif
+ 
+ #ifndef OFICONV_CITRUS_WC_T_DEFINED
+ #define OFICONV_CITRUS_WC_T_DEFINED
+diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_lock.h dcmtk-3.6.9/oficonv/libsrc/citrus_lock.h
+--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_lock.h	2025-01-21 15:53:12.646715143 +0100
++++ dcmtk-3.6.9/oficonv/libsrc/citrus_lock.h	2025-01-21 16:43:36.463693959 +0100
+@@ -31,7 +31,7 @@
+ 
+ #ifdef WITH_THREADS
+ 
+-#ifdef HAVE_WINDOWS_H
++#if defined(HAVE_WINDOWS_H) && !defined(HAVE_PTHREAD_H)  /* Favor pthread if available, for MinGW */
+ 
+ #include <windows.h>
+ #define WLOCK(lock)  AcquireSRWLockExclusive(lock);
+diff -urEb dcmtk-3.6.9.orig/oflog/include/dcmtk/oflog/thread/syncpub.h dcmtk-3.6.9/oflog/include/dcmtk/oflog/thread/syncpub.h
+--- dcmtk-3.6.9.orig/oflog/include/dcmtk/oflog/thread/syncpub.h	2025-01-21 15:53:12.605715381 +0100
++++ dcmtk-3.6.9/oflog/include/dcmtk/oflog/thread/syncpub.h	2025-01-21 15:53:27.617627527 +0100
+@@ -63,7 +63,7 @@
+ 
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ Mutex::Mutex (Mutex::Type t)
+-    : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::Mutex (t) + 0))
++    : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::Mutex (t)))
+ { }
+ 
+ 
+@@ -106,7 +106,7 @@
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ Semaphore::Semaphore (unsigned DCMTK_LOG4CPLUS_THREADED (max),
+     unsigned DCMTK_LOG4CPLUS_THREADED (initial))
+-    : sem (DCMTK_LOG4CPLUS_THREADED (new impl::Semaphore (max, initial) + 0))
++    : sem (DCMTK_LOG4CPLUS_THREADED (new impl::Semaphore (max, initial)))
+ { }
+ 
+ 
+@@ -190,7 +190,7 @@
+ 
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ ManualResetEvent::ManualResetEvent (bool DCMTK_LOG4CPLUS_THREADED (sig))
+-    : ev (DCMTK_LOG4CPLUS_THREADED (new impl::ManualResetEvent (sig) + 0))
++    : ev (DCMTK_LOG4CPLUS_THREADED (new impl::ManualResetEvent (sig)))
+ { }
+ 
+ 
+@@ -252,7 +252,7 @@
+ 
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ SharedMutex::SharedMutex ()
+-    : sm (DCMTK_LOG4CPLUS_THREADED (new impl::SharedMutex + 0))
++    : sm (DCMTK_LOG4CPLUS_THREADED (new impl::SharedMutex))
+ { }
+ 
+ 
+diff -urEb dcmtk-3.6.9.orig/oflog/libsrc/oflog.cc dcmtk-3.6.9/oflog/libsrc/oflog.cc
+--- dcmtk-3.6.9.orig/oflog/libsrc/oflog.cc	2025-01-21 15:53:12.606715376 +0100
++++ dcmtk-3.6.9/oflog/libsrc/oflog.cc	2025-01-21 15:53:27.617627527 +0100
+@@ -19,6 +19,11 @@
+  *
+  */
+ 
++
++#if defined(_WIN32)
++#  include <winsock2.h>
++#endif
++
+ #include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
+ #include "dcmtk/oflog/oflog.h"
+ 
+diff -urEb dcmtk-3.6.9.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.9/ofstd/include/dcmtk/ofstd/offile.h
+--- dcmtk-3.6.9.orig/ofstd/include/dcmtk/ofstd/offile.h	2025-01-21 15:53:12.651715114 +0100
++++ dcmtk-3.6.9/ofstd/include/dcmtk/ofstd/offile.h	2025-01-21 15:53:27.618627521 +0100
+@@ -569,7 +569,7 @@
+    */
+   void setlinebuf()
+   {
+-#if defined(_WIN32) || defined(__hpux)
++#if defined(_WIN32) || defined(__hpux) || defined(__LSB_VERSION__)
+     this->setvbuf(NULL, _IOLBF, 0);
+ #else
+     :: setlinebuf(file_);
+diff -urEb dcmtk-3.6.9.orig/ofstd/libsrc/ofstub.cc dcmtk-3.6.9/ofstd/libsrc/ofstub.cc
+--- dcmtk-3.6.9.orig/ofstd/libsrc/ofstub.cc	2025-01-21 15:53:12.652715108 +0100
++++ dcmtk-3.6.9/ofstd/libsrc/ofstub.cc	2025-01-21 15:53:27.618627521 +0100
+@@ -35,6 +35,10 @@
+ #include <windows.h>
+ #endif /* HAVE_WINDOWS_H */
+ 
++#if defined(__LSB_VERSION__)
++#include <errno.h>
++#endif
++
+ #define EXITCODE_CANNOT_DETERMINE_DIR        90
+ #define EXITCODE_EXEC_FAILED                 91
+ #define EXITCODE_ILLEGAL_PARAMS              92
--- a/OrthancFramework/Resources/ProtocolBuffers/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/ProtocolBuffers/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/ProtocolBuffers/ProtobufLibrary.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/ProtocolBuffers/ProtobufLibrary.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/RetrieveCACertificates.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/RetrieveCACertificates.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/Samples/MicroService/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/Samples/MicroService/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/Samples/MicroService/Sample.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/Samples/MicroService/Sample.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/ThirdParty/icu/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/ThirdParty/icu/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/ThirdParty/icu/Version.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/ThirdParty/icu/Version.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/Toolchains/CrossToolchain.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/Toolchains/CrossToolchain.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain32.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain32.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain64.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain64.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/Toolchains/MinGWToolchain.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/Toolchains/MinGWToolchain.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Resources/WindowsResources.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Resources/WindowsResources.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/SharedLibrary/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/SharedLibrary/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/SharedLibrary/DllMain.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/SharedLibrary/DllMain.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/SharedLibrary/OrthancFramework.h.in	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/SharedLibrary/OrthancFramework.h.in	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Cache/ICachePageProvider.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Cache/ICachePageProvider.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Cache/ICacheable.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Cache/ICacheable.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Cache/LeastRecentlyUsedIndex.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Cache/LeastRecentlyUsedIndex.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Cache/MemoryCache.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Cache/MemoryCache.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Cache/MemoryCache.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Cache/MemoryCache.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Cache/MemoryObjectCache.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Cache/MemoryObjectCache.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Cache/MemoryObjectCache.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Cache/MemoryObjectCache.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Cache/MemoryStringCache.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Cache/MemoryStringCache.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Cache/MemoryStringCache.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Cache/MemoryStringCache.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Cache/SharedArchive.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Cache/SharedArchive.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Cache/SharedArchive.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Cache/SharedArchive.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/ChunkedBuffer.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/ChunkedBuffer.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/ChunkedBuffer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/ChunkedBuffer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compatibility.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compatibility.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/DeflateBaseCompressor.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/DeflateBaseCompressor.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/DeflateBaseCompressor.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/DeflateBaseCompressor.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/GzipCompressor.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/GzipCompressor.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/GzipCompressor.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/GzipCompressor.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/HierarchicalZipWriter.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/HierarchicalZipWriter.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/HierarchicalZipWriter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/HierarchicalZipWriter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/IBufferCompressor.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/IBufferCompressor.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/IBufferCompressor.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/IBufferCompressor.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/ZipReader.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/ZipReader.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/ZipReader.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/ZipReader.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/ZipWriter.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/ZipWriter.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/ZipWriter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/ZipWriter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/ZlibCompressor.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/ZlibCompressor.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Compression/ZlibCompressor.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Compression/ZlibCompressor.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomArray.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomArray.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomArray.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomArray.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomElement.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomElement.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomElement.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomElement.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomInstanceHasher.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomInstanceHasher.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomInstanceHasher.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomInstanceHasher.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomMap.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomMap.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -800,6 +800,17 @@
     return false;
   }
 
+  bool DicomMap::HasMetaInformationTags(const std::set<DicomTag>& tags)
+  {
+    for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it)
+    {
+      if (it->GetGroup() == 0x0002)
+      {
+        return true;
+      }
+    }
+    return false;
+  }
 
   void DicomMap::GetMainDicomTags(std::set<DicomTag>& target,
                                   ResourceType level)
@@ -1322,7 +1333,22 @@
       return value->CopyToString(result, allowBinary);
     }
   }
-    
+
+  bool DicomMap::LookupStringValues(std::set<std::string>& results,
+                                    const DicomTag& tag,
+                                    bool allowBinary) const
+  {
+    std::string tmp;
+    if (LookupStringValue(tmp, tag, allowBinary))
+    {
+      Toolbox::SplitString(results, tmp, '\\');
+      return true;
+    }
+
+    return false;
+  }
+
+
   bool DicomMap::ParseInteger32(int32_t& result,
                                 const DicomTag& tag) const
   {
--- a/OrthancFramework/Sources/DicomFormat/DicomMap.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomMap.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -146,6 +146,8 @@
 
     static bool HasComputedTags(const std::set<DicomTag>& tags);
 
+    static bool HasMetaInformationTags(const std::set<DicomTag>& tags);
+
     static void GetMainDicomTags(std::set<DicomTag>& target,
                                  ResourceType level);
 
@@ -179,7 +181,11 @@
     bool LookupStringValue(std::string& result,
                            const DicomTag& tag,
                            bool allowBinary) const;
-    
+
+    bool LookupStringValues(std::set<std::string>& results,
+                           const DicomTag& tag,
+                           bool allowBinary) const;
+
     bool ParseInteger32(int32_t& result,
                         const DicomTag& tag) const;
 
--- a/OrthancFramework/Sources/DicomFormat/DicomPath.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomPath.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomPath.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomPath.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -247,6 +247,10 @@
           
         pos += length + 12;
       }
+      else
+      {
+        throw OrthancException(ErrorCode_BadFileFormat, "Invalid DICOM File: Unable to parse Meta Header");
+      }
     }
 
     if (pos != block.size())
--- a/OrthancFramework/Sources/DicomFormat/DicomStreamReader.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomStreamReader.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomTag.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomTag.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomTag.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomTag.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomValue.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomValue.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/DicomValue.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomValue.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/StreamBlockReader.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/StreamBlockReader.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomFormat/StreamBlockReader.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomFormat/StreamBlockReader.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -173,7 +173,6 @@
 
   DicomAssociation::DicomAssociation()
   {
-    role_ = DicomAssociationRole_Default;
     isOpen_ = false;
     net_ = NULL; 
     params_ = NULL;
@@ -198,16 +197,6 @@
   }
 
 
-  void DicomAssociation::SetRole(DicomAssociationRole role)
-  {
-    if (role_ != role)
-    {
-      Close();
-      role_ = role;
-    }
-  }
-
-  
   void DicomAssociation::ClearPresentationContexts()
   {
     Close();
@@ -215,7 +204,26 @@
     proposed_.reserve(MAX_PROPOSED_PRESENTATIONS);
   }
 
-  
+
+  static T_ASC_SC_ROLE GetDcmtkRole(DicomAssociationRole role)
+  {
+    switch (role)
+    {
+      case DicomAssociationRole_Default:
+        return ASC_SC_ROLE_DEFAULT;
+
+      case DicomAssociationRole_Scu:
+        return ASC_SC_ROLE_SCU;
+
+      case DicomAssociationRole_Scp:
+        return ASC_SC_ROLE_SCP;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
   void DicomAssociation::Open(const DicomAssociationParameters& parameters)
   {
     if (isOpen_)
@@ -240,24 +248,6 @@
       dcmConnectionTimeout.set(acseTimeout);
     }
       
-    T_ASC_SC_ROLE dcmtkRole;
-    switch (role_)
-    {
-      case DicomAssociationRole_Default:
-        dcmtkRole = ASC_SC_ROLE_DEFAULT;
-        break;
-
-      case DicomAssociationRole_Scu:
-        dcmtkRole = ASC_SC_ROLE_SCU;
-        break;
-
-      case DicomAssociationRole_Scp:
-        dcmtkRole = ASC_SC_ROLE_SCP;
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
 
     assert(net_ == NULL &&
            params_ == NULL &&
@@ -291,7 +281,12 @@
                                   "no timeout") << ")";
 
     CheckConnecting(parameters, ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ acseTimeout, &net_));
+#if DCMTK_VERSION_NUMBER >= 368
+    CheckConnecting(parameters, ASC_createAssociationParameters(&params_, parameters.GetMaximumPduLength(), acseTimeout));
+#else
+    // from 3.6.8, this version is obsolete
     CheckConnecting(parameters, ASC_createAssociationParameters(&params_, parameters.GetMaximumPduLength()));
+#endif
 
 #if ORTHANC_ENABLE_SSL == 1
     if (parameters.GetRemoteModality().IsDicomTlsEnabled())
@@ -351,12 +346,12 @@
       assert(presentationContextId <= 255);
       const char* abstractSyntax = proposed_[i].abstractSyntax_.c_str();
 
-      const std::set<DicomTransferSyntax>& source = proposed_[i].transferSyntaxes_;
+      const std::list<DicomTransferSyntax>& source = proposed_[i].transferSyntaxes_;
           
       std::vector<const char*> transferSyntaxes;
       transferSyntaxes.reserve(source.size());
           
-      for (std::set<DicomTransferSyntax>::const_iterator
+      for (std::list<DicomTransferSyntax>::const_iterator
              it = source.begin(); it != source.end(); ++it)
       {
         transferSyntaxes.push_back(GetTransferSyntaxUid(*it));
@@ -365,7 +360,7 @@
       assert(!transferSyntaxes.empty());
       CheckConnecting(parameters, ASC_addPresentationContext(
                         params_, presentationContextId, abstractSyntax,
-                        &transferSyntaxes[0], transferSyntaxes.size(), dcmtkRole));
+                        &transferSyntaxes[0], transferSyntaxes.size(), GetDcmtkRole(proposed_[i].role_)));
 
       presentationContextId += 2;
     }
@@ -456,36 +451,56 @@
     }
   }
 
+  void DicomAssociation::ProposeGenericPresentationContext(const std::string& abstractSyntax,
+                                                           DicomAssociationRole role)
+  {
+    std::list<DicomTransferSyntax> ts;
+    ts.push_back(DicomTransferSyntax_LittleEndianExplicit); // the most standard one first !
+    ts.push_back(DicomTransferSyntax_LittleEndianImplicit);
+    ts.push_back(DicomTransferSyntax_BigEndianExplicit);  // Retired but was historicaly proposed by Orthanc
+    ProposePresentationContext(abstractSyntax, ts, role);
+  }
     
   void DicomAssociation::ProposeGenericPresentationContext(const std::string& abstractSyntax)
   {
-    std::set<DicomTransferSyntax> ts;
-    ts.insert(DicomTransferSyntax_LittleEndianImplicit);
-    ts.insert(DicomTransferSyntax_LittleEndianExplicit);
-    ts.insert(DicomTransferSyntax_BigEndianExplicit);  // Retired
-    ProposePresentationContext(abstractSyntax, ts);
+    ProposeGenericPresentationContext(abstractSyntax, DicomAssociationRole_Default);
   }
 
     
   void DicomAssociation::ProposePresentationContext(const std::string& abstractSyntax,
                                                     DicomTransferSyntax transferSyntax)
   {
-    std::set<DicomTransferSyntax> ts;
-    ts.insert(transferSyntax);
-    ProposePresentationContext(abstractSyntax, ts);
+    ProposePresentationContext(abstractSyntax, transferSyntax, DicomAssociationRole_Default);
   }
 
-    
+
+  void DicomAssociation::ProposePresentationContext(const std::string& abstractSyntax,
+                                                    DicomTransferSyntax transferSyntax,
+                                                    DicomAssociationRole role)
+  {
+    std::list<DicomTransferSyntax> ts;
+    ts.push_back(transferSyntax);
+    ProposePresentationContext(abstractSyntax, ts, role);
+  }
+
   size_t DicomAssociation::GetRemainingPropositions() const
   {
     assert(proposed_.size() <= MAX_PROPOSED_PRESENTATIONS);
     return MAX_PROPOSED_PRESENTATIONS - proposed_.size();
   }
     
+  void DicomAssociation::ProposePresentationContext(
+    const std::string& abstractSyntax,
+    const std::list<DicomTransferSyntax>& transferSyntaxes)
+  {
+    ProposePresentationContext(abstractSyntax, transferSyntaxes, DicomAssociationRole_Default);
+  }
+
 
   void DicomAssociation::ProposePresentationContext(
     const std::string& abstractSyntax,
-    const std::set<DicomTransferSyntax>& transferSyntaxes)
+    const std::list<DicomTransferSyntax>& transferSyntaxes,
+    DicomAssociationRole role)
   {
     if (transferSyntaxes.empty())
     {
@@ -507,6 +522,7 @@
     ProposedPresentationContext context;
     context.abstractSyntax_ = abstractSyntax;
     context.transferSyntaxes_ = transferSyntaxes;
+    context.role_ = role;
 
     proposed_.push_back(context);
   }
@@ -526,6 +542,35 @@
     }
   }
 
+  bool DicomAssociation::GetAssociationParameters(std::string& remoteAet,
+                                                  std::string& remoteIp,
+                                                  std::string& calledAet) const
+  {
+    T_ASC_Association& dcmtkAssoc = GetDcmtkAssociation();
+
+    DIC_AE remoteAet_C;
+    DIC_AE calledAet_C;
+    DIC_AE remoteIp_C;
+    DIC_AE calledIP_C;
+
+    if (
+#if DCMTK_VERSION_NUMBER >= 364
+      ASC_getAPTitles(dcmtkAssoc.params, remoteAet_C, sizeof(remoteAet_C), calledAet_C, sizeof(calledAet_C), NULL, 0).good() &&
+      ASC_getPresentationAddresses(dcmtkAssoc.params, remoteIp_C, sizeof(remoteIp_C), calledIP_C, sizeof(calledIP_C)).good()
+#else
+      ASC_getAPTitles(dcmtkAssoc.params, remoteAet_C, calledAet_C, NULL).good() &&
+      ASC_getPresentationAddresses(dcmtkAssoc.params, remoteIp_C, calledIP_C).good()
+#endif
+      )
+    {
+      remoteIp = std::string(/*OFSTRING_GUARD*/(remoteIp_C));
+      remoteAet = std::string(/*OFSTRING_GUARD*/(remoteAet_C));
+      calledAet = (/*OFSTRING_GUARD*/(calledAet_C));
+      return true;
+    }
+
+    return false;
+  }
     
   T_ASC_Network& DicomAssociation::GetDcmtkNetwork() const
   {
@@ -649,13 +694,12 @@
     DicomAssociation association;
 
     {
-      std::set<DicomTransferSyntax> transferSyntaxes;
-      transferSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
-      transferSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
+      std::list<DicomTransferSyntax> transferSyntaxes;
+      transferSyntaxes.push_back(DicomTransferSyntax_LittleEndianExplicit);
+      transferSyntaxes.push_back(DicomTransferSyntax_LittleEndianImplicit);
 
-      association.SetRole(DicomAssociationRole_Scp);
       association.ProposePresentationContext(UID_StorageCommitmentPushModelSOPClass,
-                                             transferSyntaxes);
+                                             transferSyntaxes, DicomAssociationRole_Scp);
     }
       
     association.Open(parameters);
@@ -828,13 +872,13 @@
     DicomAssociation association;
 
     {
-      std::set<DicomTransferSyntax> transferSyntaxes;
-      transferSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
-      transferSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
+      std::list<DicomTransferSyntax> transferSyntaxes;
+      transferSyntaxes.push_back(DicomTransferSyntax_LittleEndianExplicit);
+      transferSyntaxes.push_back(DicomTransferSyntax_LittleEndianImplicit);
       
-      association.SetRole(DicomAssociationRole_Default);
+      // association.SetRole(DicomAssociationRole_Default);
       association.ProposePresentationContext(UID_StorageCommitmentPushModelSOPClass,
-                                             transferSyntaxes);
+                                             transferSyntaxes, DicomAssociationRole_Default);
     }
       
     association.Open(parameters);
--- a/OrthancFramework/Sources/DicomNetworking/DicomAssociation.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociation.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -44,6 +44,7 @@
 #include <stdint.h>   // For uint8_t
 #include <boost/noncopyable.hpp>
 #include <set>
+#include <list>
 
 namespace Orthanc
 {
@@ -58,13 +59,13 @@
     struct ProposedPresentationContext
     {
       std::string                    abstractSyntax_;
-      std::set<DicomTransferSyntax>  transferSyntaxes_;
+      std::list<DicomTransferSyntax> transferSyntaxes_;
+      DicomAssociationRole           role_;
     };
 
     typedef std::map<std::string, std::map<DicomTransferSyntax, uint8_t> >
     AcceptedPresentationContexts;
 
-    DicomAssociationRole                      role_;
     bool                                      isOpen_;
     std::vector<ProposedPresentationContext>  proposed_;
     AcceptedPresentationContexts              accepted_;
@@ -95,8 +96,6 @@
       return isOpen_;
     }
 
-    void SetRole(DicomAssociationRole role);
-
     void ClearPresentationContexts();
 
     void Open(const DicomAssociationParameters& parameters);
@@ -109,6 +108,13 @@
 
     void ProposeGenericPresentationContext(const std::string& abstractSyntax);
 
+    void ProposeGenericPresentationContext(const std::string& abstractSyntax,
+                                           DicomAssociationRole role);
+
+    void ProposePresentationContext(const std::string& abstractSyntax,
+                                    DicomTransferSyntax transferSyntax,
+                                    DicomAssociationRole role);
+
     void ProposePresentationContext(const std::string& abstractSyntax,
                                     DicomTransferSyntax transferSyntax);
 
@@ -116,12 +122,21 @@
 
     void ProposePresentationContext(
       const std::string& abstractSyntax,
-      const std::set<DicomTransferSyntax>& transferSyntaxes);
-    
+      const std::list<DicomTransferSyntax>& transferSyntaxes);
+
+    void ProposePresentationContext(
+      const std::string& abstractSyntax,
+      const std::list<DicomTransferSyntax>& transferSyntaxes,
+      DicomAssociationRole role);
+
     T_ASC_Association& GetDcmtkAssociation() const;
 
     T_ASC_Network& GetDcmtkNetwork() const;
 
+    bool GetAssociationParameters(std::string& remoteAet,
+                                  std::string& remoteIp,
+                                  std::string& calledAet) const;
+
     static void CheckCondition(const OFCondition& cond,
                                const DicomAssociationParameters& parameters,
                                const std::string& command);
--- a/OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -234,15 +234,58 @@
 
 
 
-  void DicomControlUserConnection::SetupPresentationContexts()
+  void DicomControlUserConnection::SetupPresentationContexts(
+    ScuOperationFlags scuOperation,
+    const std::set<std::string>& acceptedStorageSopClasses,
+    const std::list<DicomTransferSyntax>& proposedStorageTransferSyntaxes)
   {
     assert(association_.get() != NULL);
-    association_->ProposeGenericPresentationContext(UID_VerificationSOPClass);
-    association_->ProposeGenericPresentationContext(UID_FINDPatientRootQueryRetrieveInformationModel);
-    association_->ProposeGenericPresentationContext(UID_MOVEPatientRootQueryRetrieveInformationModel);
-    association_->ProposeGenericPresentationContext(UID_FINDStudyRootQueryRetrieveInformationModel);
-    association_->ProposeGenericPresentationContext(UID_MOVEStudyRootQueryRetrieveInformationModel);
-    association_->ProposeGenericPresentationContext(UID_FINDModalityWorklistInformationModel);
+
+    if ((scuOperation & ScuOperationFlags_Echo) != 0)
+    {
+      association_->ProposeGenericPresentationContext(UID_VerificationSOPClass);
+    }
+
+    if ((scuOperation & ScuOperationFlags_FindPatient) != 0)
+    {
+      association_->ProposeGenericPresentationContext(UID_FINDPatientRootQueryRetrieveInformationModel);
+    }
+
+    if ((scuOperation & ScuOperationFlags_FindStudy) != 0)
+    {
+      association_->ProposeGenericPresentationContext(UID_FINDStudyRootQueryRetrieveInformationModel);
+    }
+
+    if ((scuOperation & ScuOperationFlags_FindWorklist) != 0)
+    {
+      association_->ProposeGenericPresentationContext(UID_FINDModalityWorklistInformationModel);
+    }
+
+    if ((scuOperation & ScuOperationFlags_MovePatient) != 0)
+    {
+      association_->ProposeGenericPresentationContext(UID_MOVEPatientRootQueryRetrieveInformationModel);
+    }
+
+    if ((scuOperation & ScuOperationFlags_MoveStudy) != 0)
+    {
+      association_->ProposeGenericPresentationContext(UID_MOVEStudyRootQueryRetrieveInformationModel);
+    }
+
+    if ((scuOperation & ScuOperationFlags_Get) != 0)
+    {
+      association_->ProposeGenericPresentationContext(UID_GETStudyRootQueryRetrieveInformationModel);
+      association_->ProposeGenericPresentationContext(UID_GETPatientRootQueryRetrieveInformationModel);
+
+      if (acceptedStorageSopClasses.size() == 0)
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls); // the acceptedStorageSopClassUids should always be defined for a C-Get
+      }
+
+      for (std::set<std::string>::const_iterator it = acceptedStorageSopClasses.begin(); it != acceptedStorageSopClasses.end(); ++it)
+      {
+        association_->ProposePresentationContext(*it, proposedStorageTransferSyntaxes, DicomAssociationRole_Scp);
+      }
+    }
   }
     
 
@@ -350,6 +393,21 @@
     }
   }
 
+  void MoveProgressCallback(void *callbackData,
+                            T_DIMSE_C_MoveRQ *request,
+                            int responseCount, 
+                            T_DIMSE_C_MoveRSP *response)
+  {
+    DicomControlUserConnection::IProgressListener* listener = reinterpret_cast<DicomControlUserConnection::IProgressListener*>(callbackData);
+    if (listener)
+    {
+      listener->OnProgressUpdated(response->NumberOfRemainingSubOperations,
+                                  response->NumberOfCompletedSubOperations,
+                                  response->NumberOfFailedSubOperations,
+                                  response->NumberOfWarningSubOperations);
+    }
+  }
+
     
   void DicomControlUserConnection::MoveInternal(const std::string& targetAet,
                                                 ResourceType level,
@@ -391,7 +449,8 @@
     DcmDataset* statusDetail = NULL;
     DcmDataset* responseIdentifiers = NULL;
     OFCondition cond = DIMSE_moveUser(
-      &association_->GetDcmtkAssociation(), presID, &request, dataset, /*moveCallback*/ NULL, NULL,
+      &association_->GetDcmtkAssociation(), presID, &request, dataset, 
+      (progressListener_ != NULL ? MoveProgressCallback : NULL), progressListener_,
       /*opt_blockMode*/ (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
       /*opt_dimse_timeout*/ parameters_.GetTimeout(),
       &association_->GetDcmtkNetwork(), /*subOpCallback*/ NULL, NULL,
@@ -413,6 +472,14 @@
       OFString str;
       CLOG(TRACE, DICOM) << "Received Final Move Response:" << std::endl
                          << DIMSE_dumpMessage(str, response, DIMSE_INCOMING);
+
+      if (progressListener_ != NULL)
+      {
+        progressListener_->OnProgressUpdated(response.NumberOfRemainingSubOperations,
+                                             response.NumberOfCompletedSubOperations,
+                                             response.NumberOfFailedSubOperations,
+                                             response.NumberOfWarningSubOperations);
+      }
     }
     
     /**
@@ -445,11 +512,240 @@
   }
     
 
-  DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params) :
+  void DicomControlUserConnection::Get(const DicomMap& findResult,
+                                       CGetInstanceReceivedCallback instanceReceivedCallback,
+                                       void* callbackContext)
+  {
+    assert(association_.get() != NULL);
+    association_->Open(parameters_);
+
+    std::unique_ptr<ParsedDicomFile> query(
+      ConvertQueryFields(findResult, parameters_.GetRemoteModality().GetManufacturer()));
+    DcmDataset* queryDataset = query->GetDcmtkObject().getDataset();
+
+    std::string remoteAet;
+    std::string remoteIp;
+    std::string calledAet;
+
+    association_->GetAssociationParameters(remoteAet, remoteIp, calledAet);
+
+    const char* sopClass = NULL;
+    const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent();
+    ResourceType level = StringToResourceType(tmp.c_str());
+    switch (level)
+    {
+      case ResourceType_Patient:
+        sopClass = UID_GETPatientRootQueryRetrieveInformationModel;
+        break;
+      case ResourceType_Study:
+      case ResourceType_Series:
+      case ResourceType_Instance:
+        sopClass = UID_GETStudyRootQueryRetrieveInformationModel;
+        break;
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Figure out which of the accepted presentation contexts should be used
+    int cgetPresID = ASC_findAcceptedPresentationContextID(&association_->GetDcmtkAssociation(), sopClass);
+    if (cgetPresID == 0)
+    {
+      throw OrthancException(ErrorCode_DicomGetUnavailable,
+                             "Remote AET is " + parameters_.GetRemoteModality().GetApplicationEntityTitle());
+    }
+
+    T_DIMSE_Message msgGetRequest;
+    memset((char*)&msgGetRequest, 0, sizeof(msgGetRequest));
+    msgGetRequest.CommandField = DIMSE_C_GET_RQ;
+
+    T_DIMSE_C_GetRQ* request = &(msgGetRequest.msg.CGetRQ);
+    request->MessageID = association_->GetDcmtkAssociation().nextMsgID++;
+    strncpy(request->AffectedSOPClassUID, sopClass, DIC_UI_LEN);
+    request->Priority = DIMSE_PRIORITY_MEDIUM;
+    request->DataSetType = DIMSE_DATASET_PRESENT;
+
+    {
+      OFString str;
+      CLOG(TRACE, DICOM) << "Sending Get Request:" << std::endl
+                         << DIMSE_dumpMessage(str, *request, DIMSE_OUTGOING, NULL, cgetPresID);
+    }
+    
+    OFCondition cond = DIMSE_sendMessageUsingMemoryData(
+          &(association_->GetDcmtkAssociation()), cgetPresID, &msgGetRequest, NULL /* statusDetail */, queryDataset,
+          NULL, NULL, NULL /* commandSet */);
+      
+    if (cond.bad())
+    {
+        OFString tempStr;
+        CLOG(TRACE, DICOM) << "Failed sending C-GET request: " << DimseCondition::dump(tempStr, cond);
+        // return cond;
+    }
+
+    // equivalent to handleCGETSession in DCMTK
+    bool continueSession = true;
+
+    // As long we want to continue (usually, as long as we receive more objects,
+    // i.e. the final C-GET response has not arrived yet)
+    while (continueSession)
+    {
+        T_DIMSE_Message rsp;
+        // Make sure everything is zeroed (especially options)
+        memset((char*)&rsp, 0, sizeof(rsp));
+
+        // DcmDataset* statusDetail = NULL;
+        T_ASC_PresentationContextID cmdPresId = 0;
+
+        OFCondition result = DIMSE_receiveCommand(&(association_->GetDcmtkAssociation()),
+                                                  (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
+                                                  parameters_.GetTimeout(),
+                                                  &cmdPresId,
+                                                  &rsp,
+                                                  NULL /* statusDetail */,
+                                                  NULL /* not interested in the command set */);
+
+        if (result.bad())
+        {
+          OFString tempStr;
+          CLOG(TRACE, DICOM) << "Failed receiving DIMSE command: " << DimseCondition::dump(tempStr, result);
+          // delete statusDetail;
+          break;  // TODO: return value
+        }
+        // Handle C-GET Response
+        if (rsp.CommandField == DIMSE_C_GET_RSP)
+        {
+          {
+            OFString tempStr;
+            CLOG(TRACE, DICOM) << "Received C-GET Response: " << std::endl
+              << DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, cmdPresId);
+          }
+
+          if (progressListener_ != NULL)
+          {
+            progressListener_->OnProgressUpdated(rsp.msg.CGetRSP.NumberOfRemainingSubOperations,
+                                                  rsp.msg.CGetRSP.NumberOfCompletedSubOperations,
+                                                  rsp.msg.CGetRSP.NumberOfFailedSubOperations,
+                                                  rsp.msg.CGetRSP.NumberOfWarningSubOperations);
+          }
+
+          if (rsp.msg.CGetRSP.DimseStatus == 0x0000)  // final success message
+          {
+            continueSession = false;
+          }
+        }
+        // Handle C-STORE Request
+        else if (rsp.CommandField == DIMSE_C_STORE_RQ)
+        {
+          {
+            OFString tempStr;
+            CLOG(TRACE, DICOM) << "Received C-STORE Request: " << std::endl
+              << DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, cmdPresId);
+          }
+
+          T_DIMSE_C_StoreRQ* storeRequest = &(rsp.msg.CStoreRQ);
+
+          // Check if dataset is announced correctly
+          if (rsp.msg.CStoreRQ.DataSetType == DIMSE_DATASET_NULL)
+          {
+            CLOG(WARNING, DICOM) << "C-GET SCU handler: Incoming C-STORE with no dataset";
+          }
+
+          Uint16 desiredCStoreReturnStatus = 0;
+          DcmDataset* dataObject = NULL;
+
+          // Receive dataset
+          result = DIMSE_receiveDataSetInMemory(&(association_->GetDcmtkAssociation()),
+                                                  (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
+                                                  parameters_.GetTimeout(),
+                                                  &cmdPresId,
+                                                  &dataObject,
+                                                  NULL, NULL);
+
+          if (result.bad())
+          {
+            LOG(WARNING) << "C-GET SCU handler: Failed to receive dataset: " << result.text();
+            desiredCStoreReturnStatus = STATUS_STORE_Error_CannotUnderstand;
+          }
+          else
+          {
+            // callback the OrthancServer with the received data
+            if (instanceReceivedCallback != NULL)
+            {
+              desiredCStoreReturnStatus = instanceReceivedCallback(callbackContext, *dataObject, remoteAet, remoteIp, calledAet);
+            }
+
+            // send the Store response
+            T_DIMSE_Message storeResponse;
+            memset((char*)&storeResponse, 0, sizeof(storeResponse));
+            storeResponse.CommandField         = DIMSE_C_STORE_RSP;
+
+            T_DIMSE_C_StoreRSP& storeRsp       = storeResponse.msg.CStoreRSP;
+            storeRsp.MessageIDBeingRespondedTo = storeRequest->MessageID;
+            storeRsp.DimseStatus               = desiredCStoreReturnStatus;
+            storeRsp.DataSetType               = DIMSE_DATASET_NULL;
+
+            OFStandard::strlcpy(
+                storeRsp.AffectedSOPClassUID, storeRequest->AffectedSOPClassUID, sizeof(storeRsp.AffectedSOPClassUID));
+            OFStandard::strlcpy(
+                storeRsp.AffectedSOPInstanceUID, storeRequest->AffectedSOPInstanceUID, sizeof(storeRsp.AffectedSOPInstanceUID));
+            storeRsp.opts = O_STORE_AFFECTEDSOPCLASSUID | O_STORE_AFFECTEDSOPINSTANCEUID;
+
+            result = DIMSE_sendMessageUsingMemoryData(&(association_->GetDcmtkAssociation()), 
+                                                      cmdPresId, 
+                                                      &storeResponse, NULL /* statusDetail */, NULL /* dataObject */,
+                                                      NULL, NULL, NULL /* commandSet */);
+            if (result.bad())
+            {
+              continueSession = false;
+            }
+            else
+            {
+              OFString tempStr;
+              CLOG(TRACE, DICOM) << "Sent C-STORE Response: " << std::endl
+                << DIMSE_dumpMessage(tempStr, storeResponse, DIMSE_OUTGOING, NULL, cmdPresId);
+            }
+          }
+        }
+        // Handle other DIMSE command (error since other command than GET/STORE not expected)
+        else
+        {
+          CLOG(WARNING, DICOM) << "Expected C-GET response or C-STORE request but received DIMSE command 0x"
+                               << std::hex << std::setfill('0') << std::setw(4)
+                               << static_cast<unsigned int>(rsp.CommandField);
+          
+          result          = DIMSE_BADCOMMANDTYPE;
+          continueSession = false;
+        }
+
+        // delete statusDetail; // should be NULL if not existing or added to response list
+        // statusDetail = NULL;
+    }
+    /* All responses received or break signal occurred */
+
+    // return result;
+}
+
+
+  DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params, ScuOperationFlags scuOperation) :
     parameters_(params),
-    association_(new DicomAssociation)
+    association_(new DicomAssociation),
+    progressListener_(NULL)
   {
-    SetupPresentationContexts();
+    assert((scuOperation & ScuOperationFlags_Get) == 0);  // you must provide acceptedStorageSopClassUids for Get SCU
+    std::set<std::string> emptyStorageSopClasses;
+    std::list<DicomTransferSyntax> emptyStorageTransferSyntaxes;
+
+    SetupPresentationContexts(scuOperation, emptyStorageSopClasses, emptyStorageTransferSyntaxes);
+  }
+    
+  DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params, 
+                                                         ScuOperationFlags scuOperation,
+                                                         const std::set<std::string>& acceptedStorageSopClasses,
+                                                         const std::list<DicomTransferSyntax>& proposedStorageTransferSyntaxes) :
+    parameters_(params),
+    association_(new DicomAssociation),
+    progressListener_(NULL)
+  {
+    SetupPresentationContexts(scuOperation, acceptedStorageSopClasses, proposedStorageTransferSyntaxes);
   }
     
 
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -32,18 +32,55 @@
 #include "DicomFindAnswers.h"
 
 #include <boost/noncopyable.hpp>
+#include <list>
 
 namespace Orthanc
 {
   class DicomAssociation;  // Forward declaration for PImpl design pattern
   
+  typedef uint16_t (*CGetInstanceReceivedCallback) (void *callbackContext,
+                                                    DcmDataset& dataset,
+                                                    const std::string& remoteAet,
+                                                    const std::string& remoteIp,
+                                                    const std::string& calledAet);
+
+
+  enum ScuOperationFlags
+  {
+    ScuOperationFlags_Echo = 1 << 0,
+    ScuOperationFlags_FindPatient = 1 << 1,
+    ScuOperationFlags_FindStudy = 1 << 2,
+    ScuOperationFlags_FindWorklist = 1 << 3,
+    ScuOperationFlags_MoveStudy = 1 << 4,
+    ScuOperationFlags_MovePatient = 1 << 5,
+    // C-Store is not using DicomControlUserConnection but DicomStoreUserConnection
+    ScuOperationFlags_Get = 1 << 6,
+
+    ScuOperationFlags_Find = ScuOperationFlags_FindPatient | ScuOperationFlags_FindStudy | ScuOperationFlags_FindWorklist,
+    ScuOperationFlags_Move = ScuOperationFlags_MoveStudy | ScuOperationFlags_MovePatient,
+    ScuOperationFlags_All = ScuOperationFlags_Echo | ScuOperationFlags_Find | ScuOperationFlags_Move | ScuOperationFlags_Get
+  };
+
   class DicomControlUserConnection : public boost::noncopyable
   {
+  public:
+    class IProgressListener
+    {
+    public:
+      virtual void OnProgressUpdated(uint16_t nbRemainingSubOperations,
+                                     uint16_t nbCompletedSubOperations,
+                                     uint16_t nbFailedSubOperations,
+                                     uint16_t nbWarningSubOperations) = 0;
+    };
+
   private:
     DicomAssociationParameters           parameters_;
     boost::shared_ptr<DicomAssociation>  association_;
+    IProgressListener*                   progressListener_;
 
-    void SetupPresentationContexts();
+    void SetupPresentationContexts(ScuOperationFlags scuOperation,
+                                   const std::set<std::string>& acceptedStorageSopClasses,
+                                   const std::list<DicomTransferSyntax>& proposedStorageTransferSyntaxes);
 
     void FindInternal(DicomFindAnswers& answers,
                       DcmDataset* dataset,
@@ -56,8 +93,14 @@
                       const DicomMap& fields);
     
   public:
-    explicit DicomControlUserConnection(const DicomAssociationParameters& params);
-    
+    explicit DicomControlUserConnection(const DicomAssociationParameters& params, ScuOperationFlags scuOperation);
+
+    // specific constructor for CGet SCU
+    explicit DicomControlUserConnection(const DicomAssociationParameters& params, 
+                                        ScuOperationFlags scuOperation,
+                                        const std::set<std::string>& acceptedStorageSopClasses,
+                                        const std::list<DicomTransferSyntax>& proposedStorageTransferSyntaxes);
+
     const DicomAssociationParameters& GetParameters() const
     {
       return parameters_;
@@ -67,11 +110,20 @@
 
     bool Echo();
 
+    void SetProgressListener(IProgressListener* progressListener)
+    {
+      progressListener_ = progressListener;
+    }
+
     void Find(DicomFindAnswers& result,
               ResourceType level,
               const DicomMap& originalFields,
               bool normalize);
 
+    void Get(const DicomMap& getQuery,
+             CGetInstanceReceivedCallback instanceReceivedCallback,
+             void* callbackContext);
+
     void Move(const std::string& targetAet,
               ResourceType level,
               const DicomMap& findResult);
--- a/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -202,7 +202,7 @@
                                 DicomToJsonFormat format) const
   {
     const ParsedDicomFile& answer = GetAnswer(index);
-    answer.DatasetToJson(target, format, DicomToJsonFlags_None, 0);
+    answer.DatasetToJson(target, format, DicomToJsonFlags_IncludePrivateTags, 0);
   }
 
 
--- a/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/DicomServer.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomServer.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/DicomServer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomServer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -57,7 +57,7 @@
                                                      bool hasPreferred,
                                                      DicomTransferSyntax preferred)
   {
-    typedef std::list< std::set<DicomTransferSyntax> >  GroupsOfSyntaxes;
+    typedef std::list< std::list<DicomTransferSyntax> >  GroupsOfSyntaxes;
 
     GroupsOfSyntaxes  groups;
 
@@ -65,8 +65,8 @@
     for (std::set<DicomTransferSyntax>::const_iterator
            it = sourceSyntaxes.begin(); it != sourceSyntaxes.end(); ++it)
     {
-      std::set<DicomTransferSyntax> group;
-      group.insert(*it);
+      std::list<DicomTransferSyntax> group;
+      group.push_back(*it);
       groups.push_back(group);
     }
 
@@ -74,8 +74,8 @@
     if (hasPreferred &&
         sourceSyntaxes.find(preferred) == sourceSyntaxes.end())
     {
-      std::set<DicomTransferSyntax> group;
-      group.insert(preferred);
+      std::list<DicomTransferSyntax> group;
+      group.push_back(preferred);
       groups.push_back(group);
     }
 
@@ -89,7 +89,7 @@
         DicomTransferSyntax_BigEndianExplicit
       };
 
-      std::set<DicomTransferSyntax> group;
+      std::list<DicomTransferSyntax> group;
 
       for (size_t i = 0; i < N; i++)
       {
@@ -97,7 +97,7 @@
         if (sourceSyntaxes.find(syntax) == sourceSyntaxes.end() &&
             (!hasPreferred || preferred != syntax))
         {
-          group.insert(syntax);
+          group.push_back(syntax);
         }
       }
 
@@ -651,7 +651,7 @@
           s += " " + std::string(GetTransferSyntaxUid(*it));
         }
         
-        throw OrthancException(ErrorCode_NotImplemented, "Cannot transcode instance of SOPClassUID " + 
+        throw OrthancException(ErrorCode_InternalError, "Cannot transcode instance of SOPClassUID " + 
                                sopClassUid + " from " +
                                std::string(GetTransferSyntaxUid(sourceSyntax)) +
                                " to one of [" + s + " ]");
--- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IApplicationEntityFilter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IApplicationEntityFilter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -28,6 +28,7 @@
 
 #include <boost/noncopyable.hpp>
 #include <string>
+#include <list>
 
 namespace Orthanc
 {
@@ -47,13 +48,23 @@
                                   const std::string& calledAet,
                                   DicomRequestType type) = 0;
 
+    // Get the set of TransferSyntaxes that are accepted when negotiation a C-Store association, acting as SCP when it has been initiated by the C-Store SCU.
     virtual void GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& target,
                                              const std::string& remoteIp,
                                              const std::string& remoteAet,
                                              const std::string& calledAet) = 0;
-    
+
+    // Get the list of TransferSyntaxes that are proposed when initiating a C-Store SCP which actually only happens in a C-Get SCU
+    virtual void GetProposedStorageTransferSyntaxes(std::list<DicomTransferSyntax>& target,
+                                                    const std::string& remoteIp,
+                                                    const std::string& remoteAet,
+                                                    const std::string& calledAet) = 0;
+
     virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp,
                                            const std::string& remoteAet,
                                            const std::string& calledAet) = 0;
+
+    virtual void GetAcceptedSopClasses(std::set<std::string>& sopClasses,
+                                       size_t maxCount) = 0;
   };
 }
--- a/OrthancFramework/Sources/DicomNetworking/IFindRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IFindRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IFindRequestHandlerFactory.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IFindRequestHandlerFactory.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IGetRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IGetRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IGetRequestHandlerFactory.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IGetRequestHandlerFactory.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IMoveRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IMoveRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IMoveRequestHandlerFactory.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IMoveRequestHandlerFactory.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IStorageCommitmentRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IStorageCommitmentRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IStorageCommitmentRequestHandlerFactory.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IStorageCommitmentRequestHandlerFactory.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IStoreRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IStoreRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IStoreRequestHandlerFactory.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IStoreRequestHandlerFactory.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IWorklistRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IWorklistRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/IWorklistRequestHandlerFactory.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/IWorklistRequestHandlerFactory.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -502,13 +502,21 @@
           }
           else                                         // see dcmqrsrv.cc lines 839 - 876
           {
+            std::set<std::string> acceptedStorageClasses;
+
+            if (server.HasApplicationEntityFilter())
+            {
+              server.GetApplicationEntityFilter().GetAcceptedSopClasses(acceptedStorageClasses, 0);
+            }
+
             /* accept storage syntaxes with proposed role */
             int npc = ASC_countPresentationContexts(assoc->params);
             for (int i = 0; i < npc; i++)
             {
               T_ASC_PresentationContext pc;
               ASC_getPresentationContext(assoc->params, i, &pc);
-              if (dcmIsaStorageSOPClassUID(pc.abstractSyntax))
+              if (acceptedStorageClasses.find(pc.abstractSyntax) != acceptedStorageClasses.end()
+                 || (!server.HasApplicationEntityFilter() && dcmIsaStorageSOPClassUID(pc.abstractSyntax)))  // previous behavior kept for compatibility in case the server does not have an ApplicationEntityFilter
               {
                 /**
                  * We are prepared to accept whatever role the caller
--- a/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -44,6 +44,16 @@
 #endif
 
 
+#if ORTHANC_ENABLE_PLUGINS == 1
+#  if defined(__ORTHANC_FILE__)
+//   Prevents the system-wide DCMTK library from leaking the
+//   full path of this source file in "DCMTLS_ERROR()"
+#    undef __FILE__
+#    define __FILE__ __ORTHANC_FILE__
+#  endif
+#endif
+
+
 namespace Orthanc
 {
   namespace Internals
--- a/OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/Internals/FindScp.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/FindScp.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/Internals/FindScp.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/FindScp.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/Internals/GetScp.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/GetScp.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/Internals/GetScp.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/GetScp.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/Internals/MoveScp.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/MoveScp.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/Internals/MoveScp.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/MoveScp.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/NetworkingCompatibility.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/NetworkingCompatibility.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -50,6 +50,7 @@
 static const char* KEY_USE_DICOM_TLS = "UseDicomTls";
 static const char* KEY_LOCAL_AET = "LocalAet";
 static const char* KEY_TIMEOUT = "Timeout";
+static const char* KEY_RETRIEVE_METHOD = "RetrieveMethod";
 
 
 namespace Orthanc
@@ -72,6 +73,7 @@
     useDicomTls_ = false;
     localAet_.clear();
     timeout_ = 0;
+    retrieveMethod_ = RetrieveMethod_SystemDefault;
   }
 
 
@@ -308,6 +310,17 @@
     {
       timeout_ = SerializationToolbox::ReadUnsignedInteger(serialized, KEY_TIMEOUT);
     }
+
+    if (serialized.isMember(KEY_RETRIEVE_METHOD))
+    {
+      retrieveMethod_ = StringToRetrieveMethod
+        (SerializationToolbox::ReadString(serialized, KEY_RETRIEVE_METHOD));
+    }   
+    else
+    {
+      retrieveMethod_ = RetrieveMethod_SystemDefault;
+    }
+
   }
 
 
@@ -427,6 +440,7 @@
       target[KEY_USE_DICOM_TLS] = useDicomTls_;
       target[KEY_LOCAL_AET] = localAet_;
       target[KEY_TIMEOUT] = timeout_;
+      target[KEY_RETRIEVE_METHOD] = EnumerationToString(retrieveMethod_);
     }
     else
     {
@@ -521,4 +535,14 @@
   {
     return timeout_ != 0;
   }
+
+  RetrieveMethod RemoteModalityParameters::GetRetrieveMethod() const
+  {
+    return retrieveMethod_;
+  }
+
+  void RemoteModalityParameters::SetRetrieveMethod(RetrieveMethod retrieveMethod)
+  {
+    retrieveMethod_ = retrieveMethod;
+  }
 }
--- a/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -51,6 +51,7 @@
     bool                  useDicomTls_;
     std::string           localAet_;
     uint32_t              timeout_;
+    RetrieveMethod        retrieveMethod_;   // New in Orthanc 1.12.6
     
     void Clear();
 
@@ -118,5 +119,10 @@
     uint32_t GetTimeout() const;
 
     bool HasTimeout() const;    
+
+    RetrieveMethod GetRetrieveMethod() const;
+
+    void SetRetrieveMethod(RetrieveMethod retrieveMethod);
+
   };
 }
--- a/OrthancFramework/Sources/DicomNetworking/TimeoutDicomConnectionManager.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/TimeoutDicomConnectionManager.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomNetworking/TimeoutDicomConnectionManager.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/TimeoutDicomConnectionManager.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -50,8 +50,9 @@
 
 namespace Orthanc
 {
-  DcmtkTranscoder::DcmtkTranscoder() :
-    lossyQuality_(90)
+  DcmtkTranscoder::DcmtkTranscoder(unsigned int maxConcurrentExecutions) :
+    lossyQuality_(90),
+    maxConcurrentExecutionsSemaphore_(maxConcurrentExecutions)
   {
   }
 
@@ -317,6 +318,8 @@
                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                   bool allowNewSopInstanceUid)
   {
+    Semaphore::Locker lock(maxConcurrentExecutionsSemaphore_); // limit the number of concurrent executions
+
     target.Clear();
     
     DicomTransferSyntax sourceSyntax;
--- a/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -33,6 +33,8 @@
 #endif
 
 #include "IDicomTranscoder.h"
+#include "../MultiThreading/Semaphore.h"
+
 
 namespace Orthanc
 {
@@ -40,7 +42,8 @@
   {
   private:
     unsigned int  lossyQuality_;
-    
+    Semaphore maxConcurrentExecutionsSemaphore_;
+
     bool InplaceTranscode(DicomTransferSyntax& selectedSyntax /* out */,
                           std::string& failureReason /* out */,
                           DcmFileFormat& dicom,
@@ -48,7 +51,7 @@
                           bool allowNewSopInstanceUid);
     
   public:
-    DcmtkTranscoder();
+    explicit DcmtkTranscoder(unsigned int maxConcurrentExecutions);
 
     void SetLossyQuality(unsigned int quality);
 
--- a/OrthancFramework/Sources/DicomParsing/DicomDirWriter.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomDirWriter.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/DicomDirWriter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomDirWriter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -28,6 +28,7 @@
 #include "../Logging.h"
 #include "../OrthancException.h"
 #include "../Toolbox.h"
+#include "../SerializationToolbox.h"
 #include "FromDcmtkBridge.h"
 
 #include <boost/math/special_functions/round.hpp>
@@ -341,6 +342,31 @@
     }
   }
 
+  Json::Value DicomWebJsonVisitor::FormatDecimalString(double value, const std::string& originalString)
+  {
+    try
+    {
+      long long a = boost::math::llround<double>(value);
+
+      double d = fabs(value - static_cast<double>(a));
+
+      if (d <= std::numeric_limits<double>::epsilon() * 100.0)
+      {
+        return FormatInteger(a);  // if the decimal number is an integer, you can represent it as an integer  
+      }
+      else
+      {
+        return Json::Value(originalString);  // keep the original string to avoid rounding errors e.g, transforming "0.143" into 0.14299999999999
+      }
+    }
+    catch (boost::math::rounding_error&)
+    {
+      // Can occur if "long long" is too small to receive this value
+      // (e.g. infinity)
+      return Json::Value(originalString);
+    }
+  }
+
   DicomWebJsonVisitor::DicomWebJsonVisitor() :
     formatter_(NULL)
   {
@@ -677,14 +703,33 @@
                 case ValueRepresentation_DecimalString:
                 {
                   std::string t = Toolbox::StripSpaces(tokens[i]);
+                  boost::replace_all(t, ",", "."); // some invalid files uses "," instead of "."
+                  
+                  // remove invalid/useless trailing decimal separator
+                  if (t.size() > 0 && t[t.size()-1] == '.')
+                  {
+                    t.resize(t.size() -1);
+                  }
+
                   if (t.empty())
                   {
                     node[KEY_VALUE].append(Json::nullValue);
                   }
                   else
                   {
-                    double tmp = boost::lexical_cast<double>(t);
-                    node[KEY_VALUE].append(FormatDouble(tmp));
+                    // https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.3.html
+                    // DS values can be represented as String or Number in Json.
+                    // For IS, DS, SV and UV, a JSON String representation can be used to preserve the original format during transformation of the representation, or if needed to avoid losing precision of a decimal string.
+                    // Since 1.12.5, always use the string repesentation.  Before, decimal numbers were represented as double which led to loss of precision (e.g: 0.143 represented as 0.1429999999)
+                    double tmp;
+                    if (SerializationToolbox::ParseDouble(tmp, t)) // make sure that the string contains a valid decimal number
+                    {
+                      node[KEY_VALUE].append(t);
+                    }
+                    else
+                    {
+                      throw boost::bad_lexical_cast();
+                    }
                   }
 
                   break;
--- a/OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -74,6 +74,8 @@
 
     static Json::Value FormatDouble(double value);
 
+    static Json::Value FormatDecimalString(double value, const std::string& originalString);
+
   public:
     DicomWebJsonVisitor();
 
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge_TransferSyntaxes.impl.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge_TransferSyntaxes.impl.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/ITagVisitor.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ITagVisitor.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomCache.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomCache.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomCache.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomCache.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomDir.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomDir.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomDir.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomDir.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/ToDcmtkBridge.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ToDcmtkBridge.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/DicomParsing/ToDcmtkBridge.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ToDcmtkBridge.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Endianness.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Endianness.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/EnumerationDictionary.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/EnumerationDictionary.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Enumerations.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Enumerations.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -372,6 +372,9 @@
       case ErrorCode_NoCGetHandler:
         return "No request handler factory for DICOM C-GET SCP";
 
+      case ErrorCode_DicomGetUnavailable:
+        return "DicomUserConnection: The C-GET command is not supported by the remote SCP";
+
       case ErrorCode_UnsupportedMediaType:
         return "Unsupported media type";
 
@@ -2493,6 +2496,44 @@
     }
   }
 
+  RetrieveMethod StringToRetrieveMethod(const std::string& str)
+  {
+    if (str == "C-MOVE")
+    {
+      return RetrieveMethod_Move;
+    }
+    else if (str == "C-GET")
+    {
+      return RetrieveMethod_Get;
+    }
+    else if (str == "SystemDefault")
+    {
+      return RetrieveMethod_SystemDefault;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "RetrieveMethod can be \"C-MOVE\", \"C-GET\" or \"SystemDefault\": " + str);
+    }    
+  }
+
+  const char* EnumerationToString(RetrieveMethod method)
+  {
+    switch (method)
+    {
+      case RetrieveMethod_Get:
+        return "C-GET";
+
+      case RetrieveMethod_Move:
+        return "C-MOVE";
+
+      case RetrieveMethod_SystemDefault:
+        return "SystemDefault";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
 }
 
 
--- a/OrthancFramework/Sources/Enumerations.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Enumerations.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -234,6 +234,7 @@
     ErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
     ErrorCode_NoStorageCommitmentHandler = 2043    /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */,
     ErrorCode_NoCGetHandler = 2044    /*!< No request handler factory for DICOM C-GET SCP */,
+    ErrorCode_DicomGetUnavailable = 2045    /*!< DicomUserConnection: The C-GET command is not supported by the remote SCP */,
     ErrorCode_UnsupportedMediaType = 3000    /*!< Unsupported media type */,
     ErrorCode_START_PLUGINS = 1000000
   };
@@ -793,6 +794,14 @@
     ResourceType_Instance = 4
   };
 
+  enum RetrieveMethod                         // new in Orthanc 1.12.6
+  {
+    RetrieveMethod_Move = 1,
+    RetrieveMethod_Get = 2,
+
+    RetrieveMethod_SystemDefault = 65535
+  };
+
 
   ORTHANC_PUBLIC
   const char* EnumerationToString(ErrorCode code);
@@ -849,6 +858,9 @@
   const char* EnumerationToString(DicomToJsonFormat format);
 
   ORTHANC_PUBLIC
+  const char* EnumerationToString(RetrieveMethod method);
+
+  ORTHANC_PUBLIC
   Encoding StringToEncoding(const char* encoding);
 
   ORTHANC_PUBLIC
@@ -947,4 +959,7 @@
 
   ORTHANC_PUBLIC
   void GetAllDicomTransferSyntaxes(std::set<DicomTransferSyntax>& target);
+
+  ORTHANC_PUBLIC 
+  RetrieveMethod StringToRetrieveMethod(const std::string& str);
 }
--- a/OrthancFramework/Sources/Enumerations_TransferSyntaxes.impl.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Enumerations_TransferSyntaxes.impl.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/FileBuffer.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileBuffer.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/FileBuffer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileBuffer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/FileStorage/FileInfo.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileStorage/FileInfo.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/FileStorage/FileInfo.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileStorage/FileInfo.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -176,11 +176,11 @@
         LOG(INFO) << "Created attachment \"" << uuid << "\" (" << timer.GetHumanTransferSpeed(true, size) << ")";
         return;
       }
-      catch (OrthancException& ex)
+      catch (OrthancException& e)
       {
         if (retryCount >= maxRetryCount)
         {
-          throw ex;
+          throw;
         }
       }
     }
--- a/OrthancFramework/Sources/FileStorage/FilesystemStorage.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileStorage/FilesystemStorage.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/FileStorage/IStorageArea.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileStorage/IStorageArea.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/FileStorage/MemoryStorageArea.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileStorage/MemoryStorageArea.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/FileStorage/MemoryStorageArea.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileStorage/MemoryStorageArea.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -28,16 +28,18 @@
 
 #include "../Logging.h"
 #include "../StringMemoryBuffer.h"
-#include "../Compatibility.h"
 #include "../Compression/ZlibCompressor.h"
 #include "../MetricsRegistry.h"
 #include "../OrthancException.h"
+#include "../SerializationToolbox.h"
 #include "../Toolbox.h"
 
 #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
 #  include "../HttpServer/HttpStreamTranscoder.h"
 #endif
 
+#include <boost/algorithm/string.hpp>
+
 
 static const std::string METRICS_CREATE_DURATION = "orthanc_storage_create_duration_ms";
 static const std::string METRICS_READ_DURATION = "orthanc_storage_read_duration_ms";
@@ -50,6 +52,212 @@
 
 namespace Orthanc
 {
+  void StorageAccessor::Range::SanityCheck() const
+  {
+    if (hasStart_ && hasEnd_ && start_ > end_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  StorageAccessor::Range::Range():
+    hasStart_(false),
+    start_(0),
+    hasEnd_(false),
+    end_(0)
+  {
+  }
+
+  void StorageAccessor::Range::SetStartInclusive(uint64_t start)
+  {
+    hasStart_ = true;
+    start_ = start;
+  }
+
+  void StorageAccessor::Range::SetEndInclusive(uint64_t end)
+  {
+    hasEnd_ = true;
+    end_ = end;
+  }
+
+  uint64_t StorageAccessor::Range::GetStartInclusive() const
+  {
+    if (!hasStart_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else if (hasEnd_ && start_ > end_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return start_;
+    }
+  }
+
+  uint64_t StorageAccessor::Range::GetEndInclusive() const
+  {
+    if (!hasEnd_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else if (hasStart_ && start_ > end_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return end_;
+    }
+  }
+
+  std::string StorageAccessor::Range::FormatHttpContentRange(uint64_t fullSize) const
+  {
+    SanityCheck();
+
+    if (fullSize == 0 ||
+        (hasStart_ && start_ >= fullSize) ||
+        (hasEnd_ && end_ >= fullSize))
+    {
+      throw OrthancException(ErrorCode_BadRange);
+    }
+
+    std::string s = "bytes ";
+
+    if (hasStart_)
+    {
+      s += boost::lexical_cast<std::string>(start_);
+    }
+    else
+    {
+      s += "0";
+    }
+
+    s += "-";
+
+    if (hasEnd_)
+    {
+      s += boost::lexical_cast<std::string>(end_);
+    }
+    else
+    {
+      s += boost::lexical_cast<std::string>(fullSize - 1);
+    }
+
+    return s + "/" + boost::lexical_cast<std::string>(fullSize);
+  }
+
+  void StorageAccessor::Range::Extract(std::string &target,
+                                       const std::string &source) const
+  {
+    SanityCheck();
+
+    if (hasStart_ && start_ >= source.size())
+    {
+      throw OrthancException(ErrorCode_BadRange);
+    }
+
+    if (hasEnd_ && end_ >= source.size())
+    {
+      throw OrthancException(ErrorCode_BadRange);
+    }
+
+    if (hasStart_ && hasEnd_)
+    {
+      target = source.substr(start_, end_ - start_ + 1);
+    }
+    else if (hasStart_)
+    {
+      target = source.substr(start_, source.size() - start_);
+    }
+    else if (hasEnd_)
+    {
+      target = source.substr(0, end_ + 1);
+    }
+    else
+    {
+      target = source;
+    }
+  }
+
+  uint64_t StorageAccessor::Range::GetContentLength(uint64_t fullSize) const
+  {
+    SanityCheck();
+
+    if (fullSize == 0)
+    {
+      throw OrthancException(ErrorCode_BadRange);
+    }
+
+    if (hasStart_ && start_ >= fullSize)
+    {
+      throw OrthancException(ErrorCode_BadRange);
+    }
+
+    if (hasEnd_ && end_ >= fullSize)
+    {
+      throw OrthancException(ErrorCode_BadRange);
+    }
+
+    if (hasStart_ && hasEnd_)
+    {
+      return end_ - start_ + 1;
+    }
+    else if (hasStart_)
+    {
+      return fullSize - start_;
+    }
+    else if (hasEnd_)
+    {
+      return end_ + 1;
+    }
+    else
+    {
+      return fullSize;
+    }
+  }
+
+  StorageAccessor::Range StorageAccessor::Range::ParseHttpRange(const std::string& s)
+  {
+    static const std::string BYTES = "bytes=";
+
+    if (!boost::starts_with(s, BYTES))
+    {
+      throw OrthancException(ErrorCode_BadRange);  // Range not satisfiable
+    }
+
+    std::vector<std::string> tokens;
+    Orthanc::Toolbox::TokenizeString(tokens, s.substr(BYTES.length()), '-');
+
+    if (tokens.size() != 2)
+    {
+      throw OrthancException(ErrorCode_BadRange);
+    }
+
+    Range range;
+
+    uint64_t tmp;
+    if (!tokens[0].empty())
+    {
+      if (SerializationToolbox::ParseUnsignedInteger64(tmp, tokens[0]))
+      {
+        range.SetStartInclusive(tmp);
+      }
+    }
+
+    if (!tokens[1].empty())
+    {
+      if (SerializationToolbox::ParseUnsignedInteger64(tmp, tokens[1]))
+      {
+        range.SetEndInclusive(tmp);
+      }
+    }
+
+    range.SanityCheck();
+    return range;
+  }
+
   class StorageAccessor::MetricsTimer : public boost::noncopyable
   {
   private:
@@ -432,15 +640,6 @@
   }
 
 
-  void ReadStartRangeFromAreaInternal(std::string& target,
-                                      IStorageArea& area,
-                                      const std::string& fileUuid,
-                                      FileContentType contentType,
-                                      uint64_t end /* exclusive */)
-  {
-
-  }
-
   void StorageAccessor::ReadStartRange(std::string& target,
                                        const FileInfo& info,
                                        uint64_t end /* exclusive */)
@@ -511,6 +710,79 @@
   }
 
 
+  void StorageAccessor::ReadRange(std::string &target,
+                                  const FileInfo &info,
+                                  const Range &range,
+                                  bool uncompressIfNeeded)
+  {
+    if (uncompressIfNeeded &&
+        info.GetCompressionType() != CompressionType_None)
+    {
+      // An uncompression is needed in this case
+      if (cache_ != NULL)
+      {
+        StorageCache::Accessor cacheAccessor(*cache_);
+
+        std::string content;
+        if (cacheAccessor.Fetch(content, info.GetUuid(), info.GetContentType()))
+        {
+          range.Extract(target, content);
+          return;
+        }
+      }
+
+      std::string content;
+      Read(content, info);
+      range.Extract(target, content);
+    }
+    else
+    {
+      // Access to the raw attachment is sufficient in this case
+      if (info.GetCompressionType() == CompressionType_None &&
+          cache_ != NULL)
+      {
+        // Check out whether the raw attachment is already present in the cache, by chance
+        StorageCache::Accessor cacheAccessor(*cache_);
+
+        std::string content;
+        if (cacheAccessor.Fetch(content, info.GetUuid(), info.GetContentType()))
+        {
+          range.Extract(target, content);
+          return;
+        }
+      }
+
+      if (range.HasEnd() &&
+        range.GetEndInclusive() >= info.GetCompressedSize())
+      {
+        throw OrthancException(ErrorCode_BadRange);
+      }
+
+      std::unique_ptr<IMemoryBuffer> buffer;
+
+      if (range.HasStart() &&
+          range.HasEnd())
+      {
+        buffer.reset(area_.ReadRange(info.GetUuid(), info.GetContentType(), range.GetStartInclusive(), range.GetEndInclusive() + 1, info.GetCustomData()));
+      }
+      else if (range.HasStart())
+      {
+        buffer.reset(area_.ReadRange(info.GetUuid(), info.GetContentType(), range.GetStartInclusive(), info.GetCompressedSize(), info.GetCustomData()));
+      }
+      else if (range.HasEnd())
+      {
+        buffer.reset(area_.ReadRange(info.GetUuid(), info.GetContentType(), 0, range.GetEndInclusive() + 1, info.GetCustomData()));
+      }
+      else
+      {
+        buffer.reset(area_.Read(info.GetUuid(), info.GetContentType(), info.GetCustomData()));
+      }
+
+      buffer->MoveToString(target);
+    }
+  }
+
+
 #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
   void StorageAccessor::SetupSender(BufferHttpSender& sender,
                                     const FileInfo& info,
--- a/OrthancFramework/Sources/FileStorage/StorageAccessor.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileStorage/StorageAccessor.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -65,6 +65,48 @@
    **/
   class ORTHANC_PUBLIC StorageAccessor : boost::noncopyable
   {
+  public:
+    class ORTHANC_PUBLIC Range
+    {
+    private:
+      bool      hasStart_;
+      uint64_t  start_;
+      bool      hasEnd_;
+      uint64_t  end_;
+
+      void SanityCheck() const;
+
+    public:
+      Range();
+
+      void SetStartInclusive(uint64_t start);
+
+      void SetEndInclusive(uint64_t end);
+
+      bool HasStart() const
+      {
+        return hasStart_;
+      }
+
+      bool HasEnd() const
+      {
+        return hasEnd_;
+      }
+
+      uint64_t GetStartInclusive() const;
+
+      uint64_t GetEndInclusive() const;
+
+      std::string FormatHttpContentRange(uint64_t fullSize) const;
+
+      void Extract(std::string& target,
+                   const std::string& source) const;
+
+      uint64_t GetContentLength(uint64_t fullSize) const;
+
+      static Range ParseHttpRange(const std::string& s);
+    };
+
   private:
     class MetricsTimer;
 
@@ -149,6 +191,11 @@
 
     void Remove(const FileInfo& info);
 
+    void ReadRange(std::string& target,
+                   const FileInfo& info,
+                   const Range& range,
+                   bool uncompressIfNeeded);
+
 #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
     void AnswerFile(HttpOutput& output,
                     const FileInfo& info,
--- a/OrthancFramework/Sources/FileStorage/StorageCache.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileStorage/StorageCache.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/FileStorage/StorageCache.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/FileStorage/StorageCache.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpClient.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpClient.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -860,7 +860,18 @@
 
     if (verifyPeers_)
     {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str()));
+#if defined(CURLSSLOPT_NATIVE_CA)   // from curl v 8.2.0     
+      if (caCertificates_.empty())  // use native CA store (equivalent to --ca-native)
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA));
+      }
+      else 
+#endif
+      {
+        // use provided CA file (equivalent to --cacert)
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str()));
+      }
+      
       CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 2));  // libcurl default is strict verifyhost
       CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 1)); 
     }
@@ -1187,8 +1198,8 @@
     {
       if (httpsVerifyCertificates.empty())
       {
-        LOG(WARNING) << "No certificates are provided to validate peers, "
-                     << "set \"HttpsCACertificates\" if you need to do HTTPS requests";
+        LOG(WARNING) << "No certificates are provided to validate peers.  Orthanc will use the native CA store. "
+                     << "Set \"HttpsCACertificates\" if you need to do HTTPS requests and use custom CAs.";
       }
       else
       {
--- a/OrthancFramework/Sources/HttpClient.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpClient.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/BufferHttpSender.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/BufferHttpSender.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/BufferHttpSender.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/BufferHttpSender.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/CStringMatcher.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/CStringMatcher.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/CStringMatcher.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/CStringMatcher.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/FilesystemHttpHandler.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/FilesystemHttpHandler.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/FilesystemHttpHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/FilesystemHttpHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/HttpContentNegociation.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpContentNegociation.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/HttpContentNegociation.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpContentNegociation.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/HttpFileSender.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpFileSender.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/HttpFileSender.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpFileSender.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/HttpOutput.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpOutput.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -62,7 +62,8 @@
     contentPosition_(0),
     keepAlive_(isKeepAlive),
     keepAliveTimeout_(keepAliveTimeout),
-    hasXContentTypeOptions_(false)
+    hasXContentTypeOptions_(false),
+    hasContentType_(false)
   {
   }
 
@@ -105,6 +106,7 @@
 
   void HttpOutput::StateMachine::SetContentType(const char* contentType)
   {
+    hasContentType_ = true;
     AddHeader("Content-Type", contentType);
   }
 
@@ -380,7 +382,8 @@
     
     stateMachine_.SetHttpStatus(status);
 
-    if (messageSize > 0)
+    if (messageSize > 0 &&
+      !stateMachine_.HasContentType())
     {
       // Assume that the body always contains a textual description of the error
       stateMachine_.SetContentType("text/plain");
@@ -471,6 +474,10 @@
     return stateMachine_.GetState() == StateMachine::State_WritingMultipart;
   }
 
+  bool HttpOutput::IsWritingStream() const
+  {
+    return stateMachine_.GetState() == StateMachine::State_WritingStream;
+  }
   
   void HttpOutput::Answer(const void* buffer,
                           size_t length)
@@ -974,4 +981,21 @@
 
     stateMachine_.CloseStream();
   }
+
+  void HttpOutput::StartStream(const std::string& contentType)
+  {
+    stateMachine_.StartStream(contentType.c_str());
+  }
+
+  void HttpOutput::SendStreamItem(const void* data,
+                                  size_t size)
+  {
+    stateMachine_.SendStreamItem(data, size);
+  }
+
+  void HttpOutput::CloseStream()
+  {
+    stateMachine_.CloseStream();
+  }
+
 }
--- a/OrthancFramework/Sources/HttpServer/HttpOutput.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpOutput.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -66,6 +66,7 @@
       unsigned int keepAliveTimeout_;
       std::list<std::string> headers_;
       bool hasXContentTypeOptions_;
+      bool hasContentType_;
 
       std::string multipartBoundary_;
       std::string multipartContentType_;
@@ -125,6 +126,11 @@
                           size_t size);
 
       void CloseStream();
+
+      bool HasContentType() const
+      {
+        return hasContentType_;
+      }
     };
 
     StateMachine stateMachine_;
@@ -218,5 +224,14 @@
      * used to handle compression using "Content-Encoding".
      **/
     void AnswerWithoutBuffering(IHttpStreamAnswer& stream);
+
+    void StartStream(const std::string& contentType);
+
+    void SendStreamItem(const void* data,
+                        size_t size);
+
+    void CloseStream();
+
+    bool IsWritingStream() const;
   };
 }
--- a/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/HttpServer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpServer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/HttpStreamTranscoder.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpStreamTranscoder.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/HttpStreamTranscoder.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpStreamTranscoder.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/HttpToolbox.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpToolbox.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/HttpToolbox.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpToolbox.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/IHttpHandler.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/IHttpHandler.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/IHttpHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/IHttpHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/IHttpOutputStream.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/IHttpOutputStream.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/IHttpStreamAnswer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/IHttpStreamAnswer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/IIncomingHttpRequestFilter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/IIncomingHttpRequestFilter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/IWebDavBucket.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/IWebDavBucket.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/MultipartStreamReader.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/MultipartStreamReader.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/MultipartStreamReader.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/MultipartStreamReader.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/StringHttpOutput.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/StringHttpOutput.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/StringHttpOutput.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/StringHttpOutput.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/StringMatcher.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/StringMatcher.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/StringMatcher.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/StringMatcher.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/WebDavStorage.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/WebDavStorage.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/HttpServer/WebDavStorage.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/HttpServer/WebDavStorage.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/IDynamicObject.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/IDynamicObject.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/IMemoryBuffer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/IMemoryBuffer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/Font.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/Font.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/Font.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/Font.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/FontRegistry.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/FontRegistry.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/FontRegistry.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/FontRegistry.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/IImageWriter.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/IImageWriter.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/IImageWriter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/IImageWriter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/Image.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/Image.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/Image.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/Image.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/ImageAccessor.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/ImageAccessor.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -139,7 +139,7 @@
     return pitch_;
   }
 
-  unsigned int ImageAccessor::GetSize() const
+  size_t ImageAccessor::GetSize() const
   {
     return GetHeight() * GetPitch();
   }
@@ -165,7 +165,7 @@
   {
     if (buffer_ != NULL)
     {
-      return buffer_ + y * pitch_;
+      return buffer_ + static_cast<size_t>(y) * static_cast<size_t>(pitch_);
     }
     else
     {
@@ -184,7 +184,7 @@
 
     if (buffer_ != NULL)
     {
-      return buffer_ + y * pitch_;
+      return buffer_ + static_cast<size_t>(y) * static_cast<size_t>(pitch_);
     }
     else
     {
@@ -325,8 +325,8 @@
     else
     {
       uint8_t* p = (buffer_ + 
-                    y * pitch_ + 
-                    x * GetBytesPerPixel());
+                    static_cast<size_t>(y) * static_cast<size_t>(pitch_) +
+                    static_cast<size_t>(x) * static_cast<size_t>(GetBytesPerPixel()));
 
       if (readOnly_)
       {
--- a/OrthancFramework/Sources/Images/ImageAccessor.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/ImageAccessor.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -86,7 +86,7 @@
 
     unsigned int GetPitch() const;
 
-    unsigned int GetSize() const;
+    size_t GetSize() const;
 
     const void* GetConstBuffer() const;
 
--- a/OrthancFramework/Sources/Images/ImageBuffer.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/ImageBuffer.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -47,7 +47,7 @@
       */
 
       pitch_ = GetBytesPerPixel() * width_;
-      size_t size = pitch_ * height_;
+      size_t size = static_cast<size_t>(pitch_) * static_cast<size_t>(height_);
 
       if (size == 0)
       {
--- a/OrthancFramework/Sources/Images/ImageBuffer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/ImageBuffer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/ImageProcessing.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/ImageProcessing.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -2888,8 +2888,7 @@
     const unsigned int width = image.GetWidth();
     const unsigned int height = image.GetHeight();
     const unsigned int pitch = image.GetPitch();
-    uint8_t* buffer = reinterpret_cast<uint8_t*>(image.GetBuffer());
-        
+
     if (image.GetFormat() != PixelFormat_RGB24 ||
         pitch < 3 * width)
     {
@@ -2898,7 +2897,7 @@
 
     for (unsigned int y = 0; y < height; y++)
     {
-      uint8_t* p = buffer + y * pitch;
+      uint8_t* p = reinterpret_cast<uint8_t*>(image.GetRow(y));
           
       for (unsigned int x = 0; x < width; x++, p += 3)
       {
--- a/OrthancFramework/Sources/Images/ImageProcessing.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/ImageProcessing.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/ImageTraits.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/ImageTraits.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/JpegErrorManager.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/JpegErrorManager.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/JpegErrorManager.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/JpegErrorManager.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/JpegReader.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/JpegReader.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/JpegReader.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/JpegReader.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/JpegWriter.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/JpegWriter.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/JpegWriter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/JpegWriter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/NumpyWriter.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/NumpyWriter.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/NumpyWriter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/NumpyWriter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/PamReader.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/PamReader.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/PamReader.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/PamReader.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/PamWriter.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/PamWriter.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/PamWriter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/PamWriter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/PixelTraits.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/PixelTraits.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/PngReader.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/PngReader.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/PngReader.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/PngReader.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/PngWriter.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/PngWriter.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Images/PngWriter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Images/PngWriter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/GenericJobUnserializer.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/GenericJobUnserializer.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/GenericJobUnserializer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/GenericJobUnserializer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/IJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/IJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -49,13 +49,18 @@
     // For pausing/canceling/ending jobs: This method must release allocated resources
     virtual void Stop(JobStopReason reason) = 0;
 
-    virtual float GetProgress() = 0;
+    virtual float GetProgress() const = 0;
 
-    virtual void GetJobType(std::string& target) = 0;
+    virtual bool NeedsProgressUpdateBetweenSteps() const // only for jobs whose progress is updated by outside events (like C-Move and C-Get)
+    {
+      return false;
+    }
+
+    virtual void GetJobType(std::string& target) const = 0;
     
-    virtual void GetPublicContent(Json::Value& value) = 0;
+    virtual void GetPublicContent(Json::Value& value) const = 0;
 
-    virtual bool Serialize(Json::Value& value) = 0;
+    virtual bool Serialize(Json::Value& value) const = 0;
 
     // This function can only be called if the job has reached its
     // "success" state
--- a/OrthancFramework/Sources/JobsEngine/IJobUnserializer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/IJobUnserializer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/JobInfo.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobInfo.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -54,7 +54,8 @@
                    const JobStatus& status,
                    const boost::posix_time::ptime& creationTime,
                    const boost::posix_time::ptime& lastStateChangeTime,
-                   const boost::posix_time::time_duration& runtime) :
+                   const boost::posix_time::time_duration& runtime,
+                   const IJob& job) :
     id_(id),
     priority_(priority),
     state_(state),
@@ -68,11 +69,16 @@
     if (state_ == JobState_Running)
     {
       float ms = static_cast<float>(runtime_.total_milliseconds());
+      if (job.NeedsProgressUpdateBetweenSteps())
+      {
+        status_.UpdateProgress(job);
+      }
 
-      if (status_.GetProgress() > 0.01f &&
+      float progress = status_.GetProgress();
+
+      if (progress > 0.01f &&
           ms > 0.01f)
       {
-        float progress = status_.GetProgress();
         long long remaining = boost::math::llround(ms / progress * (1.0f - progress));
         eta_ = timestamp_ + boost::posix_time::milliseconds(remaining);
         hasEta_ = true;
--- a/OrthancFramework/Sources/JobsEngine/JobInfo.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobInfo.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -51,7 +51,8 @@
             const JobStatus& status,
             const boost::posix_time::ptime& creationTime,
             const boost::posix_time::ptime& lastStateChangeTime,
-            const boost::posix_time::time_duration& runtime) ORTHANC_LOCAL;
+            const boost::posix_time::time_duration& runtime,
+            const IJob& job) ORTHANC_LOCAL;
 
     JobInfo();
 
--- a/OrthancFramework/Sources/JobsEngine/JobStatus.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobStatus.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -41,7 +41,7 @@
   
   JobStatus::JobStatus(ErrorCode code,
                        const std::string& details,
-                       IJob& job) :
+                       const IJob& job) :
     errorCode_(code),
     progress_(job.GetProgress()),
     publicContent_(Json::objectValue),
--- a/OrthancFramework/Sources/JobsEngine/JobStatus.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobStatus.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -44,7 +44,7 @@
 
     JobStatus(ErrorCode code,
               const std::string& details,
-              IJob& job);
+              const IJob& job);
 
     ErrorCode GetErrorCode() const
     {
@@ -61,6 +61,11 @@
       return progress_;
     }
 
+    void UpdateProgress(const IJob& job)
+    {
+      progress_ = job.GetProgress();
+    }
+
     const std::string& GetJobType() const
     {
       return jobType_;
--- a/OrthancFramework/Sources/JobsEngine/JobStepResult.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobStepResult.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/JobStepResult.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobStepResult.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/JobsEngine.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobsEngine.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -132,7 +132,10 @@
 
       if (running.IsValid())
       {
-        CLOG(INFO, JOBS) << "Executing job with priority " << running.GetPriority()
+        std::string jobType;
+        running.GetJob().GetJobType(jobType);
+
+        CLOG(INFO, JOBS) << "Executing " << jobType << " job with priority " << running.GetPriority()
                          << " in worker thread " << workerIndex << ": " << running.GetId();
 
         while (engine->IsRunning())
--- a/OrthancFramework/Sources/JobsEngine/JobsEngine.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobsEngine.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -223,8 +223,14 @@
       lastStateChangeTime_ = time;
     }
 
-    const boost::posix_time::time_duration& GetRuntime() const
+    boost::posix_time::time_duration GetRuntime() const
     {
+      if (state_ == JobState_Running)
+      {
+        const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
+        return now - lastStateChangeTime_;
+      }
+
       return runtime_;
     }
 
@@ -644,7 +650,8 @@
                        handler.GetLastStatus(),
                        handler.GetCreationTime(),
                        handler.GetLastStateChangeTime(),
-                       handler.GetRuntime());
+                       handler.GetRuntime(),
+                       handler.GetJob());
       return true;
     }
   }
@@ -792,7 +799,10 @@
         }
       }
 
-      LOG(INFO) << "New job submitted with priority " << priority << ": " << id;
+      std::string jobType;
+      handler->GetJob().GetJobType(jobType);
+
+      LOG(INFO) << "New " << jobType << " job submitted with priority " << priority << ": " << id;
 
       if (observer_ != NULL)
       {
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/Operations/IJobOperation.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/IJobOperation.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/Operations/IJobOperationValue.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/IJobOperationValue.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/Operations/JobOperationValues.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/JobOperationValues.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/Operations/JobOperationValues.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/JobOperationValues.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/Operations/NullOperationValue.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/NullOperationValue.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/Operations/NullOperationValue.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/NullOperationValue.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -398,7 +398,7 @@
   }
 
 
-  float SequenceOfOperationsJob::GetProgress()
+  float SequenceOfOperationsJob::GetProgress() const
   {
     boost::mutex::scoped_lock lock(mutex_);
       
@@ -406,13 +406,13 @@
             static_cast<float>(operations_.size() + 1));
   }
 
-  void SequenceOfOperationsJob::GetJobType(std::string& target)
+  void SequenceOfOperationsJob::GetJobType(std::string& target) const
   {
     target = "SequenceOfOperations";
   }
 
 
-  void SequenceOfOperationsJob::GetPublicContent(Json::Value& value)
+  void SequenceOfOperationsJob::GetPublicContent(Json::Value& value) const
   {
     boost::mutex::scoped_lock lock(mutex_);
 
@@ -421,7 +421,7 @@
   }
 
 
-  bool SequenceOfOperationsJob::Serialize(Json::Value& value)
+  bool SequenceOfOperationsJob::Serialize(Json::Value& value) const
   {
     boost::mutex::scoped_lock lock(mutex_);
 
--- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -54,7 +54,7 @@
 
     std::string                       description_;
     bool                              done_;
-    boost::mutex                      mutex_;
+    mutable boost::mutex              mutex_;
     std::vector<Operation*>           operations_;
     size_t                            current_;
     boost::condition_variable         operationAdded_;
@@ -117,13 +117,13 @@
 
     virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
 
-    virtual float GetProgress() ORTHANC_OVERRIDE;
+    virtual float GetProgress() const ORTHANC_OVERRIDE;
 
-    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE;
+    virtual void GetJobType(std::string& target) const ORTHANC_OVERRIDE;
 
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& value) const ORTHANC_OVERRIDE;
 
     virtual bool GetOutput(std::string& output,
                            MimeType& mime,
--- a/OrthancFramework/Sources/JobsEngine/Operations/StringOperationValue.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/StringOperationValue.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/Operations/StringOperationValue.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/StringOperationValue.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -137,7 +137,7 @@
   }
 
 
-  float SetOfCommandsJob::GetProgress()
+  float SetOfCommandsJob::GetProgress() const
   {
     if (commands_.empty())
     {
@@ -237,13 +237,13 @@
   static const char* KEY_COMMANDS = "Commands";
 
   
-  void SetOfCommandsJob::GetPublicContent(Json::Value& value)
+  void SetOfCommandsJob::GetPublicContent(Json::Value& value) const
   {
     value[KEY_DESCRIPTION] = GetDescription();
   }    
 
 
-  bool SetOfCommandsJob::Serialize(Json::Value& target)
+  bool SetOfCommandsJob::Serialize(Json::Value& target) const
   {
     target = Json::objectValue;
 
--- a/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -92,7 +92,7 @@
     
     virtual void Start() ORTHANC_OVERRIDE;
     
-    virtual float GetProgress() ORTHANC_OVERRIDE;
+    virtual float GetProgress() const ORTHANC_OVERRIDE;
 
     bool IsStarted() const;
 
@@ -100,9 +100,9 @@
       
     virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE;
     
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
     
-    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& target) const ORTHANC_OVERRIDE;
 
     virtual bool GetOutput(std::string& output,
                            MimeType& mime,
--- a/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -209,7 +209,7 @@
   static const char* KEY_FAILED_INSTANCES = "FailedInstances";
   static const char* KEY_PARENT_RESOURCES = "ParentResources";
 
-  void SetOfInstancesJob::GetPublicContent(Json::Value& target)
+  void SetOfInstancesJob::GetPublicContent(Json::Value& target) const
   {
     SetOfCommandsJob::GetPublicContent(target);
     target["InstancesCount"] = static_cast<uint32_t>(GetInstancesCount());
@@ -222,7 +222,7 @@
   }
 
 
-  bool SetOfInstancesJob::Serialize(Json::Value& target) 
+  bool SetOfInstancesJob::Serialize(Json::Value& target) const 
   {
     if (SetOfCommandsJob::Serialize(target))
     {
--- a/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -77,8 +77,8 @@
 
     virtual void Reset() ORTHANC_OVERRIDE;
 
-    virtual void GetPublicContent(Json::Value& target) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& target) const ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& target) const ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancFramework/Sources/Logging.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Logging.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Logging.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Logging.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Lua/LuaContext.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Lua/LuaContext.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Lua/LuaContext.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Lua/LuaContext.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -124,5 +124,17 @@
                                       lua_State* state,
                                       int top,
                                       bool keyToLowerCase);
+
+#if ORTHANC_ENABLE_CURL == 1
+    void SetHttpsVerifyPeers(bool verify)
+    {
+      httpClient_.SetHttpsVerifyPeers(verify);
+    }
+
+    bool IsHttpsVerifyPeers() const
+    {
+      return httpClient_.IsHttpsVerifyPeers();
+    }
+#endif
   };
 }
--- a/OrthancFramework/Sources/Lua/LuaFunctionCall.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Lua/LuaFunctionCall.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Lua/LuaFunctionCall.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Lua/LuaFunctionCall.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MallocMemoryBuffer.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MallocMemoryBuffer.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MallocMemoryBuffer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MallocMemoryBuffer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MetricsRegistry.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MetricsRegistry.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MetricsRegistry.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MetricsRegistry.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MultiThreading/IRunnableBySteps.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MultiThreading/IRunnableBySteps.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MultiThreading/Mutex.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MultiThreading/Mutex.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MultiThreading/RunnableWorkersPool.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MultiThreading/RunnableWorkersPool.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MultiThreading/RunnableWorkersPool.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MultiThreading/RunnableWorkersPool.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MultiThreading/Semaphore.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MultiThreading/Semaphore.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MultiThreading/Semaphore.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MultiThreading/Semaphore.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MultiThreading/SharedMessageQueue.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MultiThreading/SharedMessageQueue.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/MultiThreading/SharedMessageQueue.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/MultiThreading/SharedMessageQueue.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/OrthancException.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/OrthancException.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/OrthancException.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/OrthancException.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/OrthancFramework.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/OrthancFramework.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/OrthancFramework.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/OrthancFramework.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Pkcs11.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Pkcs11.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Pkcs11.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Pkcs11.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/PrecompiledHeaders.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/PrecompiledHeaders.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/PrecompiledHeaders.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/PrecompiledHeaders.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApi.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApi.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApi.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApi.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiCall.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiCall.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiCall.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiCall.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiDeleteCall.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiDeleteCall.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiGetCall.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiGetCall.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiGetCall.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiGetCall.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiHierarchy.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiHierarchy.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiHierarchy.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiHierarchy.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiOutput.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiOutput.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiOutput.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiOutput.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiPath.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiPath.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiPath.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiPath.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiPostCall.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiPostCall.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/RestApi/RestApiPutCall.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiPutCall.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/SQLite/Connection.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/Connection.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/Connection.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/Connection.h	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/FunctionContext.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/FunctionContext.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -17,7 +17,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of the CHU of Liege, nor the names of its
+ *    * Neither the name of the University Hospital of Liege, nor the names of its
  * contributors may be used to endorse or promote products derived
  * from this software without specific prior written permission.
  *
--- a/OrthancFramework/Sources/SQLite/FunctionContext.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/FunctionContext.h	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -17,7 +17,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of the CHU of Liege, nor the names of its
+ *    * Neither the name of the University Hospital of Liege, nor the names of its
  * contributors may be used to endorse or promote products derived
  * from this software without specific prior written permission.
  *
--- a/OrthancFramework/Sources/SQLite/IScalarFunction.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/IScalarFunction.h	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -17,7 +17,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of the CHU of Liege, nor the names of its
+ *    * Neither the name of the University Hospital of Liege, nor the names of its
  * contributors may be used to endorse or promote products derived
  * from this software without specific prior written permission.
  *
--- a/OrthancFramework/Sources/SQLite/ITransaction.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/ITransaction.h	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/NonCopyable.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/NonCopyable.h	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -17,7 +17,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/OrthancSQLiteException.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/OrthancSQLiteException.h	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/SQLiteTypes.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/SQLiteTypes.h	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -17,7 +17,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of the CHU of Liege, nor the names of its
+ *    * Neither the name of the University Hospital of Liege, nor the names of its
  * contributors may be used to endorse or promote products derived
  * from this software without specific prior written permission.
  *
--- a/OrthancFramework/Sources/SQLite/Statement.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/Statement.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/Statement.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/Statement.h	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/StatementId.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/StatementId.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/StatementId.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/StatementId.h	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/StatementReference.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/StatementReference.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/StatementReference.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/StatementReference.h	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/Transaction.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/Transaction.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SQLite/Transaction.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SQLite/Transaction.h	Thu Jan 30 17:41:33 2025 +0100
@@ -2,10 +2,10 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  *
  * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@orthanc-labs.com>,
- * Medical Physics Department, CHU of Liege, Belgium
+ * Medical Physics Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -19,7 +19,7 @@
  * copyright notice, this list of conditions and the following disclaimer
  * in the documentation and/or other materials provided with the
  * distribution.
- *    * Neither the name of Google Inc., the name of the CHU of Liege,
+ *    * Neither the name of Google Inc., the name of the University Hospital of Liege,
  * nor the names of its contributors may be used to endorse or promote
  * products derived from this software without specific prior written
  * permission.
--- a/OrthancFramework/Sources/SerializationToolbox.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SerializationToolbox.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/SerializationToolbox.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SerializationToolbox.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/SharedLibrary.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SharedLibrary.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/SharedLibrary.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SharedLibrary.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/StringMemoryBuffer.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/StringMemoryBuffer.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/StringMemoryBuffer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/StringMemoryBuffer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/SystemToolbox.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SystemToolbox.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/SystemToolbox.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/SystemToolbox.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/TemporaryFile.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/TemporaryFile.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/TemporaryFile.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/TemporaryFile.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/Toolbox.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Toolbox.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -812,14 +812,32 @@
       sha1.process_bytes(data, size);
     }
 
+#if BOOST_VERSION >= 108600
+    unsigned char digest[20];
+
+    // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide
+    assert(sizeof(digest) == (160 / 8));
+    assert(sizeof(boost::uuids::detail::sha1::digest_type) == 20);
+
+    // From Boost 1.86, digest_type is "unsigned char[20]" while it was "unsigned int[5]"" in previous versions.
+    // Always perform the cast even if it is useless for Boost < 1.86
+    sha1.get_digest(digest);
+
+    result.resize(8 * 5 + 4);
+    sprintf(&result[0], "%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x",
+            digest[0], digest[1], digest[2], digest[3],
+            digest[4], digest[5], digest[6], digest[7],
+            digest[8], digest[9], digest[10], digest[11],
+            digest[12], digest[13], digest[14], digest[15],
+            digest[16], digest[17], digest[18], digest[19]);
+
+#else
     unsigned int digest[5];
     // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide
     assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8));
     assert(sizeof(boost::uuids::detail::sha1::digest_type) == 20);
-    
-    // From Boost 1.86, digest_type is "unsigned char[20]" while it was "unsigned int[5]"" in previous versions.
-    // Always perform the cast even if it is useless for Boost < 1.86
-    sha1.get_digest(*(reinterpret_cast<boost::uuids::detail::sha1::digest_type*>(digest)));
+
+    sha1.get_digest(digest);
 
     result.resize(8 * 5 + 4);
     sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x",
@@ -828,6 +846,9 @@
             digest[2],
             digest[3],
             digest[4]);
+
+#endif
+
   }
 
   void Toolbox::ComputeSHA1(std::string& result,
--- a/OrthancFramework/Sources/Toolbox.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/Toolbox.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -262,7 +262,7 @@
       }
     }
 
-    // returns true if all element of 'needles' are found in 'haystack'
+    // returns the elements that are both in a and b
     template <typename T> static void GetIntersection(std::set<T>& target, const std::set<T>& a, const std::set<T>& b)
     {
       target.clear();
--- a/OrthancFramework/Sources/WebServiceParameters.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/WebServiceParameters.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/Sources/WebServiceParameters.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/Sources/WebServiceParameters.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -806,6 +806,7 @@
   dicom.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "SB1^SB2^SB3^SB4^SB5");
   dicom.ReplacePlainString(DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "1\\2.3\\4");
   dicom.ReplacePlainString(DICOM_TAG_IMAGE_POSITION_PATIENT, "");
+  dicom.ReplacePlainString(DICOM_TAG_PIXEL_SPACING, "0,143\\0,143");  // seen in https://discourse.orthanc-server.org/t/dicomwebplugin-does-not-return-series-metadata-properly/5195
 
   DicomWebJsonVisitor visitor;
   dicom.Apply(visitor);
@@ -817,10 +818,10 @@
     ASSERT_EQ(EnumerationToString(ValueRepresentation_DecimalString), tag["vr"].asString());
     ASSERT_EQ(2u, tag.getMemberNames().size());
     ASSERT_EQ(3u, value.size());
-    ASSERT_EQ(Json::realValue, value[1].type());
-    ASSERT_FLOAT_EQ(1.0f, value[0].asFloat());
-    ASSERT_FLOAT_EQ(2.3f, value[1].asFloat());
-    ASSERT_FLOAT_EQ(4.0f, value[2].asFloat());
+    ASSERT_EQ(Json::stringValue, value[1].type());  // since Orthanc 1.12.5, this is now stored as a string
+    ASSERT_EQ("1", value[0].asString());
+    ASSERT_EQ("2.3", value[1].asString());
+    ASSERT_EQ("4", value[2].asString());
   }
 
   {
@@ -829,13 +830,23 @@
     ASSERT_EQ(1u, tag.getMemberNames().size());
   }
 
+  {
+    const Json::Value& tag = visitor.GetResult() ["00280030"];  // PixelSpacing
+    const Json::Value& value = tag["Value"];
+
+    ASSERT_EQ(EnumerationToString(ValueRepresentation_DecimalString), tag["vr"].asString());
+    ASSERT_EQ(2u, value.size());
+    ASSERT_EQ("0.143", value[0].asString());
+    ASSERT_EQ("0.143", value[1].asString());
+  }
+
   std::string xml;
   visitor.FormatXml(xml);
 
   {
     DicomMap m;
     m.FromDicomWeb(visitor.GetResult());
-    ASSERT_EQ(3u, m.GetSize());
+    ASSERT_EQ(4u, m.GetSize());
 
     std::string s;
     ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_PATIENT_NAME, false));
@@ -871,12 +882,12 @@
     ASSERT_EQ(EnumerationToString(ValueRepresentation_DecimalString), tag["vr"].asString());
     ASSERT_EQ(2u, tag.getMemberNames().size());
     ASSERT_EQ(4u, value.size());
-    ASSERT_EQ(Json::realValue, value[0].type());
+    ASSERT_EQ(Json::stringValue, value[0].type());
     ASSERT_EQ(Json::nullValue, value[1].type());
     ASSERT_EQ(Json::nullValue, value[2].type());
-    ASSERT_EQ(Json::realValue, value[3].type());
-    ASSERT_FLOAT_EQ(1.5f, value[0].asFloat());
-    ASSERT_FLOAT_EQ(2.5f, value[3].asFloat());
+    ASSERT_EQ(Json::stringValue, value[3].type());
+    ASSERT_EQ("1.5", value[0].asString());
+    ASSERT_EQ("2.5", value[3].asString());
   }
 
   std::string xml;
@@ -914,8 +925,8 @@
   target.FromDicomWeb(visitor.GetResult());
 
   ASSERT_EQ("DS", visitor.GetResult() ["00280030"]["vr"].asString());
-  ASSERT_FLOAT_EQ(1.5f, visitor.GetResult() ["00280030"]["Value"][0].asFloat());
-  ASSERT_FLOAT_EQ(1.3f, visitor.GetResult() ["00280030"]["Value"][1].asFloat());
+  ASSERT_EQ("1.5", visitor.GetResult() ["00280030"]["Value"][0].asString());
+  ASSERT_EQ("1.3", visitor.GetResult() ["00280030"]["Value"][1].asString());
 
   std::string s;
   ASSERT_TRUE(target.LookupStringValue(s, DICOM_TAG_PIXEL_SPACING, false));
--- a/OrthancFramework/UnitTestsSources/FileStorageTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/FileStorageTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -239,3 +239,92 @@
   ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid(), FileContentType_Unknown), OrthancException);
   */
 }
+
+
+TEST(StorageAccessor, Range)
+{
+  {
+    StorageAccessor::Range range;
+    ASSERT_FALSE(range.HasStart());
+    ASSERT_FALSE(range.HasEnd());
+    ASSERT_THROW(range.GetStartInclusive(), OrthancException);
+    ASSERT_THROW(range.GetEndInclusive(), OrthancException);
+    ASSERT_EQ("bytes 0-99/100", range.FormatHttpContentRange(100));
+    ASSERT_EQ("bytes 0-0/1", range.FormatHttpContentRange(1));
+    ASSERT_THROW(range.FormatHttpContentRange(0), OrthancException);
+    ASSERT_EQ(100u, range.GetContentLength(100));
+    ASSERT_EQ(1u, range.GetContentLength(1));
+    ASSERT_THROW(range.GetContentLength(0), OrthancException);
+
+    range.SetStartInclusive(10);
+    ASSERT_TRUE(range.HasStart());
+    ASSERT_FALSE(range.HasEnd());
+    ASSERT_EQ(10u, range.GetStartInclusive());
+    ASSERT_THROW(range.GetEndInclusive(), OrthancException);
+    ASSERT_EQ("bytes 10-99/100", range.FormatHttpContentRange(100));
+    ASSERT_EQ("bytes 10-10/11", range.FormatHttpContentRange(11));
+    ASSERT_THROW(range.FormatHttpContentRange(10), OrthancException);
+    ASSERT_EQ(90u, range.GetContentLength(100));
+    ASSERT_EQ(1u, range.GetContentLength(11));
+    ASSERT_THROW(range.GetContentLength(10), OrthancException);
+
+    range.SetEndInclusive(30);
+    ASSERT_TRUE(range.HasStart());
+    ASSERT_TRUE(range.HasEnd());
+    ASSERT_EQ(10u, range.GetStartInclusive());
+    ASSERT_EQ(30u, range.GetEndInclusive());
+    ASSERT_EQ("bytes 10-30/100", range.FormatHttpContentRange(100));
+    ASSERT_EQ("bytes 10-30/31", range.FormatHttpContentRange(31));
+    ASSERT_THROW(range.FormatHttpContentRange(30), OrthancException);
+    ASSERT_EQ(21u, range.GetContentLength(100));
+    ASSERT_EQ(21u, range.GetContentLength(31));
+    ASSERT_THROW(range.GetContentLength(30), OrthancException);
+  }
+
+  {
+    StorageAccessor::Range range;
+    range.SetEndInclusive(20);
+    ASSERT_FALSE(range.HasStart());
+    ASSERT_TRUE(range.HasEnd());
+    ASSERT_THROW(range.GetStartInclusive(), OrthancException);
+    ASSERT_EQ(20u, range.GetEndInclusive());
+    ASSERT_EQ("bytes 0-20/100", range.FormatHttpContentRange(100));
+    ASSERT_EQ("bytes 0-20/21", range.FormatHttpContentRange(21));
+    ASSERT_THROW(range.FormatHttpContentRange(20), OrthancException);
+    ASSERT_EQ(21u, range.GetContentLength(100));
+    ASSERT_EQ(21u, range.GetContentLength(21));
+    ASSERT_THROW(range.GetContentLength(20), OrthancException);
+  }
+
+  {
+    StorageAccessor::Range range = StorageAccessor::Range::ParseHttpRange("bytes=1-30");
+    ASSERT_TRUE(range.HasStart());
+    ASSERT_TRUE(range.HasEnd());
+    ASSERT_EQ(1u, range.GetStartInclusive());
+    ASSERT_EQ(30u, range.GetEndInclusive());
+    ASSERT_EQ("bytes 1-30/100", range.FormatHttpContentRange(100));
+  }
+
+  ASSERT_THROW(StorageAccessor::Range::ParseHttpRange("bytes="), OrthancException);
+  ASSERT_THROW(StorageAccessor::Range::ParseHttpRange("bytes=-1-30"), OrthancException);
+  ASSERT_THROW(StorageAccessor::Range::ParseHttpRange("bytes=100-30"), OrthancException);
+
+  ASSERT_EQ("bytes 0-99/100", StorageAccessor::Range::ParseHttpRange("bytes=-").FormatHttpContentRange(100));
+  ASSERT_EQ("bytes 0-10/100", StorageAccessor::Range::ParseHttpRange("bytes=-10").FormatHttpContentRange(100));
+  ASSERT_EQ("bytes 10-99/100", StorageAccessor::Range::ParseHttpRange("bytes=10-").FormatHttpContentRange(100));
+
+  {
+    std::string s;
+    StorageAccessor::Range::ParseHttpRange("bytes=1-2").Extract(s, "Hello");
+    ASSERT_EQ("el", s);
+    StorageAccessor::Range::ParseHttpRange("bytes=-2").Extract(s, "Hello");
+    ASSERT_EQ("Hel", s);
+    StorageAccessor::Range::ParseHttpRange("bytes=3-").Extract(s, "Hello");
+    ASSERT_EQ("lo", s);
+    StorageAccessor::Range::ParseHttpRange("bytes=-").Extract(s, "Hello");
+    ASSERT_EQ("Hello", s);
+    StorageAccessor::Range::ParseHttpRange("bytes=4-").Extract(s, "Hello");
+    ASSERT_EQ("o", s);
+    ASSERT_THROW(StorageAccessor::Range::ParseHttpRange("bytes=5-").Extract(s, "Hello"), OrthancException);
+  }
+}
\ No newline at end of file
--- a/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -2043,7 +2043,7 @@
   ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["vr"].asString());
   ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["Value"][0].asString());
   ASSERT_EQ("DS", visitor.GetResult() ["00101020"]["vr"].asString());
-  ASSERT_FLOAT_EQ(42.0f, visitor.GetResult() ["00101020"]["Value"][0].asFloat());
+  ASSERT_EQ("42", visitor.GetResult() ["00101020"]["Value"][0].asString());
   ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["vr"].asString());
   ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["Value"][0].asString());
   ASSERT_EQ("FL", visitor.GetResult() ["00109431"]["vr"].asString());
@@ -3561,7 +3561,7 @@
   scu.SetCommonClassesProposed(false);
   scu.SetRetiredBigEndianProposed(true);
 
-  DcmtkTranscoder transcoder;
+  DcmtkTranscoder transcoder(1);
 
   for (int j = 0; j < 2; j++)
   {
@@ -3618,7 +3618,7 @@
   DicomTransferSyntax sourceSyntax;
   ASSERT_TRUE(FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, *toto));
 
-  DcmtkTranscoder transcoder;
+  DcmtkTranscoder transcoder(1);
 
   for (int i = 0; i <= DicomTransferSyntax_XML; i++)
   {
--- a/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/ImageTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/ImageTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/JobsTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -103,24 +103,24 @@
     {
     }
 
-    virtual float GetProgress() ORTHANC_OVERRIDE
+    virtual float GetProgress() const ORTHANC_OVERRIDE
     {
       return static_cast<float>(count_) / static_cast<float>(steps_ - 1);
     }
 
-    virtual void GetJobType(std::string& type) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& type) const ORTHANC_OVERRIDE
     {
       type = "DummyJob";
     }
 
-    virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE
+    virtual bool Serialize(Json::Value& value) const ORTHANC_OVERRIDE
     {
       value = Json::objectValue;
       value["Type"] = "DummyJob";
       return true;
     }
 
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE
     {
       value["hello"] = "world";
     }
@@ -199,7 +199,7 @@
     {
     }
 
-    virtual void GetJobType(std::string& s) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& s) const ORTHANC_OVERRIDE
     {
       s = "DummyInstancesJob";
     }
@@ -783,7 +783,7 @@
 
 
 static bool CheckIdempotentSerialization(IJobUnserializer& unserializer,
-                                         IJob& job)
+                                         const IJob& job)
 {
   Json::Value a = 42;
   
@@ -809,7 +809,7 @@
 
 
 static bool CheckIdempotentSetOfInstances(IJobUnserializer& unserializer,
-                                          SetOfInstancesJob& job)
+                                          const SetOfInstancesJob& job)
 {
   Json::Value a = 42;
   
--- a/OrthancFramework/UnitTestsSources/JpegLosslessTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/JpegLosslessTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/LoggingTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/LoggingTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/LuaTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/LuaTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
@@ -73,19 +73,24 @@
   ASSERT_TRUE(c.IsVerbose());
   c.SetVerbose(false);
   ASSERT_FALSE(c.IsVerbose());
+  ASSERT_TRUE(c.IsRedirectionFollowed());
+  c.SetRedirectionFollowed(false);
+  ASSERT_FALSE(c.IsRedirectionFollowed());
 
 #if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1
-  // The "http://www.orthanc-server.com/downloads/third-party/" does
-  // not automatically redirect to HTTPS, so we cas use it even if the
-  // OpenSSL/HTTPS support is disabled in curl
-  const std::string BASE = "http://www.orthanc-server.com/downloads/third-party/";
+  // The "http://httpbin.org/get" URL does not automatically redirect
+  // to HTTPS, so we can use it even if the OpenSSL/HTTPS support is
+  // disabled in curl
 
+  const std::string URL = "http://httpbin.org/get";
+  
   Json::Value v;
-  c.SetUrl(BASE + "Product.json");
+  c.SetUrl(URL);
 
   c.Apply(v);
   ASSERT_TRUE(v.type() == Json::objectValue);
-  ASSERT_TRUE(v.isMember("Description"));
+  ASSERT_TRUE(v.isMember("url"));
+  ASSERT_EQ(URL, v["url"].asString());
 #endif
 }
 #endif
--- a/OrthancFramework/UnitTestsSources/SQLiteChromiumTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/SQLiteChromiumTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/SQLiteTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/SQLiteTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/SharedLibraryUnitTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/SharedLibraryUnitTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/StreamTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/StreamTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/ToolboxTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/ToolboxTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancFramework/UnitTestsSources/ZipTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/ZipTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 Lesser General Public License
--- a/OrthancServer/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -89,7 +89,7 @@
 #####################################################################
 
 set(ORTHANC_SERVER_SOURCES
-  ${CMAKE_SOURCE_DIR}/Sources/Database/BaseDatabaseWrapper.cpp
+  ${CMAKE_SOURCE_DIR}/Sources/Database/BaseCompatibilityTransaction.cpp
   ${CMAKE_SOURCE_DIR}/Sources/Database/Compatibility/DatabaseLookup.cpp
   ${CMAKE_SOURCE_DIR}/Sources/Database/Compatibility/GenericFind.cpp
   ${CMAKE_SOURCE_DIR}/Sources/Database/Compatibility/ICreateInstance.cpp
@@ -139,6 +139,8 @@
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/ArchiveJob.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/CleaningInstancesJob.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/DicomModalityStoreJob.cpp
+  ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp
+  ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/DicomGetScuJob.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/DicomMoveScuJob.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/LuaJobManager.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/MergeStudyJob.cpp
@@ -155,6 +157,7 @@
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/StorageCommitmentScpJob.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/ThreadedSetOfInstancesJob.cpp  
   ${CMAKE_SOURCE_DIR}/Sources/ServerToolbox.cpp
+  ${CMAKE_SOURCE_DIR}/Sources/SimpleInstanceOrdering.cpp
   ${CMAKE_SOURCE_DIR}/Sources/SliceOrdering.cpp
   ${CMAKE_SOURCE_DIR}/Sources/StorageCommitmentReports.cpp
   )
@@ -184,6 +187,7 @@
   ${CMAKE_SOURCE_DIR}/UnitTestsSources/DatabaseLookupTests.cpp
   ${CMAKE_SOURCE_DIR}/UnitTestsSources/LuaServerTests.cpp
   ${CMAKE_SOURCE_DIR}/UnitTestsSources/PluginsTests.cpp
+  ${CMAKE_SOURCE_DIR}/UnitTestsSources/ServerConfigTests.cpp
   ${CMAKE_SOURCE_DIR}/UnitTestsSources/ServerIndexTests.cpp
   ${CMAKE_SOURCE_DIR}/UnitTestsSources/ServerJobsTests.cpp
   ${CMAKE_SOURCE_DIR}/UnitTestsSources/SizeOfTests.cpp
--- a/OrthancServer/OrthancExplorer/explorer.css	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/OrthancExplorer/explorer.css	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/OrthancExplorer/explorer.js	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/OrthancExplorer/explorer.js	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/OrthancExplorer/file-upload.js	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/OrthancExplorer/file-upload.js	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/OrthancExplorer/query-retrieve.js	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/OrthancExplorer/query-retrieve.js	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -341,9 +341,9 @@
       success: function(system) {
         $('#retrieve-target').val(system['DicomAet']);
 
+        $('#retrieve-form').unbind('submit');
         $('#retrieve-form').submit(function(event) {
           var aet;
-
           event.preventDefault();
 
           aet = $('#retrieve-target').val();
--- a/OrthancServer/Plugins/Engine/IPluginServiceProvider.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/IPluginServiceProvider.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -44,7 +44,7 @@
 namespace Orthanc
 {
   class OrthancPluginDatabase::Transaction :
-    public BaseDatabaseWrapper::BaseTransaction,
+    public BaseCompatibilityTransaction,
     public Compatibility::ICreateInstance,
     public Compatibility::IGetChildrenMetadata,
     public Compatibility::ILookupResources,
@@ -808,10 +808,10 @@
     }
 
 
-    virtual void GetAllPublicIds(std::list<std::string>& target,
-                                 ResourceType resourceType,
-                                 int64_t since,
-                                 uint32_t limit) ORTHANC_OVERRIDE
+    virtual void GetAllPublicIdsCompatibility(std::list<std::string>& target,
+                                              ResourceType resourceType,
+                                              int64_t since,
+                                              uint32_t limit) ORTHANC_OVERRIDE
     {
       if (that_.extensions_.getAllPublicIdsWithLimit != NULL)
       {
@@ -1662,4 +1662,9 @@
       LOG(WARNING) << "Received an answer from the database index plugin, but not transaction is active";
     }
   }
+
+  uint64_t OrthancPluginDatabase::MeasureLatency()
+  {
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
 }
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -26,7 +26,7 @@
 #if ORTHANC_ENABLE_PLUGINS == 1
 
 #include "../../../OrthancFramework/Sources/SharedLibrary.h"
-#include "../../Sources/Database/BaseDatabaseWrapper.h"
+#include "../../Sources/Database/BaseCompatibilityTransaction.h"
 #include "../Include/orthanc/OrthancCDatabasePlugin.h"
 #include "PluginsErrorDictionary.h"
 
@@ -46,7 +46,7 @@
    * able to rollback the modifications. Read-only accesses didn't
    * start a transaction, as they were protected by the global mutex.
    **/
-  class OrthancPluginDatabase : public BaseDatabaseWrapper
+  class OrthancPluginDatabase : public IDatabaseWrapper
   {
   private:
     class Transaction;
@@ -111,6 +111,13 @@
     }
 
     void AnswerReceived(const _OrthancPluginDatabaseAnswer& answer);
+
+    uint64_t MeasureLatency() ORTHANC_OVERRIDE;
+
+    bool HasIntegratedFind() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };
 }
 
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -46,7 +46,7 @@
 
 namespace Orthanc
 {
-  class OrthancPluginDatabaseV3::Transaction : public BaseDatabaseWrapper::BaseTransaction
+  class OrthancPluginDatabaseV3::Transaction : public BaseCompatibilityTransaction
   {
   private:
     OrthancPluginDatabaseV3&           that_;
@@ -388,10 +388,10 @@
     }
 
     
-    virtual void GetAllPublicIds(std::list<std::string>& target,
-                                 ResourceType resourceType,
-                                 int64_t since,
-                                 uint32_t limit) ORTHANC_OVERRIDE
+    virtual void GetAllPublicIdsCompatibility(std::list<std::string>& target,
+                                              ResourceType resourceType,
+                                              int64_t since,
+                                              uint32_t limit) ORTHANC_OVERRIDE
     {
       CheckSuccess(that_.backend_.getAllPublicIdsWithLimit(
                      transaction_, Plugins::Convert(resourceType),
@@ -1258,4 +1258,9 @@
     }
   }
 
+  uint64_t OrthancPluginDatabaseV3::MeasureLatency()
+  {
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
 }
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -26,13 +26,13 @@
 #if ORTHANC_ENABLE_PLUGINS == 1
 
 #include "../../../OrthancFramework/Sources/SharedLibrary.h"
-#include "../../Sources/Database/BaseDatabaseWrapper.h"
+#include "../../Sources/Database/BaseCompatibilityTransaction.h"
 #include "../Include/orthanc/OrthancCDatabasePlugin.h"
 #include "PluginsErrorDictionary.h"
 
 namespace Orthanc
 {
-  class OrthancPluginDatabaseV3 : public BaseDatabaseWrapper
+  class OrthancPluginDatabaseV3 : public IDatabaseWrapper
   {
   private:
     class Transaction;
@@ -82,6 +82,13 @@
     {
       return dbCapabilities_;
     }
+
+    uint64_t MeasureLatency() ORTHANC_OVERRIDE;
+
+    bool HasIntegratedFind() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };
 }
 
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -267,6 +267,24 @@
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
+
+    switch (source.GetCast())
+    {
+      case FindRequest::OrderingCast_Int:
+        target.set_cast(DatabasePluginMessages::ORDERING_CAST_INT);
+        break;
+
+      case FindRequest::OrderingCast_Float:
+        target.set_cast(DatabasePluginMessages::ORDERING_CAST_FLOAT);
+        break;
+
+      case FindRequest::OrderingCast_String:
+        target.set_cast(DatabasePluginMessages::ORDERING_CAST_STRING);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
   }
 
   static DatabasePluginMessages::LabelsConstraintType Convert(LabelsConstraint constraint)
@@ -292,6 +310,7 @@
                       const FindRequest::ChildrenSpecification& source)
   {
     target.set_retrieve_identifiers(source.IsRetrieveIdentifiers());
+    target.set_retrieve_count(source.IsRetrieveCount());
 
     for (std::set<MetadataType>::const_iterator it = source.GetMetadata().begin(); it != source.GetMetadata().end(); ++it)
     {
@@ -319,7 +338,8 @@
 
     for (int i = 0; i < source.metadata().size(); i++)
     {
-      target.AddMetadata(level, static_cast<MetadataType>(source.metadata(i).key()), source.metadata(i).value());
+      target.AddMetadata(level, static_cast<MetadataType>(source.metadata(i).key()),
+                         source.metadata(i).value(), source.metadata(i).revision());
     }
   }
 
@@ -333,24 +353,18 @@
       target.AddChildIdentifier(level, source.identifiers(i));
     }
 
+    target.SetChildrenCount(level, source.count());
+
     for (int i = 0; i < source.main_dicom_tags().size(); i++)
     {
       const DicomTag tag(source.main_dicom_tags(i).group(), source.main_dicom_tags(i).element());
-
-      for (int j = 0; j < source.main_dicom_tags(i).values().size(); j++)
-      {
-        target.AddChildrenMainDicomTagValue(level, tag, source.main_dicom_tags(i).values(j));
-      }
+      target.AddChildrenMainDicomTagValue(level, tag, source.main_dicom_tags(i).value());
     }
 
     for (int i = 0; i < source.metadata().size(); i++)
     {
       MetadataType key = static_cast<MetadataType>(source.metadata(i).key());
-
-      for (int j = 0; j < source.metadata(i).values().size(); j++)
-      {
-        target.AddChildrenMetadataValue(level, key, source.metadata(i).values(j));
-      }
+      target.AddChildrenMetadataValue(level, key, source.metadata(i).value());
     }
   }
 
@@ -399,7 +413,9 @@
   }
 
   
-  class OrthancPluginDatabaseV4::Transaction : public IDatabaseWrapper::ITransaction
+  class OrthancPluginDatabaseV4::Transaction :
+    public IDatabaseWrapper::ITransaction,
+    public IDatabaseWrapper::ICompatibilityTransaction
   {
   private:
     OrthancPluginDatabaseV4&  database_;
@@ -561,7 +577,7 @@
       request.mutable_add_attachment()->mutable_attachment()->set_compression_type(attachment.GetCompressionType());
       request.mutable_add_attachment()->mutable_attachment()->set_compressed_size(attachment.GetCompressedSize());
       request.mutable_add_attachment()->mutable_attachment()->set_compressed_hash(attachment.GetCompressedMD5());        
-      request.mutable_add_attachment()->mutable_attachment()->set_custom_data(attachment.GetCustomData());        // new in 1.12.6
+      request.mutable_add_attachment()->mutable_attachment()->set_custom_data(attachment.GetCustomData());        // new in 1.12.7
       request.mutable_add_attachment()->set_revision(revision);
 
       ExecuteTransaction(DatabasePluginMessages::OPERATION_ADD_ATTACHMENT, request);
@@ -675,10 +691,10 @@
     }
 
     
-    virtual void GetAllPublicIds(std::list<std::string>& target,
-                                 ResourceType resourceType,
-                                 int64_t since,
-                                 uint32_t limit) ORTHANC_OVERRIDE
+    virtual void GetAllPublicIdsCompatibility(std::list<std::string>& target,
+                                              ResourceType resourceType,
+                                              int64_t since,
+                                              uint32_t limit) ORTHANC_OVERRIDE
     {
       DatabasePluginMessages::TransactionRequest request;
       request.mutable_get_all_public_ids_with_limits()->set_resource_type(Convert(resourceType));
@@ -1477,6 +1493,63 @@
     }
 
 
+    virtual void ExecuteCount(uint64_t& count,
+                              const FindRequest& request,
+                              const Capabilities& capabilities) ORTHANC_OVERRIDE
+    {
+      if (capabilities.HasFindSupport())
+      {
+        DatabasePluginMessages::TransactionRequest dbRequest;
+        dbRequest.mutable_find()->set_level(Convert(request.GetLevel()));
+
+        if (request.GetOrthancIdentifiers().HasPatientId())
+        {
+          dbRequest.mutable_find()->set_orthanc_id_patient(request.GetOrthancIdentifiers().GetPatientId());
+        }
+
+        if (request.GetOrthancIdentifiers().HasStudyId())
+        {
+          dbRequest.mutable_find()->set_orthanc_id_study(request.GetOrthancIdentifiers().GetStudyId());
+        }
+
+        if (request.GetOrthancIdentifiers().HasSeriesId())
+        {
+          dbRequest.mutable_find()->set_orthanc_id_series(request.GetOrthancIdentifiers().GetSeriesId());
+        }
+
+        if (request.GetOrthancIdentifiers().HasInstanceId())
+        {
+          dbRequest.mutable_find()->set_orthanc_id_instance(request.GetOrthancIdentifiers().GetInstanceId());
+        }
+
+        for (size_t i = 0; i < request.GetDicomTagConstraints().GetSize(); i++)
+        {
+          Convert(*dbRequest.mutable_find()->add_dicom_tag_constraints(), request.GetDicomTagConstraints().GetConstraint(i));
+        }
+
+        for (std::deque<DatabaseMetadataConstraint*>::const_iterator it = request.GetMetadataConstraint().begin(); it != request.GetMetadataConstraint().end(); ++it)
+        {
+          Convert(*dbRequest.mutable_find()->add_metadata_constraints(), *(*it)); 
+        }
+
+        for (std::set<std::string>::const_iterator it = request.GetLabels().begin(); it != request.GetLabels().end(); ++it)
+        {
+          dbRequest.mutable_find()->add_labels(*it);
+        }
+
+        dbRequest.mutable_find()->set_labels_constraint(Convert(request.GetLabelsConstraint()));
+
+        DatabasePluginMessages::TransactionResponse dbResponse;
+        ExecuteTransaction(dbResponse, DatabasePluginMessages::OPERATION_COUNT_RESOURCES, dbRequest);
+
+        count = dbResponse.count_resources().count();
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_NotImplemented);
+      }
+    }
+
     virtual void ExecuteFind(FindResponse& response,
                              const FindRequest& request,
                              const Capabilities& capabilities) ORTHANC_OVERRIDE
@@ -1603,14 +1676,19 @@
             target->SetParentIdentifier(source.parent_public_id());
           }
 
-          for (int i = 0; i < source.labels().size(); i++)
+          for (int j = 0; j < source.labels().size(); j++)
           {
-            target->AddLabel(source.labels(i));
+            target->AddLabel(source.labels(j));
           }
 
-          for (int i = 0; i < source.attachments().size(); i++)
+          if (source.attachments().size() != source.attachments_revisions().size())
           {
-            target->AddAttachment(Convert(source.attachments(i)));
+            throw OrthancException(ErrorCode_DatabasePlugin);
+          }
+
+          for (int j = 0; j < source.attachments().size(); j++)
+          {
+            target->AddAttachment(Convert(source.attachments(j)), source.attachments_revisions(j));
           }
 
           Convert(*target, ResourceType_Patient, source.patient_content());
@@ -1655,12 +1733,12 @@
               request.IsRetrieveOneInstanceMetadataAndAttachments())
           {
             std::map<MetadataType, std::string> metadata;
-            for (int i = 0; i < source.one_instance_metadata().size(); i++)
+            for (int j = 0; j < source.one_instance_metadata().size(); j++)
             {
-              MetadataType key = static_cast<MetadataType>(source.one_instance_metadata(i).key());
+              MetadataType key = static_cast<MetadataType>(source.one_instance_metadata(j).key());
               if (metadata.find(key) == metadata.end())
               {
-                metadata[key] = source.one_instance_metadata(i).value();
+                metadata[key] = source.one_instance_metadata(j).value();
               }
               else
               {
@@ -1670,9 +1748,9 @@
 
             std::map<FileContentType, FileInfo> attachments;
 
-            for (int i = 0; i < source.one_instance_attachments().size(); i++)
+            for (int j = 0; j < source.one_instance_attachments().size(); j++)
             {
-              FileInfo info(Convert(source.one_instance_attachments(i)));
+              FileInfo info(Convert(source.one_instance_attachments(j)));
               if (attachments.find(info.GetContentType()) == attachments.end())
               {
                 attachments[info.GetContentType()] = info;
@@ -1707,7 +1785,7 @@
       }
       else
       {
-        Compatibility::GenericFind find(*this);
+        Compatibility::GenericFind find(*this, *this);
         find.ExecuteFind(identifiers, capabilities, request);
       }
     }
@@ -1725,7 +1803,7 @@
       }
       else
       {
-        Compatibility::GenericFind find(*this);
+        Compatibility::GenericFind find(*this, *this);
         find.ExecuteExpand(response, capabilities, request, identifier);
       }
     }
@@ -1815,7 +1893,7 @@
       dbCapabilities_.SetRevisionsSupport(systemInfo.supports_revisions());
       dbCapabilities_.SetLabelsSupport(systemInfo.supports_labels());
       dbCapabilities_.SetAtomicIncrementGlobalProperty(systemInfo.supports_increment_global_property());
-      dbCapabilities_.SetUpdateAndGetStatistics(systemInfo.has_update_and_get_statistics());
+      dbCapabilities_.SetHasUpdateAndGetStatistics(systemInfo.has_update_and_get_statistics());
       dbCapabilities_.SetMeasureLatency(systemInfo.has_measure_latency());
       dbCapabilities_.SetHasExtendedChanges(systemInfo.has_extended_changes());
       dbCapabilities_.SetHasFindSupport(systemInfo.supports_find());
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -1471,40 +1471,41 @@
     class PluginHttpOutput : public boost::noncopyable
     {
     private:
-      enum MultipartState
-      {
-        MultipartState_None,
-        MultipartState_FirstPart,
-        MultipartState_SecondPart,
-        MultipartState_NextParts
+      enum State
+      {
+        State_None,
+        State_MultipartFirstPart,
+        State_MultipartSecondPart,
+        State_MultipartNextParts,
+        State_WritingStream
       };
 
       HttpOutput&                 output_;
       std::unique_ptr<std::string>  errorDetails_;
       bool                        logDetails_;
-      MultipartState              multipartState_;
+      State                       state_;
       std::string                 multipartSubType_;
       std::string                 multipartContentType_;
       std::string                 multipartFirstPart_;
       std::map<std::string, std::string>  multipartFirstHeaders_;
-      
+
     public:
       explicit PluginHttpOutput(HttpOutput& output) :
         output_(output),
         logDetails_(false),
-        multipartState_(MultipartState_None)
+        state_(State_None)
       {
       }
 
       HttpOutput& GetOutput()
       {
-        if (multipartState_ == MultipartState_None)
+        if (state_ == State_None)
         {
           return output_;
         }
         else
         {
-          // Must use "SendMultipartItem()" on multipart streams
+          // Must use "SendMultipartItem()" on multipart streams or "SendStreamChunk()"
           throw OrthancException(ErrorCode_BadSequenceOfCalls);
         }
       }
@@ -1541,17 +1542,44 @@
       void StartMultipart(const char* subType,
                           const char* contentType)
       {
-        if (multipartState_ != MultipartState_None)
+        if (state_ != State_None)
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+        else
+        {
+          state_ = State_MultipartFirstPart;
+          multipartSubType_ = subType;
+          multipartContentType_ = contentType;
+        }
+      }
+
+      void StartStream(const char* contentType)
+      {
+        if (state_ != State_None)
         {
           throw OrthancException(ErrorCode_BadSequenceOfCalls);
         }
         else
         {
-          multipartState_ = MultipartState_FirstPart;
-          multipartSubType_ = subType;
-          multipartContentType_ = contentType;
-        }
-      }
+          output_.StartStream(contentType);
+          state_ = State_WritingStream;
+        }
+      }
+
+      void SendStreamItem(const void* data,
+                          size_t size)
+      {
+        if (state_ != State_WritingStream)
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+        else
+        {
+          output_.SendStreamItem(data, size);
+        }
+      }
+
 
       void SendMultipartItem(const void* data,
                              size_t size,
@@ -1562,19 +1590,19 @@
           throw OrthancException(ErrorCode_NullPointer);
         }
 
-        switch (multipartState_)
-        {
-          case MultipartState_None:
+        switch (state_)
+        {
+          case State_None:
             // Must call "StartMultipart()" before
             throw OrthancException(ErrorCode_BadSequenceOfCalls);
 
-          case MultipartState_FirstPart:
+          case State_MultipartFirstPart:
             multipartFirstPart_.assign(reinterpret_cast<const char*>(data), size);
             multipartFirstHeaders_ = headers;
-            multipartState_ = MultipartState_SecondPart;
+            state_ = State_MultipartSecondPart;
             break;
 
-          case MultipartState_SecondPart:
+          case State_MultipartSecondPart:
             // Start an actual stream for chunked transfer as soon as
             // there are more than 2 elements in the multipart stream
             output_.StartMultipart(multipartSubType_, multipartContentType_);
@@ -1583,10 +1611,10 @@
             multipartFirstPart_.clear();  // Release memory
 
             output_.SendMultipartItem(data, size, headers);
-            multipartState_ = MultipartState_NextParts;
+            state_ = State_MultipartNextParts;
             break;
 
-          case MultipartState_NextParts:
+          case State_MultipartNextParts:
             output_.SendMultipartItem(data, size, headers);
             break;
 
@@ -1600,21 +1628,21 @@
       {
         if (error == OrthancPluginErrorCode_Success)
         {
-          switch (multipartState_)
+          switch (state_)
           {
-            case MultipartState_None:
+            case State_None:
               assert(!output_.IsWritingMultipart());
               break;
 
-            case MultipartState_FirstPart:   // Multipart started, but no part was sent
-            case MultipartState_SecondPart:  // Multipart started, first part is pending
+            case State_MultipartFirstPart:   // Multipart started, but no part was sent
+            case State_MultipartSecondPart:  // Multipart started, first part is pending
             {
               assert(!output_.IsWritingMultipart());
               std::vector<const void*> parts;
               std::vector<size_t> sizes;
               std::vector<const std::map<std::string, std::string>*> headers;
 
-              if (multipartState_ == MultipartState_SecondPart)
+              if (state_ == State_MultipartSecondPart)
               {
                 parts.push_back(multipartFirstPart_.c_str());
                 sizes.push_back(multipartFirstPart_.size());
@@ -1626,9 +1654,15 @@
               break;
             }
 
-            case MultipartState_NextParts:
+            case State_MultipartNextParts:
               assert(output_.IsWritingMultipart());
               output_.CloseMultipart();
+              break;
+
+            case State_WritingStream:
+              assert(output_.IsWritingStream());
+              output_.CloseStream();
+              break;
 
             default:
               throw OrthancException(ErrorCode_InternalError);
@@ -4766,9 +4800,8 @@
             PImpl::ServerContextReference lock(*pimpl_);
 
             std::string s;
-            int64_t revision;  // unused
             if (lock.GetContext().GetIndex().LookupMetadata(
-                  s, revision, params.instanceId,
+                  s, params.instanceId,
                   ResourceType_Instance, MetadataType_Instance_PixelDataVR))
             {
               hasPixelData = true;
@@ -4787,7 +4820,7 @@
               }
             }
             else if (lock.GetContext().GetIndex().LookupMetadata(
-                       s, revision, params.instanceId,
+                       s, params.instanceId,
                        ResourceType_Instance, MetadataType_Instance_PixelDataOffset))
             {
               // This file was stored by an older version of Orthanc,
@@ -4902,7 +4935,7 @@
 
     if (entry == NULL)
     {
-      throw OrthancException(ErrorCode_UnknownDicomTag);
+      throw OrthancException(ErrorCode_UnknownDicomTag, p.name);
     }
     else
     {
@@ -5144,6 +5177,21 @@
         ApplySendMultipartItem2(parameters);
         return true;
 
+      case _OrthancPluginService_StartStreamAnswer:
+      {
+        const _OrthancPluginStartStreamAnswer& p =
+          *reinterpret_cast<const _OrthancPluginStartStreamAnswer*>(parameters);
+        reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->StartStream(p.contentType);
+        return true;
+      }
+
+      case _OrthancPluginService_SendStreamChunk:
+      {
+        const _OrthancPluginAnswerBuffer& p =
+          *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
+        reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendStreamItem(p.answer, p.answerSize);
+        return true;
+      }
       case _OrthancPluginService_ReadFile:
       {
         const _OrthancPluginReadFile& p =
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Engine/PluginsEnumerations.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Engine/PluginsErrorDictionary.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsErrorDictionary.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Engine/PluginsErrorDictionary.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsErrorDictionary.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Engine/PluginsJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -147,7 +147,7 @@
     }
   }
 
-  float PluginsJob::GetProgress()
+  float PluginsJob::GetProgress() const
   {
     return parameters_.getProgress(parameters_.job);
   }
@@ -194,7 +194,7 @@
     };
   }
   
-  void PluginsJob::GetPublicContent(Json::Value& value)
+  void PluginsJob::GetPublicContent(Json::Value& value) const
   {
     if (parameters_.getContent != NULL)
     {
@@ -232,7 +232,7 @@
     }
   }
 
-  bool PluginsJob::Serialize(Json::Value& value)
+  bool PluginsJob::Serialize(Json::Value& value) const
   {
     if (parameters_.getSerialized != NULL)
     {
--- a/OrthancServer/Plugins/Engine/PluginsJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -58,16 +58,16 @@
 
     virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
 
-    virtual float GetProgress() ORTHANC_OVERRIDE;
+    virtual float GetProgress() const ORTHANC_OVERRIDE;
 
-    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& target) const ORTHANC_OVERRIDE
     {
       target = type_;
     }
     
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& value) const ORTHANC_OVERRIDE;
 
     virtual bool GetOutput(std::string& output,
                            MimeType& mime,
--- a/OrthancServer/Plugins/Engine/PluginsManager.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsManager.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -244,8 +244,21 @@
   {
     if (!boost::filesystem::exists(path))
     {
-      LOG(ERROR) << "Inexistent path to plugins: " << path;
-      return;
+      boost::filesystem::path p(path);
+      std::string extension = p.extension().string();
+      Toolbox::ToLowerCase(extension);
+
+      if (extension == PLUGIN_EXTENSION)
+      { 
+        // if this is a plugin path, fail to start
+        throw OrthancException(ErrorCode_SharedLibrary, "Inexistent path to plugin: " + path);
+      }
+      else
+      { 
+        // it might be a directory -> just log a warning
+        LOG(WARNING) << "Inexistent path to plugins: " << path;
+        return;
+      }
     }
 
     if (boost::filesystem::is_directory(path))
--- a/OrthancServer/Plugins/Engine/PluginsManager.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsManager.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Thu Jan 30 17:41:33 2025 +0100
@@ -7,8 +7,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Thu Jan 30 17:41:33 2025 +0100
@@ -86,8 +86,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -121,7 +121,7 @@
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     12
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  6
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  7
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -324,6 +324,7 @@
     OrthancPluginErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
     OrthancPluginErrorCode_NoStorageCommitmentHandler = 2043    /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */,
     OrthancPluginErrorCode_NoCGetHandler = 2044    /*!< No request handler factory for DICOM C-GET SCP */,
+    OrthancPluginErrorCode_DicomGetUnavailable = 2045    /*!< DicomUserConnection: The C-GET command is not supported by the remote SCP */,
     OrthancPluginErrorCode_UnsupportedMediaType = 3000    /*!< Unsupported media type */,
 
     _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
@@ -508,6 +509,8 @@
     _OrthancPluginService_CompressAndAnswerImage = 2011,
     _OrthancPluginService_SendMultipartItem2 = 2012,
     _OrthancPluginService_SetHttpErrorDetails = 2013,
+    _OrthancPluginService_StartStreamAnswer = 2014,
+    _OrthancPluginService_SendStreamChunk = 2015,
 
     /* Access to the Orthanc database and API */
     _OrthancPluginService_GetDicomForInstance = 3000,
@@ -1444,7 +1447,7 @@
   /**
    * @brief Callback for writing to the storage area.
    *
-   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
+   * Signature of a callback function that is triggered when Orthanc writes an instance to the storage area.
    *
    * @param customData The custom data of the attachment (out)
    * @param uuid The UUID of the file.
@@ -9727,6 +9730,67 @@
     context->InvokeService(context, _OrthancPluginService_LogMessage, &m);
   }
 
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              contentType;
+  } _OrthancPluginStartStreamAnswer;
+
+  /**
+   * @brief Start an HTTP stream answer.
+   *
+   * Initiates an HTTP stream answer, as the result of a REST request.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param contentType The MIME type of the items in the stream answer.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginSendStreamChunk()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartStreamAnswer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              contentType)
+  {
+    _OrthancPluginStartStreamAnswer params;
+    params.output = output;
+    params.contentType = contentType;
+    return context->InvokeService(context, _OrthancPluginService_StartStreamAnswer, &params);
+  }
+
+
+  /**
+   * @brief Send a chunk as a part of an HTTP stream answer.
+   *
+   * This function sends a chunk as part of an HTTP stream
+   * answer that was initiated by OrthancPluginStartStreamAnswer().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the item.
+   * @param answerSize Number of bytes of the item.
+   * @return 0 if success, or the error code if failure (this notably happens
+   * if the connection is closed by the client).
+   * @see OrthancPluginStartStreamAnswer()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendStreamChunk(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const void*              answer,
+    uint32_t                 answerSize)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = NULL;
+    return context->InvokeService(context, _OrthancPluginService_SendStreamChunk, &params);
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif
--- a/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -55,7 +55,7 @@
   int32   compression_type = 5;  // opaque "CompressionType" in Orthanc
   uint64  compressed_size = 6;
   string  compressed_hash = 7;
-  string  custom_data = 8;       // added in v 1.12.5
+  string  custom_data = 8;       // added in v 1.12.7
 }
 
 enum ResourceType {
@@ -89,6 +89,12 @@
   ORDERING_DIRECTION_DESC = 1;
 }
 
+enum OrderingCast {
+  ORDERING_CAST_STRING = 0;
+  ORDERING_CAST_INT = 1;
+  ORDERING_CAST_FLOAT = 2;
+}
+
 message ServerIndexChange {
   int64         seq = 1;
   int32         change_type = 2;   // opaque "ChangeType" in Orthanc
@@ -315,6 +321,7 @@
   OPERATION_UPDATE_AND_GET_STATISTICS = 49;   // New in Orthanc 1.12.3
   OPERATION_FIND = 50;                        // New in Orthanc 1.12.5
   OPERATION_GET_CHANGES_EXTENDED = 51;        // New in Orthanc 1.12.5
+  OPERATION_COUNT_RESOURCES = 52;             // New in Orthanc 1.12.5
 }
 
 message Rollback {
@@ -877,15 +884,17 @@
       bool retrieve_identifiers = 1;
       repeated int32 retrieve_metadata = 2;
       repeated Tag retrieve_main_dicom_tags = 3;
+      bool retrieve_count = 4;
     }
     message Ordering {
       OrderingKeyType key_type = 1;
       OrderingDirection direction = 2;
-      uint32 tag_group = 3;
-      uint32 tag_element = 4;
-      bool is_identifier_tag = 5;
-      ResourceType tag_level = 6;
-      int32 metadata = 7;
+      OrderingCast cast = 3;
+      uint32 tag_group = 4;
+      uint32 tag_element = 5;
+      bool is_identifier_tag = 6;
+      ResourceType tag_level = 7;
+      int32 metadata = 8;
     }
 
     // Part 1 of the request: Constraints
@@ -926,15 +935,7 @@
     message Metadata {
       int32 key = 1;
       string value = 2;
-    }
-    message MultipleTags {
-      uint32 group = 1;
-      uint32 element = 2;
-      repeated string values = 3;
-    }
-    message MultipleMetadata {
-      int32 key = 1;
-      repeated string values = 2;
+      int64 revision = 3;
     }
     message ResourceContent {
       repeated Tag main_dicom_tags = 1;
@@ -942,8 +943,9 @@
     }
     message ChildrenContent {
       repeated string identifiers = 1;
-      repeated MultipleTags main_dicom_tags = 2;
-      repeated MultipleMetadata metadata = 3;
+      repeated Tag main_dicom_tags = 2;
+      repeated Metadata metadata = 3;  // As of Orthanc 1.12.5, the "revision" field is unused in this case
+      uint64 count = 4;
     }
 
     int64 internal_id = 1;
@@ -961,6 +963,15 @@
     string one_instance_public_id = 13;
     repeated Metadata one_instance_metadata = 14;
     repeated FileInfo one_instance_attachments = 15;
+    repeated int64 attachments_revisions = 16;
+  }
+}
+
+message CountResources
+{
+  message Response
+  {
+    uint64 count = 1;
   }
 }
 
@@ -1020,6 +1031,7 @@
   UpdateAndGetStatistics.Request          update_and_get_statistics = 149;
   Find.Request                            find = 150;
   GetChangesExtended.Request              get_changes_extended = 151;
+  Find.Request                            count_resources = 152;
 }
 
 message TransactionResponse {
@@ -1073,8 +1085,9 @@
   ListLabels.Response                      list_labels = 147;
   IncrementGlobalProperty.Response         increment_global_property = 148;
   UpdateAndGetStatistics.Response          update_and_get_statistics = 149;
-  repeated Find.Response                   find = 150;   // One message per found resources
+  repeated Find.Response                   find = 150;   // One message per found resource
   GetChangesExtended.Response              get_changes_extended = 151;
+  CountResources.Response                  count_resources = 152;
 }
 
 enum RequestType {
--- a/OrthancServer/Plugins/Samples/AutomatedJpeg2kCompression/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/AutomatedJpeg2kCompression/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/AutomatedJpeg2kCompression/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/AutomatedJpeg2kCompression/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/Basic/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/Basic/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/Basic/Plugin.c	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/Basic/Plugin.c	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -334,9 +334,9 @@
     std::vector<const char*> headersValues_;
 
   public:
-    explicit PluginHttpHeaders(const std::map<std::string, std::string>& httpHeaders)
-    {
-      for (std::map<std::string, std::string>::const_iterator
+    explicit PluginHttpHeaders(const HttpHeaders& httpHeaders)
+    {
+      for (HttpHeaders::const_iterator
              it = httpHeaders.begin(); it != httpHeaders.end(); ++it)
       {
         headersKeys_.push_back(it->first.c_str());
@@ -361,7 +361,7 @@
   };
 
   bool MemoryBuffer::RestApiGet(const std::string& uri,
-                                const std::map<std::string, std::string>& httpHeaders,
+                                const HttpHeaders& httpHeaders,
                                 bool applyPlugins)
   {
     Clear();
@@ -400,7 +400,7 @@
   bool MemoryBuffer::RestApiPost(const std::string& uri,
                                  const void* body,
                                  size_t bodySize,
-                                 const std::map<std::string, std::string>& httpHeaders,
+                                 const HttpHeaders& httpHeaders,
                                  bool applyPlugins)
   {
     MemoryBuffer answerHeaders;
@@ -422,7 +422,7 @@
 
   bool MemoryBuffer::RestApiPost(const std::string& uri,
                                  const Json::Value& body,
-                                 const std::map<std::string, std::string>& httpHeaders,
+                                 const HttpHeaders& httpHeaders,
                                  bool applyPlugins)
   {
     std::string s;
@@ -1490,7 +1490,7 @@
 
   bool RestApiGetString(std::string& result,
                         const std::string& uri,
-                        const std::map<std::string, std::string>& httpHeaders,
+                        const HttpHeaders& httpHeaders,
                         bool applyPlugins)
   {
     MemoryBuffer answer;
@@ -1508,7 +1508,7 @@
 
   bool RestApiGet(Json::Value& result,
                   const std::string& uri,
-                  const std::map<std::string, std::string>& httpHeaders,
+                  const HttpHeaders& httpHeaders,
                   bool applyPlugins)
   {
     MemoryBuffer answer;
@@ -1598,7 +1598,7 @@
   bool RestApiPost(Json::Value& result,
                    const std::string& uri,
                    const Json::Value& body,
-                   const std::map<std::string, std::string>& httpHeaders,
+                   const HttpHeaders& httpHeaders,
                    bool applyPlugins)
   {
     MemoryBuffer answer;
@@ -1963,7 +1963,7 @@
   bool OrthancPeers::DoGet(MemoryBuffer& target,
                            size_t index,
                            const std::string& uri,
-                           const std::map<std::string, std::string>& headers) const
+                           const HttpHeaders& headers) const
   {
     if (index >= index_.size())
     {
@@ -1994,7 +1994,7 @@
   bool OrthancPeers::DoGet(MemoryBuffer& target,
                            const std::string& name,
                            const std::string& uri,
-                           const std::map<std::string, std::string>& headers) const
+                           const HttpHeaders& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
@@ -2005,7 +2005,7 @@
   bool OrthancPeers::DoGet(Json::Value& target,
                            size_t index,
                            const std::string& uri,
-                           const std::map<std::string, std::string>& headers) const
+                           const HttpHeaders& headers) const
   {
     MemoryBuffer buffer;
 
@@ -2024,7 +2024,7 @@
   bool OrthancPeers::DoGet(Json::Value& target,
                            const std::string& name,
                            const std::string& uri,
-                           const std::map<std::string, std::string>& headers) const
+                           const HttpHeaders& headers) const
   {
     MemoryBuffer buffer;
 
@@ -2044,7 +2044,7 @@
                             const std::string& name,
                             const std::string& uri,
                             const std::string& body,
-                            const std::map<std::string, std::string>& headers) const
+                            const HttpHeaders& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
@@ -2056,7 +2056,7 @@
                             size_t index,
                             const std::string& uri,
                             const std::string& body,
-                            const std::map<std::string, std::string>& headers) const
+                            const HttpHeaders& headers) const
   {
     MemoryBuffer buffer;
 
@@ -2076,7 +2076,7 @@
                             const std::string& name,
                             const std::string& uri,
                             const std::string& body,
-                            const std::map<std::string, std::string>& headers) const
+                            const HttpHeaders& headers) const
   {
     MemoryBuffer buffer;
 
@@ -2096,7 +2096,7 @@
                             size_t index,
                             const std::string& uri,
                             const std::string& body,
-                            const std::map<std::string, std::string>& headers) const
+                            const HttpHeaders& headers) const
   {
     if (index >= index_.size())
     {
@@ -2133,7 +2133,7 @@
   bool OrthancPeers::DoPut(size_t index,
                            const std::string& uri,
                            const std::string& body,
-                           const std::map<std::string, std::string>& headers) const
+                           const HttpHeaders& headers) const
   {
     if (index >= index_.size())
     {
@@ -2169,7 +2169,7 @@
   bool OrthancPeers::DoPut(const std::string& name,
                            const std::string& uri,
                            const std::string& body,
-                           const std::map<std::string, std::string>& headers) const
+                           const HttpHeaders& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
@@ -2179,7 +2179,7 @@
 
   bool OrthancPeers::DoDelete(size_t index,
                               const std::string& uri,
-                              const std::map<std::string, std::string>& headers) const
+                              const HttpHeaders& headers) const
   {
     if (index >= index_.size())
     {
@@ -2208,7 +2208,7 @@
 
   bool OrthancPeers::DoDelete(const std::string& name,
                               const std::string& uri,
-                              const std::map<std::string, std::string>& headers) const
+                              const HttpHeaders& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
@@ -2923,12 +2923,12 @@
       std::vector<const char*>  headersValues_;
 
     public:
-      HeadersWrapper(const HttpClient::HttpHeaders& headers)
+      HeadersWrapper(const HttpHeaders& headers)
       {
         headersKeys_.reserve(headers.size());
         headersValues_.reserve(headers.size());
 
-        for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
+        for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
         {
           headersKeys_.push_back(it->first.c_str());
           headersValues_.push_back(it->second.c_str());
@@ -3076,11 +3076,11 @@
     class MemoryAnswer : public HttpClient::IAnswer
     {
     private:
-      HttpClient::HttpHeaders  headers_;
-      ChunkedBuffer            body_;
+      HttpHeaders    headers_;
+      ChunkedBuffer  body_;
 
     public:
-      const HttpClient::HttpHeaders& GetHeaders() const
+      const HttpHeaders& GetHeaders() const
       {
         return headers_;
       }
@@ -3168,6 +3168,35 @@
 #endif    
 
 
+  static void DecodeHttpHeaders(HttpHeaders& target,
+                                const MemoryBuffer& source)
+  {
+    Json::Value v;
+    source.ToJson(v);
+
+    if (v.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    Json::Value::Members members = v.getMemberNames();
+    target.clear();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& h = v[members[i]];
+      if (h.type() != Json::stringValue)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+      }
+      else
+      {
+        target[members[i]] = h.asString();
+      }
+    }
+  }
+
+
   void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
                                         HttpHeaders& answerHeaders,
                                         std::string& answerBody,
@@ -3208,30 +3237,7 @@
       ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
     }
 
-    Json::Value v;
-    answerHeadersBuffer.ToJson(v);
-
-    if (v.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    Json::Value::Members members = v.getMemberNames();
-    answerHeaders.clear();
-
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      const Json::Value& h = v[members[i]];
-      if (h.type() != Json::stringValue)
-      {
-        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-      }
-      else
-      {
-        answerHeaders[members[i]] = h.asString();
-      }
-    }
-
+    DecodeHttpHeaders(answerHeaders, answerHeadersBuffer);
     answerBodyBuffer.ToString(answerBody);
   }
 
@@ -4061,7 +4067,7 @@
   }
 #endif
 
-  void GetHttpHeaders(std::map<std::string, std::string>& result, const OrthancPluginHttpRequest* request)
+  void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request)
   {
     result.clear();
 
@@ -4114,4 +4120,135 @@
     SetPluginProperty(pluginIdentifier, _OrthancPluginProperty_OrthancExplorer, javascript);
 #endif
   }
+
+
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+  RestApiClient::RestApiClient() :
+    method_(OrthancPluginHttpMethod_Get),
+    path_("/"),
+    afterPlugins_(false),
+    httpStatus_(0)
+  {
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+  void RestApiClient::AddRequestHeader(const std::string& key,
+                                       const std::string& value)
+  {
+    if (requestHeaders_.find(key) == requestHeaders_.end())
+    {
+      requestHeaders_[key] = value;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+  bool RestApiClient::Execute()
+  {
+    if (requestBody_.size() > 0xffffffffu)
+    {
+      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    PluginHttpHeaders converted(requestHeaders_);
+
+    MemoryBuffer body;
+    MemoryBuffer headers;
+
+    OrthancPluginErrorCode code = OrthancPluginCallRestApi(GetGlobalContext(), *body, *headers, &httpStatus_, method_, path_.c_str(),
+                                                           requestHeaders_.size(), converted.GetKeys(), converted.GetValues(),
+                                                           requestBody_.c_str(), requestBody_.size(), afterPlugins_ ? 1 : 0);
+
+    answerHeaders_.clear();
+    answerBody_.clear();
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      if (httpStatus_ == 0)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+      }
+
+      DecodeHttpHeaders(answerHeaders_, headers);
+      body.ToString(answerBody_);
+      return true;
+    }
+    else
+    {
+      if (code == OrthancPluginErrorCode_UnknownResource ||
+          code == OrthancPluginErrorCode_InexistentItem)
+      {
+        httpStatus_ = 404;
+        return false;
+      }
+      else
+      {
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+      }
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+  uint16_t RestApiClient::GetHttpStatus() const
+  {
+    if (httpStatus_ == 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+    else
+    {
+      return httpStatus_;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+  bool RestApiClient::LookupAnswerHeader(std::string& value,
+                                         const std::string& key) const
+  {
+    if (httpStatus_ == 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+    else
+    {
+      HttpHeaders::const_iterator found = answerHeaders_.find(key);
+      if (found == answerHeaders_.end())
+      {
+        return false;
+      }
+      else
+      {
+        value = found->second;
+        return true;
+      }
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+  const std::string& RestApiClient::GetAnswerBody() const
+  {
+    if (httpStatus_ == 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+    }
+    else
+    {
+      return answerBody_;
+    }
+  }
+#endif
 }
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -170,6 +170,8 @@
 
 namespace OrthancPlugins
 {
+  typedef std::map<std::string, std::string>  HttpHeaders;
+
   typedef void (*RestCallback) (OrthancPluginRestOutput* output,
                                 const char* url,
                                 const OrthancPluginHttpRequest* request);
@@ -257,7 +259,7 @@
                     bool applyPlugins);
 
     bool RestApiGet(const std::string& uri,
-                    const std::map<std::string, std::string>& httpHeaders,
+                    const HttpHeaders& httpHeaders,
                     bool applyPlugins);
 
     bool RestApiPost(const std::string& uri,
@@ -277,13 +279,13 @@
 #if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
     bool RestApiPost(const std::string& uri,
                      const Json::Value& body,
-                     const std::map<std::string, std::string>& httpHeaders,
+                     const HttpHeaders& httpHeaders,
                      bool applyPlugins);
 
     bool RestApiPost(const std::string& uri,
                      const void* body,
                      size_t bodySize,
-                     const std::map<std::string, std::string>& httpHeaders,
+                     const HttpHeaders& httpHeaders,
                      bool applyPlugins);
 #endif
 
@@ -581,7 +583,7 @@
 
   bool RestApiGet(Json::Value& result,
                   const std::string& uri,
-                  const std::map<std::string, std::string>& httpHeaders,
+                  const HttpHeaders& httpHeaders,
                   bool applyPlugins);
 
   bool RestApiGetString(std::string& result,
@@ -590,7 +592,7 @@
 
   bool RestApiGetString(std::string& result,
                         const std::string& uri,
-                        const std::map<std::string, std::string>& httpHeaders,
+                        const HttpHeaders& httpHeaders,
                         bool applyPlugins);
 
   bool RestApiPost(std::string& result,
@@ -609,7 +611,7 @@
   bool RestApiPost(Json::Value& result,
                    const std::string& uri,
                    const Json::Value& body,
-                   const std::map<std::string, std::string>& httpHeaders,
+                   const HttpHeaders& httpHeaders,
                    bool applyPlugins);
 #endif
 
@@ -829,64 +831,64 @@
     bool DoGet(MemoryBuffer& target,
                size_t index,
                const std::string& uri,
-               const std::map<std::string, std::string>& headers) const;
+               const HttpHeaders& headers) const;
 
     bool DoGet(MemoryBuffer& target,
                const std::string& name,
                const std::string& uri,
-               const std::map<std::string, std::string>& headers) const;
+               const HttpHeaders& headers) const;
 
     bool DoGet(Json::Value& target,
                size_t index,
                const std::string& uri,
-               const std::map<std::string, std::string>& headers) const;
+               const HttpHeaders& headers) const;
 
     bool DoGet(Json::Value& target,
                const std::string& name,
                const std::string& uri,
-               const std::map<std::string, std::string>& headers) const;
+               const HttpHeaders& headers) const;
 
     bool DoPost(MemoryBuffer& target,
                 size_t index,
                 const std::string& uri,
                 const std::string& body,
-                const std::map<std::string, std::string>& headers) const;
+                const HttpHeaders& headers) const;
 
     bool DoPost(MemoryBuffer& target,
                 const std::string& name,
                 const std::string& uri,
                 const std::string& body,
-                const std::map<std::string, std::string>& headers) const;
+                const HttpHeaders& headers) const;
 
     bool DoPost(Json::Value& target,
                 size_t index,
                 const std::string& uri,
                 const std::string& body,
-                const std::map<std::string, std::string>& headers) const;
+                const HttpHeaders& headers) const;
 
     bool DoPost(Json::Value& target,
                 const std::string& name,
                 const std::string& uri,
                 const std::string& body,
-                const std::map<std::string, std::string>& headers) const;
+                const HttpHeaders& headers) const;
 
     bool DoPut(size_t index,
                const std::string& uri,
                const std::string& body,
-               const std::map<std::string, std::string>& headers) const;
+               const HttpHeaders& headers) const;
 
     bool DoPut(const std::string& name,
                const std::string& uri,
                const std::string& body,
-               const std::map<std::string, std::string>& headers) const;
+               const HttpHeaders& headers) const;
 
     bool DoDelete(size_t index,
                   const std::string& uri,
-                  const std::map<std::string, std::string>& headers) const;
+                  const HttpHeaders& headers) const;
 
     bool DoDelete(const std::string& name,
                   const std::string& uri,
-                  const std::map<std::string, std::string>& headers) const;
+                  const HttpHeaders& headers) const;
   };
 #endif
 
@@ -996,8 +998,6 @@
   class HttpClient : public boost::noncopyable
   {
   public:
-    typedef std::map<std::string, std::string>  HttpHeaders;
-
     class IRequestBody : public boost::noncopyable
     {
     public:
@@ -1397,7 +1397,7 @@
   };
 
 // helper method to convert Http headers from the plugin SDK to a std::map
-void GetHttpHeaders(std::map<std::string, std::string>& result, const OrthancPluginHttpRequest* request);
+void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request);
 
 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
   class IWebDavCollection : public boost::noncopyable
@@ -1508,4 +1508,88 @@
 
   void ExtendOrthancExplorer(const std::string& pluginIdentifier,
                              const std::string& javascript);
+
+
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+  class RestApiClient : public boost::noncopyable
+  {
+  private:
+    // Request
+    OrthancPluginHttpMethod  method_;
+    std::string              path_;
+    HttpHeaders              requestHeaders_;
+    std::string              requestBody_;
+    bool                     afterPlugins_;
+
+    // Answer
+    uint16_t                 httpStatus_;
+    HttpHeaders              answerHeaders_;
+    std::string              answerBody_;
+
+  public:
+    RestApiClient();
+
+    void SetMethod(OrthancPluginHttpMethod method)
+    {
+      method_ = method;
+    }
+
+    OrthancPluginHttpMethod GetMethod() const
+    {
+      return method_;
+    }
+
+    void SetPath(const std::string& path)
+    {
+      path_ = path;
+    }
+
+    const std::string& GetPath() const
+    {
+      return path_;
+    }
+
+    void AddRequestHeader(const std::string& key,
+                          const std::string& value);
+
+    const HttpHeaders& GetRequestHeaders() const
+    {
+      return requestHeaders_;
+    }
+
+    void SetRequestBody(const std::string& body)
+    {
+      requestBody_ = body;
+    }
+
+    void SwapRequestBody(std::string& body)
+    {
+      requestBody_.swap(body);
+    }
+
+    void SetAfterPlugins(bool afterPlugins)
+    {
+      afterPlugins_ = afterPlugins;
+    }
+
+    bool IsAfterPlugins() const
+    {
+      return afterPlugins_;
+    }
+
+    const std::string& GetRequestBody() const
+    {
+      return requestBody_;
+    }
+
+    bool Execute();
+
+    uint16_t GetHttpStatus() const;
+
+    bool LookupAnswerHeader(std::string& value,
+                            const std::string& key) const;
+
+    const std::string& GetAnswerBody() const;
+  };
+#endif
 }
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginException.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginException.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/Common/OrthancPlugins.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPlugins.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginsExports.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginsExports.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/ConnectivityChecks/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/ConnectivityChecks/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/ConnectivityChecks/JavaScriptLibraries.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/ConnectivityChecks/JavaScriptLibraries.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/ConnectivityChecks/OrthancFrameworkDependencies.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/ConnectivityChecks/OrthancFrameworkDependencies.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/ConnectivityChecks/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/ConnectivityChecks/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/ConnectivityChecks/WebResources/app.js	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/ConnectivityChecks/WebResources/app.js	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/CustomImageDecoder/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/CustomImageDecoder/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/CustomImageDecoder/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/CustomImageDecoder/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/DelayedDeletion/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/DelayedDeletion/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/DelayedDeletion/LargeDeleteJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/DelayedDeletion/LargeDeleteJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/DelayedDeletion/LargeDeleteJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/DelayedDeletion/LargeDeleteJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/DelayedDeletion/OrthancFrameworkDependencies.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/DelayedDeletion/OrthancFrameworkDependencies.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/DelayedDeletion/PendingDeletionsDatabase.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/DelayedDeletion/PendingDeletionsDatabase.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/DelayedDeletion/PendingDeletionsDatabase.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/DelayedDeletion/PendingDeletionsDatabase.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/DelayedDeletion/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/DelayedDeletion/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -566,7 +566,7 @@
         {
           Json::Value result;
 
-          if (needsReconstruct || needsReingest)
+          if (needsReconstruct || needsReingest ||force_)
           {
             Json::Value request;
             if (needsReingest)
@@ -859,12 +859,15 @@
               "StorageCompressionChange": true,
               "MainDicomTagsChange": true,
               "UnnecessaryDicomAsJsonFiles": true,
+              "IngestTranscodingChange": true,
               "DicomWebCacheChange": true   // new in 1.12.2
             },
 
-            // When rebuilding MainDicomTags, limit to a single level of resource.
-            // Allowed values: "Patient", "Study", "Series", "Instance"
-            "LimitMainDicomTagsReconstructLevel": "Study"
+            // When rebuilding MainDicomTags, limit to a single level of resource
+            // which can greatly improve performances e.g. if you have only updated 
+            // the Study level ExtraMainDicomTags.
+            // Allowed values: "Patient", "Study", "Series", "Instance", "All"
+            "LimitMainDicomTagsReconstructLevel": "All"
 
           }
         }
@@ -887,11 +890,12 @@
         triggerOnDicomWebCacheChange_ = triggers.GetBooleanValue("DicomWebCacheChange", true);
       }
 
-      limitMainDicomTagsReconstructLevel_ = housekeeper.GetStringValue("LimitMainDicomTagsReconstructLevel", "");
+      limitMainDicomTagsReconstructLevel_ = housekeeper.GetStringValue("LimitMainDicomTagsReconstructLevel", "All");
       if (limitMainDicomTagsReconstructLevel_ != "Patient" && limitMainDicomTagsReconstructLevel_ != "Study"
-        && limitMainDicomTagsReconstructLevel_ != "Series" && limitMainDicomTagsReconstructLevel_ != "Instance")
+        && limitMainDicomTagsReconstructLevel_ != "Series" && limitMainDicomTagsReconstructLevel_ != "Instance" && limitMainDicomTagsReconstructLevel_ != "All")
       {
         ORTHANC_PLUGINS_LOG_ERROR("Housekeeper invalid value for 'LimitMainDicomTagsReconstructLevel': '" + limitMainDicomTagsReconstructLevel_ + "'");
+        return -1;
       }
       else if (limitMainDicomTagsReconstructLevel_ == "Patient")
       {
--- a/OrthancServer/Plugins/Samples/ModalityWorklists/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/ModalityWorklists/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/ModalityWorklists/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/ModalityWorklists/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -29,10 +29,11 @@
 #include "../../../../OrthancFramework/Sources/OrthancException.h"
 
 #include "../Common/OrthancPluginCppWrapper.h"
-
+#include <dcmtk/dcmdata/dcuid.h>        /* for variable dcmAllStorageSOPClassUIDs */
 
 DicomFilter::DicomFilter() :
-  hasAcceptedTransferSyntaxes_(false)
+  hasAcceptedTransferSyntaxes_(false),
+  hasAcceptedStorageClasses_(false)
 {
   {
     OrthancPlugins::OrthancConfiguration config;
@@ -200,6 +201,18 @@
 }
 
 
+void DicomFilter::GetProposedStorageTransferSyntaxes(std::list<Orthanc::DicomTransferSyntax>& target,
+                                                     const std::string& remoteIp,
+                                                     const std::string& remoteAet,
+                                                     const std::string& calledAet)
+{
+  // default TS
+  target.push_back(Orthanc::DicomTransferSyntax_LittleEndianExplicit);
+  target.push_back(Orthanc::DicomTransferSyntax_LittleEndianImplicit);
+  target.push_back(Orthanc::DicomTransferSyntax_BigEndianExplicit);
+}
+
+
 bool DicomFilter::IsUnknownSopClassAccepted(const std::string& remoteIp,
                                             const std::string& remoteAet,
                                             const std::string& calledAet)
@@ -207,3 +220,46 @@
   boost::shared_lock<boost::shared_mutex>  lock(mutex_);
   return unknownSopClassAccepted_;
 }
+
+
+void DicomFilter::GetAcceptedSopClasses(std::set<std::string>& sopClasses, size_t maxCount)
+{
+  boost::unique_lock<boost::shared_mutex>  lock(mutex_);
+
+  if (!hasAcceptedStorageClasses_)
+  {
+    Json::Value jsonSopClasses;
+
+    if (!OrthancPlugins::RestApiGet(jsonSopClasses, "/tools/accepted-sop-classes", false) ||
+        jsonSopClasses.type() != Json::arrayValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      for (Json::Value::ArrayIndex i = 0; i < jsonSopClasses.size(); i++)
+      {
+        if (jsonSopClasses[i].type() != Json::stringValue)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+        else
+        {
+          acceptedStorageClasses_.insert(jsonSopClasses[i].asString());
+        }
+      }
+    }
+
+    hasAcceptedStorageClasses_ = true;
+  }
+
+  std::set<std::string>::const_iterator it = acceptedStorageClasses_.begin();
+    size_t count = 0;
+
+  while (it != acceptedStorageClasses_.end() && (maxCount == 0 || count < maxCount))
+  {
+    sopClasses.insert(*it);
+    count++;
+    ++it;
+  }
+}
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -44,6 +44,8 @@
 
   bool hasAcceptedTransferSyntaxes_;
   std::set<Orthanc::DicomTransferSyntax>  acceptedTransferSyntaxes_;
+  bool hasAcceptedStorageClasses_;
+  std::set<std::string>                   acceptedStorageClasses_;
 
 public:
   DicomFilter();
@@ -62,7 +64,15 @@
                                            const std::string& remoteAet,
                                            const std::string& calledAet) ORTHANC_OVERRIDE;
 
+  virtual void GetProposedStorageTransferSyntaxes(std::list<Orthanc::DicomTransferSyntax>& target,
+                                                  const std::string& remoteIp,
+                                                  const std::string& remoteAet,
+                                                  const std::string& calledAet) ORTHANC_OVERRIDE;
+
+
   virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp,
                                          const std::string& remoteAet,
                                          const std::string& calledAet) ORTHANC_OVERRIDE;
+  
+  virtual void GetAcceptedSopClasses(std::set<std::string>& sopClasses, size_t maxCount) ORTHANC_OVERRIDE;
 };
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/PluginEnumerations.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/PluginEnumerations.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/PluginToolbox.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/PluginToolbox.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/PluginToolbox.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/PluginToolbox.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/Sanitizer/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/Sanitizer/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/Sanitizer/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/Sanitizer/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/ServeFolders/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/ServeFolders/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/ServeFolders/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/ServeFolders/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/StorageArea/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/StorageArea/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/StorageArea/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/StorageArea/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/StorageCommitmentScp/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/StorageCommitmentScp/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/StorageCommitmentScp/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/StorageCommitmentScp/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/WebDavFilesystem/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/WebDavFilesystem/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/WebDavFilesystem/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/WebDavFilesystem/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/WebSkeleton/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/WebSkeleton/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/WebSkeleton/Configuration.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/WebSkeleton/Configuration.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/WebSkeleton/Framework/EmbedResources.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/WebSkeleton/Framework/EmbedResources.py	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/WebSkeleton/Framework/Framework.cmake	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/WebSkeleton/Framework/Framework.cmake	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Configuration.json	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Configuration.json	Thu Jan 30 17:41:33 2025 +0100
@@ -15,15 +15,11 @@
   // Path to the directory that holds the heavyweight files (i.e. the
   // raw DICOM instances). Backslashes must be either escaped by
   // doubling them, or replaced by forward slashes "/".
-  // If a relative path is provided, it is relative to the configuration
-  // file path.  It is advised to provide an absolute path.
   "StorageDirectory" : "OrthancStorage",
 
   // Path to the directory that holds the SQLite index (if unset, the
   // value of StorageDirectory is used). This index could be stored on
   // a RAM-drive or a SSD device for performance reasons.
-  // If a relative path is provided, it is relative to the configuration
-  // file path.  It is advised to provide an absolute path.
   "IndexDirectory" : "OrthancStorage",
 
   // Path to the directory where Orthanc stores its large temporary
@@ -206,6 +202,43 @@
   // SOP classes (aka. "promiscuous mode")
   "UnknownSopClassAccepted" : false,
 
+  // The list of accepted Storage SOP classes.
+  // If empty or not defined, this list defaults
+  // to all storage classes defined in DCMTK in case of 
+  // C-STORE SCP and to a reduced list of 120 common storage
+  // classes in case of C-GET SCU.
+  // List of DCMTK default Storage SOP Classes:
+  // https://github.com/DCMTK/dcmtk/blob/410ffe2019b9db6a8f4036daac742a6f5e4d36c2/dcmdata/libsrc/dcuid.cc#L664
+  // Each entry can contain wildcards ("?" or "*") to add
+  // subsets of SOP classes that are defined in DCMTK defaults.
+  // "?" accepts any single character at that position.
+  // "*" accepts any string value at that position.
+  // If you want to add a a SOP class that is not defined in
+  // DCMTK defaults, you must add it explicitely.
+  // (new in Orthanc 1.12.6)
+  // Example to add a non standard class
+  // and keep the default ones:
+  // "AcceptedSopClasses" : [
+  //   "1.3.12.2.1107.5.9.1",
+  //   "1.2.840.*"
+  // ]
+  // Example to limit to 2 SOP Classes:
+  // "AcceptedSopClasses" : [
+  //   "1.2.840.10008.5.1.4.1.1.2",
+  //   "1.2.840.10008.5.1.4.1.1.4"
+  // ]
+
+  // The list of rejected Storage SOP classes.
+  // This configuration is only meaningful if
+  // "AcceptedSopClasses" is using regular expressions
+  // or if it is not defined.
+  // Each entry can contain wildcards ("?" or "*").
+  // (new in Orthanc 1.12.6)
+  // "RejectedSopClasses" : [
+  //   "1.2.840.10008.5.1.4.1.1.2",
+  //   "1.2.840.10008.5.1.4.1.1.4"
+  // ]
+
   // Set the timeout (in seconds) after which the DICOM associations
   // are closed by the Orthanc SCP (server) if no further DIMSE
   // command is received from the SCU (client).
@@ -463,6 +496,10 @@
      * for Orthanc when initiating an SCU to this very specific
      * modality. Similarly, "Timeout" allows one to overwrite the
      * global value "DicomScuTimeout" on a per-modality basis.
+     *
+     * The "RetrieveMethod" option allows one to overwrite the global
+     * "DicomDefaultRetrieveMethod" configuration option for this
+     * specific modality. (Allowed values: "C-MOVE" or "C-GET").
      **/
     //"untrusted" : {
     //  "AET" : "ORTHANC",
@@ -479,7 +516,8 @@
     //  "AllowTranscoding" : true,         // new in 1.7.0
     //  "UseDicomTls" : false,             // new in 1.9.0
     //  "LocalAet" : "HELLO",              // new in 1.9.0
-    //  "Timeout" : 60                     // new in 1.9.1
+    //  "Timeout" : 60,                    // new in 1.9.1
+    //  "RetrieveMethod": "C-MOVE"         // new in 1.12.6
     //}
   },
 
@@ -493,6 +531,13 @@
   // accept C-FIND requests from Orthanc (new in Orthanc 1.8.1).
   "DicomEchoChecksFind" : false,
 
+  // Wheter Orthanc uses C-MOVE or C-GET to retrieve a resource after
+  // a C-Find (when calling /queries/.../retrieve).
+  // This configuration can be overriden for each modality by providing
+  // "RetrieveMethod" in the "DicomModalities" entry.
+  // (new in Orthanc 1.12.6)
+  "DicomDefaultRetrieveMethod" : "C-MOVE",
+
   // The timeout (in seconds) after which the DICOM associations are
   // considered as closed by the Orthanc SCU (client) if the remote
   // DICOM SCP (server) does not answer.
@@ -576,10 +621,10 @@
   // Set the timeout for HTTP requests issued by Orthanc (in seconds).
   "HttpTimeout" : 60,
 
-  // Enable the verification of the peers during HTTPS requests. This
-  // option must be set to "false" if using self-signed certificates.
-  // Pay attention that setting this option to "false" results in
-  // security risks!
+  // Enable the verification of the peers certificates during HTTPS 
+  // requests. Setting this option to false is equivalent to the 
+  // "--insecure" curl option. Pay attention that setting this option 
+  // to "false" results in security risks!
   // Reference: http://curl.haxx.se/docs/sslcerts.html
   "HttpsVerifyPeers" : true,
 
@@ -589,7 +634,11 @@
   // verify the peers. The file may contain multiple CA
   // certificates. The certificate(s) must be in PEM format." On
   // Debian-based systems, this option can be set to
-  // "/etc/ssl/certs/ca-certificates.crt"
+  // "/etc/ssl/certs/ca-certificates.crt".
+  // Starting with Orthanc 1.12.6 and provided that Orthanc has been
+  // built with libcurl > 8.2.0, when this option is empty,
+  // Orthanc uses the operating system native CA store ("--ca-native"
+  // option)
   "HttpsCACertificates" : "",
 
 
@@ -1008,14 +1057,29 @@
     // from a lower resource level; e.g. when requesting "StudyDescription" at
     // Patient level.
     // (new in Orthanc 1.12.5)
-    "W005_RequestingTagFromLowerResourceLevel": true
+    "W005_RequestingTagFromLowerResourceLevel": true,
+
+    // Display a warning when a user performs a find request and requests a tag
+    // from the DICOM Meta Header.
+    // (new in Orthanc 1.12.5)
+    "W006_RequestingTagFromMetaHeader": true,
+
+    // Display a warning when a user requests a tag that can not be read from disk
+    // because "StorageAccessOnFind" is set to "Never".
+    // (new in Orthanc 1.12.5)
+    "W007_MissingRequestedTagsNotReadFromDisk": true
   },
 
   // Configure Orthanc in read only mode.
   // In this mode, many Orthanc features that requires a write access to the 
   // Index DB or the disk storage won't be available at all.
   // (new in Orthanc 1.12.5)
-  "ReadOnly" : false
+  "ReadOnly" : false,
 
+  // Maximum number of DCMTK transcoders that are simultaneously running
+  // at any given time. A value of "0" indicates to use all the
+  // available CPU logical cores. Prior to Orthanc 1.12.6, there were not limit.
+  // (new in Orthanc 1.12.6)
+  "MaximumConcurrentDcmtkTranscoders" : 0
 
 }
--- a/OrthancServer/Resources/DicomConformanceStatement.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/DicomConformanceStatement.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/DicomConformanceStatement.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/DicomConformanceStatement.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -209,6 +209,16 @@
   MOVEStudyRootQueryRetrieveInformationModel    | 1.2.840.10008.5.1.4.1.2.2.2
 
 
+-------------------
+Get SCU Conformance
+-------------------
+
+Orthanc supports the following SOP Classes as an SCU for C-Get:
+
+  GETPatientRootQueryRetrieveInformationModel    | 1.2.840.10008.5.1.4.1.2.1.3
+  GETStudyRootQueryRetrieveInformationModel      | 1.2.840.10008.5.1.4.1.2.2.3
+
+
 -----------------
 Transfer Syntaxes
 -----------------
--- a/OrthancServer/Resources/Fonts/GenerateFont.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Fonts/GenerateFont.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/GenerateAnonymizationProfile.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/GenerateAnonymizationProfile.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,205 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "LookupIdentifierQuery.h"
-
-#include "../../Core/DicomParsing/FromDcmtkBridge.h"
-#include "../../Core/OrthancException.h"
-#include "../ServerToolbox.h"
-#include "SetOfResources.h"
-
-#include <cassert>
-
-
-
-namespace Orthanc
-{
-  LookupIdentifierQuery::SingleConstraint::
-  SingleConstraint(const DicomTag& tag,
-                   IdentifierConstraintType type,
-                   const std::string& value) : 
-    tag_(tag),
-    type_(type),
-    value_(ServerToolbox::NormalizeIdentifier(value))
-  {
-  }
-
-
-  LookupIdentifierQuery::RangeConstraint::
-  RangeConstraint(const DicomTag& tag,
-                  const std::string& start,
-                  const std::string& end) : 
-    tag_(tag),
-    start_(ServerToolbox::NormalizeIdentifier(start)),
-    end_(ServerToolbox::NormalizeIdentifier(end))
-  {
-  }
-
-
-  LookupIdentifierQuery::Disjunction::~Disjunction()
-  {
-    for (size_t i = 0; i < singleConstraints_.size(); i++)
-    {
-      delete singleConstraints_[i];
-    }
-
-    for (size_t i = 0; i < rangeConstraints_.size(); i++)
-    {
-      delete rangeConstraints_[i];
-    }
-  }
-
-
-  void LookupIdentifierQuery::Disjunction::Add(const DicomTag& tag,
-                                               IdentifierConstraintType type,
-                                               const std::string& value)
-  {
-    singleConstraints_.push_back(new SingleConstraint(tag, type, value));
-  }
-
-
-  void LookupIdentifierQuery::Disjunction::AddRange(const DicomTag& tag,
-                                                    const std::string& start,
-                                                    const std::string& end)
-  {
-    rangeConstraints_.push_back(new RangeConstraint(tag, start, end));
-  }
-
-
-  LookupIdentifierQuery::~LookupIdentifierQuery()
-  {
-    for (Disjunctions::iterator it = disjunctions_.begin();
-         it != disjunctions_.end(); ++it)
-    {
-      delete *it;
-    }
-  }
-
-
-  bool LookupIdentifierQuery::IsIdentifier(const DicomTag& tag)
-  {
-    return ServerToolbox::IsIdentifier(tag, level_);
-  }
-
-
-  void LookupIdentifierQuery::AddConstraint(DicomTag tag,
-                                            IdentifierConstraintType type,
-                                            const std::string& value)
-  {
-    assert(IsIdentifier(tag));
-    disjunctions_.push_back(new Disjunction);
-    disjunctions_.back()->Add(tag, type, value);
-  }
-
-
-  void LookupIdentifierQuery::AddRange(DicomTag tag,
-                                       const std::string& start,
-                                       const std::string& end)
-  {
-    assert(IsIdentifier(tag));
-    disjunctions_.push_back(new Disjunction);
-    disjunctions_.back()->AddRange(tag, start, end);
-  }
-
-
-  LookupIdentifierQuery::Disjunction& LookupIdentifierQuery::AddDisjunction()
-  {
-    disjunctions_.push_back(new Disjunction);
-    return *disjunctions_.back();
-  }
-
-
-  void LookupIdentifierQuery::Apply(std::list<std::string>& result,
-                                    IDatabaseWrapper& database)
-  {
-    SetOfResources resources(database, level_);
-    Apply(resources, database);
-
-    resources.Flatten(result);
-  }
-
-
-  void LookupIdentifierQuery::Apply(SetOfResources& result,
-                                    IDatabaseWrapper& database)
-  {
-    for (size_t i = 0; i < disjunctions_.size(); i++)
-    {
-      std::list<int64_t> a;
-
-      for (size_t j = 0; j < disjunctions_[i]->GetSingleConstraintsCount(); j++)
-      {
-        const SingleConstraint& constraint = disjunctions_[i]->GetSingleConstraint(j);
-        std::list<int64_t> b;
-        database.LookupIdentifier(b, level_, constraint.GetTag(), 
-                                  constraint.GetType(), constraint.GetValue());
-
-        a.splice(a.end(), b);
-      }
-
-      for (size_t j = 0; j < disjunctions_[i]->GetRangeConstraintsCount(); j++)
-      {
-        const RangeConstraint& constraint = disjunctions_[i]->GetRangeConstraint(j);
-        std::list<int64_t> b;
-        database.LookupIdentifierRange(b, level_, constraint.GetTag(), 
-                                       constraint.GetStart(), constraint.GetEnd());
-
-        a.splice(a.end(), b);
-      }
-
-      result.Intersect(a);
-    }
-  }
-
-
-  void LookupIdentifierQuery::Print(std::ostream& s) const
-  {
-    s << "Constraint: " << std::endl;
-    for (Disjunctions::const_iterator
-           it = disjunctions_.begin(); it != disjunctions_.end(); ++it)
-    {
-      if (it == disjunctions_.begin())
-        s << "   ";
-      else
-        s << "OR ";
-
-      for (size_t j = 0; j < (*it)->GetSingleConstraintsCount(); j++)
-      {
-        const SingleConstraint& c = (*it)->GetSingleConstraint(j);
-        s << FromDcmtkBridge::GetTagName(c.GetTag(), "");
-
-        switch (c.GetType())
-        {
-          case IdentifierConstraintType_Equal: s << " == "; break;
-          case IdentifierConstraintType_SmallerOrEqual: s << " <= "; break;
-          case IdentifierConstraintType_GreaterOrEqual: s << " >= "; break;
-          case IdentifierConstraintType_Wildcard: s << " ~= "; break;
-          default:
-            s << " ? ";
-        }
-
-        s << c.GetValue() << std::endl;
-      }
-    }
-  }
-}
--- a/OrthancServer/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,197 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../IDatabaseWrapper.h"
-
-#include "SetOfResources.h"
-
-#include <vector>
-#include <boost/noncopyable.hpp>
-
-namespace Orthanc
-{
-  /**
-   * Primitive for wildcard matching, as defined in DICOM:
-   * http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_C.2.html#sect_C.2.2.2.4
-   * 
-   * "Any occurrence of an "*" or a "?", then "*" shall match any
-   * sequence of characters (including a zero length value) and "?"
-   * shall match any single character. This matching is case
-   * sensitive, except for Attributes with an PN Value
-   * Representation (e.g., Patient Name (0010,0010))."
-   * 
-   * Pay attention to the fact that "*" (resp. "?") generally
-   * corresponds to "%" (resp. "_") in primitive LIKE of SQL. The
-   * values "%", "_", "\" should in the user request should
-   * respectively be escaped as "\%", "\_" and "\\".
-   *
-   * This matching must be case sensitive: The special case of PN VR
-   * is taken into consideration by normalizing the query string in
-   * method "NormalizeIdentifier()".
-   **/
-
-  class LookupIdentifierQuery : public boost::noncopyable
-  {
-    // This class encodes a conjunction ("AND") of disjunctions. Each
-    // disjunction represents an "OR" of several constraints.
-
-  public:
-    class SingleConstraint
-    {
-    private:
-      DicomTag                  tag_;
-      IdentifierConstraintType  type_;
-      std::string               value_;
-
-    public:
-      SingleConstraint(const DicomTag& tag,
-                       IdentifierConstraintType type,
-                       const std::string& value);
-
-      const DicomTag& GetTag() const
-      {
-        return tag_;
-      }
-
-      IdentifierConstraintType GetType() const
-      {
-        return type_;
-      }
-      
-      const std::string& GetValue() const
-      {
-        return value_;
-      }
-    };
-
-
-    class RangeConstraint
-    {
-    private:
-      DicomTag     tag_;
-      std::string  start_;
-      std::string  end_;
-
-    public:
-      RangeConstraint(const DicomTag& tag,
-                      const std::string& start,
-                      const std::string& end);
-
-      const DicomTag& GetTag() const
-      {
-        return tag_;
-      }
-
-      const std::string& GetStart() const
-      {
-        return start_;
-      }
-
-      const std::string& GetEnd() const
-      {
-        return end_;
-      }
-    };
-
-
-    class Disjunction : public boost::noncopyable
-    {
-    private:
-      std::vector<SingleConstraint*>  singleConstraints_;
-      std::vector<RangeConstraint*>   rangeConstraints_;
-
-    public:
-      ~Disjunction();
-
-      void Add(const DicomTag& tag,
-               IdentifierConstraintType type,
-               const std::string& value);
-
-      void AddRange(const DicomTag& tag,
-                    const std::string& start,
-                    const std::string& end);
-
-      size_t GetSingleConstraintsCount() const
-      {
-        return singleConstraints_.size();
-      }
-
-      const SingleConstraint&  GetSingleConstraint(size_t i) const
-      {
-        return *singleConstraints_[i];
-      }
-
-      size_t GetRangeConstraintsCount() const
-      {
-        return rangeConstraints_.size();
-      }
-
-      const RangeConstraint&  GetRangeConstraint(size_t i) const
-      {
-        return *rangeConstraints_[i];
-      }
-    };
-
-
-  private:
-    typedef std::vector<Disjunction*>  Disjunctions;
-
-    ResourceType  level_;
-    Disjunctions  disjunctions_;
-
-  public:
-    LookupIdentifierQuery(ResourceType level) : level_(level)
-    {
-    }
-
-    ~LookupIdentifierQuery();
-
-    bool IsIdentifier(const DicomTag& tag);
-
-    void AddConstraint(DicomTag tag,
-                       IdentifierConstraintType type,
-                       const std::string& value);
-
-    void AddRange(DicomTag tag,
-                  const std::string& start,
-                  const std::string& end);
-
-    Disjunction& AddDisjunction();
-
-    ResourceType GetLevel() const
-    {
-      return level_;
-    }
-
-    // The database must be locked
-    void Apply(std::list<std::string>& result,
-               IDatabaseWrapper& database);
-
-    void Apply(SetOfResources& result,
-               IDatabaseWrapper& database);
-
-    void Print(std::ostream& s) const;
-  };
-}
--- a/OrthancServer/Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,469 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "LookupResource.h"
-
-#include "../../Core/OrthancException.h"
-#include "../../Core/FileStorage/StorageAccessor.h"
-#include "../ServerToolbox.h"
-#include "../../Core/DicomParsing/FromDcmtkBridge.h"
-
-
-namespace Orthanc
-{
-  static bool DoesDicomMapMatch(const DicomMap& dicom,
-                                const DicomTag& tag,
-                                const IFindConstraint& constraint)
-  {
-    const DicomValue* value = dicom.TestAndGetValue(tag);
-
-    return (value != NULL &&
-            !value->IsNull() &&
-            !value->IsBinary() &&
-            constraint.Match(value->GetContent()));
-  }
-
-  
-  LookupResource::Level::Level(ResourceType level) : level_(level)
-  {
-    const DicomTag* tags = NULL;
-    size_t size;
-    
-    ServerToolbox::LoadIdentifiers(tags, size, level);
-    
-    for (size_t i = 0; i < size; i++)
-    {
-      identifiers_.insert(tags[i]);
-    }
-    
-    DicomMap::LoadMainDicomTags(tags, size, level);
-    
-    for (size_t i = 0; i < size; i++)
-    {
-      if (identifiers_.find(tags[i]) == identifiers_.end())
-      {
-        mainTags_.insert(tags[i]);
-      }
-    }    
-  }
-
-  LookupResource::Level::~Level()
-  {
-    for (Constraints::iterator it = mainTagsConstraints_.begin();
-         it != mainTagsConstraints_.end(); ++it)
-    {
-      delete it->second;
-    }
-
-    for (Constraints::iterator it = identifiersConstraints_.begin();
-         it != identifiersConstraints_.end(); ++it)
-    {
-      delete it->second;
-    }
-  }
-
-  bool LookupResource::Level::Add(const DicomTag& tag,
-                                  std::auto_ptr<IFindConstraint>& constraint)
-  {
-    if (identifiers_.find(tag) != identifiers_.end())
-    {
-      if (level_ == ResourceType_Patient)
-      {
-        // The filters on the patient level must be cloned to the study level
-        identifiersConstraints_[tag] = constraint->Clone();
-      }
-      else
-      {
-        identifiersConstraints_[tag] = constraint.release();
-      }
-
-      return true;
-    }
-    else if (mainTags_.find(tag) != mainTags_.end())
-    {
-      if (level_ == ResourceType_Patient)
-      {
-        // The filters on the patient level must be cloned to the study level
-        mainTagsConstraints_[tag] = constraint->Clone();
-      }
-      else
-      {
-        mainTagsConstraints_[tag] = constraint.release();
-      }
-
-      return true;
-    }
-    else
-    {
-      // This is not a main DICOM tag
-      return false;
-    }
-  }
-
-
-  bool LookupResource::Level::IsMatch(const DicomMap& dicom) const
-  {
-    for (Constraints::const_iterator it = identifiersConstraints_.begin();
-         it != identifiersConstraints_.end(); ++it)
-    {
-      assert(it->second != NULL);
-
-      if (!DoesDicomMapMatch(dicom, it->first, *it->second))
-      {
-        return false;
-      }
-    }
-
-    for (Constraints::const_iterator it = mainTagsConstraints_.begin();
-         it != mainTagsConstraints_.end(); ++it)
-    {
-      assert(it->second != NULL);
-
-      if (!DoesDicomMapMatch(dicom, it->first, *it->second))
-      {
-        return false;
-      }
-    }
-
-    return true;
-  }
-  
-
-  LookupResource::LookupResource(ResourceType level) : level_(level)
-  {
-    switch (level)
-    {
-      case ResourceType_Patient:
-        levels_[ResourceType_Patient] = new Level(ResourceType_Patient);
-        break;
-
-      case ResourceType_Instance:
-        levels_[ResourceType_Instance] = new Level(ResourceType_Instance);
-        // Do not add "break" here
-
-      case ResourceType_Series:
-        levels_[ResourceType_Series] = new Level(ResourceType_Series);
-        // Do not add "break" here
-
-      case ResourceType_Study:
-        levels_[ResourceType_Study] = new Level(ResourceType_Study);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-
-
-  LookupResource::~LookupResource()
-  {
-    for (Levels::iterator it = levels_.begin();
-         it != levels_.end(); ++it)
-    {
-      delete it->second;
-    }
-
-    for (Constraints::iterator it = unoptimizedConstraints_.begin();
-         it != unoptimizedConstraints_.end(); ++it)
-    {
-      delete it->second;
-    }    
-  }
-
-
-
-  bool LookupResource::AddInternal(ResourceType level,
-                                   const DicomTag& tag,
-                                   std::auto_ptr<IFindConstraint>& constraint)
-  {
-    Levels::iterator it = levels_.find(level);
-    if (it != levels_.end())
-    {
-      if (it->second->Add(tag, constraint))
-      {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-
-  void LookupResource::Add(const DicomTag& tag,
-                           IFindConstraint* constraint)
-  {
-    std::auto_ptr<IFindConstraint> c(constraint);
-
-    if (!AddInternal(ResourceType_Patient, tag, c) &&
-        !AddInternal(ResourceType_Study, tag, c) &&
-        !AddInternal(ResourceType_Series, tag, c) &&
-        !AddInternal(ResourceType_Instance, tag, c))
-    {
-      unoptimizedConstraints_[tag] = c.release();
-    }
-  }
-
-
-  static bool Match(const DicomMap& tags,
-                    const DicomTag& tag,
-                    const IFindConstraint& constraint)
-  {
-    const DicomValue* value = tags.TestAndGetValue(tag);
-
-    if (value == NULL ||
-        value->IsNull() ||
-        value->IsBinary())
-    {
-      return false;
-    }
-    else
-    {
-      return constraint.Match(value->GetContent());
-    }
-  }
-
-
-  void LookupResource::Level::Apply(SetOfResources& candidates,
-                                    IDatabaseWrapper& database) const
-  {
-    // First, use the indexed identifiers
-    LookupIdentifierQuery query(level_);
-
-    for (Constraints::const_iterator it = identifiersConstraints_.begin(); 
-         it != identifiersConstraints_.end(); ++it)
-    {
-      it->second->Setup(query, it->first);
-    }
-
-    query.Apply(candidates, database);
-
-    /*{
-      query.Print(std::cout);
-      std::list<int64_t>  source;
-      candidates.Flatten(source);
-      printf("=> %d\n", source.size());
-      }*/
-
-    // Secondly, filter using the main DICOM tags
-    if (!identifiersConstraints_.empty() ||
-        !mainTagsConstraints_.empty())
-    {
-      std::list<int64_t>  source;
-      candidates.Flatten(source);
-      candidates.Clear();
-
-      std::list<int64_t>  filtered;
-      for (std::list<int64_t>::const_iterator candidate = source.begin(); 
-           candidate != source.end(); ++candidate)
-      {
-        DicomMap tags;
-        database.GetMainDicomTags(tags, *candidate);
-
-        bool match = true;
-
-        // Re-apply the identifier constraints, as their "Setup"
-        // method is less restrictive than their "Match" method
-        for (Constraints::const_iterator it = identifiersConstraints_.begin(); 
-             match && it != identifiersConstraints_.end(); ++it)
-        {
-          if (!Match(tags, it->first, *it->second))
-          {
-            match = false;
-          }
-        }
-
-        for (Constraints::const_iterator it = mainTagsConstraints_.begin(); 
-             match && it != mainTagsConstraints_.end(); ++it)
-        {
-          if (!Match(tags, it->first, *it->second))
-          {
-            match = false;
-          }
-        }
-
-        if (match)
-        {
-          filtered.push_back(*candidate);
-        }
-      }
-      
-      candidates.Intersect(filtered);
-    }
-  }
-
-
-
-  bool LookupResource::IsMatch(const DicomMap& dicom) const
-  {
-    for (Levels::const_iterator it = levels_.begin(); it != levels_.end(); ++it)
-    {
-      if (!it->second->IsMatch(dicom))
-      {
-        return false;
-      }
-    }
-
-    for (Constraints::const_iterator it = unoptimizedConstraints_.begin(); 
-         it != unoptimizedConstraints_.end(); ++it)
-    {
-      assert(it->second != NULL);
-
-      if (!DoesDicomMapMatch(dicom, it->first, *it->second))
-      {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-
-  void LookupResource::ApplyLevel(SetOfResources& candidates,
-                                  ResourceType level,
-                                  IDatabaseWrapper& database) const
-  {
-    Levels::const_iterator it = levels_.find(level);
-    if (it != levels_.end())
-    {
-      it->second->Apply(candidates, database);
-    }
-
-    if (level == ResourceType_Study &&
-        modalitiesInStudy_.get() != NULL)
-    {
-      // There is a constraint on the "ModalitiesInStudy" DICOM
-      // extension. Check out whether one child series has one of the
-      // allowed modalities
-      std::list<int64_t> allStudies, matchingStudies;
-      candidates.Flatten(allStudies);
- 
-      for (std::list<int64_t>::const_iterator
-             study = allStudies.begin(); study != allStudies.end(); ++study)
-      {
-        std::list<int64_t> childrenSeries;
-        database.GetChildrenInternalId(childrenSeries, *study);
-
-        for (std::list<int64_t>::const_iterator
-               series = childrenSeries.begin(); series != childrenSeries.end(); ++series)
-        {
-          DicomMap tags;
-          database.GetMainDicomTags(tags, *series);
-
-          const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY);
-          if (value != NULL &&
-              !value->IsNull() &&
-              !value->IsBinary())
-          {
-            if (modalitiesInStudy_->Match(value->GetContent()))
-            {
-              matchingStudies.push_back(*study);
-              break;
-            }
-          }
-        }
-      }
-
-      candidates.Intersect(matchingStudies);
-    }
-  }
-
-
-  void LookupResource::FindCandidates(std::list<int64_t>& result,
-                                      IDatabaseWrapper& database) const
-  {
-    ResourceType startingLevel;
-    if (level_ == ResourceType_Patient)
-    {
-      startingLevel = ResourceType_Patient;
-    }
-    else
-    {
-      startingLevel = ResourceType_Study;
-    }
-
-    SetOfResources candidates(database, startingLevel);
-
-    switch (level_)
-    {
-      case ResourceType_Patient:
-        ApplyLevel(candidates, ResourceType_Patient, database);
-        break;
-
-      case ResourceType_Study:
-        ApplyLevel(candidates, ResourceType_Study, database);
-        break;
-
-      case ResourceType_Series:
-        ApplyLevel(candidates, ResourceType_Study, database);
-        candidates.GoDown();
-        ApplyLevel(candidates, ResourceType_Series, database);
-        break;
-
-      case ResourceType_Instance:
-        ApplyLevel(candidates, ResourceType_Study, database);
-        candidates.GoDown();
-        ApplyLevel(candidates, ResourceType_Series, database);
-        candidates.GoDown();
-        ApplyLevel(candidates, ResourceType_Instance, database);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    candidates.Flatten(result);
-  }
-
-
-  void LookupResource::SetModalitiesInStudy(const std::string& modalities)
-  {
-    modalitiesInStudy_.reset(new ListConstraint(true /* case sensitive */));
-    
-    std::vector<std::string> items;
-    Toolbox::TokenizeString(items, modalities, '\\');
-    
-    for (size_t i = 0; i < items.size(); i++)
-    {
-      modalitiesInStudy_->AddAllowedValue(items[i]);
-    }
-  }
-
-
-  void LookupResource::AddDicomConstraint(const DicomTag& tag,
-                                          const std::string& dicomQuery,
-                                          bool caseSensitive)
-  {
-    // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained
-    // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html  
-    if (tag == DICOM_TAG_MODALITIES_IN_STUDY)
-    {
-      SetModalitiesInStudy(dicomQuery);
-    }
-    else 
-    {
-      Add(tag, IFindConstraint::ParseDicomConstraint(tag, dicomQuery, caseSensitive));
-    }
-  }
-
-}
--- a/OrthancServer/Resources/Graveyard/DatabaseOptimizations/LookupResource.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ListConstraint.h"
-#include "SetOfResources.h"
-
-#include <memory>
-
-namespace Orthanc
-{
-  class LookupResource : public boost::noncopyable
-  {
-  private:
-    typedef std::map<DicomTag, IFindConstraint*>  Constraints;
-    
-    class Level
-    {
-    private:
-      ResourceType        level_;
-      std::set<DicomTag>  identifiers_;
-      std::set<DicomTag>  mainTags_;
-      Constraints         identifiersConstraints_;
-      Constraints         mainTagsConstraints_;
-
-    public:
-      Level(ResourceType level);
-
-      ~Level();
-
-      bool Add(const DicomTag& tag,
-               std::auto_ptr<IFindConstraint>& constraint);
-
-      void Apply(SetOfResources& candidates,
-                 IDatabaseWrapper& database) const;
-
-      bool IsMatch(const DicomMap& dicom) const;
-    };
-
-    typedef std::map<ResourceType, Level*>  Levels;
-
-    ResourceType                    level_;
-    Levels                          levels_;
-    Constraints                     unoptimizedConstraints_;   // Constraints on non-main DICOM tags
-    std::auto_ptr<ListConstraint>   modalitiesInStudy_;
-
-    bool AddInternal(ResourceType level,
-                     const DicomTag& tag,
-                     std::auto_ptr<IFindConstraint>& constraint);
-
-    void ApplyLevel(SetOfResources& candidates,
-                    ResourceType level,
-                    IDatabaseWrapper& database) const;
-
-  public:
-    LookupResource(ResourceType level);
-
-    ~LookupResource();
-
-    ResourceType GetLevel() const
-    {
-      return level_;
-    }
-
-    void SetModalitiesInStudy(const std::string& modalities); 
-
-    void Add(const DicomTag& tag,
-             IFindConstraint* constraint);   // Takes ownership
-
-    void AddDicomConstraint(const DicomTag& tag,
-                            const std::string& dicomQuery,
-                            bool caseSensitive);
-
-    void FindCandidates(std::list<int64_t>& result,
-                        IDatabaseWrapper& database) const;
-
-    bool HasOnlyMainDicomTags() const
-    {
-      return unoptimizedConstraints_.empty();
-    }
-
-    bool IsMatch(const DicomMap& dicom) const;
-  };
-}
--- a/OrthancServer/Resources/Graveyard/DatabasePluginSample/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-cmake_minimum_required(VERSION 2.8)
-
-project(SampleDatabasePlugin)
-
-# Parameters of the build
-SET(SAMPLE_DATABASE_VERSION "0.0" CACHE STRING "Version of the plugin")
-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")
-SET(STANDALONE_BUILD ON)
-
-# Advanced parameters to fine-tune linking against system libraries
-SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
-SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
-SET(USE_SYSTEM_SQLITE ON CACHE BOOL "Use the system version of SQLite")
-
-include(${CMAKE_SOURCE_DIR}/../../../Plugins/Samples/Common/OrthancPlugins.cmake)
-
-include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake)
-set(ENABLE_SQLITE ON)
-include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake)
-
-EmbedResources(
-  --system-exception  # Use "std::runtime_error" instead of "OrthancException" for embedded resources
-  PREPARE_DATABASE  ${CMAKE_SOURCE_DIR}/../../../Sources/Database/PrepareDatabase.sql
-  )
-
-message("Setting the version of the plugin to ${SAMPLE_DATABASE_VERSION}")
-
-add_definitions(
-  -DORTHANC_SQLITE_STANDALONE=1
-  -DORTHANC_ENABLE_PLUGINS=1
-  -DORTHANC_SANDBOXED=0
-  -DSAMPLE_DATABASE_VERSION="${SAMPLE_DATABASE_VERSION}"
-  )
-
-add_library(SampleDatabase SHARED 
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/DicomFormat/DicomArray.cpp
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/DicomFormat/DicomTag.cpp
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/DicomFormat/DicomValue.cpp
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/Enumerations.cpp
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/SQLite/Connection.cpp
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/SQLite/FunctionContext.cpp
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/SQLite/Statement.cpp
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/SQLite/StatementId.cpp
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/SQLite/StatementReference.cpp
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/SQLite/Transaction.cpp
-  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/Toolbox.cpp
-  ${CMAKE_SOURCE_DIR}/../../../Plugins/Engine/PluginsEnumerations.cpp
-
-  Database.cpp
-  Plugin.cpp
-  DatabaseWrapperBase.cpp
-  
-  ${BOOST_SOURCES}
-  ${JSONCPP_SOURCES}
-  ${SQLITE_SOURCES}
-  ${AUTOGENERATED_SOURCES}
-  )
-
-set_target_properties(SampleDatabase PROPERTIES 
-  VERSION ${SAMPLE_DATABASE_VERSION} 
-  SOVERSION ${SAMPLE_DATABASE_VERSION})
-
-install(
-  TARGETS SampleDatabase
-  RUNTIME DESTINATION lib    # Destination for Windows
-  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
-  )
--- a/OrthancServer/Resources/Graveyard/DatabasePluginSample/Database.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,555 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Database.h"
-
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomArray.h"
-
-#include <EmbeddedResources.h>
-#include <boost/lexical_cast.hpp>
-
-
-namespace Internals
-{
-  class SignalFileDeleted : public Orthanc::SQLite::IScalarFunction
-  {
-  private:
-    OrthancPlugins::DatabaseBackendOutput&  output_;
-
-  public:
-    SignalFileDeleted(OrthancPlugins::DatabaseBackendOutput&  output) : output_(output)
-    {
-    }
-
-    virtual const char* GetName() const
-    {
-      return "SignalFileDeleted";
-    }
-
-    virtual unsigned int GetCardinality() const
-    {
-      return 7;
-    }
-
-    virtual void Compute(Orthanc::SQLite::FunctionContext& context)
-    {
-      std::string uncompressedMD5, compressedMD5;
-
-      if (!context.IsNullValue(5))
-      {
-        uncompressedMD5 = context.GetStringValue(5);
-      }
-
-      if (!context.IsNullValue(6))
-      {
-        compressedMD5 = context.GetStringValue(6);
-      }
-      
-      output_.SignalDeletedAttachment(context.GetStringValue(0),
-                                      context.GetIntValue(1),
-                                      context.GetInt64Value(2),
-                                      uncompressedMD5,
-                                      context.GetIntValue(3),
-                                      context.GetInt64Value(4),
-                                      compressedMD5);
-    }
-  };
-
-
-  class SignalResourceDeleted : public Orthanc::SQLite::IScalarFunction
-  {
-  private:
-    OrthancPlugins::DatabaseBackendOutput&  output_;
-
-  public:
-    SignalResourceDeleted(OrthancPlugins::DatabaseBackendOutput&  output) : output_(output)
-    {
-    }
-
-    virtual const char* GetName() const
-    {
-      return "SignalResourceDeleted";
-    }
-
-    virtual unsigned int GetCardinality() const
-    {
-      return 2;
-    }
-
-    virtual void Compute(Orthanc::SQLite::FunctionContext& context)
-    {
-      output_.SignalDeletedResource(context.GetStringValue(0),
-                                    Orthanc::Plugins::Convert(static_cast<Orthanc::ResourceType>(context.GetIntValue(1))));
-    }
-  };
-}
-
-
-class Database::SignalRemainingAncestor : public Orthanc::SQLite::IScalarFunction
-{
-private:
-  bool hasRemainingAncestor_;
-  std::string remainingPublicId_;
-  OrthancPluginResourceType remainingType_;
-
-public:
-  SignalRemainingAncestor() : 
-    hasRemainingAncestor_(false),
-    remainingType_(OrthancPluginResourceType_Instance)  // Some dummy value
-  {
-  }
-
-  void Reset()
-  {
-    hasRemainingAncestor_ = false;
-  }
-
-  virtual const char* GetName() const
-  {
-    return "SignalRemainingAncestor";
-  }
-
-  virtual unsigned int GetCardinality() const
-  {
-    return 2;
-  }
-
-  virtual void Compute(Orthanc::SQLite::FunctionContext& context)
-  {
-    if (!hasRemainingAncestor_ ||
-        remainingType_ >= context.GetIntValue(1))
-    {
-      hasRemainingAncestor_ = true;
-      remainingPublicId_ = context.GetStringValue(0);
-      remainingType_ = Orthanc::Plugins::Convert(static_cast<Orthanc::ResourceType>(context.GetIntValue(1)));
-    }
-  }
-
-  bool HasRemainingAncestor() const
-  {
-    return hasRemainingAncestor_;
-  }
-
-  const std::string& GetRemainingAncestorId() const
-  {
-    assert(hasRemainingAncestor_);
-    return remainingPublicId_;
-  }
-
-  OrthancPluginResourceType GetRemainingAncestorType() const
-  {
-    assert(hasRemainingAncestor_);
-    return remainingType_;
-  }
-};
-
-
-
-Database::Database(const std::string& path) : 
-  path_(path),
-  base_(db_),
-  signalRemainingAncestor_(NULL)
-{
-}
-
-
-void Database::Open()
-{
-  db_.Open(path_);
-
-  db_.Execute("PRAGMA ENCODING=\"UTF-8\";");
-
-  // http://www.sqlite.org/pragma.html
-  db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;");
-  db_.Execute("PRAGMA JOURNAL_MODE=WAL;");
-  db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;");
-  db_.Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;");
-  //db_.Execute("PRAGMA TEMP_STORE=memory");
-
-  if (!db_.DoesTableExist("GlobalProperties"))
-  {
-    std::string query;
-    Orthanc::EmbeddedResources::GetFileResource(query, Orthanc::EmbeddedResources::PREPARE_DATABASE);
-    db_.Execute(query);
-  }
-
-  signalRemainingAncestor_ = new SignalRemainingAncestor;
-  db_.Register(signalRemainingAncestor_);
-  db_.Register(new Internals::SignalFileDeleted(GetOutput()));
-  db_.Register(new Internals::SignalResourceDeleted(GetOutput()));
-}
-
-
-void Database::Close()
-{
-  db_.Close();
-}
-
-
-void Database::AddAttachment(int64_t id,
-                             const OrthancPluginAttachment& attachment)
-{
-  Orthanc::FileInfo info(attachment.uuid,
-                         static_cast<Orthanc::FileContentType>(attachment.contentType),
-                         attachment.uncompressedSize,
-                         attachment.uncompressedHash,
-                         static_cast<Orthanc::CompressionType>(attachment.compressionType),
-                         attachment.compressedSize,
-                         attachment.compressedHash);
-  base_.AddAttachment(id, info);
-}
-
-
-void Database::DeleteResource(int64_t id)
-{
-  signalRemainingAncestor_->Reset();
-
-  Orthanc::SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?");
-  s.BindInt64(0, id);
-  s.Run();
-
-  if (signalRemainingAncestor_->HasRemainingAncestor())
-  {
-    GetOutput().SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorId(),
-                                        signalRemainingAncestor_->GetRemainingAncestorType());
-  }
-}
-
-
-static void Answer(OrthancPlugins::DatabaseBackendOutput& output,
-                   const Orthanc::ServerIndexChange& change)
-{
-  output.AnswerChange(change.GetSeq(), 
-                      change.GetChangeType(),
-                      Orthanc::Plugins::Convert(change.GetResourceType()),
-                      change.GetPublicId(),
-                      change.GetDate());
-}
-
-
-static void Answer(OrthancPlugins::DatabaseBackendOutput& output,
-                   const Orthanc::ExportedResource& resource)
-{
-  output.AnswerExportedResource(resource.GetSeq(),
-                                Orthanc::Plugins::Convert(resource.GetResourceType()),
-                                resource.GetPublicId(),
-                                resource.GetModality(),
-                                resource.GetDate(),
-                                resource.GetPatientId(),
-                                resource.GetStudyInstanceUid(),
-                                resource.GetSeriesInstanceUid(),
-                                resource.GetSopInstanceUid());
-}
-
-
-void Database::GetChanges(bool& done /*out*/,
-                          int64_t since,
-                          uint32_t maxResults)
-{
-  typedef std::list<Orthanc::ServerIndexChange> Changes;
-
-  Changes changes;
-  base_.GetChanges(changes, done, since, maxResults);
-
-  for (Changes::const_iterator it = changes.begin(); it != changes.end(); ++it)
-  {
-    Answer(GetOutput(), *it);
-  }
-}
-
-
-void Database::GetExportedResources(bool& done /*out*/,
-                                    int64_t since,
-                                    uint32_t maxResults)
-{
-  typedef std::list<Orthanc::ExportedResource> Resources;
-
-  Resources resources;
-  base_.GetExportedResources(resources, done, since, maxResults);
-
-  for (Resources::const_iterator it = resources.begin(); it != resources.end(); ++it)
-  {
-    Answer(GetOutput(), *it);
-  }
-}
-
-
-void Database::GetLastChange()
-{
-  std::list<Orthanc::ServerIndexChange> change;
-  Orthanc::ErrorCode code = base_.GetLastChange(change);
-  
-  if (code != Orthanc::ErrorCode_Success)
-  {
-    throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code));
-  }
-
-  if (!change.empty())
-  {
-    Answer(GetOutput(), change.front());
-  }
-}
-
-
-void Database::GetLastExportedResource()
-{
-  std::list<Orthanc::ExportedResource> resource;
-  base_.GetLastExportedResource(resource);
-  
-  if (!resource.empty())
-  {
-    Answer(GetOutput(), resource.front());
-  }
-}
-
-
-void Database::GetMainDicomTags(int64_t id)
-{
-  Orthanc::DicomMap tags;
-  base_.GetMainDicomTags(tags, id);
-
-  Orthanc::DicomArray arr(tags);
-  for (size_t i = 0; i < arr.GetSize(); i++)
-  {
-    GetOutput().AnswerDicomTag(arr.GetElement(i).GetTag().GetGroup(),
-                               arr.GetElement(i).GetTag().GetElement(),
-                               arr.GetElement(i).GetValue().GetContent());
-  }
-}
-
-
-std::string Database::GetPublicId(int64_t resourceId)
-{
-  std::string id;
-  if (base_.GetPublicId(id, resourceId))
-  {
-    return id;
-  }
-  else
-  {
-    throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_UnknownResource);
-  }
-}
-
-
-OrthancPluginResourceType Database::GetResourceType(int64_t resourceId)
-{
-  Orthanc::ResourceType  result;
-  Orthanc::ErrorCode  code = base_.GetResourceType(result, resourceId);
-
-  if (code == Orthanc::ErrorCode_Success)
-  {
-    return Orthanc::Plugins::Convert(result);
-  }
-  else
-  {
-    throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code));
-  }
-}
-
-
-
-template <typename I>
-static void ConvertList(std::list<int32_t>& target,
-                        const std::list<I>& source)
-{
-  for (typename std::list<I>::const_iterator 
-         it = source.begin(); it != source.end(); ++it)
-  {
-    target.push_back(*it);
-  }
-}
-
-
-void Database::ListAvailableMetadata(std::list<int32_t>& target /*out*/,
-                                     int64_t id)
-{
-  std::list<Orthanc::MetadataType> tmp;
-  base_.ListAvailableMetadata(tmp, id);
-  ConvertList(target, tmp);
-}
-
-
-void Database::ListAvailableAttachments(std::list<int32_t>& target /*out*/,
-                                        int64_t id)
-{
-  std::list<Orthanc::FileContentType> tmp;
-  base_.ListAvailableAttachments(tmp, id);
-  ConvertList(target, tmp);
-}
-
-
-void Database::LogChange(const OrthancPluginChange& change)
-{
-  int64_t id;
-  OrthancPluginResourceType type;
-  if (!LookupResource(id, type, change.publicId) ||
-      type != change.resourceType)
-  {
-    throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_DatabasePlugin);
-  }
-
-  Orthanc::ServerIndexChange tmp(change.seq,
-                                 static_cast<Orthanc::ChangeType>(change.changeType),
-                                 Orthanc::Plugins::Convert(change.resourceType),
-                                 change.publicId,
-                                 change.date);
-
-  base_.LogChange(id, tmp);
-}
-
-
-void Database::LogExportedResource(const OrthancPluginExportedResource& resource) 
-{
-  Orthanc::ExportedResource tmp(resource.seq,
-                                Orthanc::Plugins::Convert(resource.resourceType),
-                                resource.publicId,
-                                resource.modality,
-                                resource.date,
-                                resource.patientId,
-                                resource.studyInstanceUid,
-                                resource.seriesInstanceUid,
-                                resource.sopInstanceUid);
-
-  base_.LogExportedResource(tmp);
-}
-
-    
-bool Database::LookupAttachment(int64_t id,
-                                int32_t contentType)
-{
-  Orthanc::FileInfo attachment;
-  if (base_.LookupAttachment(attachment, id, static_cast<Orthanc::FileContentType>(contentType)))
-  {
-    GetOutput().AnswerAttachment(attachment.GetUuid(),
-                                 attachment.GetContentType(),
-                                 attachment.GetUncompressedSize(),
-                                 attachment.GetUncompressedMD5(),
-                                 attachment.GetCompressionType(),
-                                 attachment.GetCompressedSize(),
-                                 attachment.GetCompressedMD5());
-    return true;
-  }
-  else
-  {
-    return false;
-  }
-}
-
-
-bool Database::LookupParent(int64_t& parentId /*out*/,
-                            int64_t resourceId)
-{
-  bool found;
-  Orthanc::ErrorCode code = base_.LookupParent(found, parentId, resourceId);
-
-  if (code == Orthanc::ErrorCode_Success)
-  {
-    return found;
-  }
-  else
-  {
-    throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code));
-  }
-}
-
-
-bool Database::LookupResource(int64_t& id /*out*/,
-                              OrthancPluginResourceType& type /*out*/,
-                              const char* publicId)
-{
-  Orthanc::ResourceType tmp;
-  if (base_.LookupResource(id, tmp, publicId))
-  {
-    type = Orthanc::Plugins::Convert(tmp);
-    return true;
-  }
-  else
-  {
-    return false;
-  }
-}
-
-
-void Database::StartTransaction()
-{
-  transaction_.reset(new Orthanc::SQLite::Transaction(db_));
-  transaction_->Begin();
-}
-
-
-void Database::RollbackTransaction()
-{
-  transaction_->Rollback();
-  transaction_.reset(NULL);
-}
-
-
-void Database::CommitTransaction()
-{
-  transaction_->Commit();
-  transaction_.reset(NULL);
-}
-
-
-uint32_t Database::GetDatabaseVersion()
-{
-  std::string version;
-
-  if (!LookupGlobalProperty(version, Orthanc::GlobalProperty_DatabaseSchemaVersion))
-  {
-    throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError);
-  }
-
-  try
-  {
-    return boost::lexical_cast<uint32_t>(version);
-  }
-  catch (boost::bad_lexical_cast&)
-  {
-    throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError);
-  }
-}
-
-
-void Database::UpgradeDatabase(uint32_t  targetVersion,
-                               OrthancPluginStorageArea* storageArea)
-{
-  if (targetVersion == 6)
-  {
-    OrthancPluginErrorCode code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea, 
-                                                                        OrthancPluginResourceType_Study);
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea, 
-                                                   OrthancPluginResourceType_Series);
-    }
-
-    if (code != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancPlugins::DatabaseException(code);
-    }
-
-    base_.SetGlobalProperty(Orthanc::GlobalProperty_DatabaseSchemaVersion, "6");
-  }
-}
--- a/OrthancServer/Resources/Graveyard/DatabasePluginSample/Database.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,275 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "OrthancCppDatabasePlugin.h"
-
-#include "../../../../OrthancFramework/Sources/SQLite/Connection.h"
-#include "../../../../OrthancFramework/Sources/SQLite/Transaction.h"
-#include "../../../Plugins/Engine/PluginsEnumerations.h"
-#include "DatabaseWrapperBase.h"
-
-#include <memory>
-
-class Database : public OrthancPlugins::IDatabaseBackend
-{
-private:
-  class SignalRemainingAncestor;
-
-  std::string                   path_;
-  Orthanc::SQLite::Connection   db_;
-  Orthanc::DatabaseWrapperBase  base_;
-  SignalRemainingAncestor*      signalRemainingAncestor_;
-
-  std::auto_ptr<Orthanc::SQLite::Transaction>  transaction_;
-
-public:
-  Database(const std::string& path);
-
-  virtual void Open();
-
-  virtual void Close();
-
-  virtual void AddAttachment(int64_t id,
-                             const OrthancPluginAttachment& attachment);
-
-  virtual void AttachChild(int64_t parent,
-                           int64_t child)
-  {
-    base_.AttachChild(parent, child);
-  }
-
-  virtual void ClearChanges()
-  {
-    db_.Execute("DELETE FROM Changes");    
-  }
-
-  virtual void ClearExportedResources()
-  {
-    db_.Execute("DELETE FROM ExportedResources");    
-  }
-
-  virtual int64_t CreateResource(const char* publicId,
-                                 OrthancPluginResourceType type)
-  {
-    return base_.CreateResource(publicId, Orthanc::Plugins::Convert(type));
-  }
-
-  virtual void DeleteAttachment(int64_t id,
-                                int32_t attachment)
-  {
-    base_.DeleteAttachment(id, static_cast<Orthanc::FileContentType>(attachment));
-  }
-
-  virtual void DeleteMetadata(int64_t id,
-                              int32_t metadataType)
-  {
-    base_.DeleteMetadata(id, static_cast<Orthanc::MetadataType>(metadataType));
-  }
-
-  virtual void DeleteResource(int64_t id);
-
-  virtual void GetAllInternalIds(std::list<int64_t>& target,
-                                 OrthancPluginResourceType resourceType)
-  {
-    base_.GetAllInternalIds(target, Orthanc::Plugins::Convert(resourceType));
-  }
-
-  virtual void GetAllPublicIds(std::list<std::string>& target,
-                               OrthancPluginResourceType resourceType)
-  {
-    base_.GetAllPublicIds(target, Orthanc::Plugins::Convert(resourceType));
-  }
-
-  virtual void GetAllPublicIds(std::list<std::string>& target,
-                               OrthancPluginResourceType resourceType,
-                               uint64_t since,
-                               uint64_t limit)
-  {
-    base_.GetAllPublicIds(target, Orthanc::Plugins::Convert(resourceType), since, limit);
-  }
-
-  virtual void GetChanges(bool& done /*out*/,
-                          int64_t since,
-                          uint32_t maxResults);
-
-  virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/,
-                                     int64_t id)
-  {
-    base_.GetChildrenInternalId(target, id);
-  }
-
-  virtual void GetChildrenPublicId(std::list<std::string>& target /*out*/,
-                                   int64_t id)
-  {
-    base_.GetChildrenPublicId(target, id);
-  }
-
-  virtual void GetExportedResources(bool& done /*out*/,
-                                    int64_t since,
-                                    uint32_t maxResults);
-
-  virtual void GetLastChange();
-
-  virtual void GetLastExportedResource();
-
-  virtual void GetMainDicomTags(int64_t id);
-
-  virtual std::string GetPublicId(int64_t resourceId);
-
-  virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType)
-  {
-    return base_.GetResourceCount(Orthanc::Plugins::Convert(resourceType));
-  }
-
-  virtual OrthancPluginResourceType GetResourceType(int64_t resourceId);
-
-  virtual uint64_t GetTotalCompressedSize()
-  {
-    return base_.GetTotalCompressedSize();
-  }
-    
-  virtual uint64_t GetTotalUncompressedSize()
-  {
-    return base_.GetTotalUncompressedSize();
-  }
-
-  virtual bool IsExistingResource(int64_t internalId)
-  {
-    return base_.IsExistingResource(internalId);
-  }
-
-  virtual bool IsProtectedPatient(int64_t internalId)
-  {
-    return base_.IsProtectedPatient(internalId);
-  }
-
-  virtual void ListAvailableMetadata(std::list<int32_t>& target /*out*/,
-                                     int64_t id);
-
-  virtual void ListAvailableAttachments(std::list<int32_t>& target /*out*/,
-                                        int64_t id);
-
-  virtual void LogChange(const OrthancPluginChange& change);
-
-  virtual void LogExportedResource(const OrthancPluginExportedResource& resource);
-    
-  virtual bool LookupAttachment(int64_t id,
-                                int32_t contentType);
-
-  virtual bool LookupGlobalProperty(std::string& target /*out*/,
-                                    int32_t property)
-  {
-    return base_.LookupGlobalProperty(target, static_cast<Orthanc::GlobalProperty>(property));
-  }
-
-  virtual void LookupIdentifier(std::list<int64_t>& target /*out*/,
-                                OrthancPluginResourceType level,
-                                uint16_t group,
-                                uint16_t element,
-                                OrthancPluginIdentifierConstraint constraint,
-                                const char* value)
-  {
-    base_.LookupIdentifier(target, Orthanc::Plugins::Convert(level),
-                           Orthanc::DicomTag(group, element), 
-                           Orthanc::Plugins::Convert(constraint), value);
-  }
-
-  virtual bool LookupMetadata(std::string& target /*out*/,
-                              int64_t id,
-                              int32_t metadataType)
-  {
-    return base_.LookupMetadata(target, id, static_cast<Orthanc::MetadataType>(metadataType));
-  }
-
-  virtual bool LookupParent(int64_t& parentId /*out*/,
-                            int64_t resourceId);
-
-  virtual bool LookupResource(int64_t& id /*out*/,
-                              OrthancPluginResourceType& type /*out*/,
-                              const char* publicId);
-
-  virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/)
-  {
-    return base_.SelectPatientToRecycle(internalId);
-  }
-
-  virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/,
-                                      int64_t patientIdToAvoid)
-  {
-    return base_.SelectPatientToRecycle(internalId, patientIdToAvoid);
-  }
-
-
-  virtual void SetGlobalProperty(int32_t property,
-                                 const char* value)
-  {
-    base_.SetGlobalProperty(static_cast<Orthanc::GlobalProperty>(property), value);
-  }
-
-  virtual void SetMainDicomTag(int64_t id,
-                               uint16_t group,
-                               uint16_t element,
-                               const char* value)
-  {
-    base_.SetMainDicomTag(id, Orthanc::DicomTag(group, element), value);
-  }
-
-  virtual void SetIdentifierTag(int64_t id,
-                                uint16_t group,
-                                uint16_t element,
-                                const char* value)
-  {
-    base_.SetIdentifierTag(id, Orthanc::DicomTag(group, element), value);
-  }
-
-  virtual void SetMetadata(int64_t id,
-                           int32_t metadataType,
-                           const char* value)
-  {
-    base_.SetMetadata(id, static_cast<Orthanc::MetadataType>(metadataType), value);
-  }
-
-  virtual void SetProtectedPatient(int64_t internalId, 
-                                   bool isProtected)
-  {
-    base_.SetProtectedPatient(internalId, isProtected);
-  }
-
-  virtual void StartTransaction();
-
-  virtual void RollbackTransaction();
-
-  virtual void CommitTransaction();
-
-  virtual uint32_t GetDatabaseVersion();
-
-  virtual void UpgradeDatabase(uint32_t  targetVersion,
-                               OrthancPluginStorageArea* storageArea);
-
-  virtual void ClearMainDicomTags(int64_t internalId)
-  {
-    base_.ClearMainDicomTags(internalId);
-  }
-};
--- a/OrthancServer/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,747 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../../../Sources/PrecompiledHeadersServer.h"
-#include "DatabaseWrapperBase.h"
-
-#include <stdio.h>
-#include <memory>
-
-namespace Orthanc
-{
-  void DatabaseWrapperBase::SetGlobalProperty(GlobalProperty property,
-                                              const std::string& value)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)");
-    s.BindInt(0, property);
-    s.BindString(1, value);
-    s.Run();
-  }
-
-  bool DatabaseWrapperBase::LookupGlobalProperty(std::string& target,
-                                                 GlobalProperty property)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT value FROM GlobalProperties WHERE property=?");
-    s.BindInt(0, property);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      target = s.ColumnString(0);
-      return true;
-    }
-  }
-
-  int64_t DatabaseWrapperBase::CreateResource(const std::string& publicId,
-                                              ResourceType type)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)");
-    s.BindInt(0, type);
-    s.BindString(1, publicId);
-    s.Run();
-    return db_.GetLastInsertRowId();
-  }
-
-  bool DatabaseWrapperBase::LookupResource(int64_t& id,
-                                           ResourceType& type,
-                                           const std::string& publicId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT internalId, resourceType FROM Resources WHERE publicId=?");
-    s.BindString(0, publicId);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      id = s.ColumnInt(0);
-      type = static_cast<ResourceType>(s.ColumnInt(1));
-
-      // Check whether there is a single resource with this public id
-      assert(!s.Step());
-
-      return true;
-    }
-  }
-
-  ErrorCode DatabaseWrapperBase::LookupParent(bool& found,
-                                              int64_t& parentId,
-                                              int64_t resourceId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT parentId FROM Resources WHERE internalId=?");
-    s.BindInt64(0, resourceId);
-
-    if (!s.Step())
-    {
-      return ErrorCode_UnknownResource;
-    }
-
-    if (s.ColumnIsNull(0))
-    {
-      found = false;
-    }
-    else
-    {
-      found = true;
-      parentId = s.ColumnInt(0);
-    }
-
-    return ErrorCode_Success;
-  }
-
-  bool DatabaseWrapperBase::GetPublicId(std::string& result,
-                                        int64_t resourceId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT publicId FROM Resources WHERE internalId=?");
-    s.BindInt64(0, resourceId);
-    
-    if (!s.Step())
-    { 
-      return false;
-    }
-    else
-    {
-      result = s.ColumnString(0);
-      return true;
-    }
-  }
-
-
-  ErrorCode DatabaseWrapperBase::GetResourceType(ResourceType& result,
-                                                 int64_t resourceId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT resourceType FROM Resources WHERE internalId=?");
-    s.BindInt64(0, resourceId);
-    
-    if (s.Step())
-    {
-      result = static_cast<ResourceType>(s.ColumnInt(0));
-      return ErrorCode_Success;
-    }
-    else
-    { 
-      return ErrorCode_UnknownResource;
-    }
-  }
-
-
-  void DatabaseWrapperBase::AttachChild(int64_t parent,
-                                        int64_t child)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
-    s.BindInt64(0, parent);
-    s.BindInt64(1, child);
-    s.Run();
-  }
-
-
-  void DatabaseWrapperBase::SetMetadata(int64_t id,
-                                        MetadataType type,
-                                        const std::string& value)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)");
-    s.BindInt64(0, id);
-    s.BindInt(1, type);
-    s.BindString(2, value);
-    s.Run();
-  }
-
-  void DatabaseWrapperBase::DeleteMetadata(int64_t id,
-                                           MetadataType type)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, type);
-    s.Run();
-  }
-
-  bool DatabaseWrapperBase::LookupMetadata(std::string& target,
-                                           int64_t id,
-                                           MetadataType type)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT value FROM Metadata WHERE id=? AND type=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, type);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      target = s.ColumnString(0);
-      return true;
-    }
-  }
-
-  void DatabaseWrapperBase::ListAvailableMetadata(std::list<MetadataType>& target,
-                                                  int64_t id)
-  {
-    target.clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?");
-    s.BindInt64(0, id);
-
-    while (s.Step())
-    {
-      target.push_back(static_cast<MetadataType>(s.ColumnInt(0)));
-    }
-  }
-
-
-  void DatabaseWrapperBase::AddAttachment(int64_t id,
-                                          const FileInfo& attachment)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
-    s.BindInt64(0, id);
-    s.BindInt(1, attachment.GetContentType());
-    s.BindString(2, attachment.GetUuid());
-    s.BindInt64(3, attachment.GetCompressedSize());
-    s.BindInt64(4, attachment.GetUncompressedSize());
-    s.BindInt(5, attachment.GetCompressionType());
-    s.BindString(6, attachment.GetUncompressedMD5());
-    s.BindString(7, attachment.GetCompressedMD5());
-    s.Run();
-  }
-
-
-  void DatabaseWrapperBase::DeleteAttachment(int64_t id,
-                                             FileContentType attachment)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, attachment);
-    s.Run();
-  }
-
-
-
-  void DatabaseWrapperBase::ListAvailableAttachments(std::list<FileContentType>& target,
-                                                     int64_t id)
-  {
-    target.clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT fileType FROM AttachedFiles WHERE id=?");
-    s.BindInt64(0, id);
-
-    while (s.Step())
-    {
-      target.push_back(static_cast<FileContentType>(s.ColumnInt(0)));
-    }
-  }
-
-  bool DatabaseWrapperBase::LookupAttachment(FileInfo& attachment,
-                                             int64_t id,
-                                             FileContentType contentType)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, contentType);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      attachment = FileInfo(s.ColumnString(0),
-                            contentType,
-                            s.ColumnInt64(1),
-                            s.ColumnString(4),
-                            static_cast<CompressionType>(s.ColumnInt(2)),
-                            s.ColumnInt64(3),
-                            s.ColumnString(5));
-      return true;
-    }
-  }
-
-
-  void DatabaseWrapperBase::ClearMainDicomTags(int64_t id)
-  {
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?");
-      s.BindInt64(0, id);
-      s.Run();
-    }
-
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?");
-      s.BindInt64(0, id);
-      s.Run();
-    }
-  }
-
-
-  void DatabaseWrapperBase::SetMainDicomTag(int64_t id,
-                                            const DicomTag& tag,
-                                            const std::string& value)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
-    s.BindInt64(0, id);
-    s.BindInt(1, tag.GetGroup());
-    s.BindInt(2, tag.GetElement());
-    s.BindString(3, value);
-    s.Run();
-  }
-
-
-  void DatabaseWrapperBase::SetIdentifierTag(int64_t id,
-                                             const DicomTag& tag,
-                                             const std::string& value)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
-    s.BindInt64(0, id);
-    s.BindInt(1, tag.GetGroup());
-    s.BindInt(2, tag.GetElement());
-    s.BindString(3, value);
-    s.Run();
-  }
-
-
-  void DatabaseWrapperBase::GetMainDicomTags(DicomMap& map,
-                                             int64_t id)
-  {
-    map.Clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?");
-    s.BindInt64(0, id);
-    while (s.Step())
-    {
-      map.SetValue(s.ColumnInt(1),
-                   s.ColumnInt(2),
-                   s.ColumnString(3), false);
-    }
-  }
-
-
-
-  void DatabaseWrapperBase::GetChildrenPublicId(std::list<std::string>& target,
-                                                int64_t id)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b  "
-                        "WHERE a.parentId = b.internalId AND b.internalId = ?");     
-    s.BindInt64(0, id);
-
-    target.clear();
-
-    while (s.Step())
-    {
-      target.push_back(s.ColumnString(0));
-    }
-  }
-
-
-  void DatabaseWrapperBase::GetChildrenInternalId(std::list<int64_t>& target,
-                                                  int64_t id)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b  "
-                        "WHERE a.parentId = b.internalId AND b.internalId = ?");     
-    s.BindInt64(0, id);
-
-    target.clear();
-
-    while (s.Step())
-    {
-      target.push_back(s.ColumnInt64(0));
-    }
-  }
-
-
-  void DatabaseWrapperBase::LogChange(int64_t internalId,
-                                      const ServerIndexChange& change)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)");
-    s.BindInt(0, change.GetChangeType());
-    s.BindInt64(1, internalId);
-    s.BindInt(2, change.GetResourceType());
-    s.BindString(3, change.GetDate());
-    s.Run();
-  }
-
-
-  ErrorCode DatabaseWrapperBase::GetChangesInternal(std::list<ServerIndexChange>& target,
-                                                    bool& done,
-                                                    SQLite::Statement& s,
-                                                    uint32_t maxResults)
-  {
-    target.clear();
-
-    while (target.size() < maxResults && s.Step())
-    {
-      int64_t seq = s.ColumnInt64(0);
-      ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1));
-      ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3));
-      const std::string& date = s.ColumnString(4);
-
-      int64_t internalId = s.ColumnInt64(2);
-      std::string publicId;
-      if (!GetPublicId(publicId, internalId))
-      {
-        return ErrorCode_UnknownResource;
-      }
-
-      target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date));
-    }
-
-    done = !(target.size() == maxResults && s.Step());
-    return ErrorCode_Success;
-  }
-
-
-  ErrorCode DatabaseWrapperBase::GetChanges(std::list<ServerIndexChange>& target,
-                                            bool& done,
-                                            int64_t since,
-                                            uint32_t maxResults)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?");
-    s.BindInt64(0, since);
-    s.BindInt(1, maxResults + 1);
-    return GetChangesInternal(target, done, s, maxResults);
-  }
-
-  ErrorCode DatabaseWrapperBase::GetLastChange(std::list<ServerIndexChange>& target)
-  {
-    bool done;  // Ignored
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1");
-    return GetChangesInternal(target, done, s, 1);
-  }
-
-
-  void DatabaseWrapperBase::LogExportedResource(const ExportedResource& resource)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
-
-    s.BindInt(0, resource.GetResourceType());
-    s.BindString(1, resource.GetPublicId());
-    s.BindString(2, resource.GetModality());
-    s.BindString(3, resource.GetPatientId());
-    s.BindString(4, resource.GetStudyInstanceUid());
-    s.BindString(5, resource.GetSeriesInstanceUid());
-    s.BindString(6, resource.GetSopInstanceUid());
-    s.BindString(7, resource.GetDate());
-    s.Run();      
-  }
-
-
-  void DatabaseWrapperBase::GetExportedResourcesInternal(std::list<ExportedResource>& target,
-                                                         bool& done,
-                                                         SQLite::Statement& s,
-                                                         uint32_t maxResults)
-  {
-    target.clear();
-
-    while (target.size() < maxResults && s.Step())
-    {
-      int64_t seq = s.ColumnInt64(0);
-      ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1));
-      std::string publicId = s.ColumnString(2);
-
-      ExportedResource resource(seq, 
-                                resourceType,
-                                publicId,
-                                s.ColumnString(3),  // modality
-                                s.ColumnString(8),  // date
-                                s.ColumnString(4),  // patient ID
-                                s.ColumnString(5),  // study instance UID
-                                s.ColumnString(6),  // series instance UID
-                                s.ColumnString(7)); // sop instance UID
-
-      target.push_back(resource);
-    }
-
-    done = !(target.size() == maxResults && s.Step());
-  }
-
-
-  void DatabaseWrapperBase::GetExportedResources(std::list<ExportedResource>& target,
-                                                 bool& done,
-                                                 int64_t since,
-                                                 uint32_t maxResults)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?");
-    s.BindInt64(0, since);
-    s.BindInt(1, maxResults + 1);
-    GetExportedResourcesInternal(target, done, s, maxResults);
-  }
-
-    
-  void DatabaseWrapperBase::GetLastExportedResource(std::list<ExportedResource>& target)
-  {
-    bool done;  // Ignored
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1");
-    GetExportedResourcesInternal(target, done, s, 1);
-  }
-
-
-    
-  uint64_t DatabaseWrapperBase::GetTotalCompressedSize()
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles");
-    s.Run();
-    return static_cast<uint64_t>(s.ColumnInt64(0));
-  }
-
-    
-  uint64_t DatabaseWrapperBase::GetTotalUncompressedSize()
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles");
-    s.Run();
-    return static_cast<uint64_t>(s.ColumnInt64(0));
-  }
-
-  void DatabaseWrapperBase::GetAllInternalIds(std::list<int64_t>& target,
-                                              ResourceType resourceType)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT internalId FROM Resources WHERE resourceType=?");
-    s.BindInt(0, resourceType);
-
-    target.clear();
-    while (s.Step())
-    {
-      target.push_back(s.ColumnInt64(0));
-    }
-  }
-
-
-  void DatabaseWrapperBase::GetAllPublicIds(std::list<std::string>& target,
-                                            ResourceType resourceType)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?");
-    s.BindInt(0, resourceType);
-
-    target.clear();
-    while (s.Step())
-    {
-      target.push_back(s.ColumnString(0));
-    }
-  }
-
-  void DatabaseWrapperBase::GetAllPublicIds(std::list<std::string>& target,
-                                            ResourceType resourceType,
-                                            size_t since,
-                                            size_t limit)
-  {
-    if (limit == 0)
-    {
-      target.clear();
-      return;
-    }
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=? LIMIT ? OFFSET ?");
-    s.BindInt(0, resourceType);
-    s.BindInt64(1, limit);
-    s.BindInt64(2, since);
-
-    target.clear();
-    while (s.Step())
-    {
-      target.push_back(s.ColumnString(0));
-    }
-  }
-
-
-  uint64_t DatabaseWrapperBase::GetResourceCount(ResourceType resourceType)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT COUNT(*) FROM Resources WHERE resourceType=?");
-    s.BindInt(0, resourceType);
-    
-    if (!s.Step())
-    {
-      return 0;
-    }
-    else
-    {
-      int64_t c = s.ColumnInt(0);
-      assert(!s.Step());
-      return c;
-    }
-  }
-
-
-  bool DatabaseWrapperBase::SelectPatientToRecycle(int64_t& internalId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                        "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1");
-   
-    if (!s.Step())
-    {
-      // No patient remaining or all the patients are protected
-      return false;
-    }
-    else
-    {
-      internalId = s.ColumnInt(0);
-      return true;
-    }    
-  }
-
-  bool DatabaseWrapperBase::SelectPatientToRecycle(int64_t& internalId,
-                                                   int64_t patientIdToAvoid)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                        "SELECT patientId FROM PatientRecyclingOrder "
-                        "WHERE patientId != ? ORDER BY seq ASC LIMIT 1");
-    s.BindInt64(0, patientIdToAvoid);
-
-    if (!s.Step())
-    {
-      // No patient remaining or all the patients are protected
-      return false;
-    }
-    else
-    {
-      internalId = s.ColumnInt(0);
-      return true;
-    }   
-  }
-
-  bool DatabaseWrapperBase::IsProtectedPatient(int64_t internalId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                        "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?");
-    s.BindInt64(0, internalId);
-    return !s.Step();
-  }
-
-  void DatabaseWrapperBase::SetProtectedPatient(int64_t internalId, 
-                                                bool isProtected)
-  {
-    if (isProtected)
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
-      s.BindInt64(0, internalId);
-      s.Run();
-    }
-    else if (IsProtectedPatient(internalId))
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
-      s.BindInt64(0, internalId);
-      s.Run();
-    }
-    else
-    {
-      // Nothing to do: The patient is already unprotected
-    }
-  }
-
-
-
-  bool DatabaseWrapperBase::IsExistingResource(int64_t internalId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT * FROM Resources WHERE internalId=?");
-    s.BindInt64(0, internalId);
-    return s.Step();
-  }
-
-
-
-  void DatabaseWrapperBase::LookupIdentifier(std::list<int64_t>& target,
-                                             ResourceType level,
-                                             const DicomTag& tag,
-                                             IdentifierConstraintType type,
-                                             const std::string& value)
-  {
-    static const char* COMMON = ("SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE "
-                                 "d.id = r.internalId AND r.resourceType=? AND "
-                                 "d.tagGroup=? AND d.tagElement=? AND ");
-
-    std::auto_ptr<SQLite::Statement> s;
-
-    switch (type)
-    {
-      case IdentifierConstraintType_GreaterOrEqual:
-        s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value>=?"));
-        break;
-
-      case IdentifierConstraintType_SmallerOrEqual:
-        s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value<=?"));
-        break;
-
-      case IdentifierConstraintType_Wildcard:
-        s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value GLOB ?"));
-        break;
-
-      case IdentifierConstraintType_Equal:
-      default:
-        s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value=?"));
-        break;
-    }
-
-    assert(s.get() != NULL);
-
-    s->BindInt(0, level);
-    s->BindInt(1, tag.GetGroup());
-    s->BindInt(2, tag.GetElement());
-    s->BindString(3, value);
-
-    target.clear();
-
-    while (s->Step())
-    {
-      target.push_back(s->ColumnInt64(0));
-    }    
-  }
-
-
-  void DatabaseWrapperBase::LookupIdentifierRange(std::list<int64_t>& target,
-                                                  ResourceType level,
-                                                  const DicomTag& tag,
-                                                  const std::string& start,
-                                                  const std::string& end)
-  {
-    SQLite::Statement statement(db_, SQLITE_FROM_HERE,
-                                "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE "
-                                "d.id = r.internalId AND r.resourceType=? AND "
-                                "d.tagGroup=? AND d.tagElement=? AND d.value>=? AND d.value<=?");
-
-    statement.BindInt(0, level);
-    statement.BindInt(1, tag.GetGroup());
-    statement.BindInt(2, tag.GetElement());
-    statement.BindString(3, start);
-    statement.BindString(4, end);
-
-    target.clear();
-
-    while (statement.Step())
-    {
-      target.push_back(statement.ColumnInt64(0));
-    }    
-  }
-}
--- a/OrthancServer/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,200 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomMap.h"
-#include "../../../../OrthancFramework/Sources/DicomFormat/DicomTag.h"
-#include "../../../../OrthancFramework/Sources/Enumerations.h"
-#include "../../../../OrthancFramework/Sources/FileStorage/FileInfo.h"
-#include "../../../../OrthancFramework/Sources/SQLite/Connection.h"
-#include "../../../Sources/ExportedResource.h"
-#include "../../../Sources/ServerIndexChange.h"
-#include "../../../Sources/ServerEnumerations.h"
-
-#include <list>
-
-
-namespace Orthanc
-{
-  /**
-   * This class is shared between the Orthanc core and the sample
-   * database plugin whose code is in
-   * "../Plugins/Samples/DatabasePlugin".
-   **/
-  class DatabaseWrapperBase
-  {
-  private:
-    SQLite::Connection&  db_;
-
-    ErrorCode GetChangesInternal(std::list<ServerIndexChange>& target,
-                                 bool& done,
-                                 SQLite::Statement& s,
-                                 uint32_t maxResults);
-
-    void GetExportedResourcesInternal(std::list<ExportedResource>& target,
-                                      bool& done,
-                                      SQLite::Statement& s,
-                                      uint32_t maxResults);
-
-  public:
-    DatabaseWrapperBase(SQLite::Connection& db) : db_(db)
-    {
-    }
-
-    void SetGlobalProperty(GlobalProperty property,
-                           const std::string& value);
-
-    bool LookupGlobalProperty(std::string& target,
-                              GlobalProperty property);
-
-    int64_t CreateResource(const std::string& publicId,
-                           ResourceType type);
-
-    bool LookupResource(int64_t& id,
-                        ResourceType& type,
-                        const std::string& publicId);
-
-    ErrorCode LookupParent(bool& found,
-                           int64_t& parentId,
-                           int64_t resourceId);
-
-    bool GetPublicId(std::string& result,
-                     int64_t resourceId);
-
-    ErrorCode GetResourceType(ResourceType& result,
-                              int64_t resourceId);
-
-    void AttachChild(int64_t parent,
-                     int64_t child);
-
-    void SetMetadata(int64_t id,
-                     MetadataType type,
-                     const std::string& value);
-
-    void DeleteMetadata(int64_t id,
-                        MetadataType type);
-
-    bool LookupMetadata(std::string& target,
-                        int64_t id,
-                        MetadataType type);
-
-    void ListAvailableMetadata(std::list<MetadataType>& target,
-                               int64_t id);
-
-    void AddAttachment(int64_t id,
-                       const FileInfo& attachment);
-
-    void DeleteAttachment(int64_t id,
-                          FileContentType attachment);
-
-    void ListAvailableAttachments(std::list<FileContentType>& target,
-                                  int64_t id);
-
-    bool LookupAttachment(FileInfo& attachment,
-                          int64_t id,
-                          FileContentType contentType);
-
-
-    void ClearMainDicomTags(int64_t id);
-
-
-    void SetMainDicomTag(int64_t id,
-                         const DicomTag& tag,
-                         const std::string& value);
-
-    void SetIdentifierTag(int64_t id,
-                          const DicomTag& tag,
-                          const std::string& value);
-
-    void GetMainDicomTags(DicomMap& map,
-                          int64_t id);
-
-    void GetChildrenPublicId(std::list<std::string>& target,
-                             int64_t id);
-
-    void GetChildrenInternalId(std::list<int64_t>& target,
-                               int64_t id);
-
-    void LogChange(int64_t internalId,
-                   const ServerIndexChange& change);
-
-    ErrorCode GetChanges(std::list<ServerIndexChange>& target,
-                         bool& done,
-                         int64_t since,
-                         uint32_t maxResults);
-
-    ErrorCode GetLastChange(std::list<ServerIndexChange>& target);
-
-    void LogExportedResource(const ExportedResource& resource);
-
-    void GetExportedResources(std::list<ExportedResource>& target,
-                              bool& done,
-                              int64_t since,
-                              uint32_t maxResults);
-    
-    void GetLastExportedResource(std::list<ExportedResource>& target);
-    
-    uint64_t GetTotalCompressedSize();
-    
-    uint64_t GetTotalUncompressedSize();
-
-    void GetAllInternalIds(std::list<int64_t>& target,
-                           ResourceType resourceType);
-
-    void GetAllPublicIds(std::list<std::string>& target,
-                         ResourceType resourceType);
-
-    void GetAllPublicIds(std::list<std::string>& target,
-                         ResourceType resourceType,
-                         size_t since,
-                         size_t limit);
-
-    uint64_t GetResourceCount(ResourceType resourceType);
-
-    bool SelectPatientToRecycle(int64_t& internalId);
-
-    bool SelectPatientToRecycle(int64_t& internalId,
-                                int64_t patientIdToAvoid);
-
-    bool IsProtectedPatient(int64_t internalId);
-
-    void SetProtectedPatient(int64_t internalId, 
-                             bool isProtected);
-
-    bool IsExistingResource(int64_t internalId);
-
-    void LookupIdentifier(std::list<int64_t>& result,
-                          ResourceType level,
-                          const DicomTag& tag,
-                          IdentifierConstraintType type,
-                          const std::string& value);
-
-    void LookupIdentifierRange(std::list<int64_t>& result,
-                               ResourceType level,
-                               const DicomTag& tag,
-                               const std::string& start,
-                               const std::string& end);
-  };
-}
-
--- a/OrthancServer/Resources/Graveyard/DatabasePluginSample/Plugin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Database.h"
-
-#include <memory>
-#include <iostream>
-#include <boost/algorithm/string/predicate.hpp>
-
-static OrthancPluginContext*  context_ = NULL;
-static std::auto_ptr<OrthancPlugins::IDatabaseBackend>  backend_;
-
-
-extern "C"
-{
-  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
-  {
-    context_ = c;
-    OrthancPluginLogWarning(context_, "Sample plugin is initializing");
-
-    /* Check the version of the Orthanc core */
-    if (OrthancPluginCheckVersion(c) == 0)
-    {
-      char info[256];
-      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
-              c->orthancVersion,
-              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
-              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
-              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
-      OrthancPluginLogError(context_, info);
-      return -1;
-    }
-
-    std::string path = "SampleDatabase.sqlite";
-    uint32_t argCount = OrthancPluginGetCommandLineArgumentsCount(context_);
-    for (uint32_t i = 0; i < argCount; i++)
-    {
-      char* tmp = OrthancPluginGetCommandLineArgument(context_, i);
-      std::string argument(tmp);
-      OrthancPluginFreeString(context_, tmp);
-
-      if (boost::starts_with(argument, "--database="))
-      {
-        path = argument.substr(11);
-      }
-    }
-
-    std::string s = "Using the following SQLite database: " + path;
-    OrthancPluginLogWarning(context_, s.c_str());
-
-    backend_.reset(new Database(path));
-    OrthancPlugins::DatabaseBackendAdapter::Register(context_, *backend_);
-
-    return 0;
-  }
-
-  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
-  {
-    backend_.reset(NULL);
-  }
-
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
-  {
-    return "sample-database";
-  }
-
-
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
-  {
-    return "1.0";
-  }
-}
--- a/OrthancServer/Resources/Graveyard/FindRefactoringForSQLite.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,167 +0,0 @@
-#if 0
-    // TODO-FIND: Remove this implementation, as it should be done by
-    // the compatibility mode implemented by "GenericFind"
-    
-    virtual void ExecuteFind(FindResponse& response,
-                             const FindRequest& request, 
-                             const std::vector<DatabaseConstraint>& normalized) ORTHANC_OVERRIDE
-    {
-#if 0
-      Compatibility::GenericFind find(*this);
-      find.Execute(response, request);
-#else
-      {
-        SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS FilteredResourcesIds");
-        s.Run();
-      }
-
-      {
-
-        LookupFormatter formatter;
-
-        std::string sqlLookup;
-        LookupFormatter::Apply(sqlLookup, 
-                               formatter, 
-                               normalized, 
-                               request.GetLevel(),
-                               request.GetLabels(),
-                               request.GetLabelsConstraint(),
-                               (request.HasLimits() ? request.GetLimitsCount() : 0));  // TODO: handles since and count
-
-        {
-          // first create a temporary table that with the filtered and ordered results
-          sqlLookup = "CREATE TEMPORARY TABLE FilteredResourcesIds AS " + sqlLookup;
-
-          SQLite::Statement statement(db_, SQLITE_FROM_HERE_DYNAMIC(sqlLookup), sqlLookup);
-          formatter.Bind(statement);
-          statement.Run();
-        }
-
-        {
-          // create the response item with the public ids only
-          SQLite::Statement statement(db_, SQLITE_FROM_HERE, "SELECT publicId FROM FilteredResourcesIds");
-          formatter.Bind(statement);
-
-          while (statement.Step())
-          {
-            const std::string resourceId = statement.ColumnString(0);
-            response.Add(new FindResponse::Resource(request.GetLevel(), resourceId));
-          }
-        }
-
-        // request Each response content through INNER JOIN with the temporary table
-        if (request.IsRetrieveMainDicomTags())
-        {
-          // TODO-FIND: handle the case where we request tags from multiple levels
-          SQLite::Statement statement(db_, SQLITE_FROM_HERE, 
-                                      "SELECT publicId, tagGroup, tagElement, value FROM MainDicomTags AS tags "
-                                      "  INNER JOIN FilteredResourcesIds  ON tags.id = FilteredResourcesIds.internalId");
-          formatter.Bind(statement);
-
-          while (statement.Step())
-          {
-            const std::string& resourceId = statement.ColumnString(0);
-            assert(response.HasResource(resourceId));
-            response.GetResource(resourceId).AddStringDicomTag(statement.ColumnInt(1),
-                                                               statement.ColumnInt(2),
-                                                               statement.ColumnString(3));
-          }
-        }
-
-        if (request.IsRetrieveChildrenIdentifiers())
-        {
-          SQLite::Statement statement(db_, SQLITE_FROM_HERE, 
-                                      "SELECT filtered.publicId, childLevel.publicId AS childPublicId "
-                                      "FROM Resources as currentLevel "
-                                      "    INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = currentLevel.internalId "
-                                      "    INNER JOIN Resources childLevel ON childLevel.parentId = currentLevel.internalId");
-          formatter.Bind(statement);
-
-          while (statement.Step())
-          {
-            const std::string& resourceId = statement.ColumnString(0);
-            assert(response.HasResource(resourceId));
-            response.GetResource(resourceId).AddChildIdentifier(GetChildResourceType(request.GetLevel()), statement.ColumnString(1));
-          }
-        }
-
-        if (request.IsRetrieveParentIdentifier())
-        {
-          SQLite::Statement statement(db_, SQLITE_FROM_HERE, 
-                                      "SELECT filtered.publicId, parentLevel.publicId AS parentPublicId "
-                                      "FROM Resources as currentLevel "
-                                      "    INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = currentLevel.internalId "
-                                      "    INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId");
-
-          while (statement.Step())
-          {
-            const std::string& resourceId = statement.ColumnString(0);
-            const std::string& parentId = statement.ColumnString(1);
-            assert(response.HasResource(resourceId));
-            response.GetResource(resourceId).SetParentIdentifier(parentId);
-          }
-        }
-
-        if (request.IsRetrieveMetadata())
-        {
-          SQLite::Statement statement(db_, SQLITE_FROM_HERE, 
-                                      "SELECT filtered.publicId, metadata.type, metadata.value "
-                                      "FROM Metadata "
-                                      "  INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = Metadata.id");
-
-          while (statement.Step())
-          {
-            const std::string& resourceId = statement.ColumnString(0);
-            assert(response.HasResource(resourceId));
-            response.GetResource(resourceId).AddMetadata(static_cast<MetadataType>(statement.ColumnInt(1)),
-                                                         statement.ColumnString(2));
-          }
-        }
-
-        if (request.IsRetrieveLabels())
-        {
-          SQLite::Statement statement(db_, SQLITE_FROM_HERE, 
-                                      "SELECT filtered.publicId, label "
-                                      "FROM Labels "
-                                      "  INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = Labels.id");
-
-          while (statement.Step())
-          {
-            const std::string& resourceId = statement.ColumnString(0);
-            assert(response.HasResource(resourceId));
-            response.GetResource(resourceId).AddLabel(statement.ColumnString(1));
-          }
-        }
-
-        if (request.IsRetrieveAttachments())
-        {
-          SQLite::Statement statement(db_, SQLITE_FROM_HERE, 
-                                      "SELECT filtered.publicId, uuid, fileType, uncompressedSize, compressionType, compressedSize, "
-                                      "       uncompressedMD5, compressedMD5 "
-                                      "FROM AttachedFiles "
-                                      "  INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = AttachedFiles.id");
-
-          while (statement.Step())
-          {
-            const std::string& resourceId = statement.ColumnString(0);
-            FileInfo attachment = FileInfo(statement.ColumnString(1),
-                                           static_cast<FileContentType>(statement.ColumnInt(2)),
-                                           statement.ColumnInt64(3),
-                                           statement.ColumnString(6),
-                                           static_cast<CompressionType>(statement.ColumnInt(4)),
-                                           statement.ColumnInt64(5),
-                                           statement.ColumnString(7));
-
-            assert(response.HasResource(resourceId));
-            response.GetResource(resourceId).AddAttachment(attachment);
-          };
-        }
-
-        // TODO-FIND: implement other responseContent: ResponseContent_ChildInstanceId, ResponseContent_ChildrenMetadata (later: ResponseContent_IsStable)
-
-      }
-
-#endif
-    }
-#endif
-
--- a/OrthancServer/Resources/Graveyard/SetupAnonymization2011.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,258 +0,0 @@
-  /**
-   * This is a manual implementation by Alain Mazy. Only kept for reference.
-   * https://bitbucket.org/sjodogne/orthanc/commits/c6defdc4c611fca2ab528ba2c6937a742e0329a8?at=issue-46-anonymization
-   **/
-  
-  void DicomModification::SetupAnonymization2011()
-  {
-    // This is Table E.1-1 from PS 3.15-2011 - DICOM Part 15: Security and System Management Profiles
-    // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2011/11_15pu.pdf
-    
-    removals_.insert(DicomTag(0x0000, 0x1000));  // Affected SOP Instance UID
-    removals_.insert(DicomTag(0x0000, 0x1001));  // Requested SOP Instance UID
-    removals_.insert(DicomTag(0x0002, 0x0003));  // Media Storage SOP Instance UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances
-    removals_.insert(DicomTag(0x0004, 0x1511));  // Referenced SOP Instance UID in File
-    removals_.insert(DicomTag(0x0008, 0x0010));  // Irradiation Event UID
-    removals_.insert(DicomTag(0x0008, 0x0014));  // Instance Creator UID
-    //removals_.insert(DicomTag(0x0008, 0x0018));  // SOP Instance UID => set in Apply()
-    clearings_.insert(DicomTag(0x0008, 0x0020)); // Study Date
-    clearings_.insert(DicomTag(0x0008, 0x0021)); // Series Date
-    clearings_.insert(DicomTag(0x0008, 0x0030)); // Study Time
-    clearings_.insert(DicomTag(0x0008, 0x0031)); // Series Time
-    removals_.insert(DicomTag(0x0008, 0x0022));  // Acquisition Date
-    removals_.insert(DicomTag(0x0008, 0x0023));  // Content Date
-    removals_.insert(DicomTag(0x0008, 0x0024));  // Overlay Date
-    removals_.insert(DicomTag(0x0008, 0x0025));  // Curve Date
-    removals_.insert(DicomTag(0x0008, 0x002a));  // Acquisition DateTime
-    removals_.insert(DicomTag(0x0008, 0x0032));  // Acquisition Time
-    removals_.insert(DicomTag(0x0008, 0x0033));  // Content Time
-    removals_.insert(DicomTag(0x0008, 0x0034));  // Overlay Time
-    removals_.insert(DicomTag(0x0008, 0x0035));  // Curve Time
-    removals_.insert(DicomTag(0x0008, 0x0050));  // Accession Number
-    removals_.insert(DicomTag(0x0008, 0x0058));  // Failed SOP Instance UID List
-    removals_.insert(DicomTag(0x0008, 0x0080));  // Institution Name
-    removals_.insert(DicomTag(0x0008, 0x0081));  // Institution Address
-    removals_.insert(DicomTag(0x0008, 0x0082));  // Institution Code Sequence
-    removals_.insert(DicomTag(0x0008, 0x0090));  // Referring Physician's Name
-    removals_.insert(DicomTag(0x0008, 0x0092));  // Referring Physician's Address 
-    removals_.insert(DicomTag(0x0008, 0x0094));  // Referring Physician's Telephone Numbers 
-    removals_.insert(DicomTag(0x0008, 0x0096));  // Referring Physician's Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x010d));  // Context Group Extension Creator UID
-    removals_.insert(DicomTag(0x0008, 0x0201));  // Timezone Offset From UTC
-    removals_.insert(DicomTag(0x0008, 0x0300));  // Current Patient Location
-    removals_.insert(DicomTag(0x0008, 0x1010));  // Station Name
-    removals_.insert(DicomTag(0x0008, 0x1030));  // Study Description 
-    removals_.insert(DicomTag(0x0008, 0x103e));  // Series Description 
-    removals_.insert(DicomTag(0x0008, 0x1040));  // Institutional Department Name 
-    removals_.insert(DicomTag(0x0008, 0x1048));  // Physician(s) of Record 
-    removals_.insert(DicomTag(0x0008, 0x1049));  // Physician(s) of Record Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1050));  // Performing Physicians' Name
-    removals_.insert(DicomTag(0x0008, 0x1052));  // Performing Physicians Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1060));  // Name of Physician(s) Reading Study
-    removals_.insert(DicomTag(0x0008, 0x1062));  // Physician Reading Study Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1070));  // Operators' Name
-    removals_.insert(DicomTag(0x0008, 0x1072));  // Operators' Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1080));  // Admitting Diagnoses Description
-    removals_.insert(DicomTag(0x0008, 0x1084));  // Admitting Diagnoses Code Sequence
-    removals_.insert(DicomTag(0x0008, 0x1110));  // Referenced Study Sequence
-    removals_.insert(DicomTag(0x0008, 0x1111));  // Referenced Performed Procedure Step Sequence
-    removals_.insert(DicomTag(0x0008, 0x1120));  // Referenced Patient Sequence
-    removals_.insert(DicomTag(0x0008, 0x1140));  // Referenced Image Sequence
-    removals_.insert(DicomTag(0x0008, 0x1155));  // Referenced SOP Instance UID
-    removals_.insert(DicomTag(0x0008, 0x1195));  // Transaction UID
-    removals_.insert(DicomTag(0x0008, 0x2111));  // Derivation Description
-    removals_.insert(DicomTag(0x0008, 0x2112));  // Source Image Sequence
-    removals_.insert(DicomTag(0x0008, 0x4000));  // Identifying Comments
-    removals_.insert(DicomTag(0x0008, 0x9123));  // Creator Version UID
-    //removals_.insert(DicomTag(0x0010, 0x0010));  // Patient's Name => cf. below (*)
-    //removals_.insert(DicomTag(0x0010, 0x0020));  // Patient ID => cf. below (*)
-    removals_.insert(DicomTag(0x0010, 0x0030));  // Patient's Birth Date 
-    removals_.insert(DicomTag(0x0010, 0x0032));  // Patient's Birth Time 
-    clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex
-    removals_.insert(DicomTag(0x0010, 0x0050));  // Patient's Insurance Plan Code Sequence
-    removals_.insert(DicomTag(0x0010, 0x0101));  // Patient's Primary Language Code Sequence
-    removals_.insert(DicomTag(0x0010, 0x0102));  // Patient's Primary Language Modifier Code Sequence
-    removals_.insert(DicomTag(0x0010, 0x1000));  // Other Patient Ids
-    removals_.insert(DicomTag(0x0010, 0x1001));  // Other Patient Names 
-    removals_.insert(DicomTag(0x0010, 0x1002));  // Other Patient IDs Sequence
-    removals_.insert(DicomTag(0x0010, 0x1005));  // Patient's Birth Name
-    removals_.insert(DicomTag(0x0010, 0x1010));  // Patient's Age
-    removals_.insert(DicomTag(0x0010, 0x1020));  // Patient's Size 
-    removals_.insert(DicomTag(0x0010, 0x1030));  // Patient's Weight 
-    removals_.insert(DicomTag(0x0010, 0x1040));  // Patient's Address
-    removals_.insert(DicomTag(0x0010, 0x1050));  // Insurance Plan Identification
-    removals_.insert(DicomTag(0x0010, 0x1060));  // Patient's Mother's Birth Name
-    removals_.insert(DicomTag(0x0010, 0x1080));  // Military Rank
-    removals_.insert(DicomTag(0x0010, 0x1081));  // Branch of Service
-    removals_.insert(DicomTag(0x0010, 0x1090));  // Medical Record Locator
-    removals_.insert(DicomTag(0x0010, 0x2000));  // Medical Alerts
-    removals_.insert(DicomTag(0x0010, 0x2110));  // Allergies
-    removals_.insert(DicomTag(0x0010, 0x2150));  // Country of Residence
-    removals_.insert(DicomTag(0x0010, 0x2152));  // Region of Residence
-    removals_.insert(DicomTag(0x0010, 0x2154));  // PatientTelephoneNumbers
-    removals_.insert(DicomTag(0x0010, 0x2160));  // Ethnic Group
-    removals_.insert(DicomTag(0x0010, 0x2180));  // Occupation 
-    removals_.insert(DicomTag(0x0010, 0x21a0));  // Smoking Status
-    removals_.insert(DicomTag(0x0010, 0x21b0));  // Additional Patient's History
-    removals_.insert(DicomTag(0x0010, 0x21c0));  // Pregnancy Status
-    removals_.insert(DicomTag(0x0010, 0x21d0));  // Last Menstrual Date
-    removals_.insert(DicomTag(0x0010, 0x21f0));  // Patient's Religious Preference
-    removals_.insert(DicomTag(0x0010, 0x2203));  // Patient's Sex Neutered
-    removals_.insert(DicomTag(0x0010, 0x2297));  // Responsible Person
-    removals_.insert(DicomTag(0x0010, 0x2299));  // Responsible Organization
-    removals_.insert(DicomTag(0x0010, 0x4000));  // Patient Comments
-    removals_.insert(DicomTag(0x0018, 0x0010));  // Contrast Bolus Agent
-    removals_.insert(DicomTag(0x0018, 0x1000));  // Device Serial Number
-    removals_.insert(DicomTag(0x0018, 0x1002));  // Device UID
-    removals_.insert(DicomTag(0x0018, 0x1004));  // Plate ID
-    removals_.insert(DicomTag(0x0018, 0x1005));  // Generator ID
-    removals_.insert(DicomTag(0x0018, 0x1007));  // Cassette ID
-    removals_.insert(DicomTag(0x0018, 0x1008));  // Gantry ID
-    removals_.insert(DicomTag(0x0018, 0x1030));  // Protocol Name
-    removals_.insert(DicomTag(0x0018, 0x1400));  // Acquisition Device Processing Description
-    removals_.insert(DicomTag(0x0018, 0x4000));  // Acquisition Comments
-    removals_.insert(DicomTag(0x0018, 0x700a));  // Detector ID
-    removals_.insert(DicomTag(0x0018, 0xa003));  // Contribution Description
-    removals_.insert(DicomTag(0x0018, 0x9424));  // Acquisition Protocol Description
-    //removals_.insert(DicomTag(0x0020, 0x000d));  // Study Instance UID => set in Apply()
-    //removals_.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => set in Apply()
-    removals_.insert(DicomTag(0x0020, 0x0010));  // Study ID
-    removals_.insert(DicomTag(0x0020, 0x0052));  // Frame of Reference UID 
-    removals_.insert(DicomTag(0x0020, 0x0200));  // Synchronization Frame of Reference UID 
-    removals_.insert(DicomTag(0x0020, 0x3401));  // Modifying Device ID
-    removals_.insert(DicomTag(0x0020, 0x3404));  // Modifying Device Manufacturer
-    removals_.insert(DicomTag(0x0020, 0x3406));  // Modified Image Description
-    removals_.insert(DicomTag(0x0020, 0x4000));  // Image Comments
-    removals_.insert(DicomTag(0x0020, 0x9158));  // Frame Comments
-    removals_.insert(DicomTag(0x0020, 0x9161));  // Concatenation UID
-    removals_.insert(DicomTag(0x0020, 0x9164));  // Dimension Organization UID
-    //removals_.insert(DicomTag(0x0028, 0x1199));  // Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances
-    //removals_.insert(DicomTag(0x0028, 0x1214));  // Large Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances
-    removals_.insert(DicomTag(0x0028, 0x4000));  // Image Presentation Comments
-    removals_.insert(DicomTag(0x0032, 0x0012));  // Study ID Issuer
-    removals_.insert(DicomTag(0x0032, 0x1020));  // Scheduled Study Location
-    removals_.insert(DicomTag(0x0032, 0x1021));  // Scheduled Study Location AE Title
-    removals_.insert(DicomTag(0x0032, 0x1030));  // Reason for Study
-    removals_.insert(DicomTag(0x0032, 0x1032));  // Requesting Physician
-    removals_.insert(DicomTag(0x0032, 0x1033));  // Requesting Service
-    removals_.insert(DicomTag(0x0032, 0x1060));  // Requesting Procedure Description
-    removals_.insert(DicomTag(0x0032, 0x1070));  // Requested Contrast Agent
-    removals_.insert(DicomTag(0x0032, 0x4000));  // Study Comments
-    removals_.insert(DicomTag(0x0038, 0x0010));  // Admission ID
-    removals_.insert(DicomTag(0x0038, 0x0011));  // Issuer of Admission ID
-    removals_.insert(DicomTag(0x0038, 0x001e));  // Scheduled Patient Institution Residence
-    removals_.insert(DicomTag(0x0038, 0x0020));  // Admitting Date
-    removals_.insert(DicomTag(0x0038, 0x0021));  // Admitting Time
-    removals_.insert(DicomTag(0x0038, 0x0040));  // Discharge Diagnosis Description
-    removals_.insert(DicomTag(0x0038, 0x0050));  // Special Needs
-    removals_.insert(DicomTag(0x0038, 0x0060));  // Service Episode ID
-    removals_.insert(DicomTag(0x0038, 0x0061));  // Issuer of Service Episode ID
-    removals_.insert(DicomTag(0x0038, 0x0062));  // Service Episode Description
-    removals_.insert(DicomTag(0x0038, 0x0400));  // Patient's Institution Residence
-    removals_.insert(DicomTag(0x0038, 0x0500));  // Patient State
-    removals_.insert(DicomTag(0x0038, 0x4000));  // Visit Comments
-    removals_.insert(DicomTag(0x0038, 0x1234));  // Referenced Patient Alias Sequence
-    removals_.insert(DicomTag(0x0040, 0x0001));  // Scheduled Station AE Title
-    removals_.insert(DicomTag(0x0040, 0x0002));  // Scheduled Procedure Step Start Date
-    removals_.insert(DicomTag(0x0040, 0x0003));  // Scheduled Procedure Step Start Time
-    removals_.insert(DicomTag(0x0040, 0x0004));  // Scheduled Procedure Step End Date
-    removals_.insert(DicomTag(0x0040, 0x0005));  // Scheduled Procedure Step End Time
-    removals_.insert(DicomTag(0x0040, 0x0006));  // Scheduled Performing Physician Name
-    removals_.insert(DicomTag(0x0040, 0x0007));  // Scheduled Procedure Step Description
-    removals_.insert(DicomTag(0x0040, 0x000b));  // Scheduled Performing Physician Identification Sequence
-    removals_.insert(DicomTag(0x0040, 0x0010));  // Scheduled Station Name
-    removals_.insert(DicomTag(0x0040, 0x0011));  // Scheduled Procedure Step Location
-    removals_.insert(DicomTag(0x0040, 0x0012));  // Pre-Medication
-    removals_.insert(DicomTag(0x0040, 0x0241));  // Performed Station AE Title
-    removals_.insert(DicomTag(0x0040, 0x0242));  // Performed Station Name
-    removals_.insert(DicomTag(0x0040, 0x0243));  // Performed Location
-    removals_.insert(DicomTag(0x0040, 0x0244));  // Performed Procedure Step Start Date
-    removals_.insert(DicomTag(0x0040, 0x0245));  // Performed Procedure Step Start Time
-    removals_.insert(DicomTag(0x0040, 0x0248));  // Performed Station Name Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x0253));  // Performed Procedure Step ID
-    removals_.insert(DicomTag(0x0040, 0x0254));  // Performed Procedure Step Description
-    removals_.insert(DicomTag(0x0040, 0x0275));  // Request Attributes Sequence
-    removals_.insert(DicomTag(0x0040, 0x0280));  // Comments on Performed Procedure Step
-    removals_.insert(DicomTag(0x0040, 0x0555));  // Acquisition Context Sequence
-    removals_.insert(DicomTag(0x0040, 0x1001));  // Requested Procedure ID
-    removals_.insert(DicomTag(0x0040, 0x1010));  // Names of Intended Recipient of Results
-    removals_.insert(DicomTag(0x0040, 0x1011));  // Intended Recipient of Results Identification Sequence
-    removals_.insert(DicomTag(0x0040, 0x1004));  // Patient Transport Arrangements
-    removals_.insert(DicomTag(0x0040, 0x1005));  // Requested Procedure Location
-    removals_.insert(DicomTag(0x0040, 0x1101));  // Person Identification Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x1102));  // Person Address
-    removals_.insert(DicomTag(0x0040, 0x1103));  // Person Telephone Numbers
-    removals_.insert(DicomTag(0x0040, 0x1400));  // Requested Procedure Comments
-    removals_.insert(DicomTag(0x0040, 0x2001));  // Reason for Imaging Service Request
-    removals_.insert(DicomTag(0x0040, 0x2008));  // Order Entered By
-    removals_.insert(DicomTag(0x0040, 0x2009));  // Order Enterer Location
-    removals_.insert(DicomTag(0x0040, 0x2010));  // Order Callback Phone Number
-    removals_.insert(DicomTag(0x0040, 0x2016));  // Placer Order Number of Imaging Service Request
-    removals_.insert(DicomTag(0x0040, 0x2017));  // Filler Order Number of Imaging Service Request
-    removals_.insert(DicomTag(0x0040, 0x2400));  // Imaging Service Request Comments
-    removals_.insert(DicomTag(0x0040, 0x4023));  // Referenced General Purpose Scheduled Procedure Step Transaction UID
-    removals_.insert(DicomTag(0x0040, 0x4025));  // Scheduled Station Name Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x4027));  // Scheduled Station Geographic Location Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x4030));  // Performed Station Geographic Location Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x4034));  // Scheduled Human Performers Sequence
-    removals_.insert(DicomTag(0x0040, 0x4035));  // Actual Human Performers Sequence
-    removals_.insert(DicomTag(0x0040, 0x4036));  // Human Performers Organization
-    removals_.insert(DicomTag(0x0040, 0x4037));  // Human Performers Name
-    removals_.insert(DicomTag(0x0040, 0xa027));  // Verifying Organization
-    removals_.insert(DicomTag(0x0040, 0xa073));  // Verifying Observer Sequence
-    removals_.insert(DicomTag(0x0040, 0xa075));  // Verifying Observer Name
-    removals_.insert(DicomTag(0x0040, 0xa078));  // Author Observer Sequence
-    removals_.insert(DicomTag(0x0040, 0xa07a));  // Participant Sequence
-    removals_.insert(DicomTag(0x0040, 0xa07c));  // Custodial Organization Sequence
-    removals_.insert(DicomTag(0x0040, 0xa088));  // Verifying Observer Identification Code Sequence
-    removals_.insert(DicomTag(0x0040, 0xa123));  // Person Name
-    removals_.insert(DicomTag(0x0040, 0xa124));  // UID
-    removals_.insert(DicomTag(0x0040, 0xa730));  // Content Sequence 
-    removals_.insert(DicomTag(0x0040, 0x3001));  // Confidentiality Constraint on Patient Data Description
-    removals_.insert(DicomTag(0x0040, 0xdb0c));  // Template Extension Organization UID
-    removals_.insert(DicomTag(0x0040, 0xdb0d));  // Template Extension Creator UID
-    removals_.insert(DicomTag(0x0070, 0x0001));  // Graphic Annotation Sequence
-    removals_.insert(DicomTag(0x0070, 0x0084));  // Content Creator's Name
-    removals_.insert(DicomTag(0x0070, 0x0086));  // Content Creator's Identification Code Sequence
-    removals_.insert(DicomTag(0x0070, 0x031a));  // Fiducial UID
-    removals_.insert(DicomTag(0x0088, 0x0140));  // Storage Media File-set UID
-    removals_.insert(DicomTag(0x0088, 0x0200));  // Icon Image Sequence
-    removals_.insert(DicomTag(0x0088, 0x0904));  // Topic Title
-    removals_.insert(DicomTag(0x0088, 0x0906));  // Topic Subject
-    removals_.insert(DicomTag(0x0088, 0x0910));  // Topic Author
-    removals_.insert(DicomTag(0x0088, 0x0912));  // Topic Key Words
-    removals_.insert(DicomTag(0x0400, 0x0100));  // Digital Signature UID
-    removals_.insert(DicomTag(0x0400, 0x0402));  // Referenced Digital Signature Sequence
-    removals_.insert(DicomTag(0x0400, 0x0403));  // Referenced SOP Instance MAC Sequence
-    removals_.insert(DicomTag(0x0400, 0x0404));  // MAC
-    removals_.insert(DicomTag(0x0400, 0x0550));  // Modified Attributes Sequence
-    removals_.insert(DicomTag(0x0400, 0x0561));  // Original Attributes Sequence
-    removals_.insert(DicomTag(0x2030, 0x0020));  // Text String
-    removals_.insert(DicomTag(0x3006, 0x0024));  // Referenced Frame of Reference UID
-    removals_.insert(DicomTag(0x3006, 0x00c2));  // Related Frame of Reference UID 
-    removals_.insert(DicomTag(0x300a, 0x0013));  // Dose Reference UID
-    removals_.insert(DicomTag(0x300e, 0x0008));  // Reviewer Name
-    removals_.insert(DicomTag(0x4000, 0x0010));  // Arbitrary
-    removals_.insert(DicomTag(0x4000, 0x4000));  // Text Comments
-    removals_.insert(DicomTag(0x4008, 0x0042));  // Results ID Issuer
-    removals_.insert(DicomTag(0x4008, 0x0102));  // Interpretation Recorder
-    removals_.insert(DicomTag(0x4008, 0x010a));  // Interpretation Transcriber
-    removals_.insert(DicomTag(0x4008, 0x010b));  // Interpretation Text
-    removals_.insert(DicomTag(0x4008, 0x010c));  // Interpretation Author
-    removals_.insert(DicomTag(0x4008, 0x0111));  // Interpretation Approver Sequence
-    removals_.insert(DicomTag(0x4008, 0x0114));  // Physician Approving Interpretation
-    removals_.insert(DicomTag(0x4008, 0x0115));  // Interpretation Diagnosis Description
-    removals_.insert(DicomTag(0x4008, 0x0118));  // Results Distribution List Sequence
-    removals_.insert(DicomTag(0x4008, 0x0119));  // Distribution Name
-    removals_.insert(DicomTag(0x4008, 0x011a));  // Distribution Address
-    removals_.insert(DicomTag(0x4008, 0x0202));  // Interpretation ID Issuer
-    removals_.insert(DicomTag(0x4008, 0x0300));  // Impressions
-    removals_.insert(DicomTag(0x4008, 0x4000));  // Results Comments
-    removals_.insert(DicomTag(0xfffa, 0xfffa));  // Digital Signature Sequence
-    removals_.insert(DicomTag(0xfffc, 0xfffc));  // Data Set Trailing Padding
-    //removals_.insert(DicomTag(0x60xx, 0x4000));  // Overlay Comments => TODO
-    //removals_.insert(DicomTag(0x60xx, 0x3000));  // Overlay Data => TODO
-
-    // Set the DeidentificationMethod tag
-    ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2011);
-  }
--- a/OrthancServer/Resources/ImplementationNotes/DatabasesClassHierarchy.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/ImplementationNotes/DatabasesClassHierarchy.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -21,7 +21,7 @@
 - define it in OrthancPluginDatabaseV4
 - define a NotImplemented default implementation in BaseDatabaseWrapper
 - optionally define it in SQLiteDatabaseWrapper if it can be implemented in SQLite
-- very likely define it as a DbCapabilities in IDatabaseWrapper::DbCapabilities (e.g: Has/SetUpdateAndGetStatistics()) such that the Orthanc
+- very likely define it as a DbCapabilities in IDatabaseWrapper::DbCapabilities (e.g: Has/SetHasUpdateAndGetStatistics()) such that the Orthanc
   core knows if it can use it or not.
 
 Then, in the orthanc-databases repo, you should:
--- a/OrthancServer/Resources/PreventProtobufDirectoryLeaks.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/PreventProtobufDirectoryLeaks.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -29,13 +29,21 @@
 with open(sys.argv[1], 'r') as f:
     s = f.read()
 
-s = s.replace('__FILE__', '__ORTHANC_FILE__')
+if False:
+    # This was the version in Orthanc 1.12.4, doesn't seem to work anymore
+    s = s.replace('__FILE__', '__ORTHANC_FILE__')
 
-s = """
+    s = """
 #if !defined(__ORTHANC_FILE__)
 #  define __ORTHANC_FILE__ __FILE__
 #endif
 """ + s
+else:
+    # New version in Orthanc 1.12.5
+    s = """
+#undef __FILE__
+#define __FILE__ __ORTHANC_FILE__
+""" + s
 
 with open(sys.argv[1], 'w') as f:
     f.write(s)
--- a/OrthancServer/Resources/RunCppCheck.sh	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/RunCppCheck.sh	Thu Jan 30 17:41:33 2025 +0100
@@ -12,12 +12,12 @@
 constParameter:../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp
 knownArgument:../../OrthancFramework/UnitTestsSources/ImageTests.cpp
 knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp
-nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:316
-stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1477
+nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:321
+stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1525
 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:480
+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
@@ -27,7 +27,7 @@
 syntaxError:../../OrthancServer/UnitTestsSources/UnitTestsMain.cpp:322
 uninitMemberVar:../../OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp:417
 unreadVariable:../../OrthancFramework/Sources/FileStorage/StorageAccessor.cpp
-unreadVariable:../../OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp:1123
+unreadVariable:../../OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp:1173
 unusedFunction
 useInitializationList:../../OrthancFramework/Sources/Images/PngReader.cpp:91
 useInitializationList:../../OrthancFramework/Sources/Images/PngWriter.cpp:99
@@ -36,7 +36,7 @@
 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:3527
+assertWithSideEffect:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:3058
 assertWithSideEffect:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:286
 assertWithSideEffect:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:454
 EOF
--- a/OrthancServer/Resources/Samples/CppHelpers/Logging/ILogger.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/CppHelpers/Logging/ILogger.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/CppHelpers/Logging/NullLogger.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/CppHelpers/Logging/NullLogger.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/CppHelpers/Logging/OrthancLogger.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/CppHelpers/Logging/OrthancLogger.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/CppHelpers/Logging/OrthancLogger.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/CppHelpers/Logging/OrthancLogger.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/CppHelpers/Logging/OrthancPluginLogger.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/CppHelpers/Logging/OrthancPluginLogger.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/CppHelpers/Logging/OrthancPluginLogger.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/CppHelpers/Logging/OrthancPluginLogger.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/ImportDicomFiles/OrthancImport.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/ImportDicomFiles/OrthancImport.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Lua/CallWebService.js	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Lua/CallWebService.js	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/AnonymizeAllPatients.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/AnonymizeAllPatients.py	Thu Jan 30 17:41:33 2025 +0100
@@ -5,8 +5,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/ArchiveAllPatients.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/ArchiveAllPatients.py	Thu Jan 30 17:41:33 2025 +0100
@@ -5,8 +5,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/ArchiveStudiesInTimeRange.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/ArchiveStudiesInTimeRange.py	Thu Jan 30 17:41:33 2025 +0100
@@ -5,8 +5,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/AutoClassify.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/AutoClassify.py	Thu Jan 30 17:41:33 2025 +0100
@@ -5,8 +5,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/ChangesLoop.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/ChangesLoop.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/ContinuousPatientAnonymization.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/ContinuousPatientAnonymization.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/DeleteAllStudies.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/DeleteAllStudies.py	Thu Jan 30 17:41:33 2025 +0100
@@ -5,8 +5,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/DicomizeImage.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/DicomizeImage.py	Thu Jan 30 17:41:33 2025 +0100
@@ -5,8 +5,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/DownloadAnonymized.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/DownloadAnonymized.py	Thu Jan 30 17:41:33 2025 +0100
@@ -5,8 +5,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/HighPerformanceAutoRouting.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/HighPerformanceAutoRouting.py	Thu Jan 30 17:41:33 2025 +0100
@@ -5,8 +5,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/ManualModification.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/ManualModification.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/MicroCTDicomization.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/MicroCTDicomization.py	Thu Jan 30 17:41:33 2025 +0100
@@ -5,8 +5,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/Replicate.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/Replicate.py	Thu Jan 30 17:41:33 2025 +0100
@@ -4,8 +4,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Python/RestToolbox.py	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Python/RestToolbox.py	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Tools/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Tools/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/Tools/RecoverCompressedFile.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/Tools/RecoverCompressedFile.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/WebApplications/DrawingDicomizer.js	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/WebApplications/DrawingDicomizer.js	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Samples/WebApplications/NodeToolbox.js	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Samples/WebApplications/NodeToolbox.js	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Resources/Testing/Issue32/Cpp/CMakeLists.txt	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Resources/Testing/Issue32/Cpp/CMakeLists.txt	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Database/BaseCompatibilityTransaction.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,88 @@
+/**
+ * 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/>.
+ **/
+
+
+#include "BaseCompatibilityTransaction.h"
+
+#include "../../../OrthancFramework/Sources/OrthancException.h"
+#include "Compatibility/GenericFind.h"
+
+namespace Orthanc
+{
+  int64_t BaseCompatibilityTransaction::IncrementGlobalProperty(GlobalProperty property,
+                                                                int64_t increment,
+                                                                bool shared)
+  {
+    throw OrthancException(ErrorCode_NotImplemented);  // Not supported
+  }
+
+  void BaseCompatibilityTransaction::UpdateAndGetStatistics(int64_t& patientsCount,
+                                                            int64_t& studiesCount,
+                                                            int64_t& seriesCount,
+                                                            int64_t& instancesCount,
+                                                            int64_t& compressedSize,
+                                                            int64_t& uncompressedSize)
+  {
+    throw OrthancException(ErrorCode_NotImplemented);  // Not supported
+  }
+
+  void BaseCompatibilityTransaction::GetChangesExtended(std::list<ServerIndexChange>& target /*out*/,
+                                                        bool& done /*out*/,
+                                                        int64_t since,
+                                                        int64_t to,
+                                                        uint32_t limit,
+                                                        const std::set<ChangeType>& filterType)
+  {
+    throw OrthancException(ErrorCode_NotImplemented);  // Not supported
+  }
+
+  void BaseCompatibilityTransaction::ExecuteCount(uint64_t& count,
+                                                  const FindRequest& request,
+                                                  const IDatabaseWrapper::Capabilities& capabilities)
+  {
+    throw OrthancException(ErrorCode_NotImplemented);  // Not supported
+  }
+
+  void BaseCompatibilityTransaction::ExecuteFind(FindResponse& response,
+                                                 const FindRequest& request,
+                                                 const IDatabaseWrapper::Capabilities& capabilities)
+  {
+    throw OrthancException(ErrorCode_NotImplemented);  // Not supported
+  }
+
+  void BaseCompatibilityTransaction::ExecuteFind(std::list<std::string>& identifiers,
+                                                 const IDatabaseWrapper::Capabilities& capabilities,
+                                                 const FindRequest& request)
+  {
+    Compatibility::GenericFind find(*this, *this);
+    find.ExecuteFind(identifiers, capabilities, request);
+  }
+
+  void BaseCompatibilityTransaction::ExecuteExpand(FindResponse& response,
+                                                   const IDatabaseWrapper::Capabilities& capabilities,
+                                                   const FindRequest& request,
+                                                   const std::string& identifier)
+  {
+    Compatibility::GenericFind find(*this, *this);
+    find.ExecuteExpand(response, capabilities, request, identifier);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Database/BaseCompatibilityTransaction.h	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,76 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "IDatabaseWrapper.h"
+
+namespace Orthanc
+{
+  /**
+   * This class provides a default "not implemented" implementation
+   * for all recent methods (1.12.X)
+   **/
+
+  class BaseCompatibilityTransaction :
+    public IDatabaseWrapper::ITransaction,
+    public IDatabaseWrapper::ICompatibilityTransaction
+  {
+  public:
+    virtual int64_t IncrementGlobalProperty(GlobalProperty property,
+                                            int64_t increment,
+                                            bool shared) ORTHANC_OVERRIDE;
+
+    virtual void UpdateAndGetStatistics(int64_t& patientsCount,
+                                        int64_t& studiesCount,
+                                        int64_t& seriesCount,
+                                        int64_t& instancesCount,
+                                        int64_t& compressedSize,
+                                        int64_t& uncompressedSize) ORTHANC_OVERRIDE;
+
+    virtual void ExecuteCount(uint64_t& count,
+                              const FindRequest& request,
+                              const IDatabaseWrapper::Capabilities& capabilities) ORTHANC_OVERRIDE;
+
+    virtual void ExecuteFind(FindResponse& response,
+                             const FindRequest& request,
+                             const IDatabaseWrapper::Capabilities& capabilities) ORTHANC_OVERRIDE;
+
+    virtual void ExecuteFind(std::list<std::string>& identifiers,
+                             const IDatabaseWrapper::Capabilities& capabilities,
+                             const FindRequest& request) ORTHANC_OVERRIDE;
+
+    virtual void ExecuteExpand(FindResponse& response,
+                               const IDatabaseWrapper::Capabilities& capabilities,
+                               const FindRequest& request,
+                               const std::string& identifier) ORTHANC_OVERRIDE;
+
+    virtual void GetChangesExtended(std::list<ServerIndexChange>& target /*out*/,
+                                    bool& done /*out*/,
+                                    int64_t since,
+                                    int64_t to,
+                                    uint32_t limit,
+                                    const std::set<ChangeType>& filterType) ORTHANC_OVERRIDE;
+  };
+
+}
--- a/OrthancServer/Sources/Database/BaseDatabaseWrapper.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "BaseDatabaseWrapper.h"
-
-#include "../../../OrthancFramework/Sources/OrthancException.h"
-#include "Compatibility/GenericFind.h"
-
-namespace Orthanc
-{
-  int64_t BaseDatabaseWrapper::BaseTransaction::IncrementGlobalProperty(GlobalProperty property,
-                                                                        int64_t increment,
-                                                                        bool shared)
-  {
-    throw OrthancException(ErrorCode_NotImplemented);  // Not supported
-  }
-
-
-  void BaseDatabaseWrapper::BaseTransaction::UpdateAndGetStatistics(int64_t& patientsCount,
-                                                                    int64_t& studiesCount,
-                                                                    int64_t& seriesCount,
-                                                                    int64_t& instancesCount,
-                                                                    int64_t& compressedSize,
-                                                                    int64_t& uncompressedSize)
-  {
-    throw OrthancException(ErrorCode_NotImplemented);  // Not supported
-  }
-
-  void BaseDatabaseWrapper::BaseTransaction::GetChangesExtended(std::list<ServerIndexChange>& target /*out*/,
-                                                                bool& done /*out*/,
-                                                                int64_t since,
-                                                                int64_t to,
-                                                                uint32_t limit,
-                                                                const std::set<ChangeType>& filterType)
-  {
-    throw OrthancException(ErrorCode_NotImplemented);  // Not supported
-  }
-
-
-
-  void BaseDatabaseWrapper::BaseTransaction::ExecuteFind(FindResponse& response,
-                                                         const FindRequest& request,
-                                                         const Capabilities& capabilities)
-  {
-    throw OrthancException(ErrorCode_NotImplemented);  // Not supported
-  }
-
-
-  void BaseDatabaseWrapper::BaseTransaction::ExecuteFind(std::list<std::string>& identifiers,
-                                                         const Capabilities& capabilities,
-                                                         const FindRequest& request)
-  {
-    Compatibility::GenericFind find(*this);
-    find.ExecuteFind(identifiers, capabilities, request);
-  }
-
-
-  void BaseDatabaseWrapper::BaseTransaction::ExecuteExpand(FindResponse& response,
-                                                           const Capabilities& capabilities,
-                                                           const FindRequest& request,
-                                                           const std::string& identifier)
-  {
-    Compatibility::GenericFind find(*this);
-    find.ExecuteExpand(response, capabilities, request, identifier);
-  }
-
-
-  uint64_t BaseDatabaseWrapper::MeasureLatency()
-  {
-    throw OrthancException(ErrorCode_NotImplemented);  // only implemented in V4
-  }
-}
--- a/OrthancServer/Sources/Database/BaseDatabaseWrapper.h	Wed Oct 09 11:06:20 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IDatabaseWrapper.h"
-
-namespace Orthanc
-{
-  /**
-   * This class provides a default "not implemented" implementation
-   * for all recent methods (1.12.X)
-   **/
-  class BaseDatabaseWrapper : public IDatabaseWrapper
-  {
-  public:
-    class BaseTransaction : public IDatabaseWrapper::ITransaction
-    {
-    public:
-      virtual int64_t IncrementGlobalProperty(GlobalProperty property,
-                                              int64_t increment,
-                                              bool shared) ORTHANC_OVERRIDE;
-
-      virtual void UpdateAndGetStatistics(int64_t& patientsCount,
-                                          int64_t& studiesCount,
-                                          int64_t& seriesCount,
-                                          int64_t& instancesCount,
-                                          int64_t& compressedSize,
-                                          int64_t& uncompressedSize) ORTHANC_OVERRIDE;
-
-      virtual void ExecuteFind(FindResponse& response,
-                               const FindRequest& request,
-                               const Capabilities& capabilities) ORTHANC_OVERRIDE;
-
-      virtual void ExecuteFind(std::list<std::string>& identifiers,
-                               const Capabilities& capabilities,
-                               const FindRequest& request) ORTHANC_OVERRIDE;
-
-      virtual void ExecuteExpand(FindResponse& response,
-                                 const Capabilities& capabilities,
-                                 const FindRequest& request,
-                                 const std::string& identifier) ORTHANC_OVERRIDE;
-
-      virtual void GetChangesExtended(std::list<ServerIndexChange>& target /*out*/,
-                                      bool& done /*out*/,
-                                      int64_t since,
-                                      int64_t to,
-                                      uint32_t limit,
-                                      const std::set<ChangeType>& filterType) ORTHANC_OVERRIDE;
-    };
-
-    virtual uint64_t MeasureLatency() ORTHANC_OVERRIDE;
-
-    virtual bool HasIntegratedFind() const ORTHANC_OVERRIDE
-    {
-      return false;
-    }
-  };
-}
--- a/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/GenericFind.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/GenericFind.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -33,14 +33,6 @@
 {
   namespace Compatibility
   {
-    static bool IsRequestWithoutContraint(const FindRequest& request)
-    {
-      return (request.GetDicomTagConstraints().IsEmpty() &&
-              request.GetMetadataConstraintsCount() == 0 &&
-              request.GetLabels().empty() &&
-              request.GetOrdering().empty());
-    }
-
     static void GetChildren(std::list<int64_t>& target,
                             IDatabaseWrapper::ITransaction& transaction,
                             const std::list<int64_t>& resources)
@@ -56,7 +48,7 @@
     }
 
     static void GetChildren(std::list<std::string>& target,
-                            IDatabaseWrapper::ITransaction& transaction,
+                            IDatabaseWrapper::ICompatibilityTransaction& transaction,
                             const std::list<int64_t>& resources)
     {
       target.clear();
@@ -71,6 +63,7 @@
 
     static void GetChildrenIdentifiers(std::list<std::string>& children,
                                        IDatabaseWrapper::ITransaction& transaction,
+                                       IDatabaseWrapper::ICompatibilityTransaction& compatibilityTransaction,
                                        const OrthancIdentifiers& identifiers,
                                        ResourceType topLevel,
                                        ResourceType bottomLevel)
@@ -100,7 +93,7 @@
         ResourceType nextLevel = GetChildResourceType(currentLevel);
         if (nextLevel == bottomLevel)
         {
-          GetChildren(children, transaction, currentResources);
+          GetChildren(children, compatibilityTransaction, currentResources);
         }
         else
         {
@@ -123,7 +116,12 @@
         throw OrthancException(ErrorCode_NotImplemented, "The database backend doesn't support labels");
       }
 
-      if (IsRequestWithoutContraint(request) &&
+      if (!request.GetOrdering().empty())
+      {
+        throw OrthancException(ErrorCode_NotImplemented, "The database backend doesn't support ordering");
+      }
+
+      if (!request.HasConstraints() &&
           !request.GetOrthancIdentifiers().HasPatientId() &&
           !request.GetOrthancIdentifiers().HasStudyId() &&
           !request.GetOrthancIdentifiers().HasSeriesId() &&
@@ -135,7 +133,7 @@
         }
         else if (request.GetLimitsCount() != 0)
         {
-          transaction_.GetAllPublicIds(identifiers, request.GetLevel(), request.GetLimitsSince(), request.GetLimitsCount());
+          compatibilityTransaction_.GetAllPublicIdsCompatibility(identifiers, request.GetLevel(), request.GetLimitsSince(), request.GetLimitsCount());
         }
         else
         {
@@ -155,47 +153,7 @@
           }
         }
       }
-      else if (IsRequestWithoutContraint(request) &&
-               request.GetLevel() == ResourceType_Patient &&
-               request.GetOrthancIdentifiers().HasPatientId() &&
-               !request.GetOrthancIdentifiers().HasStudyId() &&
-               !request.GetOrthancIdentifiers().HasSeriesId() &&
-               !request.GetOrthancIdentifiers().HasInstanceId())
-      {
-        // TODO-FIND: This is a trivial case for which no transaction is needed
-        identifiers.push_back(request.GetOrthancIdentifiers().GetPatientId());
-      }
-      else if (IsRequestWithoutContraint(request) &&
-               request.GetLevel() == ResourceType_Study &&
-               !request.GetOrthancIdentifiers().HasPatientId() &&
-               request.GetOrthancIdentifiers().HasStudyId() &&
-               !request.GetOrthancIdentifiers().HasSeriesId() &&
-               !request.GetOrthancIdentifiers().HasInstanceId())
-      {
-        // TODO-FIND: This is a trivial case for which no transaction is needed
-        identifiers.push_back(request.GetOrthancIdentifiers().GetStudyId());
-      }
-      else if (IsRequestWithoutContraint(request) &&
-               request.GetLevel() == ResourceType_Series &&
-               !request.GetOrthancIdentifiers().HasPatientId() &&
-               !request.GetOrthancIdentifiers().HasStudyId() &&
-               request.GetOrthancIdentifiers().HasSeriesId() &&
-               !request.GetOrthancIdentifiers().HasInstanceId())
-      {
-        // TODO-FIND: This is a trivial case for which no transaction is needed
-        identifiers.push_back(request.GetOrthancIdentifiers().GetSeriesId());
-      }
-      else if (IsRequestWithoutContraint(request) &&
-               request.GetLevel() == ResourceType_Instance &&
-               !request.GetOrthancIdentifiers().HasPatientId() &&
-               !request.GetOrthancIdentifiers().HasStudyId() &&
-               !request.GetOrthancIdentifiers().HasSeriesId() &&
-               request.GetOrthancIdentifiers().HasInstanceId())
-      {
-        // TODO-FIND: This is a trivial case for which no transaction is needed
-        identifiers.push_back(request.GetOrthancIdentifiers().GetInstanceId());
-      }
-      else if (IsRequestWithoutContraint(request) &&
+      else if (!request.HasConstraints() &&
                (request.GetLevel() == ResourceType_Study ||
                 request.GetLevel() == ResourceType_Series ||
                 request.GetLevel() == ResourceType_Instance) &&
@@ -204,9 +162,10 @@
                !request.GetOrthancIdentifiers().HasSeriesId() &&
                !request.GetOrthancIdentifiers().HasInstanceId())
       {
-        GetChildrenIdentifiers(identifiers, transaction_, request.GetOrthancIdentifiers(), ResourceType_Patient, request.GetLevel());
+        GetChildrenIdentifiers(identifiers, transaction_, compatibilityTransaction_,
+                               request.GetOrthancIdentifiers(), ResourceType_Patient, request.GetLevel());
       }
-      else if (IsRequestWithoutContraint(request) &&
+      else if (!request.HasConstraints() &&
                (request.GetLevel() == ResourceType_Series ||
                 request.GetLevel() == ResourceType_Instance) &&
                !request.GetOrthancIdentifiers().HasPatientId() &&
@@ -214,16 +173,18 @@
                !request.GetOrthancIdentifiers().HasSeriesId() &&
                !request.GetOrthancIdentifiers().HasInstanceId())
       {
-        GetChildrenIdentifiers(identifiers, transaction_, request.GetOrthancIdentifiers(), ResourceType_Study, request.GetLevel());
+        GetChildrenIdentifiers(identifiers, transaction_, compatibilityTransaction_,
+                               request.GetOrthancIdentifiers(), ResourceType_Study, request.GetLevel());
       }
-      else if (IsRequestWithoutContraint(request) &&
+      else if (!request.HasConstraints() &&
                request.GetLevel() == ResourceType_Instance &&
                !request.GetOrthancIdentifiers().HasPatientId() &&
                !request.GetOrthancIdentifiers().HasStudyId() &&
                request.GetOrthancIdentifiers().HasSeriesId() &&
                !request.GetOrthancIdentifiers().HasInstanceId())
       {
-        GetChildrenIdentifiers(identifiers, transaction_, request.GetOrthancIdentifiers(), ResourceType_Series, request.GetLevel());
+        GetChildrenIdentifiers(identifiers, transaction_, compatibilityTransaction_,
+                               request.GetOrthancIdentifiers(), ResourceType_Series, request.GetLevel());
       }
       else if (request.GetMetadataConstraintsCount() == 0 &&
                request.GetOrdering().empty() &&
@@ -232,9 +193,9 @@
                !request.GetOrthancIdentifiers().HasSeriesId() &&
                !request.GetOrthancIdentifiers().HasInstanceId())
       {
-        transaction_.ApplyLookupResources(identifiers, NULL /* TODO-FIND: Could the "instancesId" information be exploited? */,
-                                          request.GetDicomTagConstraints(), request.GetLevel(), request.GetLabels(),
-                                          request.GetLabelsConstraint(), request.HasLimits() ? request.GetLimitsCount() : 0);
+        compatibilityTransaction_.ApplyLookupResources(identifiers, NULL /* TODO-FIND: Could the "instancesId" information be exploited? */,
+                                                       request.GetDicomTagConstraints(), request.GetLevel(), request.GetLabels(),
+                                                       request.GetLabelsConstraint(), request.HasLimits() ? request.GetLimitsCount() : 0);
       }
       else
       {
@@ -388,7 +349,7 @@
 
       if (request.IsRetrieveParentIdentifier())
       {
-        if (!transaction_.LookupResourceAndParent(internalId, level, parent, identifier))
+        if (!compatibilityTransaction_.LookupResourceAndParent(internalId, level, parent, identifier))
         {
           return;  // The resource is not available anymore
         }
@@ -436,7 +397,32 @@
 
       if (request.IsRetrieveMetadata())
       {
-        transaction_.GetAllMetadata(resource->GetMetadata(level), internalId);
+        std::map<MetadataType, std::string> metadata;
+        transaction_.GetAllMetadata(metadata, internalId);
+
+        for (std::map<MetadataType, std::string>::const_iterator
+               it = metadata.begin(); it != metadata.end(); ++it)
+        {
+          if (request.IsRetrieveMetadataRevisions() &&
+              capabilities.HasRevisionsSupport())
+          {
+            std::string value;
+            int64_t revision;
+            if (transaction_.LookupMetadata(value, revision, internalId, it->first) &&
+                value == it->second)
+            {
+              resource->AddMetadata(level, it->first, it->second, revision);
+            }
+            else
+            {
+              throw OrthancException(ErrorCode_DatabasePlugin);
+            }
+          }
+          else
+          {
+            resource->AddMetadata(level, it->first, it->second, 0 /* revision not requested */);
+          }
+        }
       }
 
       {
@@ -465,7 +451,14 @@
 
           if (request.GetParentSpecification(currentLevel).IsRetrieveMetadata())
           {
-            transaction_.GetAllMetadata(resource->GetMetadata(currentLevel), currentId);
+            std::map<MetadataType, std::string> metadata;
+            transaction_.GetAllMetadata(metadata, currentId);
+
+            for (std::map<MetadataType, std::string>::const_iterator
+                   it = metadata.begin(); it != metadata.end(); ++it)
+            {
+              resource->AddMetadata(currentLevel, it->first, it->second, 0 /* revision not request */);
+            }
           }
         }
       }
@@ -473,7 +466,7 @@
       if (capabilities.HasLabelsSupport() &&
           request.IsRetrieveLabels())
       {
-        transaction_.ListLabels(resource->GetLabels(), internalId);
+        compatibilityTransaction_.ListLabels(resource->GetLabels(), internalId);
       }
 
       if (request.IsRetrieveAttachments())
@@ -488,7 +481,7 @@
           if (transaction_.LookupAttachment(info, revision, internalId, *it) &&
               info.GetContentType() == *it)
           {
-            resource->AddAttachment(info);
+            resource->AddAttachment(info, revision);
           }
           else
           {
@@ -509,17 +502,19 @@
         {
           ResourceType childrenLevel = GetChildResourceType(currentLevel);
 
-          if (request.GetChildrenSpecification(childrenLevel).IsRetrieveIdentifiers())
+          if (request.GetChildrenSpecification(childrenLevel).IsRetrieveIdentifiers() || 
+              request.GetChildrenSpecification(childrenLevel).IsRetrieveCount())
           {
             for (std::list<int64_t>::const_iterator it = currentIds.begin(); it != currentIds.end(); ++it)
             {
               std::list<std::string> ids;
-              transaction_.GetChildrenPublicId(ids, *it);
+              compatibilityTransaction_.GetChildrenPublicId(ids, *it);
 
               for (std::list<std::string>::const_iterator it2 = ids.begin(); it2 != ids.end(); ++it2)
               {
                 resource->AddChildIdentifier(childrenLevel, *it2);
               }
+              resource->IncrementChildrenCount(childrenLevel, ids.size());
             }
           }
 
--- a/OrthancServer/Sources/Database/Compatibility/GenericFind.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/GenericFind.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -33,14 +33,17 @@
     {
     private:
       IDatabaseWrapper::ITransaction&  transaction_;
+      IDatabaseWrapper::ICompatibilityTransaction&  compatibilityTransaction_;
 
       void RetrieveMainDicomTags(FindResponse::Resource& target,
                                  ResourceType level,
                                  int64_t internalId);
 
     public:
-      explicit GenericFind(IDatabaseWrapper::ITransaction& transaction) :
-        transaction_(transaction)
+      explicit GenericFind(IDatabaseWrapper::ITransaction& transaction,
+                           IDatabaseWrapper::ICompatibilityTransaction& compatibilityTransaction) :
+        transaction_(transaction),
+        compatibilityTransaction_(compatibilityTransaction)
       {
       }
 
--- a/OrthancServer/Sources/Database/Compatibility/ICreateInstance.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/ICreateInstance.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/ICreateInstance.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/ICreateInstance.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/IGetChildrenMetadata.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/IGetChildrenMetadata.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/IGetChildrenMetadata.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/IGetChildrenMetadata.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/ILookupResourceAndParent.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/ILookupResourceAndParent.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/ILookupResourceAndParent.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/ILookupResourceAndParent.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/ILookupResources.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/ILookupResources.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/ILookupResources.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/ILookupResources.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/ISetResourcesContent.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/ISetResourcesContent.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/SetOfResources.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/SetOfResources.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Compatibility/SetOfResources.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/SetOfResources.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/FindRequest.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/FindRequest.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -90,6 +90,7 @@
     labelsConstraint_(LabelsConstraint_All),
     retrieveMainDicomTags_(false),
     retrieveMetadata_(false),
+    retrieveMetadataRevisions_(false),
     retrieveLabels_(false),
     retrieveAttachments_(false),
     retrieveParentIdentifier_(false),
@@ -225,16 +226,18 @@
 
 
   void FindRequest::AddOrdering(const DicomTag& tag,
+                                OrderingCast cast,
                                 OrderingDirection direction)
   {
-    ordering_.push_back(new Ordering(Key(tag), direction));
+    ordering_.push_back(new Ordering(Key(tag), cast, direction));
   }
 
 
   void FindRequest::AddOrdering(MetadataType metadataType, 
+                                OrderingCast cast,
                                 OrderingDirection direction)
   {
-    ordering_.push_back(new Ordering(Key(metadataType), direction));
+    ordering_.push_back(new Ordering(Key(metadataType), cast, direction));
   }
 
 
@@ -281,4 +284,61 @@
       return retrieveOneInstanceMetadataAndAttachments_;
     }
   }
+
+  bool FindRequest::HasConstraints() const
+  {
+    return (!GetDicomTagConstraints().IsEmpty() ||
+            GetMetadataConstraintsCount() != 0 ||
+            !GetLabels().empty() ||
+            !GetOrdering().empty());
+  }
+
+
+  bool FindRequest::IsTrivialFind(std::string& publicId /* out */) const
+  {
+    if (HasConstraints())
+    {
+      return false;
+    }
+    else if (GetLevel() == ResourceType_Patient &&
+             GetOrthancIdentifiers().HasPatientId() &&
+             !GetOrthancIdentifiers().HasStudyId() &&
+             !GetOrthancIdentifiers().HasSeriesId() &&
+             !GetOrthancIdentifiers().HasInstanceId())
+    {
+      publicId = GetOrthancIdentifiers().GetPatientId();
+      return true;
+    }
+    else if (GetLevel() == ResourceType_Study &&
+             !GetOrthancIdentifiers().HasPatientId() &&
+             GetOrthancIdentifiers().HasStudyId() &&
+             !GetOrthancIdentifiers().HasSeriesId() &&
+             !GetOrthancIdentifiers().HasInstanceId())
+    {
+      publicId = GetOrthancIdentifiers().GetStudyId();
+      return true;
+    }
+    else if (GetLevel() == ResourceType_Series &&
+             !GetOrthancIdentifiers().HasPatientId() &&
+             !GetOrthancIdentifiers().HasStudyId() &&
+             GetOrthancIdentifiers().HasSeriesId() &&
+             !GetOrthancIdentifiers().HasInstanceId())
+    {
+      publicId = GetOrthancIdentifiers().GetSeriesId();
+      return true;
+    }
+    else if (GetLevel() == ResourceType_Instance &&
+             !GetOrthancIdentifiers().HasPatientId() &&
+             !GetOrthancIdentifiers().HasStudyId() &&
+             !GetOrthancIdentifiers().HasSeriesId() &&
+             GetOrthancIdentifiers().HasInstanceId())
+    {
+      publicId = GetOrthancIdentifiers().GetInstanceId();
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
 }
--- a/OrthancServer/Sources/Database/FindRequest.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/FindRequest.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -57,6 +57,12 @@
       OrderingDirection_Descending
     };
 
+    enum OrderingCast
+    {
+      OrderingCast_String,
+      OrderingCast_Int,
+      OrderingCast_Float
+    };
 
     class Key
     {
@@ -107,12 +113,15 @@
     {
     private:
       OrderingDirection   direction_;
+      OrderingCast        cast_;
       Key                 key_;
 
     public:
       Ordering(const Key& key,
+               OrderingCast cast,
                OrderingDirection direction) :
         direction_(direction),
+        cast_(cast),
         key_(key)
       {
       }
@@ -127,6 +136,11 @@
         return direction_;
       }
 
+      OrderingCast GetCast() const
+      {
+        return cast_;
+      }
+
       MetadataType GetMetadataType() const
       {
         return key_.GetMetadataType();
@@ -185,10 +199,12 @@
       bool                    identifiers_;
       std::set<MetadataType>  metadata_;
       std::set<DicomTag>      mainDicomTags_;
+      bool                    count_;
 
     public:
       ChildrenSpecification() :
-        identifiers_(false)
+        identifiers_(false),
+        count_(false)
       {
       }
 
@@ -202,6 +218,16 @@
         return identifiers_;
       }
 
+      void SetRetrieveCount(bool retrieve)
+      {
+        count_ = retrieve;
+      }
+
+      bool IsRetrieveCount() const
+      {
+        return count_;
+      }
+
       void AddMetadata(MetadataType metadata)
       {
         metadata_.insert(metadata);
@@ -224,7 +250,7 @@
 
       bool IsOfInterest() const
       {
-        return (identifiers_ || !metadata_.empty() || !mainDicomTags_.empty());
+        return (identifiers_ || !metadata_.empty() || !mainDicomTags_.empty() || count_);
       }
     };
 
@@ -245,6 +271,7 @@
 
     bool                                 retrieveMainDicomTags_;
     bool                                 retrieveMetadata_;
+    bool                                 retrieveMetadataRevisions_;
     bool                                 retrieveLabels_;
     bool                                 retrieveAttachments_;
     bool                                 retrieveParentIdentifier_;
@@ -317,9 +344,11 @@
     uint64_t GetLimitsCount() const;
 
     void AddOrdering(const DicomTag& tag,
+                     OrderingCast cast,
                      OrderingDirection direction);
 
     void AddOrdering(MetadataType metadataType,
+                     OrderingCast cast,
                      OrderingDirection direction);
 
     const std::deque<Ordering*>& GetOrdering() const
@@ -379,6 +408,16 @@
       return retrieveMetadata_;
     }
 
+    void SetRetrieveMetadataRevisions(bool retrieve)
+    {
+      retrieveMetadataRevisions_ = retrieve;
+    }
+
+    bool IsRetrieveMetadataRevisions() const
+    {
+      return retrieveMetadataRevisions_;
+    }
+
     void SetRetrieveLabels(bool retrieve)
     {
       retrieveLabels_ = retrieve;
@@ -423,5 +462,9 @@
     void SetRetrieveOneInstanceMetadataAndAttachments(bool retrieve);
 
     bool IsRetrieveOneInstanceMetadataAndAttachments() const;
+
+    bool HasConstraints() const;
+
+    bool IsTrivialFind(std::string& publicId /* out */) const;
   };
 }
--- a/OrthancServer/Sources/Database/FindResponse.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/FindResponse.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -333,9 +333,10 @@
 
   void FindResponse::Resource::AddMetadata(ResourceType level,
                                            MetadataType metadata,
-                                           const std::string& value)
+                                           const std::string& value,
+                                           int64_t revision)
   {
-    std::map<MetadataType, std::string>& m = GetMetadata(level);
+    std::map<MetadataType, MetadataContent>& m = GetMetadata(level);
 
     if (m.find(metadata) != m.end())
     {
@@ -343,12 +344,12 @@
     }
     else
     {
-      m[metadata] = value;
+      m[metadata] = MetadataContent(value, revision);
     }
   }
 
 
-  std::map<MetadataType, std::string>& FindResponse::Resource::GetMetadata(ResourceType level)
+  std::map<MetadataType, FindResponse::MetadataContent>& FindResponse::Resource::GetMetadata(ResourceType level)
   {
     if (!IsResourceLevelAboveOrEqual(level, level_))
     {
@@ -379,9 +380,9 @@
                                               ResourceType level,
                                               MetadataType metadata) const
   {
-    const std::map<MetadataType, std::string>& m = GetMetadata(level);
+    const std::map<MetadataType, MetadataContent>& m = GetMetadata(level);
 
-    std::map<MetadataType, std::string>::const_iterator found = m.find(metadata);
+    std::map<MetadataType, MetadataContent>::const_iterator found = m.find(metadata);
 
     if (found == m.end())
     {
@@ -389,7 +390,29 @@
     }
     else
     {
-      value = found->second;
+      value = found->second.GetValue();
+      return true;
+    }
+  }
+
+
+  bool FindResponse::Resource::LookupMetadata(std::string& value,
+                                              int64_t& revision,
+                                              ResourceType level,
+                                              MetadataType metadata) const
+  {
+    const std::map<MetadataType, MetadataContent>& m = GetMetadata(level);
+
+    std::map<MetadataType, MetadataContent>::const_iterator found = m.find(metadata);
+
+    if (found == m.end())
+    {
+      return false;
+    }
+    else
+    {
+      value = found->second.GetValue();
+      revision = found->second.GetRevision();
       return true;
     }
   }
@@ -455,11 +478,14 @@
   }
 
 
-  void FindResponse::Resource::AddAttachment(const FileInfo& attachment)
+  void FindResponse::Resource::AddAttachment(const FileInfo& attachment,
+                                             int64_t revision)
   {
-    if (attachments_.find(attachment.GetContentType()) == attachments_.end())
+    if (attachments_.find(attachment.GetContentType()) == attachments_.end() &&
+        revisions_.find(attachment.GetContentType()) == revisions_.end())
     {
       attachments_[attachment.GetContentType()] = attachment;
+      revisions_[attachment.GetContentType()] = revision;
     }
     else
     {
@@ -468,17 +494,48 @@
   }
 
 
-  bool FindResponse::Resource::LookupAttachment(FileInfo& target, FileContentType type) const
+  bool FindResponse::Resource::LookupAttachment(FileInfo& target,
+                                                int64_t& revision,
+                                                FileContentType type) const
   {
     std::map<FileContentType, FileInfo>::const_iterator it = attachments_.find(type);
+    std::map<FileContentType, int64_t>::const_iterator it2 = revisions_.find(type);
+
     if (it != attachments_.end())
     {
-      target = it->second;
-      return true;
+      if (it2 != revisions_.end())
+      {
+        target = it->second;
+        revision = it2->second;
+        return true;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
     }
     else
     {
-      return false;
+      if (it2 == revisions_.end())
+      {
+        return false;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  void FindResponse::Resource::ListAttachments(std::set<FileContentType>& target) const
+  {
+    target.clear();
+
+    for (std::map<FileContentType, FileInfo>::const_iterator
+           it = attachments_.begin(); it != attachments_.end(); ++it)
+    {
+      target.insert(it->first);
     }
   }
 
@@ -626,6 +683,18 @@
   }
 
 
+  static void DebugMetadata(Json::Value& target,
+                            const std::map<MetadataType, FindResponse::MetadataContent>& m)
+  {
+    target = Json::objectValue;
+
+    for (std::map<MetadataType, FindResponse::MetadataContent>::const_iterator it = m.begin(); it != m.end(); ++it)
+    {
+      target[EnumerationToString(it->first)] = it->second.GetValue();
+    }
+  }
+
+
   static void DebugAddAttachment(Json::Value& target,
                                  const FileInfo& info)
   {
--- a/OrthancServer/Sources/Database/FindResponse.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/FindResponse.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -73,10 +73,16 @@
       typedef std::map<DicomTag, std::set<std::string>* >      MainDicomTagValues;
 
       std::set<std::string>  identifiers_;
+      uint64_t               count_;
       MetadataValues         metadataValues_;
       MainDicomTagValues     mainDicomTagValues_;
 
     public:
+      ChildrenInformation()
+      : count_(0)
+      {
+      }
+
       ~ChildrenInformation();
 
       void AddIdentifier(const std::string& identifier);
@@ -86,6 +92,21 @@
         return identifiers_;
       }
 
+      void SetCount(uint64_t count)
+      {
+        count_ = count;
+      }
+
+      void IncrementCount(uint64_t count)
+      {
+        count_ += count;
+      }
+
+      uint64_t GetCount() const
+      {
+        return count_;
+      }
+
       void AddMetadataValue(MetadataType metadata,
                             const std::string& value);
 
@@ -101,6 +122,48 @@
 
 
   public:
+    class MetadataContent
+    {
+    private:
+      std::string  value_;
+      int64_t      revision_;
+
+    public:
+      MetadataContent() :
+        revision_(0)
+      {
+      }
+
+      MetadataContent(const std::string& value,
+                      int64_t revision) :
+        value_(value),
+        revision_(revision)
+      {
+      }
+
+      explicit MetadataContent(const std::string& value) :
+        value_(value),
+        revision_(0)
+      {
+      }
+
+      const std::string& GetValue() const
+      {
+        return value_;
+      }
+
+      int64_t GetRevision() const
+      {
+        return revision_;
+      }
+
+      void SetRevision(int64_t revision)
+      {
+        revision_ = revision;
+      }
+    };
+
+
     class Resource : public boost::noncopyable
     {
     private:
@@ -114,15 +177,16 @@
       MainDicomTagsAtLevel                  mainDicomTagsStudy_;
       MainDicomTagsAtLevel                  mainDicomTagsSeries_;
       MainDicomTagsAtLevel                  mainDicomTagsInstance_;
-      std::map<MetadataType, std::string>   metadataPatient_;
-      std::map<MetadataType, std::string>   metadataStudy_;
-      std::map<MetadataType, std::string>   metadataSeries_;
-      std::map<MetadataType, std::string>   metadataInstance_;
+      std::map<MetadataType, MetadataContent>   metadataPatient_;
+      std::map<MetadataType, MetadataContent>   metadataStudy_;
+      std::map<MetadataType, MetadataContent>   metadataSeries_;
+      std::map<MetadataType, MetadataContent>   metadataInstance_;
       ChildrenInformation                   childrenStudiesInformation_;
       ChildrenInformation                   childrenSeriesInformation_;
       ChildrenInformation                   childrenInstancesInformation_;
       std::set<std::string>                 labels_;
       std::map<FileContentType, FileInfo>   attachments_;
+      std::map<FileContentType, int64_t>    revisions_;
       bool                                  hasOneInstanceMetadataAndAttachments_;
       std::string                           oneInstancePublicId_;
       std::map<MetadataType, std::string>   oneInstanceMetadata_;
@@ -199,11 +263,12 @@
 
       void AddMetadata(ResourceType level,
                        MetadataType metadata,
-                       const std::string& value);
+                       const std::string& value,
+                       int64_t revision);
 
-      std::map<MetadataType, std::string>& GetMetadata(ResourceType level);
+      std::map<MetadataType, MetadataContent>& GetMetadata(ResourceType level);
 
-      const std::map<MetadataType, std::string>& GetMetadata(ResourceType level) const
+      const std::map<MetadataType, MetadataContent>& GetMetadata(ResourceType level) const
       {
         return const_cast<Resource&>(*this).GetMetadata(level);
       }
@@ -212,6 +277,11 @@
                           ResourceType level,
                           MetadataType metadata) const;
 
+      bool LookupMetadata(std::string& value,
+                          int64_t& revision,
+                          ResourceType level,
+                          MetadataType metadata) const;
+
       void AddChildIdentifier(ResourceType level,
                               const std::string& childId)
       {
@@ -223,6 +293,23 @@
         return GetChildrenInformation(level).GetIdentifiers();
       }
 
+      void SetChildrenCount(ResourceType level,
+                            uint64_t count)
+      {
+        GetChildrenInformation(level).SetCount(count);
+      }
+
+      void IncrementChildrenCount(ResourceType level,
+                                 uint64_t count)
+      {
+        GetChildrenInformation(level).IncrementCount(count);
+      }
+
+      uint64_t GetChildrenCount(ResourceType level) const
+      {
+        return GetChildrenInformation(level).GetCount();
+      }
+
       void AddChildrenMetadataValue(ResourceType level,
                                     MetadataType metadata,
                                     const std::string& value)
@@ -263,9 +350,11 @@
         return labels_;
       }
 
-      void AddAttachment(const FileInfo& attachment);
+      void AddAttachment(const FileInfo& attachment,
+                         int64_t revision);
 
       bool LookupAttachment(FileInfo& target,
+                            int64_t& revision,
                             FileContentType type) const;
 
       const std::map<FileContentType, FileInfo>& GetAttachments() const
@@ -273,6 +362,8 @@
         return attachments_;
       }
 
+      void ListAttachments(std::set<FileContentType>& target) const;
+
       void SetOneInstanceMetadataAndAttachments(const std::string& instancePublicId,
                                                 const std::map<MetadataType, std::string>& metadata,
                                                 const std::map<FileContentType, FileInfo>& attachments);
--- a/OrthancServer/Sources/Database/IDatabaseListener.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/IDatabaseListener.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/IDatabaseWrapper.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -111,7 +111,7 @@
       {
         return hasAttachmentCustomDataSupport_;
       }
-
+      
       void SetHasExtendedChanges(bool value)
       {
         hasExtendedChanges_ = value;
@@ -132,7 +132,7 @@
         return hasAtomicIncrementGlobalProperty_;
       }
 
-      void SetUpdateAndGetStatistics(bool value)
+      void SetHasUpdateAndGetStatistics(bool value)
       {
         hasUpdateAndGetStatistics_ = value;
       }
@@ -214,11 +214,6 @@
       virtual void GetAllPublicIds(std::list<std::string>& target,
                                    ResourceType resourceType) = 0;
 
-      virtual void GetAllPublicIds(std::list<std::string>& target,
-                                   ResourceType resourceType,
-                                   int64_t since,
-                                   uint32_t limit) = 0;
-
       virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
                               bool& done /*out*/,
                               int64_t since,
@@ -227,9 +222,6 @@
       virtual void GetChildrenInternalId(std::list<int64_t>& target,
                                          int64_t id) = 0;
 
-      virtual void GetChildrenPublicId(std::list<std::string>& target,
-                                       int64_t id) = 0;
-
       virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
                                         bool& done /*out*/,
                                         int64_t since,
@@ -318,13 +310,6 @@
     
       virtual bool IsDiskSizeAbove(uint64_t threshold) = 0;
     
-      virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
-                                        std::list<std::string>* instancesId, // Can be NULL if not needed
-                                        const DatabaseDicomTagConstraints& lookup,
-                                        ResourceType queryLevel,
-                                        const std::set<std::string>& labels,
-                                        LabelsConstraint labelsConstraint,
-                                        uint32_t limit) = 0;
 
       // Returns "true" iff. the instance is new and has been inserted
       // into the database. If "false" is returned, the content of
@@ -353,16 +338,6 @@
 
 
       /**
-       * Primitives introduced in Orthanc 1.5.4
-       **/
-
-      virtual bool LookupResourceAndParent(int64_t& id,
-                                           ResourceType& type,
-                                           std::string& parentPublicId,
-                                           const std::string& publicId) = 0;
-
-
-      /**
        * Primitives introduced in Orthanc 1.12.0
        **/
 
@@ -372,10 +347,6 @@
       virtual void RemoveLabel(int64_t resource,
                                const std::string& label) = 0;
 
-      // List the labels of one single resource
-      virtual void ListLabels(std::set<std::string>& target,
-                              int64_t resource) = 0;
-
       // List all the labels that are present in any resource
       virtual void ListAllLabels(std::set<std::string>& target) = 0;
 
@@ -392,10 +363,15 @@
                                           int64_t& uncompressedSize) = 0;
 
       /**
-       * Primitives introduced in Orthanc 1.12.4
+       * Primitives introduced in Orthanc 1.12.5
        **/
 
       // This is only implemented if "HasIntegratedFind()" is "true"
+      virtual void ExecuteCount(uint64_t& count,
+                                const FindRequest& request,
+                                const Capabilities& capabilities) = 0;
+
+      // This is only implemented if "HasIntegratedFind()" is "true"
       virtual void ExecuteFind(FindResponse& response,
                                const FindRequest& request,
                                const Capabilities& capabilities) = 0;
@@ -429,6 +405,53 @@
     };
 
 
+    // TODO-FIND: Could this interface be removed?
+    class ICompatibilityTransaction : public boost::noncopyable
+    {
+    public:
+      virtual ~ICompatibilityTransaction()
+      {
+      }
+
+      virtual void GetAllPublicIdsCompatibility(std::list<std::string>& target,
+                                                ResourceType resourceType,
+                                                int64_t since,
+                                                uint32_t limit) = 0;
+
+      virtual void GetChildrenPublicId(std::list<std::string>& target,
+                                       int64_t id) = 0;
+
+      /**
+       * Primitives introduced in Orthanc 1.5.2
+       **/
+
+      virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
+                                        std::list<std::string>* instancesId, // Can be NULL if not needed
+                                        const DatabaseDicomTagConstraints& lookup,
+                                        ResourceType queryLevel,
+                                        const std::set<std::string>& labels,
+                                        LabelsConstraint labelsConstraint,
+                                        uint32_t limit) = 0;
+
+      /**
+       * Primitives introduced in Orthanc 1.5.4
+       **/
+
+      virtual bool LookupResourceAndParent(int64_t& id,
+                                           ResourceType& type,
+                                           std::string& parentPublicId,
+                                           const std::string& publicId) = 0;
+
+      /**
+       * Primitives introduced in Orthanc 1.12.0
+       **/
+
+      // List the labels of one single resource
+      virtual void ListLabels(std::set<std::string>& target,
+                              int64_t resource) = 0;
+    };
+
+
     virtual ~IDatabaseWrapper()
     {
     }
--- a/OrthancServer/Sources/Database/InstallLabelsTable.sql	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/InstallLabelsTable.sql	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 -- Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 -- Department, University Hospital of Liege, Belgium
 -- Copyright (C) 2017-2023 Osimis S.A., Belgium
--- Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
--- Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/InstallTrackAttachmentsSize.sql	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/InstallTrackAttachmentsSize.sql	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 -- Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 -- Department, University Hospital of Liege, Belgium
 -- Copyright (C) 2017-2023 Osimis S.A., Belgium
--- Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
--- Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/MainDicomTagsRegistry.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/MainDicomTagsRegistry.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -98,11 +98,14 @@
   }
 
 
-  bool MainDicomTagsRegistry::NormalizeLookup(DatabaseDicomTagConstraints& target,
+  bool MainDicomTagsRegistry::NormalizeLookup(bool& canBeFullyPerformedInDb,
+                                              DatabaseDicomTagConstraints& target,
                                               const DatabaseLookup& source,
-                                              ResourceType queryLevel) const
+                                              ResourceType queryLevel,
+                                              bool allowChildrenExistsQueries) const
   {
     bool isEquivalentLookup = true;
+    canBeFullyPerformedInDb = true;
 
     target.Clear();
 
@@ -110,8 +113,10 @@
     {
       ResourceType level;
       DicomTagType type;
+      const DicomTag& tag = source.GetConstraint(i).GetTag();
 
-      LookupTag(level, type, source.GetConstraint(i).GetTag());
+      LookupTag(level, type, tag);
+
 
       if (type == DicomTagType_Identifier ||
           type == DicomTagType_Main)
@@ -124,6 +129,13 @@
         }
 
         bool isEquivalentConstraint;
+        
+        // DicomIdentifiers are stored UPPERCASE -> as soon as a case senstive search happens, it is currently not possible to perform it in DB only
+        if (type == DicomTagType_Identifier && source.GetConstraint(i).IsCaseSensitive())
+        {
+          canBeFullyPerformedInDb = false;
+        }
+
         target.AddConstraint(source.GetConstraint(i).ConvertToDatabaseConstraint(isEquivalentConstraint, level, type));
 
         if (!isEquivalentConstraint)
@@ -133,7 +145,26 @@
       }
       else
       {
-        isEquivalentLookup = false;
+        if (allowChildrenExistsQueries &&
+            tag == DICOM_TAG_MODALITIES_IN_STUDY && queryLevel == ResourceType_Study)
+        {
+          // transform the query into a children query
+          std::vector<std::string> values(source.GetConstraint(i).GetValues().begin(), source.GetConstraint(i).GetValues().end());
+
+          std::unique_ptr<DatabaseDicomTagConstraint> constraint(new DatabaseDicomTagConstraint(ResourceType_Series,
+                                                                 DICOM_TAG_MODALITY,
+                                                                 false,
+                                                                 ConstraintType_List,
+                                                                 values,
+                                                                 false,
+                                                                 true));
+          target.AddConstraint(constraint.release());
+        }
+        else
+        {
+          isEquivalentLookup = false;
+          canBeFullyPerformedInDb = false;
+        }
       }
     }
 
--- a/OrthancServer/Sources/Database/MainDicomTagsRegistry.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/MainDicomTagsRegistry.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -82,8 +82,10 @@
      * constraints are less strict than the original DatabaseLookup,
      * so more resources will match them.
      **/
-    bool NormalizeLookup(DatabaseDicomTagConstraints& target,
+    bool NormalizeLookup(bool& canBeFullyPerformedInDb,
+                         DatabaseDicomTagConstraints& target,
                          const DatabaseLookup& source,
-                         ResourceType queryLevel) const;
+                         ResourceType queryLevel,
+                         bool allowChildrenExistsQueries) const;
   };
 }
--- a/OrthancServer/Sources/Database/OrthancIdentifiers.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/OrthancIdentifiers.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/OrthancIdentifiers.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/OrthancIdentifiers.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/PrepareDatabase.sql	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/PrepareDatabase.sql	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 -- Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 -- Department, University Hospital of Liege, Belgium
 -- Copyright (C) 2017-2023 Osimis S.A., Belgium
--- Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
--- Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -40,6 +40,9 @@
        );
 
 -- The following table was added in Orthanc 0.8.5 (database v5)
+-- It contains only the DICOM Tags that are commonly used for searches.
+-- All these tags are converted to UPPERCASE !
+-- These tags are also stored in the MainDicomTags table without casing modificiation.
 CREATE TABLE DicomIdentifiers(
        id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,
        tagGroup INTEGER,
@@ -52,7 +55,7 @@
        id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,
        type INTEGER,
        value TEXT,
-       -- revision INTEGER,      -- New in Orthanc 1.12.0 (added in InstallRevisionAndCustomData.sql)
+       -- revision INTEGER,      -- New in Orthanc 1.12.7 (added in InstallRevisionAndCustomData.sql)
        PRIMARY KEY(id, type)
        );
 
@@ -65,8 +68,8 @@
        compressionType INTEGER,
        uncompressedMD5 TEXT,  -- New in Orthanc 0.7.3 (database v4)
        compressedMD5 TEXT,    -- New in Orthanc 0.7.3 (database v4)
-       -- revision INTEGER,      -- New in Orthanc 1.12.0 (added in InstallRevisionAndCustomData.sql)
-       -- customData TEXT,       -- New in Orthanc 1.12.0 (added in InstallRevisionAndCustomData.sql)
+       -- revision INTEGER,      -- New in Orthanc 1.12.7 (added in InstallRevisionAndCustomData.sql)
+       -- customData TEXT,       -- New in Orthanc 1.12.7 (added in InstallRevisionAndCustomData.sql)
        PRIMARY KEY(id, fileType)
        );              
 
--- a/OrthancServer/Sources/Database/ResourcesContent.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/ResourcesContent.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/ResourcesContent.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/ResourcesContent.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -480,8 +480,9 @@
 #define C6_STRING_4 6
 #define C7_INT_1 7
 #define C8_INT_2 8
-#define C9_BIG_INT_1 9
-#define C10_BIG_INT_2 10
+#define C9_INT_3 9
+#define C10_BIG_INT_1 10
+#define C11_BIG_INT_2 11
 
 #define QUERY_LOOKUP 1
 #define QUERY_MAIN_DICOM_TAGS 2
@@ -496,10 +497,13 @@
 #define QUERY_CHILDREN_IDENTIFIERS 20
 #define QUERY_CHILDREN_MAIN_DICOM_TAGS 21
 #define QUERY_CHILDREN_METADATA 22
+#define QUERY_CHILDREN_COUNT 23
 #define QUERY_GRAND_CHILDREN_IDENTIFIERS 30
 #define QUERY_GRAND_CHILDREN_MAIN_DICOM_TAGS 31
 #define QUERY_GRAND_CHILDREN_METADATA 32
+#define QUERY_GRAND_CHILDREN_COUNT 33
 #define QUERY_GRAND_GRAND_CHILDREN_IDENTIFIERS 40
+#define QUERY_GRAND_GRAND_CHILDREN_COUNT 41
 #define QUERY_ONE_INSTANCE_IDENTIFIER 50
 #define QUERY_ONE_INSTANCE_METADATA 51
 #define QUERY_ONE_INSTANCE_ATTACHMENTS 52
@@ -507,6 +511,25 @@
 #define STRINGIFY(x) #x
 #define TOSTRING(x) STRINGIFY(x)
 
+    virtual void ExecuteCount(uint64_t& count,
+                              const FindRequest& request,
+                              const Capabilities& capabilities) ORTHANC_OVERRIDE
+    {
+      LookupFormatter formatter;
+      std::string sql;
+
+      std::string lookupSql;
+      LookupFormatter::Apply(lookupSql, formatter, request);
+
+      // base query, retrieve the ordered internalId and publicId of the selected resources
+      sql = "WITH Lookup AS (" + lookupSql + ") SELECT COUNT(*) FROM Lookup";
+      SQLite::Statement s(db_, SQLITE_FROM_HERE_DYNAMIC(sql), sql);
+      formatter.Bind(s);
+
+      s.Step();
+      count = s.ColumnInt64(0);
+    }
+
 
     virtual void ExecuteFind(FindResponse& response,
                              const FindRequest& request,
@@ -574,8 +597,9 @@
              "  NULL AS c6_string4, "
              "  NULL AS c7_int1, "
              "  NULL AS c8_int2, "
-             "  NULL AS c9_big_int1, "
-             "  NULL AS c10_big_int2 "
+             "  NULL AS c9_int3, "
+             "  NULL AS c10_big_int1, "
+             "  NULL AS c11_big_int2 "
              "  FROM Lookup ";
 
       // need one instance info ? (part 2: execute the queries)
@@ -592,8 +616,9 @@
                "    NULL AS c6_string4, "
                "    NULL AS c7_int1, "
                "    NULL AS c8_int2, "
-               "    instanceInternalId AS c9_big_int1, "
-               "    NULL AS c10_big_int2 "
+               "    NULL AS c9_int3, "
+               "    instanceInternalId AS c10_big_int1, "
+               "    NULL AS c11_big_int2 "
                "   FROM OneInstance ";
 
         sql += "   UNION SELECT"
@@ -606,8 +631,9 @@
                "    NULL AS c6_string4, "
                "    Metadata.type AS c7_int1, "
                "    NULL AS c8_int2, "
-               "    NULL AS c9_big_int1, "
-               "    NULL AS c10_big_int2 "
+               "    NULL AS c9_int3, "
+               "    NULL AS c10_big_int1, "
+               "    NULL AS c11_big_int2 "
                "   FROM OneInstance "
                "   INNER JOIN Metadata ON Metadata.id = OneInstance.instanceInternalId ";
               
@@ -621,8 +647,9 @@
                "    customData AS c6_string4, "
                "    fileType AS c7_int1, "
                "    compressionType AS c8_int2, "
-               "    compressedSize AS c9_big_int1, "
-               "    uncompressedSize AS c10_big_int2 "
+               "    revision AS c9_int3, "
+               "    compressedSize AS c10_big_int1, "
+               "    uncompressedSize AS c11_big_int2 "
                "   FROM OneInstance "
                "   INNER JOIN AttachedFiles ON AttachedFiles.id = OneInstance.instanceInternalId ";
 
@@ -641,8 +668,9 @@
                "  NULL AS c6_string4, "
                "  tagGroup AS c7_int1, "
                "  tagElement AS c8_int2, "
-               "  NULL AS c9_big_int1, "
-               "  NULL AS c10_big_int2 "
+               "  NULL AS c9_int3, "
+               "  NULL AS c10_big_int1, "
+               "  NULL AS c11_big_int2 "
                "FROM Lookup "
                "INNER JOIN MainDicomTags ON MainDicomTags.id = Lookup.internalId ";
       }
@@ -659,9 +687,10 @@
                "  NULL AS c5_string3, "
                "  NULL AS c6_string4, "
                "  type AS c7_int1, "
-               "  NULL AS c8_int2, "
-               "  NULL AS c9_big_int1, "
-               "  NULL AS c10_big_int2 "
+               "  revision AS c8_int2, "
+               "  NULL AS c9_int3, "
+               "  NULL AS c10_big_int1, "
+               "  NULL AS c11_big_int2 "
                "FROM Lookup "
                "INNER JOIN Metadata ON Metadata.id = Lookup.internalId ";
       }
@@ -679,8 +708,9 @@
                "  customData AS c6_string4, "
                "  fileType AS c7_int1, "
                "  compressionType AS c8_int2, "
-               "  compressedSize AS c9_big_int1, "
-               "  uncompressedSize AS c10_big_int2 "
+               "  revision AS c9_int3, "
+               "  compressedSize AS c10_big_int1, "
+               "  uncompressedSize AS c11_big_int2 "
                "FROM Lookup "
                "INNER JOIN AttachedFiles ON AttachedFiles.id = Lookup.internalId ";
       }
@@ -699,8 +729,9 @@
                "  NULL AS c6_string4, "
                "  NULL AS c7_int1, "
                "  NULL AS c8_int2, "
-               "  NULL AS c9_big_int1, "
-               "  NULL AS c10_big_int2 "
+               "  NULL AS c9_int3, "
+               "  NULL AS c10_big_int1, "
+               "  NULL AS c11_big_int2 "
                "FROM Lookup "
                "INNER JOIN Labels ON Labels.id = Lookup.internalId ";
       }
@@ -720,8 +751,9 @@
                  "  NULL AS c6_string4, "
                  "  tagGroup AS c7_int1, "
                  "  tagElement AS c8_int2, "
-                 "  NULL AS c9_big_int1, "
-                 "  NULL AS c10_big_int2 "
+                 "  NULL AS c9_int3, "
+                 "  NULL AS c10_big_int1, "
+                 "  NULL AS c11_big_int2 "
                  "FROM Lookup "
                  "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId "
                  "INNER JOIN MainDicomTags ON MainDicomTags.id = currentLevel.parentId ";
@@ -739,9 +771,10 @@
                  "  NULL AS c5_string3, "
                  "  NULL AS c6_string4, "
                  "  type AS c7_int1, "
-                 "  NULL AS c8_int2, "
-                 "  NULL AS c9_big_int1, "
-                 "  NULL AS c10_big_int2 "
+                 "  revision AS c8_int2, "
+                 "  NULL AS c9_int3, "
+                 "  NULL AS c10_big_int1, "
+                 "  NULL AS c11_big_int2 "
                  "FROM Lookup "
                  "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId "
                  "INNER JOIN Metadata ON Metadata.id = currentLevel.parentId ";        
@@ -762,8 +795,9 @@
                   "  NULL AS c6_string4, "
                   "  tagGroup AS c7_int1, "
                   "  tagElement AS c8_int2, "
-                  "  NULL AS c9_big_int1, "
-                  "  NULL AS c10_big_int2 "
+                  "  NULL AS c9_int3, "
+                  "  NULL AS c10_big_int1, "
+                  "  NULL AS c11_big_int2 "
                   "FROM Lookup "
                   "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId "
                   "INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId "
@@ -782,9 +816,10 @@
                   "  NULL AS c5_string3, "
                   "  NULL AS c6_string4, "
                   "  type AS c7_int1, "
-                  "  NULL AS c8_int2, "
-                  "  NULL AS c9_big_int1, "
-                  "  NULL AS c10_big_int2 "
+                  "  revision AS c8_int2, "
+                  "  NULL AS c9_int3, "
+                  "  NULL AS c10_big_int1, "
+                  "  NULL AS c11_big_int2 "
                   "FROM Lookup "
                   "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId "
                   "INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId "
@@ -806,8 +841,9 @@
                "  NULL AS c6_string4, "
                "  tagGroup AS c7_int1, "
                "  tagElement AS c8_int2, "
-               "  NULL AS c9_big_int1, "
-               "  NULL AS c10_big_int2 "
+               "  NULL AS c9_int3, "
+               "  NULL AS c10_big_int1, "
+               "  NULL AS c11_big_int2 "
                "FROM Lookup "
                "  INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId "
                "  INNER JOIN MainDicomTags ON MainDicomTags.id = childLevel.internalId AND " + JoinRequestedTags(request.GetChildrenSpecification(static_cast<ResourceType>(requestLevel + 1))); 
@@ -826,8 +862,9 @@
                 "  NULL AS c6_string4, "
                 "  tagGroup AS c7_int1, "
                 "  tagElement AS c8_int2, "
-                "  NULL AS c9_big_int1, "
-                "  NULL AS c10_big_int2 "
+                "  NULL AS c9_int3, "
+                "  NULL AS c10_big_int1, "
+                "  NULL AS c11_big_int2 "
                 "FROM Lookup "
                 "  INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId "
                 "  INNER JOIN Resources grandChildLevel ON grandChildLevel.parentId = childLevel.internalId "
@@ -847,8 +884,9 @@
                "  NULL AS c6_string4, "
                "  NULL AS c7_int1, "
                "  NULL AS c8_int2, "
-               "  NULL AS c9_big_int1, "
-               "  NULL AS c10_big_int2 "
+               "  NULL AS c9_int3, "
+               "  NULL AS c10_big_int1, "
+               "  NULL AS c11_big_int2 "
                "FROM Lookup "
                "  INNER JOIN Resources currentLevel ON currentLevel.internalId = Lookup.internalId "
                "  INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId ";
@@ -866,9 +904,10 @@
                 "  NULL AS c5_string3, "
                 "  NULL AS c6_string4, "
                 "  type AS c7_int1, "
-                "  NULL AS c8_int2, "
-                "  NULL AS c9_big_int1, "
-                "  NULL AS c10_big_int2 "
+                "  revision AS c8_int2, "
+                "  NULL AS c9_int3, "
+                "  NULL AS c10_big_int1, "
+                "  NULL AS c11_big_int2 "
                 "FROM Lookup "
                 "  INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId "
                 "  INNER JOIN Metadata ON Metadata.id = childLevel.internalId AND Metadata.type IN (" + JoinRequestedMetadata(request.GetChildrenSpecification(static_cast<ResourceType>(requestLevel + 1))) + ") ";
@@ -886,9 +925,10 @@
                 "  NULL AS c5_string3, "
                 "  NULL AS c6_string4, "
                 "  type AS c7_int1, "
-                "  NULL AS c8_int2, "
-                "  NULL AS c9_big_int1, "
-                "  NULL AS c10_big_int2 "
+                "  revision AS c8_int2, "
+                "  NULL AS c9_int3, "
+                "  NULL AS c10_big_int1, "
+                "  NULL AS c11_big_int2 "
                 "FROM Lookup "
                 "  INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId "
                 "  INNER JOIN Resources grandChildLevel ON grandChildLevel.parentId = childLevel.internalId "
@@ -910,11 +950,33 @@
                "  NULL AS c6_string4, "
                "  NULL AS c7_int1, "
                "  NULL AS c8_int2, "
-               "  NULL AS c9_big_int1, "
-               "  NULL AS c10_big_int2 "
+               "  NULL AS c9_int3, "
+               "  NULL AS c10_big_int1, "
+               "  NULL AS c11_big_int2 "
                "FROM Lookup "
                "  INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId ";
       }
+      // no need to count if we have retrieved the list of identifiers
+      else if ((requestLevel == ResourceType_Patient && request.GetChildrenSpecification(ResourceType_Study).IsRetrieveCount()) ||
+          (requestLevel == ResourceType_Study && request.GetChildrenSpecification(ResourceType_Series).IsRetrieveCount()) ||
+          (requestLevel == ResourceType_Series && request.GetChildrenSpecification(ResourceType_Instance).IsRetrieveCount()))
+      {
+        sql += "UNION SELECT "
+               "  " TOSTRING(QUERY_CHILDREN_COUNT) " AS c0_queryId, "
+               "  Lookup.internalId AS c1_internalId, "
+               "  NULL AS c2_rowNumber, "
+               "  NULL AS c3_string1, "
+               "  NULL AS c4_string2, "
+               "  NULL AS c5_string3, "
+               "  NULL AS c6_string4, "
+               "  COUNT(*) AS c7_int1, "
+               "  NULL AS c8_int2, "
+               "  NULL AS c9_int3, "
+               "  NULL AS c10_big_int1, "
+               "  NULL AS c11_big_int2 "
+               "FROM Lookup "
+               "  INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId GROUP BY Lookup.internalId ";
+      }
 
       // need grandchildren identifiers ?
       if ((requestLevel == ResourceType_Patient && request.GetChildrenSpecification(ResourceType_Series).IsRetrieveIdentifiers()) ||
@@ -930,12 +992,34 @@
               "  NULL AS c6_string4, "
               "  NULL AS c7_int1, "
               "  NULL AS c8_int2, "
-              "  NULL AS c9_big_int1, "
-              "  NULL AS c10_big_int2 "
+              "  NULL AS c9_int3, "
+              "  NULL AS c10_big_int1, "
+              "  NULL AS c11_big_int2 "
               "FROM Lookup "
               "INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId "
               "INNER JOIN Resources grandChildLevel ON childLevel.internalId = grandChildLevel.parentId ";
       }
+      // no need to count if we have retrieved the list of identifiers
+      else if ((requestLevel == ResourceType_Patient && request.GetChildrenSpecification(ResourceType_Series).IsRetrieveCount()) ||
+          (requestLevel == ResourceType_Study && request.GetChildrenSpecification(ResourceType_Instance).IsRetrieveCount()))
+      {
+        sql += "UNION SELECT "
+              "  " TOSTRING(QUERY_GRAND_CHILDREN_COUNT) " AS c0_queryId, "
+              "  Lookup.internalId AS c1_internalId, "
+              "  NULL AS c2_rowNumber, "
+              "  NULL AS c3_string1, "
+              "  NULL AS c4_string2, "
+              "  NULL AS c5_string3, "
+              "  NULL AS c6_string4, "
+              "  COUNT(*) AS c7_int1, "
+              "  NULL AS c8_int2, "
+              "  NULL AS c9_int3, "
+              "  NULL AS c10_big_int1, "
+              "  NULL AS c11_big_int2 "
+              "FROM Lookup "
+              "INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId "
+              "INNER JOIN Resources grandChildLevel ON childLevel.internalId = grandChildLevel.parentId GROUP BY Lookup.internalId ";
+      }
 
       // need grandgrandchildren identifiers ?
       if (requestLevel == ResourceType_Patient && request.GetChildrenSpecification(ResourceType_Instance).IsRetrieveIdentifiers())
@@ -950,13 +1034,35 @@
               "  NULL AS c6_string4, "
               "  NULL AS c7_int1, "
               "  NULL AS c8_int2, "
-              "  NULL AS c9_big_int1, "
-              "  NULL AS c10_big_int2 "
+              "  NULL AS c9_int3, "
+              "  NULL AS c10_big_int1, "
+              "  NULL AS c11_big_int2 "
               "FROM Lookup "
               "INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId "
               "INNER JOIN Resources grandChildLevel ON childLevel.internalId = grandChildLevel.parentId "
               "INNER JOIN Resources grandGrandChildLevel ON grandChildLevel.internalId = grandGrandChildLevel.parentId ";
       }
+      // no need to count if we have retrieved the list of identifiers
+      else if (requestLevel == ResourceType_Patient && request.GetChildrenSpecification(ResourceType_Instance).IsRetrieveCount())
+      {
+        sql += "UNION SELECT "
+              "  " TOSTRING(QUERY_GRAND_GRAND_CHILDREN_COUNT) " AS c0_queryId, "
+              "  Lookup.internalId AS c1_internalId, "
+              "  NULL AS c2_rowNumber, "
+              "  NULL AS c3_string1, "
+              "  NULL AS c4_string2, "
+              "  NULL AS c5_string3, "
+              "  NULL AS c6_string4, "
+              "  COUNT(*) AS c7_int1, "
+              "  NULL AS c8_int2, "
+              "  NULL AS c9_int3, "
+              "  NULL AS c10_big_int1, "
+              "  NULL AS c11_big_int2 "
+              "FROM Lookup "
+              "INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId "
+              "INNER JOIN Resources grandChildLevel ON childLevel.internalId = grandChildLevel.parentId "
+              "INNER JOIN Resources grandGrandChildLevel ON grandChildLevel.internalId = grandGrandChildLevel.parentId GROUP BY Lookup.internalId ";
+      }
 
 
       sql += " ORDER BY c0_queryId, c2_rowNumber";  // this is really important to make sure that the Lookup query is the first one to provide results since we use it to create the responses element !
@@ -989,11 +1095,11 @@
           case QUERY_ATTACHMENTS:
           {
             FindResponse::Resource& res = response.GetResourceByInternalId(internalId);
-            FileInfo file(s.ColumnString(C3_STRING_1), static_cast<FileContentType>(s.ColumnInt(C7_INT_1)), 
-                          s.ColumnInt64(C9_BIG_INT_1), s.ColumnString(C4_STRING_2),
+            FileInfo file(s.ColumnString(C3_STRING_1), static_cast<FileContentType>(s.ColumnInt(C7_INT_1)),
+                          s.ColumnInt64(C11_BIG_INT_2), s.ColumnString(C4_STRING_2),
                           static_cast<CompressionType>(s.ColumnInt(C8_INT_2)),
-                          s.ColumnInt64(C10_BIG_INT_2), s.ColumnString(C5_STRING_3), s.ColumnString(C6_STRING_4));
-            res.AddAttachment(file);
+                          s.ColumnInt64(C10_BIG_INT_1), s.ColumnString(C5_STRING_3), s.ColumnString(C6_STRING_4));
+            res.AddAttachment(file, s.ColumnInt(C9_INT_3));
           }; break;
 
           case QUERY_MAIN_DICOM_TAGS:
@@ -1044,7 +1150,7 @@
             FindResponse::Resource& res = response.GetResourceByInternalId(internalId);
             res.AddMetadata(static_cast<ResourceType>(requestLevel), 
                             static_cast<MetadataType>(s.ColumnInt(C7_INT_1)),
-                            s.ColumnString(C3_STRING_1));
+                            s.ColumnString(C3_STRING_1), s.ColumnInt(C8_INT_2));
           }; break;
 
           case QUERY_PARENT_METADATA:
@@ -1052,7 +1158,7 @@
             FindResponse::Resource& res = response.GetResourceByInternalId(internalId);
             res.AddMetadata(static_cast<ResourceType>(requestLevel - 1), 
                             static_cast<MetadataType>(s.ColumnInt(C7_INT_1)),
-                            s.ColumnString(C3_STRING_1));
+                            s.ColumnString(C3_STRING_1), s.ColumnInt(C8_INT_2));
           }; break;
 
           case QUERY_GRAND_PARENT_METADATA:
@@ -1060,7 +1166,7 @@
             FindResponse::Resource& res = response.GetResourceByInternalId(internalId);
             res.AddMetadata(static_cast<ResourceType>(requestLevel - 2), 
                             static_cast<MetadataType>(s.ColumnInt(C7_INT_1)),
-                            s.ColumnString(C3_STRING_1));
+                            s.ColumnString(C3_STRING_1), s.ColumnInt(C8_INT_2));
           }; break;
 
           case QUERY_CHILDREN_METADATA:
@@ -1090,6 +1196,8 @@
             FindResponse::Resource& res = response.GetResourceByInternalId(internalId);
             res.AddChildIdentifier(static_cast<ResourceType>(requestLevel + 1),
                                    s.ColumnString(C3_STRING_1));
+            res.SetChildrenCount(static_cast<ResourceType>(requestLevel + 1),
+                                 res.GetChildrenIdentifiers(static_cast<ResourceType>(requestLevel + 1)).size());
           }; break;
 
           case QUERY_GRAND_CHILDREN_IDENTIFIERS:
@@ -1097,6 +1205,8 @@
             FindResponse::Resource& res = response.GetResourceByInternalId(internalId);
             res.AddChildIdentifier(static_cast<ResourceType>(requestLevel + 2),
                                    s.ColumnString(C3_STRING_1));
+            res.SetChildrenCount(static_cast<ResourceType>(requestLevel + 2),
+                                 res.GetChildrenIdentifiers(static_cast<ResourceType>(requestLevel + 2)).size());
           }; break;
 
           case QUERY_GRAND_GRAND_CHILDREN_IDENTIFIERS:
@@ -1104,6 +1214,29 @@
             FindResponse::Resource& res = response.GetResourceByInternalId(internalId);
             res.AddChildIdentifier(static_cast<ResourceType>(requestLevel + 3),
                                    s.ColumnString(C3_STRING_1));
+            res.SetChildrenCount(static_cast<ResourceType>(requestLevel + 3),
+                                 res.GetChildrenIdentifiers(static_cast<ResourceType>(requestLevel + 3)).size());
+          }; break;
+
+          case QUERY_CHILDREN_COUNT:
+          {
+            FindResponse::Resource& res = response.GetResourceByInternalId(internalId);
+            res.SetChildrenCount(static_cast<ResourceType>(requestLevel + 1),
+                                 static_cast<uint64_t>(s.ColumnInt64(C7_INT_1)));
+          }; break;
+
+          case QUERY_GRAND_CHILDREN_COUNT:
+          {
+            FindResponse::Resource& res = response.GetResourceByInternalId(internalId);
+            res.SetChildrenCount(static_cast<ResourceType>(requestLevel + 2),
+                                 static_cast<uint64_t>(s.ColumnInt64(C7_INT_1)));
+          }; break;
+
+          case QUERY_GRAND_GRAND_CHILDREN_COUNT:
+          {
+            FindResponse::Resource& res = response.GetResourceByInternalId(internalId);
+            res.SetChildrenCount(static_cast<ResourceType>(requestLevel + 3),
+                                 static_cast<uint64_t>(s.ColumnInt64(C7_INT_1)));
           }; break;
 
           case QUERY_ONE_INSTANCE_IDENTIFIER:
@@ -1121,10 +1254,10 @@
           case QUERY_ONE_INSTANCE_ATTACHMENTS:
           {
             FindResponse::Resource& res = response.GetResourceByInternalId(internalId);
-            FileInfo file(s.ColumnString(C3_STRING_1), static_cast<FileContentType>(s.ColumnInt(C7_INT_1)), 
-                          s.ColumnInt64(C9_BIG_INT_1), s.ColumnString(C4_STRING_2),
+            FileInfo file(s.ColumnString(C3_STRING_1), static_cast<FileContentType>(s.ColumnInt(C7_INT_1)),
+                          s.ColumnInt64(C11_BIG_INT_2), s.ColumnString(C4_STRING_2),
                           static_cast<CompressionType>(s.ColumnInt(C8_INT_2)),
-                          s.ColumnInt64(C10_BIG_INT_2), s.ColumnString(C5_STRING_3), s.ColumnString(C6_STRING_4));
+                          s.ColumnInt64(C10_BIG_INT_1), s.ColumnString(C5_STRING_3), s.ColumnString(C6_STRING_4));
             res.AddOneInstanceAttachment(file);
           }; break;
 
@@ -1284,10 +1417,10 @@
     }
 
 
-    virtual void GetAllPublicIds(std::list<std::string>& target,
-                                 ResourceType resourceType,
-                                 int64_t since,
-                                 uint32_t limit) ORTHANC_OVERRIDE
+    virtual void GetAllPublicIdsCompatibility(std::list<std::string>& target,
+                                              ResourceType resourceType,
+                                              int64_t since,
+                                              uint32_t limit) ORTHANC_OVERRIDE
     {
       SQLite::Statement s(db_, SQLITE_FROM_HERE,
                           "SELECT publicId FROM Resources WHERE "
@@ -2167,7 +2300,7 @@
     dbCapabilities_.SetFlushToDisk(true);
     dbCapabilities_.SetLabelsSupport(true);
     dbCapabilities_.SetHasExtendedChanges(true);
-    dbCapabilities_.SetHasFindSupport(true);
+    dbCapabilities_.SetHasFindSupport(HasIntegratedFind());
     db_.Open(path);
   }
 
@@ -2181,7 +2314,7 @@
     dbCapabilities_.SetFlushToDisk(true);
     dbCapabilities_.SetLabelsSupport(true);
     dbCapabilities_.SetHasExtendedChanges(true);
-    dbCapabilities_.SetHasFindSupport(true);
+    dbCapabilities_.SetHasFindSupport(HasIntegratedFind());
     db_.OpenInMemory();
   }
 
@@ -2281,7 +2414,7 @@
         }
       }
 
-      // New in Orthanc 1.12.5
+      // New in Orthanc 1.12.7
       if (version_ >= 6)
       {
         if (!transaction->LookupGlobalProperty(tmp, GlobalProperty_SQLiteHasCustomDataAndRevision, true /* unused in SQLite */) 
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -23,7 +23,7 @@
 
 #pragma once
 
-#include "BaseDatabaseWrapper.h"
+#include "BaseCompatibilityTransaction.h"
 
 #include "../../../OrthancFramework/Sources/SQLite/Connection.h"
 
@@ -36,7 +36,7 @@
    * translates low-level requests into SQL statements. Mutual
    * exclusion MUST be implemented at a higher level.
    **/
-  class SQLiteDatabaseWrapper : public BaseDatabaseWrapper
+  class SQLiteDatabaseWrapper : public IDatabaseWrapper
   {
   private:
     class TransactionBase;
@@ -112,7 +112,7 @@
      * "UnitTestsTransaction" give access to additional information
      * about the underlying SQLite database to be used in unit tests.
      **/
-    class UnitTestsTransaction : public BaseDatabaseWrapper::BaseTransaction
+    class UnitTestsTransaction : public BaseCompatibilityTransaction
     {
     protected:
       SQLite::Connection& db_;
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -288,22 +288,6 @@
   }
 
 
-  static void CopyListToVector(std::vector<std::string>& target,
-                               const std::list<std::string>& source)
-  {
-    target.resize(source.size());
-
-    size_t pos = 0;
-    
-    for (std::list<std::string>::const_iterator
-           it = source.begin(); it != source.end(); ++it)
-    {
-      target[pos] = *it;
-      pos ++;
-    }      
-  }
-
-
   void StatelessDatabaseOperations::ReadWriteTransaction::LogChange(int64_t internalId,
                                                                     ChangeType changeType,
                                                                     ResourceType resourceType,
@@ -580,284 +564,32 @@
   {
     ApplyInternal(NULL, &operations);
   }
-  
-
-  bool StatelessDatabaseOperations::ExpandResource(ExpandedResource& target,
-                                                   const std::string& publicId,
-                                                   ResourceType level,
-                                                   const std::set<DicomTag>& requestedTags,
-                                                   ExpandResourceFlags expandFlags)
-  {    
-    class Operations : public ReadOnlyOperationsT6<
-      bool&, ExpandedResource&, const std::string&, ResourceType, const std::set<DicomTag>&, ExpandResourceFlags>
+
+
+  const FindResponse::Resource& StatelessDatabaseOperations::ExecuteSingleResource(FindResponse& response,
+                                                                                   const FindRequest& request)
+  {
+    ExecuteFind(response, request);
+
+    if (response.GetSize() == 0)
     {
-    private:
-      bool hasLabelsSupport_;
-
-      static bool LookupStringMetadata(std::string& result,
-                                       const std::map<MetadataType, std::string>& metadata,
-                                       MetadataType type)
-      {
-        std::map<MetadataType, std::string>::const_iterator found = metadata.find(type);
-
-        if (found == metadata.end())
-        {
-          return false;
-        }
-        else
-        {
-          result = found->second;
-          return true;
-        }
-      }
-
-
-      static bool LookupIntegerMetadata(int64_t& result,
-                                        const std::map<MetadataType, std::string>& metadata,
-                                        MetadataType type)
-      {
-        std::string s;
-        if (!LookupStringMetadata(s, metadata, type))
-        {
-          return false;
-        }
-
-        try
-        {
-          result = boost::lexical_cast<int64_t>(s);
-          return true;
-        }
-        catch (boost::bad_lexical_cast&)
-        {
-          return false;
-        }
-      }
-
-
-    public:
-      explicit Operations(bool hasLabelsSupport) :
-        hasLabelsSupport_(hasLabelsSupport)
-      {
-      }
-
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+    else if (response.GetSize() == 1)
+    {
+      if (response.GetResourceByIndex(0).GetLevel() != request.GetLevel())
       {
-        // Lookup for the requested resource
-        int64_t internalId;
-        ResourceType type;
-        std::string parent;
-        if (!transaction.LookupResourceAndParent(internalId, type, parent, tuple.get<2>()) ||
-            type != tuple.get<3>())
-        {
-          tuple.get<0>() = false;
-        }
-        else
-        {
-          ExpandedResource& target = tuple.get<1>();
-          ExpandResourceFlags expandFlags = tuple.get<5>();
-
-          // Set information about the parent resource (if it exists)
-          if (type == ResourceType_Patient)
-          {
-            if (!parent.empty())
-            {
-              throw OrthancException(ErrorCode_DatabasePlugin);
-            }
-          }
-          else
-          {
-            if (parent.empty())
-            {
-              throw OrthancException(ErrorCode_DatabasePlugin);
-            }
-
-            target.parentId_ = parent;
-          }
-
-          target.SetResource(type, tuple.get<2>());
-
-          if (expandFlags & ExpandResourceFlags_IncludeChildren)
-          {
-            // List the children resources
-            transaction.GetChildrenPublicId(target.childrenIds_, internalId);
-          }
-
-          if (expandFlags & ExpandResourceFlags_IncludeMetadata)
-          {
-            // Extract the metadata
-            transaction.GetAllMetadata(target.metadata_, internalId);
-
-            switch (type)
-            {
-              case ResourceType_Patient:
-              case ResourceType_Study:
-                break;
-
-              case ResourceType_Series:
-              {
-                int64_t i;
-                if (LookupIntegerMetadata(i, target.metadata_, MetadataType_Series_ExpectedNumberOfInstances))
-                {
-                  target.expectedNumberOfInstances_ = static_cast<int>(i);
-                  target.status_ = EnumerationToString(transaction.GetSeriesStatus(internalId, i));
-                }
-                else
-                {
-                  target.expectedNumberOfInstances_ = -1;
-                  target.status_ = EnumerationToString(SeriesStatus_Unknown);
-                }
-
-                break;
-              }
-
-              case ResourceType_Instance:
-              {
-                FileInfo attachment;
-                int64_t revision;  // ignored
-                if (!transaction.LookupAttachment(attachment, revision, internalId, FileContentType_Dicom))
-                {
-                  throw OrthancException(ErrorCode_InternalError);
-                }
-
-                target.fileSize_ = static_cast<unsigned int>(attachment.GetUncompressedSize());
-                target.fileUuid_ = attachment.GetUuid();
-
-                int64_t i;
-                if (LookupIntegerMetadata(i, target.metadata_, MetadataType_Instance_IndexInSeries))
-                {
-                  target.indexInSeries_ = static_cast<int>(i);
-                }
-                else
-                {
-                  target.indexInSeries_ = -1;
-                }
-
-                break;
-              }
-
-              default:
-                throw OrthancException(ErrorCode_InternalError);
-            }
-
-            // check the main dicom tags list has not changed since the resource was stored
-            target.mainDicomTagsSignature_ = DicomMap::GetDefaultMainDicomTagsSignatureFrom1_11(type);
-            LookupStringMetadata(target.mainDicomTagsSignature_, target.metadata_, MetadataType_MainDicomTagsSignature);
-          }
-
-          if (expandFlags & ExpandResourceFlags_IncludeMainDicomTags)
-          {
-            // read all tags from DB
-            transaction.GetMainDicomTags(target.GetMainDicomTags(), internalId);
-
-            // read all main sequences from DB
-            std::string serializedSequences;
-            if (LookupStringMetadata(serializedSequences, target.metadata_, MetadataType_MainDicomSequences))
-            {
-              Json::Value jsonMetadata;
-              Toolbox::ReadJson(jsonMetadata, serializedSequences);
-
-              assert(jsonMetadata["Version"].asInt() == 1);
-              target.GetMainDicomTags().FromDicomAsJson(jsonMetadata["Sequences"], true /* append */, true /* parseSequences */);
-            }
-
-            // check if we have access to all requestedTags or if we must get tags from parents
-            const std::set<DicomTag>& requestedTags = tuple.get<4>();
-
-            if (requestedTags.size() > 0)
-            {
-              std::set<DicomTag> savedMainDicomTags;
-              
-              FromDcmtkBridge::ParseListOfTags(savedMainDicomTags, target.mainDicomTagsSignature_);
-
-              // read parent main dicom tags as long as we have not gathered all requested tags
-              ResourceType currentLevel = target.GetLevel();
-              int64_t currentInternalId = internalId;
-              Toolbox::GetMissingsFromSet(target.missingRequestedTags_, requestedTags, savedMainDicomTags);
-
-              while ((target.missingRequestedTags_.size() > 0)
-                     && currentLevel != ResourceType_Patient)
-              {
-                currentLevel = GetParentResourceType(currentLevel);
-
-                int64_t currentParentId;
-                if (!transaction.LookupParent(currentParentId, currentInternalId))
-                {
-                  break;
-                }
-
-                std::map<MetadataType, std::string> parentMetadata;
-                transaction.GetAllMetadata(parentMetadata, currentParentId);
-
-                std::string parentMainDicomTagsSignature = DicomMap::GetDefaultMainDicomTagsSignatureFrom1_11(currentLevel);
-                LookupStringMetadata(parentMainDicomTagsSignature, parentMetadata, MetadataType_MainDicomTagsSignature);
-
-                std::set<DicomTag> parentSavedMainDicomTags;
-                FromDcmtkBridge::ParseListOfTags(parentSavedMainDicomTags, parentMainDicomTagsSignature);
-                
-                size_t previousMissingCount = target.missingRequestedTags_.size();
-                Toolbox::AppendSets(savedMainDicomTags, parentSavedMainDicomTags);
-                Toolbox::GetMissingsFromSet(target.missingRequestedTags_, requestedTags, savedMainDicomTags);
-
-                // read the parent tags from DB only if it reduces the number of missing tags
-                if (target.missingRequestedTags_.size() < previousMissingCount)
-                { 
-                  Toolbox::AppendSets(savedMainDicomTags, parentSavedMainDicomTags);
-
-                  DicomMap parentTags;
-                  transaction.GetMainDicomTags(parentTags, currentParentId);
-
-                  target.GetMainDicomTags().Merge(parentTags);
-                }
-
-                currentInternalId = currentParentId;
-              }
-            }
-          }
-
-          if ((expandFlags & ExpandResourceFlags_IncludeLabels) &&
-              hasLabelsSupport_)
-          {
-            transaction.ListLabels(target.labels_, internalId);
-          }
-
-          std::string tmp;
-
-          if (LookupStringMetadata(tmp, target.metadata_, MetadataType_AnonymizedFrom))
-          {
-            target.anonymizedFrom_ = tmp;
-          }
-
-          if (LookupStringMetadata(tmp, target.metadata_, MetadataType_ModifiedFrom))
-          {
-            target.modifiedFrom_ = tmp;
-          }
-
-          if (type == ResourceType_Patient ||
-              type == ResourceType_Study ||
-              type == ResourceType_Series)
-          {
-            target.isStable_ = !transaction.GetTransactionContext().IsUnstableResource(type, internalId);
-
-            if (LookupStringMetadata(tmp, target.metadata_, MetadataType_LastUpdate))
-            {
-              target.lastUpdate_ = tmp;
-            }
-          }
-          else
-          {
-            target.isStable_ = false;
-          }
-
-          tuple.get<0>() = true;
-        }
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+      else
+      {
+        return response.GetResourceByIndex(0);
       }
-    };
-
-    bool found;
-    Operations operations(db_.GetDatabaseCapabilities().HasLabelsSupport());
-    operations.Apply(*this, found, target, publicId, level, requestedTags, expandFlags);
-    return found;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_DatabasePlugin);
+    }
   }
 
 
@@ -865,110 +597,50 @@
                                                    const std::string& publicId,
                                                    ResourceType level)
   {
-    class Operations : public ReadOnlyOperationsT3<std::map<MetadataType, std::string>&, const std::string&, ResourceType>
+    FindRequest request(level);
+    request.SetOrthancId(level, publicId);
+    request.SetRetrieveMetadata(true);
+
+    FindResponse response;
+    std::map<MetadataType, FindResponse::MetadataContent> metadata = ExecuteSingleResource(response, request).GetMetadata(level);
+
+    target.clear();
+    for (std::map<MetadataType, FindResponse::MetadataContent>::const_iterator
+         it = metadata.begin(); it != metadata.end(); ++it)
     {
-    public:
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
-      {
-        ResourceType type;
-        int64_t id;
-        if (!transaction.LookupResource(id, type, tuple.get<1>()) ||
-            tuple.get<2>() != type)
-        {
-          throw OrthancException(ErrorCode_UnknownResource);
-        }
-        else
-        {
-          transaction.GetAllMetadata(tuple.get<0>(), id);
-        }
-      }
-    };
-
-    Operations operations;
-    operations.Apply(*this, target, publicId, level);
+      target[it->first] = it->second.GetValue();
+    }
   }
 
 
   bool StatelessDatabaseOperations::LookupAttachment(FileInfo& attachment,
                                                      int64_t& revision,
-                                                     const std::string& instancePublicId,
+                                                     ResourceType level,
+                                                     const std::string& publicId,
                                                      FileContentType contentType)
   {
-    class Operations : public ReadOnlyOperationsT5<bool&, FileInfo&, int64_t&, const std::string&, FileContentType>
-    {
-    public:
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
-      {
-        int64_t internalId;
-        ResourceType type;
-        if (!transaction.LookupResource(internalId, type, tuple.get<3>()))
-        {
-          throw OrthancException(ErrorCode_UnknownResource);
-        }
-        else if (transaction.LookupAttachment(tuple.get<1>(), tuple.get<2>(), internalId, tuple.get<4>()))
-        {
-          assert(tuple.get<1>().GetContentType() == tuple.get<4>());
-          tuple.get<0>() = true;
-        }
-        else
-        {
-          tuple.get<0>() = false;
-        }
-      }
-    };
-
-    bool found;
-    Operations operations;
-    operations.Apply(*this, found, attachment, revision, instancePublicId, contentType);
-    return found;
+    FindRequest request(level);
+    request.SetOrthancId(level, publicId);
+    request.SetRetrieveAttachments(true);
+
+    FindResponse response;
+    return ExecuteSingleResource(response, request).LookupAttachment(attachment, revision, contentType);
   }
 
 
   void StatelessDatabaseOperations::GetAllUuids(std::list<std::string>& target,
                                                 ResourceType resourceType)
   {
-    class Operations : public ReadOnlyOperationsT2<std::list<std::string>&, ResourceType>
+    // This method is tested by "orthanc-tests/Plugins/WebDav/Run.py"
+    FindRequest request(resourceType);
+
+    FindResponse response;
+    ExecuteFind(response, request);
+
+    target.clear();
+    for (size_t i = 0; i < response.GetSize(); i++)
     {
-    public:
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
-      {
-        // TODO - CANDIDATE FOR "TransactionType_Implicit"
-        transaction.GetAllPublicIds(tuple.get<0>(), tuple.get<1>());
-      }
-    };
-
-    Operations operations;
-    operations.Apply(*this, target, resourceType);
-  }
-
-
-  void StatelessDatabaseOperations::GetAllUuids(std::list<std::string>& target,
-                                                ResourceType resourceType,
-                                                size_t since,
-                                                uint32_t limit)
-  {
-    if (limit == 0)
-    {
-      target.clear();
-    }
-    else
-    {
-      class Operations : public ReadOnlyOperationsT4<std::list<std::string>&, ResourceType, size_t, size_t>
-      {
-      public:
-        virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                                const Tuple& tuple) ORTHANC_OVERRIDE
-        {
-          // TODO - CANDIDATE FOR "TransactionType_Implicit"
-          transaction.GetAllPublicIds(tuple.get<0>(), tuple.get<1>(), tuple.get<2>(), tuple.get<3>());
-        }
-      };
-
-      Operations operations;
-      operations.Apply(*this, target, resourceType, since, limit);
+      target.push_back(response.GetResourceByIndex(i).GetIdentifier());
     }
   }
 
@@ -1232,104 +904,85 @@
 
 
   void StatelessDatabaseOperations::GetChildren(std::list<std::string>& result,
+                                                ResourceType level,
                                                 const std::string& publicId)
   {
-    class Operations : public ReadOnlyOperationsT2<std::list<std::string>&, const std::string&>
+    const ResourceType childLevel = GetChildResourceType(level);
+
+    FindRequest request(level);
+    request.SetOrthancId(level, publicId);
+    request.GetChildrenSpecification(childLevel).SetRetrieveIdentifiers(true);
+
+    FindResponse response;
+    ExecuteFind(response, request);
+
+    result.clear();
+
+    for (size_t i = 0; i < response.GetSize(); i++)
     {
-    public:
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
+      const std::set<std::string>& children = response.GetResourceByIndex(i).GetChildrenIdentifiers(childLevel);
+
+      for (std::set<std::string>::const_iterator it = children.begin(); it != children.end(); ++it)
       {
-        ResourceType type;
-        int64_t resource;
-        if (!transaction.LookupResource(resource, type, tuple.get<1>()))
-        {
-          throw OrthancException(ErrorCode_UnknownResource);
-        }
-        else if (type == ResourceType_Instance)
-        {
-          // An instance cannot have a child
-          throw OrthancException(ErrorCode_BadParameterType);
-        }
-        else
-        {
-          std::list<int64_t> tmp;
-          transaction.GetChildrenInternalId(tmp, resource);
-
-          tuple.get<0>().clear();
-
-          for (std::list<int64_t>::const_iterator 
-                 it = tmp.begin(); it != tmp.end(); ++it)
-          {
-            tuple.get<0>().push_back(transaction.GetPublicId(*it));
-          }
-        }
+        result.push_back(*it);
       }
-    };
-    
-    Operations operations;
-    operations.Apply(*this, result, publicId);
+    }
+  }
+
+
+  void StatelessDatabaseOperations::GetChildInstances(std::list<std::string>& result,
+                                                      const std::string& publicId,
+                                                      ResourceType level)
+  {
+    result.clear();
+    if (level == ResourceType_Instance)
+    {
+      result.push_back(publicId);
+    }
+    else
+    {
+      FindRequest request(level);
+      request.SetOrthancId(level, publicId);
+      request.GetChildrenSpecification(ResourceType_Instance).SetRetrieveIdentifiers(true);
+
+      FindResponse response;
+      const std::set<std::string>& instances = ExecuteSingleResource(response, request).GetChildrenIdentifiers(ResourceType_Instance);
+
+      for (std::set<std::string>::const_iterator it = instances.begin(); it != instances.end(); ++it)
+      {
+        result.push_back(*it);
+      }
+    }
   }
 
 
   void StatelessDatabaseOperations::GetChildInstances(std::list<std::string>& result,
                                                       const std::string& publicId)
   {
-    class Operations : public ReadOnlyOperationsT2<std::list<std::string>&, const std::string&>
+    ResourceType level;
+    if (LookupResourceType(level, publicId))
+    {
+      GetChildInstances(result, publicId, level);
+    }
+    else
     {
-    public:
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
-      {
-        tuple.get<0>().clear();
-        
-        ResourceType type;
-        int64_t top;
-        if (!transaction.LookupResource(top, type, tuple.get<1>()))
-        {
-          throw OrthancException(ErrorCode_UnknownResource);
-        }
-        else if (type == ResourceType_Instance)
-        {
-          // The resource is already an instance: Do not go down the hierarchy
-          tuple.get<0>().push_back(tuple.get<1>());
-        }
-        else
-        {
-          std::stack<int64_t> toExplore;
-          toExplore.push(top);
-
-          std::list<int64_t> tmp;
-          while (!toExplore.empty())
-          {
-            // Get the internal ID of the current resource
-            int64_t resource = toExplore.top();
-            toExplore.pop();
-
-            // TODO - This could be optimized by seeing how many
-            // levels "type == transaction.GetResourceType(top)" is
-            // above the "instances level"
-            if (transaction.GetResourceType(resource) == ResourceType_Instance)
-            {
-              tuple.get<0>().push_back(transaction.GetPublicId(resource));
-            }
-            else
-            {
-              // Tag all the children of this resource as to be explored
-              transaction.GetChildrenInternalId(tmp, resource);
-              for (std::list<int64_t>::const_iterator 
-                     it = tmp.begin(); it != tmp.end(); ++it)
-              {
-                toExplore.push(*it);
-              }
-            }
-          }
-        }
-      }
-    };
-    
-    Operations operations;
-    operations.Apply(*this, result, publicId);
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+  }
+
+
+  bool StatelessDatabaseOperations::LookupMetadata(std::string& target,
+                                                   const std::string& publicId,
+                                                   ResourceType expectedType,
+                                                   MetadataType type)
+  {
+    FindRequest request(expectedType);
+    request.SetOrthancId(expectedType, publicId);
+    request.SetRetrieveMetadata(true);
+    request.SetRetrieveMetadataRevisions(false);  // No need to retrieve revisions
+
+    FindResponse response;
+    return ExecuteSingleResource(response, request).LookupMetadata(target, expectedType, type);
   }
 
 
@@ -1339,31 +992,13 @@
                                                    ResourceType expectedType,
                                                    MetadataType type)
   {
-    class Operations : public ReadOnlyOperationsT6<bool&, std::string&, int64_t&,
-                                                   const std::string&, ResourceType, MetadataType>
-    {
-    public:
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
-      {
-        ResourceType resourceType;
-        int64_t id;
-        if (!transaction.LookupResource(id, resourceType, tuple.get<3>()) ||
-            resourceType != tuple.get<4>())
-        {
-          throw OrthancException(ErrorCode_UnknownResource);
-        }
-        else
-        {
-          tuple.get<0>() = transaction.LookupMetadata(tuple.get<1>(), tuple.get<2>(), id, tuple.get<5>());
-        }
-      }
-    };
-
-    bool found;
-    Operations operations;
-    operations.Apply(*this, found, target, revision, publicId, expectedType, type);
-    return found;
+    FindRequest request(expectedType);
+    request.SetOrthancId(expectedType, publicId);
+    request.SetRetrieveMetadata(true);
+    request.SetRetrieveMetadataRevisions(true);  // We are asked to retrieve revisions
+
+    FindResponse response;
+    return ExecuteSingleResource(response, request).LookupMetadata(target, revision, expectedType, type);
   }
 
 
@@ -1371,28 +1006,12 @@
                                                              const std::string& publicId,
                                                              ResourceType expectedType)
   {
-    class Operations : public ReadOnlyOperationsT3<std::set<FileContentType>&, const std::string&, ResourceType>
-    {
-    public:
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
-      {
-        ResourceType type;
-        int64_t id;
-        if (!transaction.LookupResource(id, type, tuple.get<1>()) ||
-            tuple.get<2>() != type)
-        {
-          throw OrthancException(ErrorCode_UnknownResource);
-        }
-        else
-        {
-          transaction.ListAvailableAttachments(tuple.get<0>(), id);
-        }
-      }
-    };
-    
-    Operations operations;
-    operations.Apply(*this, target, publicId, expectedType);
+    FindRequest request(expectedType);
+    request.SetOrthancId(expectedType, publicId);
+    request.SetRetrieveAttachments(true);
+
+    FindResponse response;
+    ExecuteSingleResource(response, request).ListAttachments(target);
   }
 
 
@@ -1588,45 +1207,24 @@
            (level == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) ||
            (level == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) ||
            (level == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID));
-    
-    result.clear();
+
+    FindRequest request(level);
 
     DicomTagConstraint c(tag, ConstraintType_Equal, value, true, true);
 
-    DatabaseDicomTagConstraints query;
     bool isIdentical;  // unused
-    query.AddConstraint(c.ConvertToDatabaseConstraint(isIdentical, level, DicomTagType_Identifier));
-
-
-    class Operations : public IReadOnlyOperations
+    request.GetDicomTagConstraints().AddConstraint(c.ConvertToDatabaseConstraint(isIdentical, level, DicomTagType_Identifier));
+
+    FindResponse response;
+    ExecuteFind(response, request);
+
+    result.clear();
+    result.reserve(response.GetSize());
+
+    for (size_t i = 0; i < response.GetSize(); i++)
     {
-    private:
-      std::vector<std::string>&   result_;
-      const DatabaseDicomTagConstraints&  query_;
-      ResourceType                level_;
-      
-    public:
-      Operations(std::vector<std::string>& result,
-                 const DatabaseDicomTagConstraints& query,
-                 ResourceType level) :
-        result_(result),
-        query_(query),
-        level_(level)
-      {
-      }
-
-      virtual void Apply(ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE
-      {
-        // TODO - CANDIDATE FOR "TransactionType_Implicit"
-        std::list<std::string> tmp;
-        std::set<std::string> labels;
-        transaction.ApplyLookupResources(tmp, NULL, query_, level_, labels, LabelsConstraint_Any, 0);
-        CopyListToVector(result_, tmp);
-      }
-    };
-
-    Operations operations(result, query, level);
-    Apply(operations);
+      result.push_back(response.GetResourceByIndex(i).GetIdentifier());
+    }
   }
 
 
@@ -1683,139 +1281,116 @@
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-
-    class Operations : public ReadOnlyOperationsT5<bool&, DicomMap&, const std::string&, ResourceType, ResourceType>
+    FindRequest request(expectedType);
+    request.SetOrthancId(expectedType, publicId);
+    request.SetRetrieveMainDicomTags(true);
+
+    FindResponse response;
+    ExecuteFind(response, request);
+
+    if (response.GetSize() == 0)
     {
-    public:
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
+      return false;
+    }
+    else if (response.GetSize() > 1)
+    {
+      throw OrthancException(ErrorCode_DatabasePlugin);
+    }
+    else
+    {
+      result.Clear();
+      if (expectedType == ResourceType_Study)
       {
-        // Lookup for the requested resource
-        int64_t id;
-        ResourceType type;
-        if (!transaction.LookupResource(id, type, tuple.get<2>()) ||
-            type != tuple.get<3>())
-        {
-          tuple.get<0>() = false;
-        }
-        else if (type == ResourceType_Study)
+        DicomMap tmp;
+        response.GetResourceByIndex(0).GetMainDicomTags(tmp, expectedType);
+
+        switch (levelOfInterest)
         {
-          DicomMap tmp;
-          transaction.GetMainDicomTags(tmp, id);
-
-          switch (tuple.get<4>())
-          {
-            case ResourceType_Patient:
-              tmp.ExtractPatientInformation(tuple.get<1>());
-              tuple.get<0>() = true;
-              break;
-
-            case ResourceType_Study:
-              tmp.ExtractStudyInformation(tuple.get<1>());
-              tuple.get<0>() = true;
-              break;
-
-            default:
-              throw OrthancException(ErrorCode_InternalError);
-          }
+          case ResourceType_Study:
+            tmp.ExtractStudyInformation(result);
+            break;
+
+          case ResourceType_Patient:
+            tmp.ExtractPatientInformation(result);
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
         }
-        else
-        {
-          transaction.GetMainDicomTags(tuple.get<1>(), id);
-          tuple.get<0>() = true;
-        }    
       }
-    };
-
-    result.Clear();
-
-    bool found;
-    Operations operations;
-    operations.Apply(*this, found, result, publicId, expectedType, levelOfInterest);
-    return found;
+      else
+      {
+        assert(expectedType == levelOfInterest);
+        response.GetResourceByIndex(0).GetMainDicomTags(result, expectedType);
+      }
+      return true;
+    }
   }
 
 
   bool StatelessDatabaseOperations::GetAllMainDicomTags(DicomMap& result,
                                                         const std::string& instancePublicId)
   {
-    class Operations : public ReadOnlyOperationsT3<bool&, DicomMap&, const std::string&>
-    {
-    public:
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
-      {
-        // Lookup for the requested resource
-        int64_t instance;
-        ResourceType type;
-        if (!transaction.LookupResource(instance, type, tuple.get<2>()) ||
-            type != ResourceType_Instance)
-        {
-          tuple.get<0>() =  false;
-        }
-        else
-        {
-          DicomMap tmp;
-
-          transaction.GetMainDicomTags(tmp, instance);
-          tuple.get<1>().Merge(tmp);
-
-          int64_t series;
-          if (!transaction.LookupParent(series, instance))
-          {
-            throw OrthancException(ErrorCode_InternalError);
-          }
-
-          tmp.Clear();
-          transaction.GetMainDicomTags(tmp, series);
-          tuple.get<1>().Merge(tmp);
-
-          int64_t study;
-          if (!transaction.LookupParent(study, series))
-          {
-            throw OrthancException(ErrorCode_InternalError);
-          }
-
-          tmp.Clear();
-          transaction.GetMainDicomTags(tmp, study);
-          tuple.get<1>().Merge(tmp);
+    FindRequest request(ResourceType_Instance);
+    request.SetOrthancId(ResourceType_Instance, instancePublicId);
+    request.GetParentSpecification(ResourceType_Study).SetRetrieveMainDicomTags(true);
+    request.GetParentSpecification(ResourceType_Series).SetRetrieveMainDicomTags(true);
+    request.SetRetrieveMainDicomTags(true);
 
 #ifndef NDEBUG
-          {
-            // Sanity test to check that all the main DICOM tags from the
-            // patient level are copied at the study level
-        
-            int64_t patient;
-            if (!transaction.LookupParent(patient, study))
-            {
-              throw OrthancException(ErrorCode_InternalError);
-            }
-
-            tmp.Clear();
-            transaction.GetMainDicomTags(tmp, study);
-
-            std::set<DicomTag> patientTags;
-            tmp.GetTags(patientTags);
-
-            for (std::set<DicomTag>::const_iterator
-                   it = patientTags.begin(); it != patientTags.end(); ++it)
-            {
-              assert(tuple.get<1>().HasTag(*it));
-            }
-          }
+    // For sanity check below
+    request.GetParentSpecification(ResourceType_Patient).SetRetrieveMainDicomTags(true);
 #endif
-      
-          tuple.get<0>() =  true;
+
+    FindResponse response;
+    ExecuteFind(response, request);
+
+    if (response.GetSize() == 0)
+    {
+      return false;
+    }
+    else if (response.GetSize() > 1)
+    {
+      throw OrthancException(ErrorCode_DatabasePlugin);
+    }
+    else
+    {
+      const FindResponse::Resource& resource = response.GetResourceByIndex(0);
+
+      result.Clear();
+
+      DicomMap tmp;
+      resource.GetMainDicomTags(tmp, ResourceType_Instance);
+      result.Merge(tmp);
+
+      tmp.Clear();
+      resource.GetMainDicomTags(tmp, ResourceType_Series);
+      result.Merge(tmp);
+
+      tmp.Clear();
+      resource.GetMainDicomTags(tmp, ResourceType_Study);
+      result.Merge(tmp);
+
+#ifndef NDEBUG
+      {
+        // Sanity test to check that all the main DICOM tags from the
+        // patient level are copied at the study level
+        tmp.Clear();
+        resource.GetMainDicomTags(tmp, ResourceType_Patient);
+
+        std::set<DicomTag> patientTags;
+        tmp.GetTags(patientTags);
+
+        for (std::set<DicomTag>::const_iterator
+               it = patientTags.begin(); it != patientTags.end(); ++it)
+        {
+          assert(result.HasTag(*it));
         }
       }
-    };
-
-    result.Clear();
-    
-    bool found;
-    Operations operations;
-    operations.Apply(*this, found, result, instancePublicId);
-    return found;
+#endif
+
+      return true;
+    }
   }
 
 
@@ -1845,113 +1420,27 @@
                                                  const std::string& publicId,
                                                  ResourceType parentType)
   {
-    class Operations : public ReadOnlyOperationsT4<bool&, std::string&, const std::string&, ResourceType>
-    {
-    public:
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
-      {
-        ResourceType type;
-        int64_t id;
-        if (!transaction.LookupResource(id, type, tuple.get<2>()))
-        {
-          throw OrthancException(ErrorCode_UnknownResource);
-        }
-
-        while (type != tuple.get<3>())
-        {
-          int64_t parentId;
-
-          if (type == ResourceType_Patient ||    // Cannot further go up in hierarchy
-              !transaction.LookupParent(parentId, id))
-          {
-            tuple.get<0>() = false;
-            return;
-          }
-
-          id = parentId;
-          type = GetParentResourceType(type);
-        }
-
-        tuple.get<0>() = true;
-        tuple.get<1>() = transaction.GetPublicId(id);
-      }
-    };
-
-    bool found;
-    Operations operations;
-    operations.Apply(*this, found, target, publicId, parentType);
-    return found;
-  }
-
-
-  void StatelessDatabaseOperations::ApplyLookupResources(std::vector<std::string>& resourcesId,
-                                                         std::vector<std::string>* instancesId,
-                                                         const DatabaseLookup& lookup,
-                                                         ResourceType queryLevel,
-                                                         const std::set<std::string>& labels,
-                                                         LabelsConstraint labelsConstraint,
-                                                         uint32_t limit)
-  {
-    class Operations : public ReadOnlyOperationsT6<bool, const DatabaseDicomTagConstraints&, ResourceType,
-                                                   const std::set<std::string>&, LabelsConstraint, size_t>
+    const ResourceType level = GetChildResourceType(parentType);
+
+    FindRequest request(level);
+    request.SetOrthancId(level, publicId);
+    request.SetRetrieveParentIdentifier(true);
+
+    FindResponse response;
+    ExecuteFind(response, request);
+
+    if (response.GetSize() == 0)
     {
-    private:
-      std::list<std::string>  resourcesList_;
-      std::list<std::string>  instancesList_;
-      
-    public:
-      const std::list<std::string>& GetResourcesList() const
-      {
-        return resourcesList_;
-      }
-
-      const std::list<std::string>& GetInstancesList() const
-      {
-        return instancesList_;
-      }
-
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
-      {
-        // TODO - CANDIDATE FOR "TransactionType_Implicit"
-        if (tuple.get<0>())
-        {
-          transaction.ApplyLookupResources(
-            resourcesList_, &instancesList_, tuple.get<1>(), tuple.get<2>(), tuple.get<3>(), tuple.get<4>(), tuple.get<5>());
-        }
-        else
-        {
-          transaction.ApplyLookupResources(
-            resourcesList_, NULL, tuple.get<1>(), tuple.get<2>(), tuple.get<3>(), tuple.get<4>(), tuple.get<5>());
-        }
-      }
-    };
-
-    if (!labels.empty() &&
-        !db_.GetDatabaseCapabilities().HasLabelsSupport())
+      return false;
+    }
+    else if (response.GetSize() > 1)
     {
-      throw OrthancException(ErrorCode_NotImplemented, "The database backend doesn't support labels");
-    }
-
-    for (std::set<std::string>::const_iterator it = labels.begin(); it != labels.end(); ++it)
-    {
-      ServerToolbox::CheckValidLabel(*it);
+      throw OrthancException(ErrorCode_DatabasePlugin);
     }
-
-    DatabaseDicomTagConstraints normalized;
-
-    assert(mainDicomTagsRegistry_.get() != NULL);
-    mainDicomTagsRegistry_->NormalizeLookup(normalized, lookup, queryLevel);
-
-    Operations operations;
-    operations.Apply(*this, (instancesId != NULL), normalized, queryLevel, labels, labelsConstraint, limit);
-    
-    CopyListToVector(resourcesId, operations.GetResourcesList());
-
-    if (instancesId != NULL)
-    { 
-      CopyListToVector(*instancesId, operations.GetInstancesList());
+    else
+    {
+      target = response.GetResourceByIndex(0).GetParentIdentifier();
+      return true;
     }
   }
 
@@ -3596,28 +3085,12 @@
                                                const std::string& publicId,
                                                ResourceType level)
   {
-    class Operations : public ReadOnlyOperationsT3<std::set<std::string>&, const std::string&, ResourceType>
-    {
-    public:
-      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
-                              const Tuple& tuple) ORTHANC_OVERRIDE
-      {
-        ResourceType type;
-        int64_t id;
-        if (!transaction.LookupResource(id, type, tuple.get<1>()) ||
-            tuple.get<2>() != type)
-        {
-          throw OrthancException(ErrorCode_UnknownResource);
-        }
-        else
-        {
-          transaction.ListLabels(tuple.get<0>(), id);
-        }
-      }
-    };
-
-    Operations operations;
-    operations.Apply(*this, target, publicId, level);
+    FindRequest request(level);
+    request.SetOrthancId(level, publicId);
+    request.SetRetrieveLabels(true);
+
+    FindResponse response;
+    target = ExecuteSingleResource(response, request).GetLabels();
   }
 
 
@@ -3727,6 +3200,46 @@
     return db_.GetDatabaseCapabilities().HasFindSupport();
   }
 
+  void StatelessDatabaseOperations::ExecuteCount(uint64_t& count,
+                                                 const FindRequest& request)
+  {
+    class IntegratedCount : public ReadOnlyOperationsT3<uint64_t&, const FindRequest&,
+                                                       const IDatabaseWrapper::Capabilities&>
+    {
+    public:
+      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
+                              const Tuple& tuple) ORTHANC_OVERRIDE
+      {
+        transaction.ExecuteCount(tuple.get<0>(), tuple.get<1>(), tuple.get<2>());
+      }
+    };
+
+    class Compatibility : public ReadOnlyOperationsT3<uint64_t&, const FindRequest&, const IDatabaseWrapper::Capabilities&>
+    {
+    public:
+      virtual void ApplyTuple(ReadOnlyTransaction& transaction,
+                              const Tuple& tuple) ORTHANC_OVERRIDE
+      {
+        std::list<std::string> identifiers;
+        transaction.ExecuteFind(identifiers, tuple.get<2>(), tuple.get<1>());
+        tuple.get<0>() = identifiers.size();
+      }
+    };
+
+    IDatabaseWrapper::Capabilities capabilities = db_.GetDatabaseCapabilities();
+
+    if (db_.HasIntegratedFind())
+    {
+      IntegratedCount operations;
+      operations.Apply(*this, count, request, capabilities);
+    }
+    else
+    {
+      Compatibility operations;
+      operations.Apply(*this, count, request, capabilities);
+    }
+  }
+
   void StatelessDatabaseOperations::ExecuteFind(FindResponse& response,
                                                 const FindRequest& request)
   {
@@ -3781,15 +3294,25 @@
        **/
       std::list<std::string> identifiers;
 
-      FindStage find;
-      find.Apply(*this, identifiers, capabilities, request);
+      std::string publicId;
+      if (request.IsTrivialFind(publicId))
+      {
+        // This is a trivial case for which no transaction is needed
+        identifiers.push_back(publicId);
+      }
+      else
+      {
+        // Non-trival case, a transaction is needed
+        FindStage find;
+        find.Apply(*this, identifiers, capabilities, request);
+      }
 
       ExpandStage expand;
 
       for (std::list<std::string>::const_iterator it = identifiers.begin(); it != identifiers.end(); ++it)
       {
         /**
-         * Not that the resource might have been deleted (as we are in
+         * Note that the resource might have been deleted (as we are in
          * another transaction). The database engine must ignore such
          * error cases.
          **/
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -39,102 +39,6 @@
   class ParsedDicomFile;
   struct ServerIndexChange;
 
-  class ExpandedResource : public boost::noncopyable
-  {
-  private:
-    std::string                         id_;
-    ResourceType                        level_;
-    DicomMap                            tags_;  // all main tags and main sequences from DB
-
-  public:
-    std::string                         mainDicomTagsSignature_;
-    std::string                         parentId_;
-    std::list<std::string>              childrenIds_;
-    std::map<MetadataType, std::string> metadata_;
-    std::string                         anonymizedFrom_;
-    std::string                         modifiedFrom_;
-    std::string                         lastUpdate_;
-    std::set<DicomTag>                  missingRequestedTags_;
-
-    // for patients/studies/series
-    bool                                isStable_;
-
-    // for series only
-    int                                 expectedNumberOfInstances_;
-    std::string                         status_;
-
-    // for instances only
-    size_t                              fileSize_;
-    std::string                         fileUuid_;
-    int                                 indexInSeries_;
-
-    // New in Orthanc 1.12.0
-    std::set<std::string>               labels_;
-
-  public:
-    // TODO - Cleanup
-    ExpandedResource() :
-      level_(ResourceType_Instance),
-      isStable_(false),
-      expectedNumberOfInstances_(0),
-      fileSize_(0),
-      indexInSeries_(0)
-    {
-    }
-
-    void SetResource(ResourceType level,
-                     const std::string& id)
-    {
-      level_ = level;
-      id_ = id;
-    }
-
-    const std::string& GetPublicId() const
-    {
-      return id_;
-    }
-
-    ResourceType GetLevel() const
-    {
-      return level_;
-    }
-
-    DicomMap& GetMainDicomTags()
-    {
-      return tags_;
-    }
-
-    const DicomMap& GetMainDicomTags() const
-    {
-      return tags_;
-    }
-  };
-
-  enum ExpandResourceFlags
-  {
-    ExpandResourceFlags_None                    = 0,
-    // used to fetch from DB and for output
-    ExpandResourceFlags_IncludeMetadata         = (1 << 0),
-    ExpandResourceFlags_IncludeChildren         = (1 << 1),
-    ExpandResourceFlags_IncludeMainDicomTags    = (1 << 2),
-    ExpandResourceFlags_IncludeLabels           = (1 << 3),
-
-    // only used for output
-    ExpandResourceFlags_IncludeAllMetadata      = (1 << 4),  // new in Orthanc 1.12.4
-    ExpandResourceFlags_IncludeIsStable         = (1 << 5),  // new in Orthanc 1.12.4
-
-    ExpandResourceFlags_DefaultExtract = (ExpandResourceFlags_IncludeMetadata |
-                                          ExpandResourceFlags_IncludeChildren |
-                                          ExpandResourceFlags_IncludeMainDicomTags |
-                                          ExpandResourceFlags_IncludeLabels),
-
-    ExpandResourceFlags_DefaultOutput = (ExpandResourceFlags_IncludeMetadata |
-                                         ExpandResourceFlags_IncludeChildren |
-                                         ExpandResourceFlags_IncludeMainDicomTags |
-                                         ExpandResourceFlags_IncludeLabels |
-                                         ExpandResourceFlags_IncludeIsStable)
-  };
-
   class StatelessDatabaseOperations : public boost::noncopyable
   {
   public:
@@ -219,38 +123,12 @@
        * Read-only methods from "IDatabaseWrapper"
        **/
 
-      void ApplyLookupResources(std::list<std::string>& resourcesId,
-                                std::list<std::string>* instancesId, // Can be NULL if not needed
-                                const DatabaseDicomTagConstraints& lookup,
-                                ResourceType queryLevel,
-                                const std::set<std::string>& labels,  // New in Orthanc 1.12.0
-                                LabelsConstraint labelsConstraint,    // New in Orthanc 1.12.0
-                                uint32_t limit)
-      {
-        return transaction_.ApplyLookupResources(resourcesId, instancesId, lookup, queryLevel,
-                                                 labels, labelsConstraint, limit);
-      }
-
       void GetAllMetadata(std::map<MetadataType, std::string>& target,
                           int64_t id)
       {
         transaction_.GetAllMetadata(target, id);
       }
 
-      void GetAllPublicIds(std::list<std::string>& target,
-                           ResourceType resourceType)
-      {
-        return transaction_.GetAllPublicIds(target, resourceType);
-      }
-
-      void GetAllPublicIds(std::list<std::string>& target,
-                           ResourceType resourceType,
-                           size_t since,
-                           uint32_t limit)
-      {
-        return transaction_.GetAllPublicIds(target, resourceType, since, limit);
-      }  
-
       void GetChanges(std::list<ServerIndexChange>& target /*out*/,
                       bool& done /*out*/,
                       int64_t since,
@@ -275,12 +153,6 @@
         transaction_.GetChildrenInternalId(target, id);
       }
 
-      void GetChildrenPublicId(std::list<std::string>& target,
-                               int64_t id)
-      {
-        transaction_.GetChildrenPublicId(target, id);
-      }
-
       void GetExportedResources(std::list<ExportedResource>& target /*out*/,
                                 bool& done /*out*/,
                                 int64_t since,
@@ -381,20 +253,6 @@
       {
         return transaction_.LookupResource(id, type, publicId);
       }
-      
-      bool LookupResourceAndParent(int64_t& id,
-                                   ResourceType& type,
-                                   std::string& parentPublicId,
-                                   const std::string& publicId)
-      {
-        return transaction_.LookupResourceAndParent(id, type, parentPublicId, publicId);
-      }
-
-      void ListLabels(std::set<std::string>& target,
-                      int64_t id)
-      {
-        transaction_.ListLabels(target, id);
-      }
 
       void ListAllLabels(std::set<std::string>& target)
       {
@@ -407,6 +265,13 @@
       bool HasReachedMaxPatientCount(unsigned int maximumPatientCount,
                                      const std::string& patientId);
 
+      void ExecuteCount(uint64_t& count,
+                        const FindRequest& request,
+                        const IDatabaseWrapper::Capabilities& capabilities)
+      {
+        transaction_.ExecuteCount(count, request, capabilities);
+      }
+
       void ExecuteFind(FindResponse& response,
                        const FindRequest& request,
                        const IDatabaseWrapper::Capabilities& capabilities)
@@ -603,6 +468,9 @@
     void ApplyInternal(IReadOnlyOperations* readOperations,
                        IReadWriteOperations* writeOperations);
 
+    const FindResponse::Resource &ExecuteSingleResource(FindResponse &response,
+                                                        const FindRequest &request);
+
   protected:
     void StandaloneRecycling(MaxStorageMode maximumStorageMode,
                              uint64_t maximumStorageSize,
@@ -641,12 +509,6 @@
   
     void Apply(IReadWriteOperations& operations);
 
-    bool ExpandResource(ExpandedResource& target,
-                        const std::string& publicId,
-                        ResourceType level,
-                        const std::set<DicomTag>& requestedTags,
-                        ExpandResourceFlags expandFlags);
-
     void GetAllMetadata(std::map<MetadataType, std::string>& target,
                         const std::string& publicId,
                         ResourceType level);
@@ -654,11 +516,6 @@
     void GetAllUuids(std::list<std::string>& target,
                      ResourceType resourceType);
 
-    void GetAllUuids(std::list<std::string>& target,
-                     ResourceType resourceType,
-                     size_t since,
-                     uint32_t limit);
-
     void GetGlobalStatistics(/* out */ uint64_t& diskSize,
                              /* out */ uint64_t& uncompressedSize,
                              /* out */ uint64_t& countPatients, 
@@ -668,7 +525,8 @@
 
     bool LookupAttachment(FileInfo& attachment,
                           int64_t& revision,
-                          const std::string& instancePublicId,
+                          ResourceType level,
+                          const std::string& publicId,
                           FileContentType contentType);
 
     void GetChanges(Json::Value& target,
@@ -696,12 +554,23 @@
     bool IsProtectedPatient(const std::string& publicId);
 
     void GetChildren(std::list<std::string>& result,
+                     ResourceType level,
                      const std::string& publicId);
 
+    // Always prefer this flavor, which is more efficient than the flavor without "level"
+    void GetChildInstances(std::list<std::string>& result,
+                           const std::string& publicId,
+                           ResourceType level);
+
     void GetChildInstances(std::list<std::string>& result,
                            const std::string& publicId);
 
     bool LookupMetadata(std::string& target,
+                        const std::string& publicId,
+                        ResourceType expectedType,
+                        MetadataType type);
+
+    bool LookupMetadata(std::string& target,
                         int64_t& revision,
                         const std::string& publicId,
                         ResourceType expectedType,
@@ -742,7 +611,7 @@
                           ResourceType expectedType,
                           ResourceType levelOfInterest);
 
-    // Only applicable at the instance level
+    // Only applicable at the instance level, retrieves tags from patient/study/series levels
     bool GetAllMainDicomTags(DicomMap& result,
                              const std::string& instancePublicId);
 
@@ -753,14 +622,6 @@
                       const std::string& publicId,
                       ResourceType parentType);
 
-    void ApplyLookupResources(std::vector<std::string>& resourcesId,
-                              std::vector<std::string>* instancesId,  // Can be NULL if not needed
-                              const DatabaseLookup& lookup,
-                              ResourceType queryLevel,
-                              const std::set<std::string>& labels,
-                              LabelsConstraint labelsConstraint,
-                              uint32_t limit);
-
     bool DeleteResource(Json::Value& remainingAncestor /* out */,
                         const std::string& uuid,
                         ResourceType expectedType);
@@ -860,5 +721,8 @@
 
     void ExecuteFind(FindResponse& response,
                      const FindRequest& request);
+
+    void ExecuteCount(uint64_t& count,
+                      const FindRequest& request);
   };
 }
--- a/OrthancServer/Sources/Database/Upgrade3To4.sql	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Upgrade3To4.sql	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 -- Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 -- Department, University Hospital of Liege, Belgium
 -- Copyright (C) 2017-2023 Osimis S.A., Belgium
--- Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
--- Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/Upgrade4To5.sql	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/Upgrade4To5.sql	Thu Jan 30 17:41:33 2025 +0100
@@ -2,8 +2,8 @@
 -- Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 -- Department, University Hospital of Liege, Belgium
 -- Copyright (C) 2017-2023 Osimis S.A., Belgium
--- Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
--- Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/VoidDatabaseListener.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/VoidDatabaseListener.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Database/VoidDatabaseListener.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Database/VoidDatabaseListener.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/DicomInstanceOrigin.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/DicomInstanceOrigin.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/DicomInstanceOrigin.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/DicomInstanceOrigin.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/DicomInstanceToStore.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/DicomInstanceToStore.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/DicomInstanceToStore.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/DicomInstanceToStore.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/EmbeddedResourceHttpHandler.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/EmbeddedResourceHttpHandler.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/EmbeddedResourceHttpHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/EmbeddedResourceHttpHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ExportedResource.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ExportedResource.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ExportedResource.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ExportedResource.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/IDicomImageDecoder.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/IDicomImageDecoder.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/IServerListener.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/IServerListener.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/JobEvent.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/JobEvent.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/LuaScripting.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/LuaScripting.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/LuaScripting.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/LuaScripting.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/OrthancConfiguration.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancConfiguration.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -1169,6 +1169,14 @@
         {
           warning = Warnings_005_RequestingTagFromLowerResourceLevel;
         }
+        else if (name == "W006_RequestingTagFromMetaHeader")
+        {
+          warning = Warnings_006_RequestingTagFromMetaHeader;
+        }
+        else if (name == "W007_MissingRequestedTagsNotReadFromDisk")
+        {
+          warning = Warnings_007_MissingRequestedTagsNotReadFromDisk;
+        }
         else
         {
           throw OrthancException(ErrorCode_BadFileFormat, name + " is not recognized as a valid warning name");
--- a/OrthancServer/Sources/OrthancConfiguration.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancConfiguration.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/OrthancFindRequestHandler.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancFindRequestHandler.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -30,7 +30,7 @@
 #include "../../OrthancFramework/Sources/Lua/LuaFunctionCall.h"
 #include "../../OrthancFramework/Sources/MetricsRegistry.h"
 #include "OrthancConfiguration.h"
-#include "ResourceFinder.cpp"
+#include "ResourceFinder.h"
 #include "Search/DatabaseLookup.h"
 #include "ServerContext.h"
 #include "ServerToolbox.h"
@@ -82,102 +82,6 @@
   }
 
 
-  static void AddAnswer(DicomFindAnswers& answers,
-                        ServerContext& context,
-                        const std::string& publicId,
-                        const std::string& instanceId,
-                        const DicomMap& mainDicomTags,
-                        const Json::Value* dicomAsJson,
-                        ResourceType level,
-                        const DicomArray& query,
-                        const std::list<DicomTag>& sequencesToReturn,
-                        const std::string& defaultPrivateCreator,
-                        const std::map<uint16_t, std::string>& privateCreators,
-                        const std::string& retrieveAet,
-                        bool allowStorageAccess)
-  {
-    ExpandedResource resource;
-    std::set<DicomTag> requestedTags;
-    
-    query.GetTags(requestedTags);
-    requestedTags.erase(DICOM_TAG_QUERY_RETRIEVE_LEVEL); // this is not part of the answer
-
-    // reuse ExpandResource to get missing tags and computed tags (ModalitiesInStudy ...).  This code is therefore shared between C-Find, tools/find, list-resources and QIDO-RS
-    context.ExpandResource(resource, publicId, mainDicomTags, instanceId, dicomAsJson,
-                           level, requestedTags, ExpandResourceFlags_IncludeMainDicomTags, allowStorageAccess);
-
-    DicomMap result;
-
-    /**
-     * Add the mandatory "Retrieve AE Title (0008,0054)" tag, which was missing in Orthanc <= 1.7.2.
-     * http://dicom.nema.org/medical/dicom/current/output/html/part04.html#sect_C.4.1.1.3.2
-     * https://groups.google.com/g/orthanc-users/c/-7zNTKR_PMU/m/kfjwzEVNAgAJ
-     **/
-    result.SetValue(DICOM_TAG_RETRIEVE_AE_TITLE, retrieveAet, false /* not binary */);
-
-    for (size_t i = 0; i < query.GetSize(); i++)
-    {
-      if (query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL)
-      {
-        // Fix issue 30 on Google Code (QR response missing "Query/Retrieve Level" (008,0052))
-        result.SetValue(query.GetElement(i).GetTag(), query.GetElement(i).GetValue());
-      }
-      else if (query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET)
-      {
-        // Do not include the encoding, this is handled by class ParsedDicomFile
-      }
-      else
-      {
-        const DicomTag& tag = query.GetElement(i).GetTag();
-        const DicomValue* value = resource.GetMainDicomTags().TestAndGetValue(tag);
-
-        if (value != NULL &&
-            !value->IsNull() &&
-            !value->IsBinary())
-        {
-          result.SetValue(tag, value->GetContent(), false);
-        }
-        else
-        {
-          result.SetValue(tag, "", false);
-        }
-      }
-    }
-
-    if (result.GetSize() == 0 &&
-        sequencesToReturn.empty())
-    {
-      CLOG(WARNING, DICOM) << "The C-FIND request does not return any DICOM tag";
-    }
-    else if (sequencesToReturn.empty())
-    {
-      answers.Add(result);
-    }
-    else if (dicomAsJson == NULL)
-    {
-      CLOG(WARNING, DICOM) << "C-FIND query requesting a sequence, but reading JSON from disk is disabled";
-      answers.Add(result);
-    }
-    else
-    {
-      ParsedDicomFile dicom(result, GetDefaultDicomEncoding(),
-                            true /* be permissive, cf. issue #136 */, defaultPrivateCreator, privateCreators);
-
-      for (std::list<DicomTag>::const_iterator tag = sequencesToReturn.begin();
-           tag != sequencesToReturn.end(); ++tag)
-      {
-        assert(dicomAsJson != NULL);
-        const Json::Value& source = (*dicomAsJson) [tag->Format()];
-
-        CopySequence(dicom, *tag, source, defaultPrivateCreator, privateCreators);
-      }
-
-      answers.Add(dicom);
-    }
-  }
-
-
-
   bool OrthancFindRequestHandler::FilterQueryTag(std::string& value /* can be modified */,
                                                  ResourceType level,
                                                  const DicomTag& tag,
@@ -248,88 +152,6 @@
   }
 
 
-  class OrthancFindRequestHandler::LookupVisitor : public ServerContext::ILookupVisitor
-  {
-  private:
-    DicomFindAnswers&           answers_;
-    ServerContext&              context_;
-    ResourceType                level_;
-    const DicomMap&             query_;
-    DicomArray                  queryAsArray_;
-    const std::list<DicomTag>&  sequencesToReturn_;
-    std::string                 defaultPrivateCreator_;       // the private creator to use if the group is not defined in the query itself
-    const std::map<uint16_t, std::string>& privateCreators_;  // the private creators defined in the query itself
-    std::string                 retrieveAet_;
-    FindStorageAccessMode       findStorageAccessMode_;
-
-  public:
-    LookupVisitor(DicomFindAnswers&  answers,
-                  ServerContext& context,
-                  ResourceType level,
-                  const DicomMap& query,
-                  const std::list<DicomTag>& sequencesToReturn,
-                  const std::map<uint16_t, std::string>& privateCreators,
-                  FindStorageAccessMode findStorageAccessMode) :
-      answers_(answers),
-      context_(context),
-      level_(level),
-      query_(query),
-      queryAsArray_(query),
-      sequencesToReturn_(sequencesToReturn),
-      privateCreators_(privateCreators),
-      findStorageAccessMode_(findStorageAccessMode)
-    {
-      answers_.SetComplete(false);
-
-      {
-        OrthancConfiguration::ReaderLock lock;
-        defaultPrivateCreator_ = lock.GetConfiguration().GetDefaultPrivateCreator();
-        retrieveAet_ = lock.GetConfiguration().GetOrthancAET();
-      }
-    }
-
-    virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
-    {
-      // Ask the "DICOM-as-JSON" attachment only if sequences are to
-      // be returned OR if "query_" contains non-main DICOM tags!
-
-      DicomMap withoutSpecialTags;
-      withoutSpecialTags.Assign(query_);
-
-      // Check out "ComputeCounters()"
-      withoutSpecialTags.Remove(DICOM_TAG_MODALITIES_IN_STUDY);
-      withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES);
-      withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES);
-      withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES);
-      withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES);
-      withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES);
-      withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES);
-      withoutSpecialTags.Remove(DICOM_TAG_SOP_CLASSES_IN_STUDY);
-
-      // Check out "AddAnswer()"
-      withoutSpecialTags.Remove(DICOM_TAG_SPECIFIC_CHARACTER_SET);
-      withoutSpecialTags.Remove(DICOM_TAG_QUERY_RETRIEVE_LEVEL);
-      
-      return (!sequencesToReturn_.empty() ||
-              !withoutSpecialTags.HasOnlyMainDicomTags());
-    }
-      
-    virtual void MarkAsComplete() ORTHANC_OVERRIDE
-    {
-      answers_.SetComplete(true);
-    }
-
-    virtual void Visit(const std::string& publicId,
-                       const std::string& instanceId,
-                       const DicomMap& mainDicomTags,
-                       const Json::Value* dicomAsJson) ORTHANC_OVERRIDE
-    {
-      AddAnswer(answers_, context_, publicId, instanceId, mainDicomTags, dicomAsJson, level_, queryAsArray_, sequencesToReturn_,
-                defaultPrivateCreator_, privateCreators_, retrieveAet_, IsStorageAccessAllowedForAnswers(findStorageAccessMode_));
-    }
-  };
-
-
   namespace
   {
     class LookupVisitorV2 : public ResourceFinder::IVisitor
@@ -622,31 +444,12 @@
      * Run the query.
      **/
 
-    size_t limit = (level == ResourceType_Instance) ? maxInstances_ : maxResults_;
-
-
-    if (true)
-    {
-      /**
-       * EXPERIMENTAL VERSION
-       **/
-
-      ResourceFinder finder(level, ResponseContentFlags_ID);
-      finder.SetDatabaseLookup(lookup);
-      finder.AddRequestedTags(requestedTags);
+    ResourceFinder finder(level, ResponseContentFlags_ID, context_.GetFindStorageAccessMode(), context_.GetIndex().HasFindSupport());
+    finder.SetDatabaseLookup(lookup);
+    finder.AddRequestedTags(requestedTags);
 
-      LookupVisitorV2 visitor(answers, *filteredInput, sequencesToReturn, privateCreators);
-      finder.Execute(visitor, context_);
-    }
-    else
-    {
-      /**
-       * VERSION IN ORTHANC <= 1.12.4
-       **/
-
-      LookupVisitor visitor(answers, context_, level, *filteredInput, sequencesToReturn, privateCreators, context_.GetFindStorageAccessMode());
-      context_.Apply(visitor, lookup, level, 0 /* "since" is not relevant to C-FIND */, limit);
-    }
+    LookupVisitorV2 visitor(answers, *filteredInput, sequencesToReturn, privateCreators);
+    finder.Execute(visitor, context_);
   }
 
 
--- a/OrthancServer/Sources/OrthancFindRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancFindRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/OrthancGetRequestHandler.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancGetRequestHandler.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -165,6 +165,7 @@
      * 2. Select the preferred transfer syntaxes, which corresponds to
      * the source transfer syntax, plus all the uncompressed transfer
      * syntaxes if transcoding is enabled.
+     * This way, we minimize the transcoding on our side.
      **/
     
     std::list<DicomTransferSyntax> preferred;
@@ -208,7 +209,16 @@
       }
     }
 
-    // No preferred syntax was accepted
+    // No preferred syntax was accepted but, if a PC has been accepted, it means that we have accepted a TS.
+    // This maybe means that we need to transcode twice on our side (from a compressed format to another compressed format).
+    if (allowTranscoding && accepted.size() >  0)
+    {
+      Accepted::const_iterator it = accepted.begin();
+      selectedPresentationId = it->second;
+      selectedSyntax = it->first;
+      return true;
+    }
+
     return false;
   }                                                           
 
--- a/OrthancServer/Sources/OrthancGetRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancGetRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/OrthancHttpHandler.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancHttpHandler.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/OrthancHttpHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancHttpHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/OrthancInitialization.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancInitialization.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/OrthancInitialization.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancInitialization.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/OrthancMoveRequestHandler.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancMoveRequestHandler.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/OrthancMoveRequestHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancMoveRequestHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -807,7 +807,7 @@
       {
         // Retrieve all the instances of the parent resource
         std::list<std::string>  siblingInstances;
-        context.GetIndex().GetChildInstances(siblingInstances, parent);
+        context.GetIndex().GetChildInstances(siblingInstances, parent, parentType);
 
         if (siblingInstances.empty())
 	{
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -501,11 +501,15 @@
   static const std::string GET_SHORT = "short";
   static const std::string GET_REQUESTED_TAGS_OLD = "requestedTags";  // This was the only option in Orthanc <= 1.12.3
   static const std::string GET_REQUESTED_TAGS = "requested-tags";
+  static const std::string GET_RESPONSE_CONTENT = "response-content";
+  static const std::string GET_EXPAND = "expand";
 
   static const std::string POST_SIMPLIFY = "Simplify";
   static const std::string POST_FULL = "Full";
   static const std::string POST_SHORT = "Short";
   static const std::string POST_REQUESTED_TAGS = "RequestedTags";
+  static const std::string POST_RESPONSE_CONTENT = "ResponseContent";
+  static const std::string POST_EXPAND = "Expand";
 
   static const std::string DOCUMENT_SIMPLIFY =
     "report the DICOM tags in human-readable format (using the symbolic name of the tags)";
@@ -634,19 +638,100 @@
       }
       catch (OrthancException& ex)
       {
-        throw OrthancException(ErrorCode_BadRequest, std::string("Invalid requestedTags argument: ") + ex.What() + " " + ex.GetDetails());
+        throw OrthancException(ErrorCode_BadRequest, std::string("Invalid requested-tags argument: ") + ex.What() + " " + ex.GetDetails());
       }
     }
   }
 
-  void OrthancRestApi::DocumentRequestedTags(RestApiGetCall& call)
+  void OrthancRestApi::DocumentRequestedTags(RestApiCall& call)
   {
+    if (call.GetMethod() == HttpMethod_Get)
+    {
       call.GetDocumentation().SetHttpGetArgument(GET_REQUESTED_TAGS, RestApiCallDocumentation::Type_String,
                           "If present, list the DICOM Tags you want to list in the response.  This argument is a semi-column separated list "
                           "of DICOM Tags identifiers; e.g: '" + GET_REQUESTED_TAGS + "=0010,0010;PatientBirthDate'.  "
                           "The tags requested tags are returned in the 'RequestedTags' field in the response.  "
                           "Note that, if you are requesting tags that are not listed in the Main Dicom Tags stored in DB, building the response "
-                          "might be slow since Orthanc will need to access the DICOM files.  If not specified, Orthanc will return ", false);
+                          "might be slow since Orthanc will need to access the DICOM files.  If not specified, Orthanc will return "
+                          "all Main Dicom Tags to keep backward compatibility with Orthanc prior to 1.11.0.", false);
+    }
+    else if (call.GetMethod() == HttpMethod_Post)
+    {
+      call.GetDocumentation().SetRequestField(POST_REQUESTED_TAGS, RestApiCallDocumentation::Type_JsonListOfStrings,
+                            "A list of DICOM tags to include in the response (applicable only if \"Expand\" is set to true).  "
+                            "The tags requested tags are returned in the 'RequestedTags' field in the response.  "
+                            "Note that, if you are requesting tags that are not listed in the Main Dicom Tags stored in DB, building the response "
+                            "might be slow since Orthanc will need to access the DICOM files.  If not specified, Orthanc will return "
+                            "all Main Dicom Tags to keep backward compatibility with Orthanc prior to 1.11.0.", false);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
   }
 
+  void OrthancRestApi::GetResponseContentAndExpand(ResponseContentFlags& responseContent,
+                                                   const RestApiGetCall& call)
+  {
+    if (call.HasArgument(GET_RESPONSE_CONTENT))
+    {
+      std::string s = call.GetArgument(GET_RESPONSE_CONTENT, "");
+      responseContent = ResponseContentFlags_Default;
+
+      if (!s.empty())
+      {
+        std::set<std::string> splitResponseContent;
+        Toolbox::SplitString(splitResponseContent, s, ';');
+
+        for (std::set<std::string>::const_iterator it = splitResponseContent.begin(); it != splitResponseContent.end(); ++it)
+        {
+          responseContent = static_cast<ResponseContentFlags>(static_cast<uint32_t>(responseContent) | StringToResponseContent(*it));
+        }
+      }
+    }
+    else if (call.HasArgument(GET_EXPAND) && call.GetBooleanArgument("expand", true))
+    {
+      responseContent = ResponseContentFlags_ExpandTrue;
+    }
+    else
+    {
+      responseContent = ResponseContentFlags_ID;
+    }
+  }
+
+  void OrthancRestApi::DocumentResponseContentAndExpand(RestApiCall& call)
+  {
+    if (call.GetMethod() == HttpMethod_Get)
+    {
+      call.GetDocumentation().SetHttpGetArgument(GET_RESPONSE_CONTENT, RestApiCallDocumentation::Type_String,
+                            "Defines the content of response for each returned resource.  Allowed values are `MainDicomTags`, "
+                            "`Metadata`, `Children`, `Parent`, `Labels`, `Status`, `IsStable`, `Attachments`.  If not specified, Orthanc "
+                            "will return `MainDicomTags`, `Metadata`, `Children`, `Parent`, `Labels`, `Status`, `IsStable`."
+                            "e.g: '" + GET_RESPONSE_CONTENT + "=MainDicomTags;Children "
+                            "(new in Orthanc 1.12.5 - overrides `expand`)", false);
+
+      call.GetDocumentation().SetHttpGetArgument(GET_EXPAND, RestApiCallDocumentation::Type_String,
+                            "If present, retrieve detailed information about the individual resources, not only their Orthanc identifiers", false);
+
+    }
+    else if (call.GetMethod() == HttpMethod_Post)
+    {
+      call.GetDocumentation().SetRequestField(POST_RESPONSE_CONTENT, RestApiCallDocumentation::Type_JsonListOfStrings,
+                            "Defines the content of response for each returned resource. (this field, if present, overrides the \"Expand\" field).  "
+                            "Allowed values are `MainDicomTags`, "
+                            "`Metadata`, `Children`, `Parent`, `Labels`, `Status`, `IsStable`, `Attachments`.  If not specified, Orthanc "
+                            "will return `MainDicomTags`, `Metadata`, `Children`, `Parent`, `Labels`, `Status`, `IsStable`."
+                            "(new in Orthanc 1.12.5)", false);
+
+      call.GetDocumentation().SetRequestField(POST_EXPAND, RestApiCallDocumentation::Type_Boolean,
+                            "If set to \"true\", retrieve detailed information about the individual resources, not only their Orthanc identifiers", false);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+  }
+
+
 }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -157,6 +157,11 @@
     static void GetRequestedTags(std::set<DicomTag>& requestedTags,
                                  const RestApiGetCall& call);
 
-    static void DocumentRequestedTags(RestApiGetCall& call);
+    static void DocumentRequestedTags(RestApiCall& call);
+
+    static void GetResponseContentAndExpand(ResponseContentFlags& responseContent,
+                                            const RestApiGetCall& call);
+
+    static void DocumentResponseContentAndExpand(RestApiCall& call);
   };
 }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -73,7 +73,7 @@
         .SetHttpGetArgument("limit", RestApiCallDocumentation::Type_Number, "Limit the number of results", false)
         .SetHttpGetArgument("since", RestApiCallDocumentation::Type_Number, "Show only the resources since the provided index excluded", false)
         .SetHttpGetArgument("to", RestApiCallDocumentation::Type_Number, "Show only the resources till the provided index included (only available if your DB backend supports ExtendedChanges)", false)
-        .SetHttpGetArgument("type", RestApiCallDocumentation::Type_String, "Show only the changes of the provided type (only available if your DB backend supports ExtendedChanges)", false)
+        .SetHttpGetArgument("type", RestApiCallDocumentation::Type_String, "Show only the changes of the provided type (only available if your DB backend supports ExtendedChanges).  Multiple values can be provided and must be separated by a ';'.", false)
         .AddAnswerType(MimeType_Json, "The list of changes")
         .SetAnswerField("Changes", RestApiCallDocumentation::Type_JsonListOfObjects, "The individual changes")
         .SetAnswerField("Done", RestApiCallDocumentation::Type_Boolean,
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -36,6 +36,7 @@
 #include "../ServerContext.h"
 #include "../ServerJobs/DicomModalityStoreJob.h"
 #include "../ServerJobs/DicomMoveScuJob.h"
+#include "../ServerJobs/DicomGetScuJob.h"
 #include "../ServerJobs/OrthancPeerStoreJob.h"
 #include "../ServerToolbox.h"
 #include "../StorageCommitmentReports.h"
@@ -56,6 +57,7 @@
   static const char* const KEY_CHECK_FIND = "CheckFind";
   static const char* const SOP_CLASS_UID = "SOPClassUID";
   static const char* const SOP_INSTANCE_UID = "SOPInstanceUID";
+  static const char* const KEY_RETRIEVE_METHOD = "RetrieveMethod";
   
   static RemoteModalityParameters MyGetModalityUsingSymbolicName(const std::string& name)
   {
@@ -155,24 +157,31 @@
                           const DicomAssociationParameters& parameters,
                           const Json::Value& body)
   {
-    DicomControlUserConnection connection(parameters);
-
+    bool checkFind = false;
+    
+    if (body.type() == Json::objectValue &&
+        body.isMember(KEY_CHECK_FIND))
+    {
+      checkFind = SerializationToolbox::ReadBoolean(body, KEY_CHECK_FIND);
+    }
+    else
+    {
+      OrthancConfiguration::ReaderLock lock;
+      checkFind = lock.GetConfiguration().GetBooleanParameter("DicomEchoChecksFind", false);
+    }
+
+    ScuOperationFlags operations = ScuOperationFlags_Echo;
+    
+    if (checkFind)
+    {
+      operations = static_cast<ScuOperationFlags>(operations | ScuOperationFlags_Find);
+    }
+
+    DicomControlUserConnection connection(parameters, operations);
     if (connection.Echo())
     {
-      bool find = false;
-      
-      if (body.type() == Json::objectValue &&
-          body.isMember(KEY_CHECK_FIND))
-      {
-        find = SerializationToolbox::ReadBoolean(body, KEY_CHECK_FIND);
-      }
-      else
-      {
-        OrthancConfiguration::ReaderLock lock;
-        find = lock.GetConfiguration().GetBooleanParameter("DicomEchoChecksFind", false);
-      }
-
-      if (find)
+
+      if (checkFind)
       {
         // Issue a C-FIND request at the study level about a random Study Instance UID
         const std::string studyInstanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study);
@@ -385,7 +394,7 @@
     DicomFindAnswers answers(false);
 
     {
-      DicomControlUserConnection connection(GetAssociationParameters(call));
+      DicomControlUserConnection connection(GetAssociationParameters(call), ScuOperationFlags_FindPatient);
       FindPatient(answers, connection, fields);
     }
 
@@ -428,7 +437,7 @@
     DicomFindAnswers answers(false);
 
     {
-      DicomControlUserConnection connection(GetAssociationParameters(call));
+      DicomControlUserConnection connection(GetAssociationParameters(call), ScuOperationFlags_FindStudy);
       FindStudy(answers, connection, fields);
     }
 
@@ -472,7 +481,7 @@
     DicomFindAnswers answers(false);
 
     {
-      DicomControlUserConnection connection(GetAssociationParameters(call));
+      DicomControlUserConnection connection(GetAssociationParameters(call), ScuOperationFlags_FindStudy);
       FindSeries(answers, connection, fields);
     }
 
@@ -517,7 +526,7 @@
     DicomFindAnswers answers(false);
 
     {
-      DicomControlUserConnection connection(GetAssociationParameters(call));
+      DicomControlUserConnection connection(GetAssociationParameters(call), ScuOperationFlags_FindStudy);
       FindInstance(answers, connection, fields);
     }
 
@@ -566,7 +575,7 @@
       return;
     }
  
-    DicomControlUserConnection connection(GetAssociationParameters(call));
+    DicomControlUserConnection connection(GetAssociationParameters(call), ScuOperationFlags_Find);
     
     DicomFindAnswers patients(false);
     FindPatient(patients, connection, m);
@@ -914,12 +923,25 @@
 
     std::string targetAet;
     int timeout = -1;
-    
+
+    QueryAccessor query(call);
+
+    RetrieveMethod retrieveMethod = query.GetHandler().GetRemoteModality().GetRetrieveMethod();
+
     Json::Value body;
     if (call.ParseJsonRequest(body))
     {
+      OrthancConfiguration::ReaderLock lock;
+
       targetAet = Toolbox::GetJsonStringField(body, KEY_TARGET_AET, context.GetDefaultLocalApplicationEntityTitle());
       timeout = Toolbox::GetJsonIntegerField(body, KEY_TIMEOUT, -1);
+      
+      std::string strRetrieveMethod = SerializationToolbox::ReadString(body, KEY_RETRIEVE_METHOD, "");
+      
+      if (!strRetrieveMethod.empty())
+      {
+        retrieveMethod = StringToRetrieveMethod(strRetrieveMethod);
+      }
     }
     else
     {
@@ -934,45 +956,67 @@
       }
     }
     
-    std::unique_ptr<DicomMoveScuJob> job(new DicomMoveScuJob(context));
-    job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short));
-    
+    if (retrieveMethod == RetrieveMethod_SystemDefault)
+    {
+      retrieveMethod = context.GetDefaultDicomRetrieveMethod();
+    }
+
+    std::unique_ptr<DicomRetrieveScuBaseJob> job;
+
+
+    switch (retrieveMethod)
     {
-      QueryAccessor query(call);
-      job->SetTargetAet(targetAet);
-      job->SetLocalAet(query.GetHandler().GetLocalAet());
-      job->SetRemoteModality(query.GetHandler().GetRemoteModality());
-
-      if (timeout >= 0)
+      case RetrieveMethod_Move:
       {
-        // New in Orthanc 1.7.0
-        job->SetTimeout(static_cast<uint32_t>(timeout));
-      }
-      else if (query.GetHandler().HasTimeout())
+        job.reset(new DicomMoveScuJob(context));
+        (dynamic_cast<DicomMoveScuJob*>(job.get()))->SetTargetAet(targetAet);
+
+        LOG(WARNING) << "Driving C-Move SCU on remote modality "
+                    << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle()
+                    << " to target modality " << targetAet;
+      }; break;
+      case RetrieveMethod_Get:
       {
-        // New in Orthanc 1.9.1
-        job->SetTimeout(query.GetHandler().GetTimeout());
-      }
-
-      LOG(WARNING) << "Driving C-Move SCU on remote modality "
-                   << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle()
-                   << " to target modality " << targetAet;
-
-      if (allAnswers)
+        job.reset(new DicomGetScuJob(context));
+
+        LOG(WARNING) << "Driving C-Get SCU on remote modality "
+                    << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle();
+      }; break;
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short));
+
+    job->SetLocalAet(query.GetHandler().GetLocalAet());
+    job->SetRemoteModality(query.GetHandler().GetRemoteModality());
+
+    if (timeout >= 0)
+    {
+      // New in Orthanc 1.7.0
+      job->SetTimeout(static_cast<uint32_t>(timeout));
+    }
+    else if (query.GetHandler().HasTimeout())
+    {
+      // New in Orthanc 1.9.1
+      job->SetTimeout(query.GetHandler().GetTimeout());
+    }
+
+    if (allAnswers)
+    {
+      for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++)
       {
-        for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++)
-        {
-          job->AddFindAnswer(query.GetHandler(), i);
-        }
+        job->AddFindAnswer(query.GetHandler(), i);
       }
-      else
-      {
-        job->AddFindAnswer(query.GetHandler(), index);
-      }
+    }
+    else
+    {
+      job->AddFindAnswer(query.GetHandler(), index);
     }
 
     OrthancRestApi::GetApi(call).SubmitCommandsJob
       (call, job.release(), true /* synchronous by default */, body);
+
   }
 
 
@@ -989,6 +1033,10 @@
                        "`DicomAet` configuration option.", false)
       .SetRequestField(KEY_TIMEOUT, RestApiCallDocumentation::Type_Number,
                        "Timeout for the C-MOVE command, in seconds", false)
+      .SetRequestField(KEY_RETRIEVE_METHOD, RestApiCallDocumentation::Type_String,
+                        "Force usage of C-MOVE or C-GET to retrieve the resource.  If note defined in the payload, "
+                        "the retrieve method is defined in the DicomDefaultRetrieveMethod configuration or in "
+                        "DicomModalities->..->RetrieveMethod", false)
       .AddRequestType(MimeType_PlainText, "AET of the target modality");
   }
   
@@ -999,8 +1047,8 @@
     {
       DocumentRetrieveShared(call);
       call.GetDocumentation()
-        .SetSummary("Retrieve one answer")
-        .SetDescription("Start a C-MOVE SCU command as a job, in order to retrieve one answer associated with the "
+        .SetSummary("Retrieve one answer with a C-MOVE or a C-GET SCU")
+        .SetDescription("Start a C-MOVE or a C-GET SCU command as a job, in order to retrieve one answer associated with the "
                         "query/retrieve operation whose identifiers are provided in the URL: "
                         "https://orthanc.uclouvain.be/book/users/rest.html#performing-retrieve-c-move")
         .SetUriArgument("index", "Index of the answer");
@@ -1012,13 +1060,15 @@
   }
 
 
+
+
   static void RetrieveAllAnswers(RestApiPostCall& call)
   {
     if (call.IsDocumentation())
     {
       DocumentRetrieveShared(call);
       call.GetDocumentation()
-        .SetSummary("Retrieve all answers")
+        .SetSummary("Retrieve all answers with C-MOVE SCU")
         .SetDescription("Start a C-MOVE SCU command as a job, in order to retrieve all the answers associated with the "
                         "query/retrieve operation whose identifier is provided in the URL: "
                         "https://orthanc.uclouvain.be/book/users/rest.html#performing-retrieve-c-move");
@@ -1536,6 +1586,48 @@
     call.GetOutput().AnswerJson(answer);
   }
 
+  void ParseMoveGetJob(DicomRetrieveScuBaseJob& job, Json::Value& request, RestApiPostCall& call)
+  {
+    const ServerContext& context = OrthancRestApi::GetContext(call);
+
+    if (!call.ParseJsonRequest(request) ||
+        request.type() != Json::objectValue ||
+        !request.isMember(KEY_RESOURCES) ||
+        !request.isMember(KEY_LEVEL) ||
+        request[KEY_RESOURCES].type() != Json::arrayValue ||
+        request[KEY_LEVEL].type() != Json::stringValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat, "Must provide a JSON body containing fields " +
+                             std::string(KEY_RESOURCES) + " and " + std::string(KEY_LEVEL));
+    }
+
+    ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString());
+    
+    std::string localAet = Toolbox::GetJsonStringField
+      (request, KEY_LOCAL_AET, context.GetDefaultLocalApplicationEntityTitle());
+
+    const RemoteModalityParameters source =
+      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
+
+    job.SetQueryFormat(DicomToJsonFormat_Short);
+    job.SetLocalAet(localAet);
+    job.SetRemoteModality(source);
+
+    if (request.isMember(KEY_TIMEOUT))
+    {
+      job.SetTimeout(SerializationToolbox::ReadUnsignedInteger(request, KEY_TIMEOUT));
+    }
+
+    for (Json::Value::ArrayIndex i = 0; i < request[KEY_RESOURCES].size(); i++)
+    {
+      DicomMap resource;
+      FromDcmtkBridge::FromJson(resource, request[KEY_RESOURCES][i], "Resources elements");
+
+      resource.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, std::string(ResourceTypeToDicomQueryRetrieveLevel(level)), false);
+
+      job.AddQuery(resource);      
+    }
+  }
 
   /***************************************************************************
    * DICOM C-Move SCU
@@ -1569,53 +1661,15 @@
     }
 
     ServerContext& context = OrthancRestApi::GetContext(call);
-
     Json::Value request;
 
-    if (!call.ParseJsonRequest(request) ||
-        request.type() != Json::objectValue ||
-        !request.isMember(KEY_RESOURCES) ||
-        !request.isMember(KEY_LEVEL) ||
-        request[KEY_RESOURCES].type() != Json::arrayValue ||
-        request[KEY_LEVEL].type() != Json::stringValue)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat, "Must provide a JSON body containing fields " +
-                             std::string(KEY_RESOURCES) + " and " + std::string(KEY_LEVEL));
-    }
-
-    ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString());
-    
-    std::string localAet = Toolbox::GetJsonStringField
-      (request, KEY_LOCAL_AET, context.GetDefaultLocalApplicationEntityTitle());
+    std::unique_ptr<DicomMoveScuJob> job(new DicomMoveScuJob(context));
+
+    ParseMoveGetJob(*job, request, call);
+
     std::string targetAet = Toolbox::GetJsonStringField
       (request, KEY_TARGET_AET, context.GetDefaultLocalApplicationEntityTitle());
-
-    const RemoteModalityParameters source =
-      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
-
-    std::unique_ptr<DicomMoveScuJob> job(new DicomMoveScuJob(context));
-
-    job->SetQueryFormat(DicomToJsonFormat_Short);
-    
-    // QueryAccessor query(call);
     job->SetTargetAet(targetAet);
-    job->SetLocalAet(localAet);
-    job->SetRemoteModality(source);
-
-    if (request.isMember(KEY_TIMEOUT))
-    {
-      job->SetTimeout(SerializationToolbox::ReadUnsignedInteger(request, KEY_TIMEOUT));
-    }
-
-    for (Json::Value::ArrayIndex i = 0; i < request[KEY_RESOURCES].size(); i++)
-    {
-      DicomMap resource;
-      FromDcmtkBridge::FromJson(resource, request[KEY_RESOURCES][i], "Resources elements");
-
-      resource.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, std::string(ResourceTypeToDicomQueryRetrieveLevel(level)), false);
-
-      job->AddQuery(resource);      
-    }
 
     OrthancRestApi::GetApi(call).SubmitCommandsJob
       (call, job.release(), true /* synchronous by default */, request);
@@ -1623,6 +1677,51 @@
   }
 
 
+  /***************************************************************************
+   * DICOM C-Get SCU
+   ***************************************************************************/
+  
+  static void DicomGet(RestApiPostCall& call)
+  {
+    if (call.IsDocumentation())
+    {
+      OrthancRestApi::DocumentSubmitCommandsJob(call);
+      call.GetDocumentation()
+        .SetTag("Networking")
+        .SetSummary("Trigger C-GET SCU")
+        .SetDescription("Start a C-GET SCU command as a job, in order to retrieve DICOM resources "
+                        "from a remote DICOM modality whose identifier is provided in the URL: ")
+                        // "https://orthanc.uclouvain.be/book/users/rest.html#performing-c-move")   // TODO-GET
+        .SetRequestField(KEY_RESOURCES, RestApiCallDocumentation::Type_JsonListOfObjects,
+                         "List of queries identifying all the DICOM resources to be sent.  "
+                         "Usage of wildcards is prohibited and the query shall only contain DICOM ID tags.  "
+                         "Additionally, you may provide SOPClassesInStudy to limit the scope of the DICOM "
+                         "negotiation to certain SOPClassUID or to present uncommon SOPClassUID during "
+                         "the DICOM negotation.  By default, "
+                         "Orhanc will propose the most 120 common SOPClassUIDs.", true)
+        .SetRequestField(KEY_QUERY, RestApiCallDocumentation::Type_JsonObject,
+                         "A query object identifying all the DICOM resources to be retrieved", true)
+        .SetRequestField(KEY_LOCAL_AET, RestApiCallDocumentation::Type_String,
+                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option. "
+                         "Ignored if `DicomModalities` already sets `LocalAet` for this modality.", false)
+        .SetRequestField(KEY_TIMEOUT, RestApiCallDocumentation::Type_Number,
+                         "Timeout for the C-GET command, in seconds", false)
+        .SetUriArgument("id", "Identifier of the modality of interest");
+      return;
+    }
+
+    ServerContext& context = OrthancRestApi::GetContext(call);
+    Json::Value request;
+
+    std::unique_ptr<DicomGetScuJob> job(new DicomGetScuJob(context));
+
+    ParseMoveGetJob(*job, request, call);
+
+    OrthancRestApi::GetApi(call).SubmitCommandsJob
+      (call, job.release(), true /* synchronous by default */, request);
+    return;
+  }
+
 
   /***************************************************************************
    * Orthanc Peers => Store client
@@ -2213,7 +2312,7 @@
       DicomFindAnswers answers(true);
 
       {
-        DicomControlUserConnection connection(GetAssociationParameters(call, json));
+        DicomControlUserConnection connection(GetAssociationParameters(call, json), ScuOperationFlags_FindWorklist);
         connection.FindWorklist(answers, *query);
       }
 
@@ -2544,6 +2643,7 @@
     Register("/modalities/{id}/store", DicomStore);
     Register("/modalities/{id}/store-straight", DicomStoreStraight);  // New in 1.6.1
     Register("/modalities/{id}/move", DicomMove);
+    Register("/modalities/{id}/get", DicomGet);
     Register("/modalities/{id}/configuration", GetModalityConfiguration);  // New in 1.8.1
 
     // For Query/Retrieve
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -50,6 +50,8 @@
 #include <boost/math/special_functions/round.hpp>
 #include <boost/shared_ptr.hpp>
 
+#include "../../../OrthancFramework/Sources/FileStorage/StorageAccessor.h"
+
 /**
  * This semaphore is used to limit the number of concurrent HTTP
  * requests on CPU-intensive routes of the REST API, in order to
@@ -68,6 +70,14 @@
 
 namespace Orthanc
 {
+  static ResourceType GetResourceTypeFromUri(const RestApiCall& call)
+  {
+    assert(!call.GetFullUri().empty());
+    const std::string resourceType = call.GetFullUri() [0];
+    return StringToResourceType(resourceType.c_str());
+  }
+
+
   static std::string GetDocumentationSampleResource(ResourceType type)
   {
     switch (type)
@@ -144,7 +154,7 @@
       responseContent = static_cast<ResponseContentFlags>(static_cast<uint32_t>(responseContent) | ResponseContentFlags_Metadata);
     }
 
-    ResourceFinder finder(level, responseContent);
+    ResourceFinder finder(level, responseContent, context.GetFindStorageAccessMode(), context.GetIndex().HasFindSupport());
     finder.SetOrthancId(level, identifier);
     finder.SetRetrieveMetadata(retrieveMetadata);
 
@@ -154,77 +164,6 @@
 
   // List all the patients, studies, series or instances ----------------------
  
-  static void AnswerListOfResources1(RestApiOutput& output,
-                                    ServerContext& context,
-                                    const std::list<std::string>& resources,
-                                    const std::map<std::string, std::string>& instancesIds, // optional: the id of an instance for each found resource.
-                                    const std::map<std::string, boost::shared_ptr<DicomMap> >& resourcesMainDicomTags,  // optional: all tags read from DB for a resource (current level and upper levels)
-                                    const std::map<std::string, boost::shared_ptr<Json::Value> >& resourcesDicomAsJson, // optional: the dicom-as-json for each resource
-                                    ResourceType level,
-                                    bool expand,
-                                    DicomToJsonFormat format,
-                                    const std::set<DicomTag>& requestedTags,
-                                    bool allowStorageAccess)
-  {
-    Json::Value answer = Json::arrayValue;
-
-    for (std::list<std::string>::const_iterator
-           resource = resources.begin(); resource != resources.end(); ++resource)
-    {
-      if (expand)
-      {
-        Json::Value expanded;
-
-        std::map<std::string, std::string>::const_iterator instanceId = instancesIds.find(*resource);
-        if (instanceId != instancesIds.end())  // if it is found in instancesIds, it is also in resourcesDicomAsJson and mainDicomTags
-        {
-          // reuse data already collected before (e.g during lookup)
-          std::map<std::string, boost::shared_ptr<DicomMap> >::const_iterator mainDicomTags = resourcesMainDicomTags.find(*resource);
-          std::map<std::string, boost::shared_ptr<Json::Value> >::const_iterator dicomAsJson = resourcesDicomAsJson.find(*resource);
-
-          context.ExpandResource(expanded, *resource, 
-                                 *(mainDicomTags->second.get()),
-                                 instanceId->second,
-                                 dicomAsJson->second.get(),
-                                 level, format, requestedTags, allowStorageAccess);
-        }
-        else
-        {
-          context.ExpandResource(expanded, *resource, level, format, requestedTags, allowStorageAccess);
-        }
-
-        if (expanded.type() == Json::objectValue)
-        {
-          answer.append(expanded);
-        }
-      }
-      else
-      {
-        answer.append(*resource);
-      }
-    }
-
-    output.AnswerJson(answer);
-  }
-
-
-  static void AnswerListOfResources2(RestApiOutput& output,
-                                    ServerContext& context,
-                                    const std::list<std::string>& resources,
-                                    ResourceType level,
-                                    bool expand,
-                                    DicomToJsonFormat format,
-                                    const std::set<DicomTag>& requestedTags,
-                                    bool allowStorageAccess)
-  {
-    std::map<std::string, std::string> unusedInstancesIds;
-    std::map<std::string, boost::shared_ptr<DicomMap> > unusedResourcesMainDicomTags;
-    std::map<std::string, boost::shared_ptr<Json::Value> > unusedResourcesDicomAsJson;
-
-    AnswerListOfResources1(output, context, resources, unusedInstancesIds, unusedResourcesMainDicomTags, unusedResourcesDicomAsJson, level, expand, format, requestedTags, allowStorageAccess);
-  }
-
-
   template <enum ResourceType resourceType>
   static void ListResources(RestApiGetCall& call)
   {
@@ -240,103 +179,54 @@
         .SetDescription("List the Orthanc identifiers of all the available DICOM " + resources)
         .SetHttpGetArgument("limit", RestApiCallDocumentation::Type_Number, "Limit the number of results", false)
         .SetHttpGetArgument("since", RestApiCallDocumentation::Type_Number, "Show only the resources since the provided index", false)
-        .SetHttpGetArgument("expand", RestApiCallDocumentation::Type_String,
-                            "If present, retrieve detailed information about the individual " + resources, false)
         .AddAnswerType(MimeType_Json, "JSON array containing either the Orthanc identifiers, or detailed information "
                        "about the reported " + resources + " (if `expand` argument is provided)")
         .SetHttpGetSample("https://orthanc.uclouvain.be/demo/" + resources + "?since=0&limit=2", true);
+      OrthancRestApi::DocumentResponseContentAndExpand(call);
       return;
     }
+
+    // TODO-FIND: include the FindRequest options parsing like since, limit in a method (parse from get-arguments and from post payload)
+
+    std::set<DicomTag> requestedTags;
+    ResponseContentFlags responseContent;
     
-    ServerIndex& index = OrthancRestApi::GetIndex(call);
-    ServerContext& context = OrthancRestApi::GetContext(call);
-
-    if (true)
+    OrthancRestApi::GetRequestedTags(requestedTags, call);
+    OrthancRestApi::GetResponseContentAndExpand(responseContent, call);
+
+    ResourceFinder finder(resourceType, 
+                          responseContent, 
+                          OrthancRestApi::GetContext(call).GetFindStorageAccessMode(),
+                          OrthancRestApi::GetContext(call).GetIndex().HasFindSupport());
+    finder.AddRequestedTags(requestedTags);
+
+    if (call.HasArgument("limit") ||
+        call.HasArgument("since"))
     {
-      /**
-       * EXPERIMENTAL VERSION
-       **/
-
-      // TODO-FIND: include the FindRequest options parsing in a method (parse from get-arguments and from post payload)
-      // TODO-FIND: support other values for expand like expand=MainDicomTags,Labels,Parent,SeriesStatus
-      const bool expand = (call.HasArgument("expand") &&
-                           call.GetBooleanArgument("expand", true));
-
-      std::set<DicomTag> requestedTags;
-      OrthancRestApi::GetRequestedTags(requestedTags, call);
-
-      ResourceFinder finder(resourceType, (expand ? ResponseContentFlags_ExpandTrue : ResponseContentFlags_ID));
-      finder.AddRequestedTags(requestedTags);
-
-      if (call.HasArgument("limit") ||
-          call.HasArgument("since"))
+      if (!call.HasArgument("limit"))
       {
-        if (!call.HasArgument("limit"))
-        {
-          throw OrthancException(ErrorCode_BadRequest,
-                                 "Missing \"limit\" argument for GET request against: " +
-                                 call.FlattenUri());
-        }
-
-        if (!call.HasArgument("since"))
-        {
-          throw OrthancException(ErrorCode_BadRequest,
-                                 "Missing \"since\" argument for GET request against: " +
-                                 call.FlattenUri());
-        }
-
-        uint64_t since = boost::lexical_cast<uint64_t>(call.GetArgument("since", ""));
-        uint64_t limit = boost::lexical_cast<uint64_t>(call.GetArgument("limit", ""));
-        finder.SetLimitsSince(since);
-        finder.SetLimitsCount(limit);
+        throw OrthancException(ErrorCode_BadRequest,
+                               "Missing \"limit\" argument for GET request against: " +
+                               call.FlattenUri());
       }
 
-      Json::Value answer;
-      finder.Execute(answer, context, OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human), false /* no "Metadata" field */);
-      call.GetOutput().AnswerJson(answer);
-    }
-    else
-    {
-      /**
-       * VERSION IN ORTHANC <= 1.12.4
-       **/
-
-      std::list<std::string> result;
-
-      std::set<DicomTag> requestedTags;
-      OrthancRestApi::GetRequestedTags(requestedTags, call);
-
-      if (call.HasArgument("limit") ||
-          call.HasArgument("since"))
+      if (!call.HasArgument("since"))
       {
-        if (!call.HasArgument("limit"))
-        {
-          throw OrthancException(ErrorCode_BadRequest,
-                                 "Missing \"limit\" argument for GET request against: " +
-                                 call.FlattenUri());
-        }
-
-        if (!call.HasArgument("since"))
-        {
-          throw OrthancException(ErrorCode_BadRequest,
-                                 "Missing \"since\" argument for GET request against: " +
-                                 call.FlattenUri());
-        }
-
-        size_t since = boost::lexical_cast<size_t>(call.GetArgument("since", ""));
-        size_t limit = boost::lexical_cast<size_t>(call.GetArgument("limit", ""));
-        index.GetAllUuids(result, resourceType, since, limit);
+        throw OrthancException(ErrorCode_BadRequest,
+                               "Missing \"since\" argument for GET request against: " +
+                               call.FlattenUri());
       }
-      else
-      {
-        index.GetAllUuids(result, resourceType);
-      }
-
-      AnswerListOfResources2(call.GetOutput(), context, result, resourceType, call.HasArgument("expand") && call.GetBooleanArgument("expand", true),
-                            OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human),
-                            requestedTags,
-                            true /* allowStorageAccess */);
+
+      uint64_t since = boost::lexical_cast<uint64_t>(call.GetArgument("since", ""));
+      uint64_t limit = boost::lexical_cast<uint64_t>(call.GetArgument("limit", ""));
+      finder.SetLimitsSince(since);
+      finder.SetLimitsCount(limit);
     }
+
+    Json::Value answer;
+    finder.Execute(answer, OrthancRestApi::GetContext(call),
+                   OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human), false /* no "Metadata" field */);
+    call.GetOutput().AnswerJson(answer);
   }
 
 
@@ -360,39 +250,22 @@
       return;
     }
 
-    const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human);
-
     std::set<DicomTag> requestedTags;
     OrthancRestApi::GetRequestedTags(requestedTags, call);
 
-    if (true)
+    const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human);
+
+    ResourceFinder finder(resourceType, 
+                          ResponseContentFlags_ExpandTrue, 
+                          OrthancRestApi::GetContext(call).GetFindStorageAccessMode(),
+                          OrthancRestApi::GetContext(call).GetIndex().HasFindSupport());
+    finder.AddRequestedTags(requestedTags);
+    finder.SetOrthancId(resourceType, call.GetUriComponent("id", ""));
+
+    Json::Value json;
+    if (finder.ExecuteOneResource(json, OrthancRestApi::GetContext(call), format, false /* no "Metadata" field */))
     {
-      /**
-       * EXPERIMENTAL VERSION
-       **/
-
-      ResourceFinder finder(resourceType, ResponseContentFlags_ExpandTrue);
-      finder.AddRequestedTags(requestedTags);
-      finder.SetOrthancId(resourceType, call.GetUriComponent("id", ""));
-
-      Json::Value json;
-      if (finder.ExecuteOneResource(json, OrthancRestApi::GetContext(call), format, false /* no "Metadata" field */))
-      {
-        call.GetOutput().AnswerJson(json);
-      }
-    }
-    else
-    {
-      /**
-       * VERSION IN ORTHANC <= 1.12.4
-       **/
-
-      Json::Value json;
-      if (OrthancRestApi::GetContext(call).ExpandResource(
-            json, call.GetUriComponent("id", ""), resourceType, format, requestedTags, true /* allowStorageAccess */))
-      {
-        call.GetOutput().AnswerJson(json);
-      }
+      call.GetOutput().AnswerJson(json);
     }
   }
 
@@ -541,7 +414,16 @@
     else
     {
       // return the attachment without any transcoding
-      context.AnswerAttachment(call.GetOutput(), publicId, FileContentType_Dicom);
+      FileInfo info;
+      int64_t revision;
+      if (!context.GetIndex().LookupAttachment(info, revision, ResourceType_Instance, publicId, FileContentType_Dicom))
+      {
+        throw OrthancException(ErrorCode_UnknownResource);
+      }
+      else
+      {
+        context.AnswerAttachment(call.GetOutput(), info);
+      }
     }
   }
 
@@ -1718,12 +1600,13 @@
 
   static void GetResourceStatistics(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Get " + r + " statistics")
         .SetDescription("Get statistics about the given " + r)
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
@@ -1744,9 +1627,9 @@
                         "Size on the disk of the uncompressed DICOM instances associated with the " + r + ", expressed in bytes")
         .SetAnswerField("DicomUncompressedSizeMB", RestApiCallDocumentation::Type_Number,
                         "Size on the disk of the uncompressed DICOM instances associated with the " + r + ", expressed in megabytes (MB)")
-        .SetHttpGetSample(GetDocumentationSampleResource(t) + "/statistics", true);
-
-      switch (t)
+        .SetHttpGetSample(GetDocumentationSampleResource(level) + "/statistics", true);
+
+      switch (level)
       {
         // Do NOT add "break" below this point!
         case ResourceType_Patient:
@@ -1815,22 +1698,15 @@
 
   // Handling of metadata -----------------------------------------------------
 
-  static void CheckValidResourceType(const RestApiCall& call)
-  {
-    assert(!call.GetFullUri().empty());
-    const std::string resourceType = call.GetFullUri() [0];
-    StringToResourceType(resourceType.c_str());
-  }
-
-
   static void ListMetadata(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("List metadata")
         .SetDescription("Get the list of metadata that are associated with the given " + r)
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
@@ -1840,13 +1716,12 @@
                             "If present, use the numeric identifier of the metadata instead of its symbolic name", false)
         .AddAnswerType(MimeType_Json, "JSON array containing the names of the available metadata, "
                        "or JSON associative array mapping metadata to their values (if `expand` argument is provided)")
-        .SetHttpGetSample(GetDocumentationSampleResource(t) + "/metadata", true);
+        .SetHttpGetSample(GetDocumentationSampleResource(level) + "/metadata", true);
       return;
     }
 
     assert(!call.GetFullUri().empty());
     const std::string publicId = call.GetUriComponent("id", "");
-    ResourceType level = StringToResourceType(call.GetFullUri() [0].c_str());
 
     typedef std::map<MetadataType, std::string>  Metadata;
 
@@ -1978,12 +1853,13 @@
 
   static void GetMetadata(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Get metadata")
         .SetDescription("Get the value of a metadata that is associated with the given " + r)
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
@@ -1996,7 +1872,6 @@
 
     assert(!call.GetFullUri().empty());
     const std::string publicId = call.GetUriComponent("id", "");
-    const ResourceType level = StringToResourceType(call.GetFullUri() [0].c_str());
 
     std::string name = call.GetUriComponent("name", "");
     MetadataType metadata = StringToMetadata(name);
@@ -2025,12 +1900,13 @@
 
   static void DeleteMetadata(RestApiDeleteCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Delete metadata")
         .SetDescription("Delete some metadata associated with the given DICOM " + r +
                         ". This call will fail if trying to delete a system metadata (i.e. whose index is < 1024).")
@@ -2041,7 +1917,6 @@
       return;
     }
 
-    CheckValidResourceType(call);
     const std::string publicId = call.GetUriComponent("id", "");
 
     std::string name = call.GetUriComponent("name", "");
@@ -2089,12 +1964,13 @@
 
   static void SetMetadata(RestApiPutCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Set metadata")
         .SetDescription("Set the value of some metadata in the given DICOM " + r +
                         ". This call will fail if trying to modify a system metadata (i.e. whose index is < 1024).")
@@ -2105,8 +1981,6 @@
       return;
     }
 
-    CheckValidResourceType(call);
-
     std::string publicId = call.GetUriComponent("id", "");
     std::string name = call.GetUriComponent("name", "");
     MetadataType metadata = StringToMetadata(name);
@@ -2154,23 +2028,22 @@
 
   static void ListLabels(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("List labels")
         .SetDescription("Get the labels that are associated with the given " + r + " (new in Orthanc 1.12.0)")
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
         .AddAnswerType(MimeType_Json, "JSON array containing the names of the labels")
-        .SetHttpGetSample(GetDocumentationSampleResource(t) + "/labels", true);
+        .SetHttpGetSample(GetDocumentationSampleResource(level) + "/labels", true);
       return;
     }
 
-    assert(!call.GetFullUri().empty());
     const std::string publicId = call.GetUriComponent("id", "");
-    ResourceType level = StringToResourceType(call.GetFullUri() [0].c_str());
 
     std::set<std::string> labels;
     OrthancRestApi::GetIndex(call).ListLabels(labels, publicId, level);
@@ -2188,12 +2061,13 @@
 
   static void GetLabel(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Test label")
         .SetDescription("Test whether the " + r + " is associated with the given label")
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
@@ -2202,11 +2076,7 @@
       return;
     }
 
-    CheckValidResourceType(call);
-
-    assert(!call.GetFullUri().empty());
     const std::string publicId = call.GetUriComponent("id", "");
-    const ResourceType level = StringToResourceType(call.GetFullUri() [0].c_str());
 
     std::string label = call.GetUriComponent("label", "");
 
@@ -2222,12 +2092,13 @@
 
   static void AddLabel(RestApiPutCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Add label")
         .SetDescription("Associate a label with a " + r)
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
@@ -2235,10 +2106,7 @@
       return;
     }
 
-    CheckValidResourceType(call);
-
     std::string publicId = call.GetUriComponent("id", "");
-    const ResourceType level = StringToResourceType(call.GetFullUri() [0].c_str());
 
     std::string label = call.GetUriComponent("label", "");
     OrthancRestApi::GetIndex(call).ModifyLabel(publicId, level, label, StatelessDatabaseOperations::LabelOperation_Add);
@@ -2249,12 +2117,13 @@
 
   static void RemoveLabel(RestApiDeleteCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Remove label")
         .SetDescription("Remove a label associated with a " + r)
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
@@ -2262,10 +2131,7 @@
       return;
     }
 
-    CheckValidResourceType(call);
-
     std::string publicId = call.GetUriComponent("id", "");
-    const ResourceType level = StringToResourceType(call.GetFullUri() [0].c_str());
 
     std::string label = call.GetUriComponent("label", "");
     OrthancRestApi::GetIndex(call).ModifyLabel(publicId, level, label, StatelessDatabaseOperations::LabelOperation_Remove);
@@ -2278,26 +2144,26 @@
 
   static void ListAttachments(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("List attachments")
         .SetDescription("Get the list of attachments that are associated with the given " + r)
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
         .SetHttpGetArgument("full", RestApiCallDocumentation::Type_String,
                             "If present, retrieve the attachments list and their numerical ids", false)
         .AddAnswerType(MimeType_Json, "JSON array containing the names of the attachments")
-        .SetHttpGetSample(GetDocumentationSampleResource(t) + "/attachments", true);
+        .SetHttpGetSample(GetDocumentationSampleResource(level) + "/attachments", true);
       return;
     }
 
-    const std::string resourceType = call.GetFullUri() [0];
     const std::string publicId = call.GetUriComponent("id", "");
     std::set<FileContentType> attachments;
-    OrthancRestApi::GetIndex(call).ListAvailableAttachments(attachments, publicId, StringToResourceType(resourceType.c_str()));
+    OrthancRestApi::GetIndex(call).ListAvailableAttachments(attachments, publicId, level);
 
     Json::Value result;
 
@@ -2306,7 +2172,7 @@
       result = Json::objectValue;
       
       for (std::set<FileContentType>::const_iterator 
-            it = attachments.begin(); it != attachments.end(); ++it)
+             it = attachments.begin(); it != attachments.end(); ++it)
       {
         std::string key = EnumerationToString(*it);
         result[key] = static_cast<uint16_t>(*it);
@@ -2317,7 +2183,7 @@
       result = Json::arrayValue;
       
       for (std::set<FileContentType>::const_iterator 
-            it = attachments.begin(); it != attachments.end(); ++it)
+             it = attachments.begin(); it != attachments.end(); ++it)
       {
         result.append(EnumerationToString(*it));
       }
@@ -2338,17 +2204,15 @@
   }
 
   
-  static bool GetAttachmentInfo(FileInfo& info,
+  static bool GetAttachmentInfo(FileInfo& info /* out */,
+                                int64_t& revision /* out */,
+                                ResourceType level,
                                 RestApiGetCall& call)
   {
-    CheckValidResourceType(call);
- 
     const std::string publicId = call.GetUriComponent("id", "");
-    const std::string name = call.GetUriComponent("name", "");
-    FileContentType contentType = StringToContentType(name);
-
-    int64_t revision;
-    if (OrthancRestApi::GetIndex(call).LookupAttachment(info, revision, publicId, contentType))
+    FileContentType contentType = StringToContentType(call.GetUriComponent("name", ""));
+
+    if (OrthancRestApi::GetIndex(call).LookupAttachment(info, revision, level, publicId, contentType))
     {
       SetAttachmentETag(call.GetOutput(), revision, info);  // New in Orthanc 1.9.2
 
@@ -2375,10 +2239,11 @@
 
   static void GetAttachmentOperations(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       AddAttachmentDocumentation(call, r);
       call.GetDocumentation()
         .SetTag("Other")
@@ -2390,7 +2255,8 @@
     }
 
     FileInfo info;
-    if (GetAttachmentInfo(info, call))
+    int64_t revision;
+    if (GetAttachmentInfo(info, revision, level, call))
     {
       Json::Value operations = Json::arrayValue;
 
@@ -2429,12 +2295,13 @@
   template <int uncompress>
   static void GetAttachmentData(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Get attachment" + std::string(uncompress ? "" : " (no decompression)"))
         .SetDescription("Get the (binary) content of one attachment associated with the given " + r +
                         std::string(uncompress ? "" : ". The attachment will not be decompressed if `StorageCompression` is `true`."))
@@ -2442,46 +2309,60 @@
         .SetUriArgument("name", "The name of the attachment, or its index (cf. `UserContentType` configuration option)")
         .AddAnswerType(MimeType_Binary, "The attachment")
         .SetAnswerHeader("ETag", "Revision of the attachment, to be used in further `PUT` or `DELETE` operations")
-        .SetHttpHeader("If-None-Match", "Optional revision of the metadata, to check if its content has changed");
+        .SetHttpHeader("If-None-Match", "Optional revision of the attachment, to check if its content has changed")
+        .SetHttpHeader("Content-Range", "Optional content range to access part of the attachment (new in Orthanc 1.12.5)");
       return;
     }
 
     ServerContext& context = OrthancRestApi::GetContext(call);
 
-    CheckValidResourceType(call);
- 
-    std::string publicId = call.GetUriComponent("id", "");
-    FileContentType type = StringToContentType(call.GetUriComponent("name", ""));
+    bool hasRangeHeader = false;
+    StorageAccessor::Range range;
+
+    HttpToolbox::Arguments::const_iterator rangeHeader = call.GetHttpHeaders().find("range");
+    if (rangeHeader != call.GetHttpHeaders().end())
+    {
+      hasRangeHeader = true;
+      range = StorageAccessor::Range::ParseHttpRange(rangeHeader->second);
+    }
 
     FileInfo info;
-    if (GetAttachmentInfo(info, call))
+    int64_t revision;
+    if (GetAttachmentInfo(info, revision, level, call))
     {
       // NB: "SetAttachmentETag()" is already invoked by "GetAttachmentInfo()"
 
-      if (uncompress)
+      int64_t userRevision;
+      std::string userMD5;
+      if (GetRevisionHeader(userRevision, userMD5, call, "If-None-Match") &&
+          revision == userRevision &&
+          info.GetUncompressedMD5() == userMD5)
+      {
+        call.GetOutput().GetLowLevelOutput().SendStatus(HttpStatus_304_NotModified);
+        return;
+      }
+
+      if (hasRangeHeader)
       {
-        context.AnswerAttachment(call.GetOutput(), publicId, type);
+        std::string fragment;
+        context.ReadAttachmentRange(fragment, info, range, uncompress);
+
+        uint64_t fullSize = (uncompress ? info.GetUncompressedSize() : info.GetCompressedSize());
+        call.GetOutput().GetLowLevelOutput().SetContentType(MimeType_Binary);
+        call.GetOutput().GetLowLevelOutput().AddHeader("Content-Range", range.FormatHttpContentRange(fullSize));
+        call.GetOutput().GetLowLevelOutput().SendStatus(HttpStatus_206_PartialContent, fragment);
+      }
+      else if (uncompress ||
+               info.GetCompressionType() == CompressionType_None)
+      {
+        context.AnswerAttachment(call.GetOutput(), info);
       }
       else
       {
-        // Return the raw data (possibly compressed), as stored on the filesystem
+        // Access to the raw attachment (which is compressed)
         std::string content;
-        std::string attachmentId;
-        int64_t revision;
-        context.ReadAttachment(content, revision, attachmentId, publicId, type, false, true /* skipCache when you absolutely need the compressed data */);
-
-        int64_t userRevision;
-        std::string userMD5;
-        if (GetRevisionHeader(userRevision, userMD5, call, "If-None-Match") &&
-            revision == userRevision &&
-            info.GetUncompressedMD5() == userMD5)
-        {
-          call.GetOutput().GetLowLevelOutput().SendStatus(HttpStatus_304_NotModified);
-        }
-        else
-        {
-          call.GetOutput().AnswerBuffer(content, MimeType_Binary);
-        }
+        context.ReadAttachment(content, info, false /* don't uncompress */, true /* skip cache */);
+        call.GetOutput().AnswerBuffer(content, MimeType_Binary);
       }
     }
   }
@@ -2489,13 +2370,14 @@
 
   static void GetAttachmentSize(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       AddAttachmentDocumentation(call, r);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Get size of attachment")
         .SetDescription("Get the size of one attachment associated with the given " + r)
         .AddAnswerType(MimeType_PlainText, "The size of the attachment");
@@ -2503,7 +2385,8 @@
     }
 
     FileInfo info;
-    if (GetAttachmentInfo(info, call))
+    int64_t revision;
+    if (GetAttachmentInfo(info, revision, level, call))
     {
       call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedSize()), MimeType_PlainText);
     }
@@ -2511,13 +2394,14 @@
 
   static void GetAttachmentInfo(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       AddAttachmentDocumentation(call, r);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Get info about the attachment")
         .SetDescription("Get all the information about the attachment associated with the given " + r)
         .AddAnswerType(MimeType_Json, "JSON object containing the information about the attachment")
@@ -2526,7 +2410,8 @@
     }
 
     FileInfo info;
-    if (GetAttachmentInfo(info, call))
+    int64_t revision;
+    if (GetAttachmentInfo(info, revision, level, call))
     {
       Json::Value result = Json::objectValue;    
       result["Uuid"] = info.GetUuid();
@@ -2542,13 +2427,14 @@
 
   static void GetAttachmentCompressedSize(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       AddAttachmentDocumentation(call, r);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Get size of attachment on disk")
         .SetDescription("Get the size of one attachment associated with the given " + r + ", as stored on the disk. "
                         "This is different from `.../size` iff `EnableStorage` is `true`.")
@@ -2557,7 +2443,8 @@
     }
 
     FileInfo info;
-    if (GetAttachmentInfo(info, call))
+    int64_t revision;
+    if (GetAttachmentInfo(info, revision, level, call))
     {
       call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedSize()), MimeType_PlainText);
     }
@@ -2566,13 +2453,14 @@
 
   static void GetAttachmentMD5(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       AddAttachmentDocumentation(call, r);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Get MD5 of attachment")
         .SetDescription("Get the MD5 hash of one attachment associated with the given " + r)
         .AddAnswerType(MimeType_PlainText, "The MD5 of the attachment");
@@ -2580,7 +2468,8 @@
     }
 
     FileInfo info;
-    if (GetAttachmentInfo(info, call) &&
+    int64_t revision;
+    if (GetAttachmentInfo(info, revision, level, call) &&
         info.GetUncompressedMD5() != "")
     {
       call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedMD5()), MimeType_PlainText);
@@ -2590,13 +2479,14 @@
 
   static void GetAttachmentCompressedMD5(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       AddAttachmentDocumentation(call, r);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Get MD5 of attachment on disk")
         .SetDescription("Get the MD5 hash of one attachment associated with the given " + r + ", as stored on the disk. "
                         "This is different from `.../md5` iff `EnableStorage` is `true`.")
@@ -2605,7 +2495,8 @@
     }
 
     FileInfo info;
-    if (GetAttachmentInfo(info, call) &&
+    int64_t revision;
+    if (GetAttachmentInfo(info, revision, level, call) &&
         info.GetCompressedMD5() != "")
     {
       call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedMD5()), MimeType_PlainText);
@@ -2615,12 +2506,13 @@
 
   static void VerifyAttachment(RestApiPostCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Verify attachment")
         .SetDescription("Verify that the attachment is not corrupted, by validating its MD5 hash")
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
@@ -2630,7 +2522,6 @@
     }
 
     ServerContext& context = OrthancRestApi::GetContext(call);
-    CheckValidResourceType(call);
 
     std::string publicId = call.GetUriComponent("id", "");
     std::string name = call.GetUriComponent("name", "");
@@ -2638,7 +2529,7 @@
 
     FileInfo info;
     int64_t revision;  // Ignored
-    if (!OrthancRestApi::GetIndex(call).LookupAttachment(info, revision, publicId, contentType) ||
+    if (!OrthancRestApi::GetIndex(call).LookupAttachment(info, revision, level, publicId, contentType) ||
         info.GetCompressedMD5() == "" ||
         info.GetUncompressedMD5() == "")
     {
@@ -2650,9 +2541,7 @@
 
     // First check whether the compressed data is correctly stored in the disk
     std::string data;
-    std::string attachmentId;
-
-    context.ReadAttachment(data, revision, attachmentId, publicId, StringToContentType(name), false, true /* skipCache when you absolutely need the compressed data */);
+    context.ReadAttachment(data, info, false, true /* skipCache when you absolutely need the compressed data */);
 
     std::string actualMD5;
     Toolbox::ComputeMD5(actualMD5, data);
@@ -2667,7 +2556,7 @@
       }
       else
       {
-        context.ReadAttachment(data, revision, attachmentId, publicId, StringToContentType(name), true, true /* skipCache when you absolutely need the compressed data */);
+        context.ReadAttachment(data, info, true, true /* skipCache when you absolutely need the compressed data */);
         Toolbox::ComputeMD5(actualMD5, data);
         ok = (actualMD5 == info.GetUncompressedMD5());
       }
@@ -2687,12 +2576,13 @@
 
   static void UploadAttachment(RestApiPutCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Set attachment")
         .SetDescription("Attach a file to the given DICOM " + r +
                         ". This call will fail if trying to modify a system attachment (i.e. whose index is < 1024).")
@@ -2705,11 +2595,9 @@
     }
 
     ServerContext& context = OrthancRestApi::GetContext(call);
-    CheckValidResourceType(call);
  
     std::string publicId = call.GetUriComponent("id", "");
     std::string name = call.GetUriComponent("name", "");
-    ResourceType resourceType = StringToResourceType(call.GetFullUri()[0].c_str());
 
     FileContentType contentType = StringToContentType(name);
     if (IsUserContentType(contentType) ||  // It is forbidden to modify internal attachments...
@@ -2734,7 +2622,7 @@
       }
 
       int64_t newRevision;
-      context.AddAttachment(newRevision, publicId, resourceType, StringToContentType(name), call.GetBodyData(),
+      context.AddAttachment(newRevision, publicId, level, StringToContentType(name), call.GetBodyData(),
                             call.GetBodySize(), hasOldRevision, oldRevision, oldMD5);
 
       SetBufferContentETag(call.GetOutput(), newRevision, call.GetBodyData(), call.GetBodySize());  // New in Orthanc 1.9.2
@@ -2749,12 +2637,13 @@
 
   static void DeleteAttachment(RestApiDeleteCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Delete attachment")
         .SetDescription("Delete an attachment associated with the given DICOM " + r +
                         ". This call will fail if trying to delete a system attachment (i.e. whose index is < 1024).")
@@ -2765,8 +2654,6 @@
       return;
     }
 
-    CheckValidResourceType(call);
-
     std::string publicId = call.GetUriComponent("id", "");
     std::string name = call.GetUriComponent("name", "");
     FileContentType contentType = StringToContentType(name);
@@ -2838,12 +2725,13 @@
   template <enum CompressionType compression>
   static void ChangeAttachmentCompression(RestApiPostCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary(compression == CompressionType_None ? "Uncompress attachment" : "Compress attachment")
         .SetDescription("Change the compression scheme that is used to store an attachment.")
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
@@ -2851,27 +2739,25 @@
       return;
     }
 
-    CheckValidResourceType(call);
-
     std::string publicId = call.GetUriComponent("id", "");
     std::string name = call.GetUriComponent("name", "");
-    ResourceType resourceType = StringToResourceType(call.GetFullUri()[0].c_str());
     FileContentType contentType = StringToContentType(name);
 
-    OrthancRestApi::GetContext(call).ChangeAttachmentCompression(publicId, resourceType, contentType, compression);
+    OrthancRestApi::GetContext(call).ChangeAttachmentCompression(level, publicId, contentType, compression);
     call.GetOutput().AnswerBuffer("{}", MimeType_Json);
   }
 
 
   static void IsAttachmentCompressed(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       AddAttachmentDocumentation(call, r);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Is attachment compressed?")
         .SetDescription("Test whether the attachment has been stored as a compressed file on the disk.")
         .AddAnswerType(MimeType_PlainText, "`0` if the attachment was stored uncompressed, `1` if it was compressed");
@@ -2879,7 +2765,8 @@
     }
 
     FileInfo info;
-    if (GetAttachmentInfo(info, call))
+    int64_t revision;
+    if (GetAttachmentInfo(info, revision, level, call))
     {
       std::string answer = (info.GetCompressionType() == CompressionType_None) ? "0" : "1";
       call.GetOutput().AnswerBuffer(answer, MimeType_PlainText);
@@ -2917,12 +2804,13 @@
 
   static bool ExtractSharedTags(Json::Value& shared,
                                 ServerContext& context,
-                                const std::string& publicId)
+                                const std::string& publicId,
+                                ResourceType level)
   {
     // Retrieve all the instances of this patient/study/series
     typedef std::list<std::string> Instances;
     Instances instances;
-    context.GetIndex().GetChildInstances(instances, publicId);  // (*)
+    context.GetIndex().GetChildInstances(instances, publicId, level);  // (*)
 
     // Loop over the instances
     bool isFirst = true;
@@ -2992,20 +2880,21 @@
 
   static void GetSharedTags(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
       OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Full);
 
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Get shared tags")
         .SetDescription("Extract the DICOM tags whose value is constant across all the child instances of "
                         "the DICOM " + r + " whose Orthanc identifier is provided in the URL")
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
         .AddAnswerType(MimeType_Json, "JSON object containing the values of the DICOM tags")
-        .SetTruncatedJsonHttpGetSample(GetDocumentationSampleResource(t) + "/shared-tags", 5);
+        .SetTruncatedJsonHttpGetSample(GetDocumentationSampleResource(level) + "/shared-tags", 5);
       return;
     }
 
@@ -3013,7 +2902,7 @@
     std::string publicId = call.GetUriComponent("id", "");
 
     Json::Value sharedTags;
-    if (ExtractSharedTags(sharedTags, context, publicId))
+    if (ExtractSharedTags(sharedTags, context, publicId, level))
     {
       // Success: Send the value of the shared tags
       AnswerDicomAsJson(call, sharedTags, OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Full));
@@ -3088,7 +2977,7 @@
       // Retrieve all the instances of this patient/study/series
       typedef std::list<std::string> Instances;
       Instances instances;
-      context.GetIndex().GetChildInstances(instances, publicId);
+      context.GetIndex().GetChildInstances(instances, publicId, resourceType);
 
       if (instances.empty())
       {
@@ -3183,70 +3072,14 @@
   }
 
 
-  namespace 
+  enum FindType
   {
-    class FindVisitor : public ServerContext::ILookupVisitor
-    {
-    private:
-      bool                    isComplete_;
-      std::list<std::string>  resources_;
-      FindStorageAccessMode   findStorageAccessMode_;
-      
-      // cache the data we used during lookup and that we could reuse when building the answers
-      std::map<std::string, std::string> instancesIds_;         // the id of an instance for each found resource.
-      std::map<std::string, boost::shared_ptr<DicomMap> > resourcesMainDicomTags_;  // all tags read from DB for a resource (current level and upper levels)
-      std::map<std::string, boost::shared_ptr<Json::Value> > resourcesDicomAsJson_; // the dicom-as-json for a resource
-
-      DicomToJsonFormat       format_;
-
-    public:
-      explicit FindVisitor(DicomToJsonFormat format, FindStorageAccessMode findStorageAccessMode) :
-        isComplete_(false),
-        findStorageAccessMode_(findStorageAccessMode),
-        format_(format)
-      {
-      }
-      
-      virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
-      {
-        return false;   // (*)
-      }
-      
-      virtual void MarkAsComplete() ORTHANC_OVERRIDE
-      {
-        isComplete_ = true;  // Unused information as of Orthanc 1.5.0
-      }
-
-      virtual void Visit(const std::string& publicId,
-                         const std::string& instanceId,
-                         const DicomMap& mainDicomTags,
-                         const Json::Value* dicomAsJson)  ORTHANC_OVERRIDE
-      {
-        resources_.push_back(publicId);
-        instancesIds_[publicId] = instanceId;
-        resourcesMainDicomTags_[publicId].reset(mainDicomTags.Clone());
-        if (dicomAsJson != NULL)
-        {
-          resourcesDicomAsJson_[publicId].reset(new Json::Value(*dicomAsJson));  // keep our own copy because we might reuse it between lookup and answers
-        }
-        else
-        {
-          resourcesDicomAsJson_[publicId] = boost::shared_ptr<Json::Value>();
-        }
-      }
-
-      void Answer(RestApiOutput& output,
-                  ServerContext& context,
-                  ResourceType level,
-                  bool expand,
-                  const std::set<DicomTag>& requestedTags) const
-      {
-        AnswerListOfResources1(output, context, resources_, instancesIds_, resourcesMainDicomTags_, resourcesDicomAsJson_, level, expand, format_, requestedTags, IsStorageAccessAllowedForAnswers(findStorageAccessMode_));
-      }
-    };
-  }
-
-
+    FindType_Find,
+    FindType_Count
+  };
+
+
+  template <enum FindType requestType>
   static void Find(RestApiPostCall& call)
   {
     static const char* const KEY_CASE_SENSITIVE = "CaseSensitive";
@@ -3265,57 +3098,64 @@
     static const char* const KEY_PARENT_PATIENT = "ParentPatient";        // New in Orthanc 1.12.5
     static const char* const KEY_PARENT_STUDY = "ParentStudy";            // New in Orthanc 1.12.5
     static const char* const KEY_PARENT_SERIES = "ParentSeries";          // New in Orthanc 1.12.5
-    static const char* const KEY_QUERY_METADATA = "QueryMetadata";        // New in Orthanc 1.12.5
+    static const char* const KEY_METADATA_QUERY = "MetadataQuery";        // New in Orthanc 1.12.5
     static const char* const KEY_RESPONSE_CONTENT = "ResponseContent";    // New in Orthanc 1.12.5
 
     if (call.IsDocumentation())
     {
       OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human);
 
-      call.GetDocumentation()
-        .SetTag("System")
-        .SetSummary("Look for local resources")
-        .SetDescription("This URI can be used to perform a search on the content of the local Orthanc server, "
-                        "in a way that is similar to querying remote DICOM modalities using C-FIND SCU: "
-                        "https://orthanc.uclouvain.be/book/users/rest.html#performing-finds-within-orthanc")
-        .SetRequestField(KEY_CASE_SENSITIVE, RestApiCallDocumentation::Type_Boolean,
-                         "Enable case-sensitive search for PN value representations (defaults to configuration option `CaseSensitivePN`)", false)
-        .SetRequestField(KEY_EXPAND, RestApiCallDocumentation::Type_Boolean,
-                         "Also retrieve the content of the matching resources, not only their Orthanc identifiers", false)
+      RestApiCallDocumentation& doc = call.GetDocumentation();
+
+      doc.SetTag("System")
         .SetRequestField(KEY_LEVEL, RestApiCallDocumentation::Type_String,
                          "Level of the query (`Patient`, `Study`, `Series` or `Instance`)", true)
-        .SetRequestField(KEY_LIMIT, RestApiCallDocumentation::Type_Number,
-                         "Limit the number of reported resources", false)
-        .SetRequestField(KEY_SINCE, RestApiCallDocumentation::Type_Number,
-                         "Show only the resources since the provided index (in conjunction with `Limit`)", false)
-        .SetRequestField(KEY_REQUESTED_TAGS, RestApiCallDocumentation::Type_JsonListOfStrings,
-                         "A list of DICOM tags to include in the response (applicable only if \"Expand\" is set to true).  "
-                         "The tags requested tags are returned in the 'RequestedTags' field in the response.  "
-                         "Note that, if you are requesting tags that are not listed in the Main Dicom Tags stored in DB, building the response "
-                         "might be slow since Orthanc will need to access the DICOM files.  If not specified, Orthanc will return "
-                         "all Main Dicom Tags to keep backward compatibility with Orthanc prior to 1.11.0.", false)
         .SetRequestField(KEY_QUERY, RestApiCallDocumentation::Type_JsonObject,
                          "Associative array containing the filter on the values of the DICOM tags", true)
         .SetRequestField(KEY_LABELS, RestApiCallDocumentation::Type_JsonListOfStrings,
                          "List of strings specifying which labels to look for in the resources (new in Orthanc 1.12.0)", true)
         .SetRequestField(KEY_LABELS_CONSTRAINT, RestApiCallDocumentation::Type_String,
                          "Constraint on the labels, can be `All`, `Any`, or `None` (defaults to `All`, new in Orthanc 1.12.0)", true)
-        .SetRequestField(KEY_ORDER_BY, RestApiCallDocumentation::Type_JsonListOfObjects,
-                         "Array of associative arrays containing the requested ordering (new in Orthanc 1.12.5)", true)
         .SetRequestField(KEY_PARENT_PATIENT, RestApiCallDocumentation::Type_String,
                          "Limit the reported resources to descendants of this patient (new in Orthanc 1.12.5)", true)
         .SetRequestField(KEY_PARENT_STUDY, RestApiCallDocumentation::Type_String,
                          "Limit the reported resources to descendants of this study (new in Orthanc 1.12.5)", true)
         .SetRequestField(KEY_PARENT_SERIES, RestApiCallDocumentation::Type_String,
                          "Limit the reported resources to descendants of this series (new in Orthanc 1.12.5)", true)
-        .SetRequestField(KEY_QUERY_METADATA, RestApiCallDocumentation::Type_JsonObject,
-                         "Associative array containing the filter on the values of the metadata (new in Orthanc 1.12.5)", true)
-        .SetRequestField(KEY_RESPONSE_CONTENT, RestApiCallDocumentation::Type_JsonListOfStrings,
-                         "Defines the content of response for each returned resource.  Allowed values are `MainDicomTags`, "
-                         "`Metadata`, `Children`, `Parent`, `Labels`, `Status`, `IsStable`, `Attachments`.  "
-                         "(new in Orthanc 1.12.5)", true)
-        .AddAnswerType(MimeType_Json, "JSON array containing either the Orthanc identifiers, or detailed information "
-                       "about the reported resources (if `Expand` argument is `true`)");
+        .SetRequestField(KEY_METADATA_QUERY, RestApiCallDocumentation::Type_JsonObject,
+                         "Associative array containing the filter on the values of the metadata (new in Orthanc 1.12.5)", true);
+      
+      switch (requestType)
+      {
+        case FindType_Find:
+          doc.SetSummary("Look for local resources")
+          .SetRequestField(KEY_CASE_SENSITIVE, RestApiCallDocumentation::Type_Boolean,
+                           "Enable case-sensitive search for PN value representations (defaults to configuration option `CaseSensitivePN`)", false)
+          .SetDescription("This URI can be used to perform a search on the content of the local Orthanc server, "
+                          "in a way that is similar to querying remote DICOM modalities using C-FIND SCU: "
+                          "https://orthanc.uclouvain.be/book/users/rest.html#performing-finds-within-orthanc")
+          .SetRequestField(KEY_LIMIT, RestApiCallDocumentation::Type_Number,
+                          "Limit the number of reported resources", false)
+          .SetRequestField(KEY_SINCE, RestApiCallDocumentation::Type_Number,
+                          "Show only the resources since the provided index (in conjunction with `Limit`)", false)
+          .SetRequestField(KEY_ORDER_BY, RestApiCallDocumentation::Type_JsonListOfObjects,
+                          "Array of associative arrays containing the requested ordering (new in Orthanc 1.12.5)", true)
+          .AddAnswerType(MimeType_Json, "JSON array containing either the Orthanc identifiers, or detailed information "
+                        "about the reported resources (if `Expand` argument is `true`)");
+
+          OrthancRestApi::DocumentRequestedTags(call);
+          OrthancRestApi::DocumentResponseContentAndExpand(call);
+          break;
+        case FindType_Count:
+          doc.SetSummary("Count local resources")
+          .SetDescription("This URI can be used to count the resources that are matching criteria on the content of the local Orthanc server, "
+                          "in a way that is similar to tools/find")
+          .AddAnswerType(MimeType_Json, "A JSON object with the `Count` of matching resources");
+          break;
+        default:
+          throw OrthancException(ErrorCode_NotImplemented);
+      }
+        
       return;
     }
 
@@ -3346,30 +3186,6 @@
       throw OrthancException(ErrorCode_BadRequest, 
                              "Field \"" + std::string(KEY_CASE_SENSITIVE) + "\" must be a Boolean");
     }
-    else if (request.isMember(KEY_LIMIT) && 
-             request[KEY_LIMIT].type() != Json::intValue)
-    {
-      throw OrthancException(ErrorCode_BadRequest, 
-                             "Field \"" + std::string(KEY_LIMIT) + "\" must be an integer");
-    }
-    else if (request.isMember(KEY_SINCE) &&
-             request[KEY_SINCE].type() != Json::intValue)
-    {
-      throw OrthancException(ErrorCode_BadRequest, 
-                             "Field \"" + std::string(KEY_SINCE) + "\" must be an integer");
-    }
-    else if (request.isMember(KEY_REQUESTED_TAGS) &&
-             request[KEY_REQUESTED_TAGS].type() != Json::arrayValue)
-    {
-      throw OrthancException(ErrorCode_BadRequest, 
-                             "Field \"" + std::string(KEY_REQUESTED_TAGS) + "\" must be an array");
-    }
-    else if (request.isMember(KEY_RESPONSE_CONTENT) &&
-             request[KEY_RESPONSE_CONTENT].type() != Json::arrayValue)
-    {
-      throw OrthancException(ErrorCode_BadRequest, 
-                             "Field \"" + std::string(KEY_RESPONSE_CONTENT) + "\" must be an array");
-    }
     else if (request.isMember(KEY_LABELS) &&
              request[KEY_LABELS].type() != Json::arrayValue)
     {
@@ -3382,17 +3198,11 @@
       throw OrthancException(ErrorCode_BadRequest, 
                              "Field \"" + std::string(KEY_LABELS_CONSTRAINT) + "\" must be an array of strings");
     }
-    else if (request.isMember(KEY_ORDER_BY) &&
-             request[KEY_ORDER_BY].type() != Json::arrayValue)
+    else if (request.isMember(KEY_METADATA_QUERY) &&
+             request[KEY_METADATA_QUERY].type() != Json::objectValue)
     {
       throw OrthancException(ErrorCode_BadRequest, 
-                             "Field \"" + std::string(KEY_ORDER_BY) + "\" must be an array");
-    }
-    else if (request.isMember(KEY_QUERY_METADATA) &&
-             request[KEY_QUERY_METADATA].type() != Json::objectValue)
-    {
-      throw OrthancException(ErrorCode_BadRequest, 
-                             "Field \"" + std::string(KEY_QUERY_METADATA) + "\" must be an JSON object");
+                             "Field \"" + std::string(KEY_METADATA_QUERY) + "\" must be an JSON object");
     }
     else if (request.isMember(KEY_PARENT_PATIENT) &&
              request[KEY_PARENT_PATIENT].type() != Json::stringValue)
@@ -3412,72 +3222,88 @@
       throw OrthancException(ErrorCode_BadRequest, 
                              "Field \"" + std::string(KEY_PARENT_SERIES) + "\" must be a string");
     }
+    else if (requestType == FindType_Find && request.isMember(KEY_LIMIT) && 
+             request[KEY_LIMIT].type() != Json::intValue)
+    {
+      throw OrthancException(ErrorCode_BadRequest, 
+                             "Field \"" + std::string(KEY_LIMIT) + "\" must be an integer");
+    }
+    else if (requestType == FindType_Find && request.isMember(KEY_SINCE) &&
+             request[KEY_SINCE].type() != Json::intValue)
+    {
+      throw OrthancException(ErrorCode_BadRequest, 
+                             "Field \"" + std::string(KEY_SINCE) + "\" must be an integer");
+    }
+    else if (requestType == FindType_Find && request.isMember(KEY_REQUESTED_TAGS) &&
+             request[KEY_REQUESTED_TAGS].type() != Json::arrayValue)
+    {
+      throw OrthancException(ErrorCode_BadRequest, 
+                             "Field \"" + std::string(KEY_REQUESTED_TAGS) + "\" must be an array");
+    }
+    else if (requestType == FindType_Find && request.isMember(KEY_RESPONSE_CONTENT) &&
+             request[KEY_RESPONSE_CONTENT].type() != Json::arrayValue)
+    {
+      throw OrthancException(ErrorCode_BadRequest, 
+                             "Field \"" + std::string(KEY_RESPONSE_CONTENT) + "\" must be an array");
+    }
+    else if (requestType == FindType_Find && request.isMember(KEY_ORDER_BY) &&
+             request[KEY_ORDER_BY].type() != Json::arrayValue)
+    {
+      throw OrthancException(ErrorCode_BadRequest, 
+                             "Field \"" + std::string(KEY_ORDER_BY) + "\" must be an array");
+    }
     else if (true)
     {
-      /**
-       * EXPERIMENTAL VERSION
-       **/
-
       ResponseContentFlags responseContent = ResponseContentFlags_ID;
       
-      if (request.isMember(KEY_RESPONSE_CONTENT))
+      if (requestType == FindType_Find)
       {
-        responseContent = ResponseContentFlags_Default;
-
-        for (Json::ArrayIndex i = 0; i < request[KEY_RESPONSE_CONTENT].size(); ++i)
+        if (request.isMember(KEY_RESPONSE_CONTENT))
         {
-          responseContent = static_cast<ResponseContentFlags>(static_cast<uint32_t>(responseContent) | StringToResponseContent(request[KEY_RESPONSE_CONTENT][i].asString()));
+          responseContent = ResponseContentFlags_Default;
+
+          for (Json::ArrayIndex i = 0; i < request[KEY_RESPONSE_CONTENT].size(); ++i)
+          {
+            responseContent = static_cast<ResponseContentFlags>(static_cast<uint32_t>(responseContent) | StringToResponseContent(request[KEY_RESPONSE_CONTENT][i].asString()));
+          }
+        }
+        else if (request.isMember(KEY_EXPAND) && request[KEY_EXPAND].asBool())
+        {
+          responseContent = ResponseContentFlags_ExpandTrue;
         }
       }
-      else if (request.isMember(KEY_EXPAND) && request[KEY_EXPAND].asBool())
+      else if (requestType == FindType_Count)
       {
-        responseContent = ResponseContentFlags_ExpandTrue;
+        responseContent = ResponseContentFlags_INTERNAL_CountResources;
       }
 
       const ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString());
 
-      ResourceFinder finder(level, responseContent);
-      finder.SetDatabaseLimits(context.GetDatabaseLimits(level));
-
-      const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(request, DicomToJsonFormat_Human);
-
-      if (request.isMember(KEY_LIMIT))
-      {
-        int64_t tmp = request[KEY_LIMIT].asInt64();
-        if (tmp < 0)
-        {
-          throw OrthancException(ErrorCode_ParameterOutOfRange,
-                                 "Field \"" + std::string(KEY_LIMIT) + "\" must be a positive integer");
-        }
-        else if (tmp != 0)  // This is for compatibility with Orthanc 1.12.4
-        {
-          finder.SetLimitsCount(static_cast<uint64_t>(tmp));
-        }
-      }
-
-      if (request.isMember(KEY_SINCE))
-      {
-        int64_t tmp = request[KEY_SINCE].asInt64();
-        if (tmp < 0)
-        {
-          throw OrthancException(ErrorCode_ParameterOutOfRange,
-                                 "Field \"" + std::string(KEY_SINCE) + "\" must be a positive integer");
-        }
-        else
-        {
-          finder.SetLimitsSince(static_cast<uint64_t>(tmp));
-        }
-      }
-
-      {
+      ResourceFinder finder(level, responseContent, context.GetFindStorageAccessMode(), context.GetIndex().HasFindSupport());
+
+      DatabaseLookup dicomTagLookup;
+
+      { // common query code
         bool caseSensitive = false;
         if (request.isMember(KEY_CASE_SENSITIVE))
         {
           caseSensitive = request[KEY_CASE_SENSITIVE].asBool();
+
+          if (requestType == FindType_Count && caseSensitive)
+          {
+            /**
+             * Explanation: "/tools/find" uses "lookup_->IsMatch(tags)" in "ResourceFinder::Execute()"
+             * to apply case sensitiveness (as the database stores tags with PN VR in lower case).
+             * But, the purpose of "/tools/count-resources" is to speed up the counting the number of
+             * matching resources: Calling "lookup_->IsMatch(tags)" would require gathering the main
+             * DICOM tags, which would lead to no speedup wrt. "/tools/find".
+             **/
+            throw OrthancException(ErrorCode_ParameterOutOfRange, "Setting \"" + std::string(KEY_CASE_SENSITIVE) +
+                                   "\" to \"true\" is not supported by /tools/count-resources");
+          }
         }
 
         { // DICOM Tag query
-          DatabaseLookup dicomTagLookup;
 
           Json::Value::Members members = request[KEY_QUERY].getMemberNames();
           for (size_t i = 0; i < members.size(); i++)
@@ -3504,17 +3330,17 @@
         }
 
         { // Metadata query
-          Json::Value::Members members = request[KEY_QUERY_METADATA].getMemberNames();
+          Json::Value::Members members = request[KEY_METADATA_QUERY].getMemberNames();
           for (size_t i = 0; i < members.size(); i++)
           {
-            if (request[KEY_QUERY_METADATA][members[i]].type() != Json::stringValue)
+            if (request[KEY_METADATA_QUERY][members[i]].type() != Json::stringValue)
             {
               throw OrthancException(ErrorCode_BadRequest,
                                     "Tag \"" + members[i] + "\" must be associated with a string");
             }
             MetadataType metadata = StringToMetadata(members[i]);
 
-            const std::string value = request[KEY_QUERY_METADATA][members[i]].asString();
+            const std::string value = request[KEY_METADATA_QUERY][members[i]].asString();
 
             if (!value.empty())
             {
@@ -3536,251 +3362,213 @@
             }
           }
         }
-      }
-
-      if (request.isMember(KEY_REQUESTED_TAGS))
-      {
-        std::set<DicomTag> requestedTags;
-        FromDcmtkBridge::ParseListOfTags(requestedTags, request[KEY_REQUESTED_TAGS]);
-        finder.AddRequestedTags(requestedTags);
-      }
-
-      if (request.isMember(KEY_LABELS))  // New in Orthanc 1.12.0
-      {
-        for (Json::Value::ArrayIndex i = 0; i < request[KEY_LABELS].size(); i++)
-        {
-          if (request[KEY_LABELS][i].type() != Json::stringValue)
+
+        { // labels query
+          if (request.isMember(KEY_LABELS))  // New in Orthanc 1.12.0
           {
-            throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_LABELS) + "\" must contain strings");
-          }
-          else
-          {
-            finder.AddLabel(request[KEY_LABELS][i].asString());
+            for (Json::Value::ArrayIndex i = 0; i < request[KEY_LABELS].size(); i++)
+            {
+              if (request[KEY_LABELS][i].type() != Json::stringValue)
+              {
+                throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_LABELS) + "\" must contain strings");
+              }
+              else
+              {
+                finder.AddLabel(request[KEY_LABELS][i].asString());
+              }
+            }
           }
-        }
-      }
-
-      finder.SetLabelsConstraint(LabelsConstraint_All);
-
-      if (request.isMember(KEY_LABELS_CONSTRAINT))
-      {
-        const std::string& s = request[KEY_LABELS_CONSTRAINT].asString();
-        if (s == "All")
-        {
+
           finder.SetLabelsConstraint(LabelsConstraint_All);
-        }
-        else if (s == "Any")
-        {
-          finder.SetLabelsConstraint(LabelsConstraint_Any);
-        }
-        else if (s == "None")
-        {
-          finder.SetLabelsConstraint(LabelsConstraint_None);
-        }
-        else
-        {
-          throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_LABELS_CONSTRAINT) + "\" must be \"All\", \"Any\", or \"None\"");
-        }
-      }
-
-      if (request.isMember(KEY_PARENT_PATIENT)) // New in Orthanc 1.12.5
-      {
-        finder.SetOrthancId(ResourceType_Patient, request[KEY_PARENT_PATIENT].asString());
-      }
-      else if (request.isMember(KEY_PARENT_STUDY))
-      {
-        finder.SetOrthancId(ResourceType_Study, request[KEY_PARENT_STUDY].asString());
-      }
-      else if (request.isMember(KEY_PARENT_SERIES))
-      {
-        finder.SetOrthancId(ResourceType_Series, request[KEY_PARENT_SERIES].asString());
-     }
-
-      if (request.isMember(KEY_ORDER_BY))  // New in Orthanc 1.12.5
-      {
-        for (Json::Value::ArrayIndex i = 0; i < request[KEY_ORDER_BY].size(); i++)
-        {
-          if (request[KEY_ORDER_BY][i].type() != Json::objectValue)
+
+          if (request.isMember(KEY_LABELS_CONSTRAINT))
           {
-            throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY) + "\" must contain objects");
-          }
-          else
-          {
-            const Json::Value& order = request[KEY_ORDER_BY][i];
-            FindRequest::OrderingDirection direction;
-            std::string directionString;
-            std::string typeString;
-
-            if (!order.isMember(KEY_ORDER_BY_KEY) || order[KEY_ORDER_BY_KEY].type() != Json::stringValue)
+            const std::string& s = request[KEY_LABELS_CONSTRAINT].asString();
+            if (s == "All")
             {
-              throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_KEY) + "\" must be a string");
+              finder.SetLabelsConstraint(LabelsConstraint_All);
             }
-
-            if (!order.isMember(KEY_ORDER_BY_DIRECTION) || order[KEY_ORDER_BY_DIRECTION].type() != Json::stringValue)
+            else if (s == "Any")
             {
-              throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_DIRECTION) + "\" must be \"ASC\" or \"DESC\"");
+              finder.SetLabelsConstraint(LabelsConstraint_Any);
             }
-
-            Toolbox::ToLowerCase(directionString,  order[KEY_ORDER_BY_DIRECTION].asString());
-            if (directionString == "asc")
+            else if (s == "None")
             {
-              direction = FindRequest::OrderingDirection_Ascending;
-            }
-            else if (directionString == "desc")
-            {
-              direction = FindRequest::OrderingDirection_Descending;
+              finder.SetLabelsConstraint(LabelsConstraint_None);
             }
             else
             {
-              throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_DIRECTION) + "\" must be \"ASC\" or \"DESC\"");
-            }
-
-            if (!order.isMember(KEY_ORDER_BY_TYPE) || order[KEY_ORDER_BY_TYPE].type() != Json::stringValue)
-            {
-              throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_TYPE) + "\" must be \"DicomTag\" or \"Metadata\"");
-            }
-
-            Toolbox::ToLowerCase(typeString, order[KEY_ORDER_BY_TYPE].asString());
-            if (typeString == "dicomtag")
-            {
-              DicomTag tag = FromDcmtkBridge::ParseTag(order[KEY_ORDER_BY_KEY].asString());
-              finder.AddOrdering(tag, direction);
-            }
-            else if (typeString == "metadata")
-            {
-              MetadataType metadata = StringToMetadata(order[KEY_ORDER_BY_KEY].asString());
-              finder.AddOrdering(metadata, direction);
-            }
-            else
-            {
-              throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_TYPE) + "\" must be \"DicomTag\" or \"Metadata\"");
+              throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_LABELS_CONSTRAINT) + "\" must be \"All\", \"Any\", or \"None\"");
             }
           }
         }
-      }
-
-      Json::Value answer;
-      finder.Execute(answer, context, format, false /* no "Metadata" field */);
-      call.GetOutput().AnswerJson(answer);
-    }
-    else
-    {
-      /**
-       * VERSION IN ORTHANC <= 1.12.4
-       **/
-      bool expand = false;
-      if (request.isMember(KEY_EXPAND))
-      {
-        expand = request[KEY_EXPAND].asBool();
-      }
-
-      bool caseSensitive = false;
-      if (request.isMember(KEY_CASE_SENSITIVE))
-      {
-        caseSensitive = request[KEY_CASE_SENSITIVE].asBool();
-      }
-
-      size_t limit = 0;
-      if (request.isMember(KEY_LIMIT))
-      {
-        int tmp = request[KEY_LIMIT].asInt();
-        if (tmp < 0)
+
+        // parents query
+        if (request.isMember(KEY_PARENT_PATIENT)) // New in Orthanc 1.12.5
         {
-          throw OrthancException(ErrorCode_ParameterOutOfRange,
-                                 "Field \"" + std::string(KEY_LIMIT) + "\" must be a positive integer");
+          finder.SetOrthancId(ResourceType_Patient, request[KEY_PARENT_PATIENT].asString());
         }
-
-        limit = static_cast<size_t>(tmp);
-      }
-
-      size_t since = 0;
-      if (request.isMember(KEY_SINCE))
-      {
-        int tmp = request[KEY_SINCE].asInt();
-        if (tmp < 0)
+        else if (request.isMember(KEY_PARENT_STUDY))
         {
-          throw OrthancException(ErrorCode_ParameterOutOfRange,
-                                 "Field \"" + std::string(KEY_SINCE) + "\" must be a positive integer");
+          finder.SetOrthancId(ResourceType_Study, request[KEY_PARENT_STUDY].asString());
         }
-
-        since = static_cast<size_t>(tmp);
-      }
-
-      std::set<DicomTag> requestedTags;
-
-      if (request.isMember(KEY_REQUESTED_TAGS))
-      {
-        FromDcmtkBridge::ParseListOfTags(requestedTags, request[KEY_REQUESTED_TAGS]);
-      }
-
-      ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString());
-
-      DatabaseLookup query;
-
-      Json::Value::Members members = request[KEY_QUERY].getMemberNames();
-      for (size_t i = 0; i < members.size(); i++)
-      {
-        if (request[KEY_QUERY][members[i]].type() != Json::stringValue)
+        else if (request.isMember(KEY_PARENT_SERIES))
         {
-          throw OrthancException(ErrorCode_BadRequest,
-                                 "Tag \"" + members[i] + "\" must be associated with a string");
-        }
-
-        const std::string value = request[KEY_QUERY][members[i]].asString();
-
-        if (!value.empty())
-        {
-          // An empty string corresponds to an universal constraint,
-          // so we ignore it. This mimics the behavior of class
-          // "OrthancFindRequestHandler"
-          query.AddRestConstraint(FromDcmtkBridge::ParseTag(members[i]), 
-                                  value, caseSensitive, true);
+          finder.SetOrthancId(ResourceType_Series, request[KEY_PARENT_SERIES].asString());
         }
       }
 
-      std::set<std::string> labels;
-
-      if (request.isMember(KEY_LABELS))  // New in Orthanc 1.12.0
+      // response
+      if (requestType == FindType_Find)
       {
-        for (Json::Value::ArrayIndex i = 0; i < request[KEY_LABELS].size(); i++)
+        const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(request, DicomToJsonFormat_Human);
+
+        finder.SetDatabaseLimits(context.GetDatabaseLimits(level));
+
+        if ((request.isMember(KEY_SINCE) && request[KEY_SINCE].asInt64() != 0) &&
+            !finder.CanBeFullyPerformedInDb())
+        {
+            throw OrthancException(ErrorCode_BadRequest,
+                                  "Unable to use '" + std::string(KEY_SINCE) + "' in tools/find when querying tags that are not stored as MainDicomTags in the Database");
+        }
+
+        if (request.isMember(KEY_LIMIT))
         {
-          if (request[KEY_LABELS][i].type() != Json::stringValue)
+          int64_t tmp = request[KEY_LIMIT].asInt64();
+          if (tmp < 0)
+          {
+            throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                  "Field \"" + std::string(KEY_LIMIT) + "\" must be a positive integer");
+          }
+          else if (tmp != 0)  // This is for compatibility with Orthanc 1.12.4
           {
-            throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_LABELS) + "\" must contain strings");
+            finder.SetLimitsCount(static_cast<uint64_t>(tmp));
+          }
+        }
+
+        if (request.isMember(KEY_SINCE))
+        {
+          int64_t tmp = request[KEY_SINCE].asInt64();
+          if (tmp < 0)
+          {
+            throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                  "Field \"" + std::string(KEY_SINCE) + "\" must be a positive integer");
           }
           else
           {
-            labels.insert(request[KEY_LABELS][i].asString());
+            finder.SetLimitsSince(static_cast<uint64_t>(tmp));
           }
         }
-      }
-
-      LabelsConstraint labelsConstraint = LabelsConstraint_All;
-      
-      if (request.isMember(KEY_LABELS_CONSTRAINT))
-      {
-        const std::string& s = request[KEY_LABELS_CONSTRAINT].asString();
-        if (s == "All")
+
+        if (request.isMember(KEY_REQUESTED_TAGS))
         {
-          labelsConstraint = LabelsConstraint_All;
+          std::set<DicomTag> requestedTags;
+          FromDcmtkBridge::ParseListOfTags(requestedTags, request[KEY_REQUESTED_TAGS]);
+          finder.AddRequestedTags(requestedTags);
         }
-        else if (s == "Any")
+
+        if (request.isMember(KEY_ORDER_BY))  // New in Orthanc 1.12.5
         {
-          labelsConstraint = LabelsConstraint_Any;
-        }
-        else if (s == "None")
-        {
-          labelsConstraint = LabelsConstraint_None;
+          for (Json::Value::ArrayIndex i = 0; i < request[KEY_ORDER_BY].size(); i++)
+          {
+            if (request[KEY_ORDER_BY][i].type() != Json::objectValue)
+            {
+              throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY) + "\" must contain objects");
+            }
+            else
+            {
+              const Json::Value& order = request[KEY_ORDER_BY][i];
+              FindRequest::OrderingDirection direction;
+              std::string directionString;
+              std::string typeString;
+
+              if (!order.isMember(KEY_ORDER_BY_KEY) || order[KEY_ORDER_BY_KEY].type() != Json::stringValue)
+              {
+                throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_KEY) + "\" must be a string");
+              }
+
+              if (!order.isMember(KEY_ORDER_BY_DIRECTION) || order[KEY_ORDER_BY_DIRECTION].type() != Json::stringValue)
+              {
+                throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_DIRECTION) + "\" must be \"ASC\" or \"DESC\"");
+              }
+
+              Toolbox::ToLowerCase(directionString,  order[KEY_ORDER_BY_DIRECTION].asString());
+              if (directionString == "asc")
+              {
+                direction = FindRequest::OrderingDirection_Ascending;
+              }
+              else if (directionString == "desc")
+              {
+                direction = FindRequest::OrderingDirection_Descending;
+              }
+              else
+              {
+                throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_DIRECTION) + "\" must be \"ASC\" or \"DESC\"");
+              }
+
+              if (!order.isMember(KEY_ORDER_BY_TYPE) || order[KEY_ORDER_BY_TYPE].type() != Json::stringValue)
+              {
+                throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_TYPE) + "\" must be \"DicomTag\" or \"Metadata\"");
+              }
+
+              Toolbox::ToLowerCase(typeString, order[KEY_ORDER_BY_TYPE].asString());
+              if (Toolbox::StartsWith(typeString, "dicomtag"))
+              {
+                DicomTag tag = FromDcmtkBridge::ParseTag(order[KEY_ORDER_BY_KEY].asString());
+
+                if (typeString == "dicomtagasint")
+                {
+                  finder.AddOrdering(tag, FindRequest::OrderingCast_Int, direction);
+                }
+                else if (typeString == "dicomtagasfloat")
+                {
+                  finder.AddOrdering(tag, FindRequest::OrderingCast_Float, direction);
+                }
+                else
+                {
+                  finder.AddOrdering(tag, FindRequest::OrderingCast_String, direction);
+                }
+              }
+              else if (Toolbox::StartsWith(typeString, "metadata"))
+              {
+                MetadataType metadata = StringToMetadata(order[KEY_ORDER_BY_KEY].asString());
+
+                if (typeString == "metadataasint")
+                {
+                  finder.AddOrdering(metadata, FindRequest::OrderingCast_Int, direction);
+                }
+                else if (typeString == "dicomtagasfloat")
+                {
+                  finder.AddOrdering(metadata, FindRequest::OrderingCast_Float, direction);
+                }
+                else
+                {
+                  finder.AddOrdering(metadata, FindRequest::OrderingCast_String, direction);
+                }
+              }
+              else
+              {
+                throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_TYPE) + "\" must be \"DicomTag\", \"DicomTagAsInt\", \"DicomTagAsFloat\" or \"Metadata\", \"MetadataAsInt\", \"MetadataAsFloat\"");
+              }
+            }
+          }
         }
-        else
-        {
-          throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_LABELS_CONSTRAINT) + "\" must be \"All\", \"Any\", or \"None\"");
-        }
+
+        Json::Value answer;
+        finder.Execute(answer, context, format, false /* no "Metadata" field */);
+        call.GetOutput().AnswerJson(answer);
       }
-      
-      FindVisitor visitor(OrthancRestApi::GetDicomFormat(request, DicomToJsonFormat_Human), context.GetFindStorageAccessMode());
-      context.Apply(visitor, query, level, labels, labelsConstraint, since, limit);
-      visitor.Answer(call.GetOutput(), context, level, expand, requestedTags);
+      else if (requestType == FindType_Count)
+      {
+        uint64_t count = finder.Count(context);
+        Json::Value answer;
+        answer["Count"] = Json::Value::UInt64(count);
+        call.GetOutput().AnswerJson(answer);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
     }
   }
 
@@ -3809,9 +3597,6 @@
       return;
     }
 
-    ServerIndex& index = OrthancRestApi::GetIndex(call);
-    ServerContext& context = OrthancRestApi::GetContext(call);
-
     const bool expand = (!call.HasArgument("expand") ||
                          // this "expand" is the only one to have a false default value to keep backward compatibility
                          call.GetBooleanArgument("expand", false));
@@ -3820,61 +3605,30 @@
     std::set<DicomTag> requestedTags;
     OrthancRestApi::GetRequestedTags(requestedTags, call);
 
-    if (true)
-    {
-      /**
-       * EXPERIMENTAL VERSION
-       **/
-
-      ResourceFinder finder(end, (expand ? ResponseContentFlags_ExpandTrue : ResponseContentFlags_ID));
-      finder.SetOrthancId(start, call.GetUriComponent("id", ""));
-      finder.AddRequestedTags(requestedTags);
-
-      Json::Value answer;
-      finder.Execute(answer, context, format, false /* no "Metadata" field */);
-      call.GetOutput().AnswerJson(answer);
-    }
-    else
-    {
-      /**
-       * VERSION IN ORTHANC <= 1.12.4
-       **/
-      std::list<std::string> a, b, c;
-      a.push_back(call.GetUriComponent("id", ""));
-
-      ResourceType type = start;
-      while (type != end)
-      {
-        b.clear();
-
-        for (std::list<std::string>::const_iterator
-               it = a.begin(); it != a.end(); ++it)
-        {
-          index.GetChildren(c, *it);
-          b.splice(b.begin(), c);
-        }
-
-        type = GetChildResourceType(type);
-
-        a.clear();
-        a.splice(a.begin(), b);
-      }
-
-      AnswerListOfResources2(call.GetOutput(), context, a, type, expand, format, requestedTags, true /* allowStorageAccess */);
-    }
+    ResourceFinder finder(end, 
+                          (expand ? ResponseContentFlags_ExpandTrue : ResponseContentFlags_ID), 
+                          OrthancRestApi::GetContext(call).GetFindStorageAccessMode(),
+                          OrthancRestApi::GetContext(call).GetIndex().HasFindSupport());
+    finder.SetOrthancId(start, call.GetUriComponent("id", ""));
+    finder.AddRequestedTags(requestedTags);
+
+    Json::Value answer;
+    finder.Execute(answer, OrthancRestApi::GetContext(call), format, false /* no "Metadata" field */);
+    call.GetOutput().AnswerJson(answer);
   }
 
 
   static void GetChildInstancesTags(RestApiGetCall& call)
   {
+    const ResourceType level = GetResourceTypeFromUri(call);
+
     if (call.IsDocumentation())
     {
       OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Full);
 
-      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
-      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */);
       call.GetDocumentation()
-        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetTag(GetResourceTypeText(level, true /* plural */, true /* upper case */))
         .SetSummary("Get tags of instances")
         .SetDescription("Get the tags of all the child instances of the DICOM " + r +
                         " whose Orthanc identifier is provided in the URL")
@@ -3882,7 +3636,7 @@
         .SetHttpGetArgument(IGNORE_LENGTH, RestApiCallDocumentation::Type_JsonListOfStrings,
                             "Also include the DICOM tags that are provided in this list, even if their associated value is long", false)
         .AddAnswerType(MimeType_Json, "JSON object associating the Orthanc identifiers of the instances, with the values of their DICOM tags")
-        .SetTruncatedJsonHttpGetSample(GetDocumentationSampleResource(t) + "/instances-tags", 5);
+        .SetTruncatedJsonHttpGetSample(GetDocumentationSampleResource(level) + "/instances-tags", 5);
       return;
     }
 
@@ -3897,7 +3651,7 @@
     typedef std::list<std::string> Instances;
     Instances instances;
 
-    context.GetIndex().GetChildInstances(instances, publicId);  // (*)
+    context.GetIndex().GetChildInstances(instances, publicId, level);  // (*)
 
     Json::Value result = Json::objectValue;
 
@@ -3974,26 +3728,9 @@
     const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human);
 
     Json::Value resource;
-
-    if (true)
+    if (ExpandResource(resource, OrthancRestApi::GetContext(call), currentType, current, format, false))
     {
-      /**
-       * EXPERIMENTAL VERSION
-       **/
-      if (ExpandResource(resource, OrthancRestApi::GetContext(call), currentType, current, format, false))
-      {
-        call.GetOutput().AnswerJson(resource);
-      }
-    }
-    else
-    {
-      /**
-       * VERSION IN ORTHANC <= 1.12.4
-       **/
-      if (OrthancRestApi::GetContext(call).ExpandResource(resource, current, end, format, requestedTags, true /* allowStorageAccess */))
-      {
-        call.GetOutput().AnswerJson(resource);
-      }
+      call.GetOutput().AnswerJson(resource);
     }
   }
 
@@ -4120,7 +3857,7 @@
            study = studies.begin(); study != studies.end(); ++study)
     {
       std::list<std::string> instances;
-      index.GetChildInstances(instances, *study);
+      index.GetChildInstances(instances, *study, ResourceType_Study);
 
       for (std::list<std::string>::const_iterator 
              instance = instances.begin(); instance != instances.end(); ++instance)
@@ -4251,6 +3988,7 @@
 
   static void GetBulkChildren(std::set<std::string>& target,
                               ServerIndex& index,
+                              ResourceType level,
                               const std::set<std::string>& source)
   {
     target.clear();
@@ -4259,7 +3997,7 @@
            it = source.begin(); it != source.end(); ++it)
     {
       std::list<std::string> children;
-      index.GetChildren(children, *it);
+      index.GetChildren(children, level, *it);
 
       for (std::list<std::string>::const_iterator
              child = children.begin(); child != children.end(); ++child)
@@ -4270,24 +4008,6 @@
   }
 
 
-  static void AddMetadata(Json::Value& target,
-                          ServerIndex& index,
-                          const std::string& resource,
-                          ResourceType level)
-  {
-    target = Json::objectValue;
-    
-    std::map<MetadataType, std::string> content;
-    index.GetAllMetadata(content, resource, level);
-    
-    for (std::map<MetadataType, std::string>::const_iterator
-           it = content.begin(); it != content.end(); ++it)
-    {
-      target[EnumerationToString(it->first)] = it->second;
-    }
-  }
-
-
   static void BulkContent(RestApiPostCall& call)
   {
     static const char* const LEVEL = "Level";
@@ -4364,11 +4084,11 @@
               // Need to explore children
               std::set<std::string> current;
               current.insert(*it);
-              
+
               for (;;)
               {
                 std::set<std::string> children;
-                GetBulkChildren(children, index, current);
+                GetBulkChildren(children, index, type, current);
 
                 type = GetChildResourceType(type);
                 if (type == level)
@@ -4424,34 +4144,10 @@
         for (std::set<std::string>::const_iterator
                it = interest.begin(); it != interest.end(); ++it)
         {
-          if (true)
-          {
-            /**
-             * EXPERIMENTAL VERSION
-             **/
-            Json::Value item;
-            if (ExpandResource(item, OrthancRestApi::GetContext(call), level, *it, format, metadata))
-            {
-              answer.append(item);
-            }
-          }
-          else
+          Json::Value item;
+          if (ExpandResource(item, OrthancRestApi::GetContext(call), level, *it, format, metadata))
           {
-            /**
-             * VERSION IN ORTHANC <= 1.12.4
-             **/
-            Json::Value item;
-            std::set<DicomTag> emptyRequestedTags;  // not supported for bulk content
-
-            if (OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags, true /* allowStorageAccess */))
-            {
-              if (metadata)
-              {
-                AddMetadata(item[METADATA], index, *it, level);
-              }
-
-              answer.append(item);
-            }
+            answer.append(item);
           }
         }
       }
@@ -4466,38 +4162,15 @@
         {
           ResourceType level;
           Json::Value item;
-          std::set<DicomTag> emptyRequestedTags;  // not supported for bulk content
-
-          if (true)
+
+          if (index.LookupResourceType(level, *it) &&
+              ExpandResource(item, OrthancRestApi::GetContext(call), level, *it, format, metadata))
           {
-            /**
-             * EXPERIMENTAL VERSION
-             **/
-            if (index.LookupResourceType(level, *it) &&
-                ExpandResource(item, OrthancRestApi::GetContext(call), level, *it, format, metadata))
-            {
-              answer.append(item);
-            }
+            answer.append(item);
           }
           else
           {
-            /**
-             * VERSION IN ORTHANC <= 1.12.4
-             **/
-            if (index.LookupResourceType(level, *it) &&
-                OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags, true /* allowStorageAccess */))
-            {
-              if (metadata)
-              {
-                AddMetadata(item[METADATA], index, *it, level);
-              }
-
-              answer.append(item);
-            }
-            else
-            {
-              CLOG(INFO, HTTP) << "Unknown resource during a bulk content retrieval: " << *it;
-            }
+            CLOG(INFO, HTTP) << "Unknown resource during a bulk content retrieval: " << *it;
           }
         }
       }
@@ -4688,7 +4361,8 @@
     }
 
     Register("/tools/lookup", Lookup);
-    Register("/tools/find", Find);
+    Register("/tools/find", Find<FindType_Find>);
+    Register("/tools/count-resources", Find<FindType_Count>);
 
     Register("/patients/{id}/studies", GetChildResources<ResourceType_Patient, ResourceType_Study>);
     Register("/patients/{id}/series", GetChildResources<ResourceType_Patient, ResourceType_Series>);
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -75,6 +75,7 @@
     static const char* const DATABASE_BACKEND_PLUGIN = "DatabaseBackendPlugin";
     static const char* const DATABASE_VERSION = "DatabaseVersion";
     static const char* const DATABASE_SERVER_IDENTIFIER = "DatabaseServerIdentifier";
+    static const char* const DEFAULT_RETRIEVE_METHOD = "DefaultRetrieveMethod";
     static const char* const DICOM_AET = "DicomAet";
     static const char* const DICOM_PORT = "DicomPort";
     static const char* const HTTP_PORT = "HttpPort";
@@ -115,6 +116,7 @@
                         "Information about the installed storage area plugin (`null` if no such plugin is installed)")
         .SetAnswerField(DATABASE_BACKEND_PLUGIN, RestApiCallDocumentation::Type_String,
                         "Information about the installed database index plugin (`null` if no such plugin is installed)")
+        .SetAnswerField(DEFAULT_RETRIEVE_METHOD, RestApiCallDocumentation::Type_String, "The DefaultRetrieveMethod configuration")
         .SetAnswerField(DICOM_AET, RestApiCallDocumentation::Type_String, "The DICOM AET of Orthanc")
         .SetAnswerField(DICOM_PORT, RestApiCallDocumentation::Type_Number, "The port to the DICOM server of Orthanc")
         .SetAnswerField(HTTP_PORT, RestApiCallDocumentation::Type_Number, "The port to the HTTP server of Orthanc")
@@ -173,6 +175,7 @@
       result[MAXIMUM_STORAGE_SIZE] = lock.GetConfiguration().GetUnsignedIntegerParameter(MAXIMUM_STORAGE_SIZE, 0); // New in Orthanc 1.11.3
       result[MAXIMUM_PATIENT_COUNT] = lock.GetConfiguration().GetUnsignedIntegerParameter(MAXIMUM_PATIENT_COUNT, 0); // New in Orthanc 1.12.4
       result[MAXIMUM_STORAGE_MODE] = lock.GetConfiguration().GetStringParameter(MAXIMUM_STORAGE_MODE, "Recycle"); // New in Orthanc 1.11.3
+      result[DEFAULT_RETRIEVE_METHOD] = lock.GetConfiguration().GetStringParameter(DEFAULT_RETRIEVE_METHOD, "C-MOVE");
     }
 
     result[STORAGE_AREA_PLUGIN] = Json::nullValue;
@@ -478,6 +481,33 @@
     AnswerAcceptedTransferSyntaxes(call);
   }
 
+  static void GetAcceptedSopClasses(RestApiGetCall& call)
+  {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Get accepted SOPClassUID")
+        .SetDescription("Get the list of SOP Class UIDs that are accepted "
+                        "by Orthanc C-STORE SCP. This corresponds to the configuration options "
+                        "`AcceptedSopClasses` and `RejectedSopClasses`.")
+        .AddAnswerType(MimeType_Json, "JSON array containing the SOP Class UIDs");
+      return;
+    }
+
+    std::set<std::string> sopClasses;
+    OrthancRestApi::GetContext(call).GetAcceptedSopClasses(sopClasses, 0);
+    
+    Json::Value json = Json::arrayValue;
+    for (std::set<std::string>::const_iterator
+           sop = sopClasses.begin(); sop != sopClasses.end(); ++sop)
+    {
+      json.append(*sop);
+    }
+    
+    call.GetOutput().AnswerJson(json);
+  }
+
 
   static void GetUnknownSopClassAccepted(RestApiGetCall& call)
   {
@@ -1200,5 +1230,8 @@
     Register("/tools/unknown-sop-class-accepted", SetUnknownSopClassAccepted);
 
     Register("/tools/labels", ListAllLabels);  // New in Orthanc 1.12.0
+
+    Register("/tools/accepted-sop-classes", GetAcceptedSopClasses);
+
   }
 }
--- a/OrthancServer/Sources/OrthancWebDav.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancWebDav.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -74,8 +74,7 @@
                          MetadataType metadata)
   {
     std::string value;
-    int64_t revision;  // Ignored
-    if (context.GetIndex().LookupMetadata(value, revision, publicId, level, metadata))
+    if (context.GetIndex().LookupMetadata(value, publicId, level, metadata))
     {
       ParseTime(target, value);
     }
@@ -86,99 +85,6 @@
   }
 
   
-  class OrthancWebDav::DicomIdentifiersVisitor : public ServerContext::ILookupVisitor
-  {
-  private:
-    ServerContext&  context_;
-    bool            isComplete_;
-    Collection&     target_;
-    ResourceType    level_;
-
-  public:
-    DicomIdentifiersVisitor(ServerContext& context,
-                            Collection&  target,
-                            ResourceType level) :
-      context_(context),
-      isComplete_(false),
-      target_(target),
-      level_(level)
-    {
-    }
-      
-    virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
-    {
-      return false;   // (*)
-    }
-      
-    virtual void MarkAsComplete() ORTHANC_OVERRIDE
-    {
-      isComplete_ = true;  // TODO
-    }
-
-    virtual void Visit(const std::string& publicId,
-                       const std::string& instanceId   /* unused     */,
-                       const DicomMap& mainDicomTags,
-                       const Json::Value* dicomAsJson  /* unused (*) */)  ORTHANC_OVERRIDE
-    {
-      DicomTag tag(0, 0);
-      MetadataType timeMetadata;
-
-      switch (level_)
-      {
-        case ResourceType_Study:
-          tag = DICOM_TAG_STUDY_INSTANCE_UID;
-          timeMetadata = MetadataType_LastUpdate;
-          break;
-
-        case ResourceType_Series:
-          tag = DICOM_TAG_SERIES_INSTANCE_UID;
-          timeMetadata = MetadataType_LastUpdate;
-          break;
-        
-        case ResourceType_Instance:
-          tag = DICOM_TAG_SOP_INSTANCE_UID;
-          timeMetadata = MetadataType_Instance_ReceptionDate;
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-        
-      std::string s;
-      if (mainDicomTags.LookupStringValue(s, tag, false) &&
-          !s.empty())
-      {
-        std::unique_ptr<Resource> resource;
-
-        if (level_ == ResourceType_Instance)
-        {
-          FileInfo info;
-          int64_t revision;  // Ignored
-          if (context_.GetIndex().LookupAttachment(info, revision, publicId, FileContentType_Dicom))
-          {
-            std::unique_ptr<File> f(new File(s + ".dcm"));
-            f->SetMimeType(MimeType_Dicom);
-            f->SetContentLength(info.GetUncompressedSize());
-            resource.reset(f.release());
-          }
-        }
-        else
-        {
-          resource.reset(new Folder(s));
-        }
-
-        if (resource.get() != NULL)
-        {
-          boost::posix_time::ptime t;
-          LookupTime(t, context_, publicId, level_, timeMetadata);
-          resource->SetCreationTime(t);
-          target_.AddResource(resource.release());
-        }
-      }
-    }
-  };
-
-  
   class OrthancWebDav::DicomIdentifiersVisitorV2 : public ResourceFinder::IVisitor
   {
   private:
@@ -238,7 +144,8 @@
         if (resource.GetLevel() == ResourceType_Instance)
         {
           FileInfo info;
-          if (resource.LookupAttachment(info, FileContentType_Dicom))
+          int64_t revision;
+          if (resource.LookupAttachment(info, revision, FileContentType_Dicom))
           {
             std::unique_ptr<File> f(new File(uid + ".dcm"));
             f->SetMimeType(MimeType_Dicom);
@@ -271,58 +178,6 @@
   };
 
 
-  class OrthancWebDav::DicomFileVisitor : public ServerContext::ILookupVisitor
-  {
-  private:
-    ServerContext&  context_;
-    bool            success_;
-    std::string&    target_;
-    boost::posix_time::ptime&  time_;
-
-  public:
-    DicomFileVisitor(ServerContext& context,
-                     std::string& target,
-                     boost::posix_time::ptime& time) :
-      context_(context),
-      success_(false),
-      target_(target),
-      time_(time)
-    {
-    }
-
-    bool IsSuccess() const
-    {
-      return success_;
-    }
-
-    virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
-    {
-      return false;   // (*)
-    }
-      
-    virtual void MarkAsComplete() ORTHANC_OVERRIDE
-    {
-    }
-
-    virtual void Visit(const std::string& publicId,
-                       const std::string& instanceId   /* unused     */,
-                       const DicomMap& mainDicomTags,
-                       const Json::Value* dicomAsJson  /* unused (*) */)  ORTHANC_OVERRIDE
-    {
-      if (success_)
-      {
-        success_ = false;  // Two matches => Error
-      }
-      else
-      {
-        LookupTime(time_, context_, publicId, ResourceType_Instance, MetadataType_Instance_ReceptionDate);
-        context_.ReadDicom(target_, publicId);
-        success_ = true;
-      }
-    }
-  };
-  
-
   class OrthancWebDav::DicomFileVisitorV2 : public ResourceFinder::IVisitor
   {
   private:
@@ -377,67 +232,6 @@
   };
 
 
-  class OrthancWebDav::OrthancJsonVisitor : public ServerContext::ILookupVisitor
-  {
-  private:
-    ServerContext&  context_;
-    bool            success_;
-    std::string&    target_;
-    ResourceType    level_;
-
-  public:
-    OrthancJsonVisitor(ServerContext& context,
-                       std::string& target,
-                       ResourceType level) :
-      context_(context),
-      success_(false),
-      target_(target),
-      level_(level)
-    {
-    }
-
-    bool IsSuccess() const
-    {
-      return success_;
-    }
-
-    virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
-    {
-      return false;   // (*)
-    }
-      
-    virtual void MarkAsComplete() ORTHANC_OVERRIDE
-    {
-    }
-
-    virtual void Visit(const std::string& publicId,
-                       const std::string& instanceId   /* unused     */,
-                       const DicomMap& mainDicomTags,
-                       const Json::Value* dicomAsJson  /* unused (*) */)  ORTHANC_OVERRIDE
-    {
-      Json::Value resource;
-      std::set<DicomTag> emptyRequestedTags;  // not supported for webdav
-
-      if (context_.ExpandResource(resource, publicId, level_, DicomToJsonFormat_Human, emptyRequestedTags, true /* allowStorageAccess */))
-      {
-        if (success_)
-        {
-          success_ = false;  // Two matches => Error
-        }
-        else
-        {
-          target_ = resource.toStyledString();
-
-          // Replace UNIX newlines with DOS newlines 
-          boost::replace_all(target_, "\n", "\r\n");
-
-          success_ = true;
-        }
-      }
-    }
-  };
-
-
   class OrthancWebDav::ResourcesIndex : public boost::noncopyable
   {
   public:
@@ -642,7 +436,7 @@
         std::list<std::string> resources;
         try
         {
-          context_.GetIndex().GetChildren(resources, parentSeries_);
+          context_.GetIndex().GetChildren(resources, ResourceType_Series, parentSeries_);
         }
         catch (OrthancException&)
         {
@@ -658,7 +452,7 @@
 
           FileInfo info;
           int64_t revision;  // Ignored
-          if (context_.GetIndex().LookupAttachment(info, revision, *it, FileContentType_Dicom))
+          if (context_.GetIndex().LookupAttachment(info, revision, ResourceType_Instance, *it, FileContentType_Dicom))
           {
             std::unique_ptr<File> resource(new File(*it + ".dcm"));
             resource->SetMimeType(MimeType_Dicom);
@@ -711,7 +505,7 @@
         std::list<std::string> resources;
         try
         {
-          context_.GetIndex().GetChildren(resources, parentSeries_);
+          context_.GetIndex().GetChildren(resources, ResourceType_Series, parentSeries_);
         }
         catch (OrthancException&)
         {
@@ -1029,6 +823,7 @@
   class OrthancWebDav::SingleDicomResource : public ListOfResources
   {
   private:
+    ResourceType parentLevel_;
     std::string  parentId_;
     
   protected: 
@@ -1036,7 +831,7 @@
     {
       try
       {
-        GetContext().GetIndex().GetChildren(resources, parentId_);
+        GetContext().GetIndex().GetChildren(resources, parentLevel_, parentId_);
       }
       catch (OrthancException&)
       {
@@ -1068,6 +863,7 @@
                         const std::string& parentId,
                         const Templates& templates) :
       ListOfResources(context, level, templates),
+      parentLevel_(GetParentResourceType(level)),
       parentId_(parentId)
     {
     }
@@ -1142,7 +938,7 @@
 
       Visitor visitor(resources);
 
-      ResourceFinder finder(ResourceType_Study, ResponseContentFlags_ID);
+      ResourceFinder finder(ResourceType_Study, ResponseContentFlags_ID, GetContext().GetFindStorageAccessMode(), GetContext().GetIndex().HasFindSupport());
       finder.SetDatabaseLookup(query);
       finder.Execute(visitor, GetContext());
     }
@@ -1220,7 +1016,7 @@
 
       Visitor visitor;
 
-      ResourceFinder finder(ResourceType_Study, ResponseContentFlags_ID);
+      ResourceFinder finder(ResourceType_Study, ResponseContentFlags_ID, context_.GetFindStorageAccessMode(), context_.GetIndex().HasFindSupport());
       finder.SetDatabaseLookup(query);
       finder.Execute(visitor, context_);
 
@@ -1569,12 +1365,11 @@
     {
       DatabaseLookup query;
       ResourceType level;
-      size_t limit = 0;  // By default, no limits
 
       if (path.size() == 1)
       {
         level = ResourceType_Study;
-        limit = 0;  // TODO - Should we limit here?
+        // TODO - Should we limit here?
       }
       else if (path.size() == 2)
       {
@@ -1599,47 +1394,31 @@
         return false;
       }
 
-      if (true)
-      {
-        /**
-         * EXPERIMENTAL VERSION
-         **/
-
-        ResourceFinder finder(level, ResponseContentFlags_ID);
-        finder.SetDatabaseLookup(query);
-        finder.SetRetrieveMetadata(true);
+      ResourceFinder finder(level, ResponseContentFlags_ID, context_.GetFindStorageAccessMode(), context_.GetIndex().HasFindSupport());
+      finder.SetDatabaseLookup(query);
+      finder.SetRetrieveMetadata(true);
 
-        switch (level)
-        {
-          case ResourceType_Study:
-            finder.AddRequestedTag(DICOM_TAG_STUDY_INSTANCE_UID);
-            break;
-
-          case ResourceType_Series:
-            finder.AddRequestedTag(DICOM_TAG_SERIES_INSTANCE_UID);
-            break;
+      switch (level)
+      {
+        case ResourceType_Study:
+          finder.AddRequestedTag(DICOM_TAG_STUDY_INSTANCE_UID);
+          break;
 
-          case ResourceType_Instance:
-            finder.AddRequestedTag(DICOM_TAG_SOP_INSTANCE_UID);
-            finder.SetRetrieveAttachments(true);
-            break;
-
-          default:
-            throw OrthancException(ErrorCode_InternalError);
-        }
+        case ResourceType_Series:
+          finder.AddRequestedTag(DICOM_TAG_SERIES_INSTANCE_UID);
+          break;
 
-        DicomIdentifiersVisitorV2 visitor(collection);
-        finder.Execute(visitor, context_);
+        case ResourceType_Instance:
+          finder.AddRequestedTag(DICOM_TAG_SOP_INSTANCE_UID);
+          finder.SetRetrieveAttachments(true);
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
       }
-      else
-      {
-        /**
-         * VERSION IN ORTHANC <= 1.12.4
-         **/
 
-        DicomIdentifiersVisitor visitor(context_, collection, level);
-        context_.Apply(visitor, query, level, 0 /* since */, limit);
-      }
+      DicomIdentifiersVisitorV2 visitor(collection);
+      finder.Execute(visitor, context_);
 
       return true;
     }
@@ -1666,7 +1445,7 @@
                              ResourceType level,
                              const DatabaseLookup& query)
   {
-    ResourceFinder finder(level, ResponseContentFlags_ExpandTrue);
+    ResourceFinder finder(level, ResponseContentFlags_ExpandTrue, context.GetFindStorageAccessMode(), context.GetIndex().HasFindSupport());
     finder.SetDatabaseLookup(query);
 
     Json::Value expanded;
@@ -1707,23 +1486,7 @@
                                 true /* case sensitive */, true /* mandatory tag */);
 
         mime = MimeType_Json;
-
-        if (true)
-        {
-          /**
-           * EXPERIMENTAL VERSION
-           **/
-          return GetOrthancJson(content, context_, ResourceType_Study, query);
-        }
-        else
-        {
-          /**
-           * VERSION IN ORTHANC <= 1.12.4
-           **/
-          OrthancJsonVisitor visitor(context_, content, ResourceType_Study);
-          context_.Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */);
-          return visitor.IsSuccess();
-        }
+        return GetOrthancJson(content, context_, ResourceType_Study, query);
       }
       else if (path.size() == 4 &&
                path[3] == SERIES_INFO)
@@ -1735,23 +1498,7 @@
                                 true /* case sensitive */, true /* mandatory tag */);
       
         mime = MimeType_Json;
-
-        if (true)
-        {
-          /**
-           * EXPERIMENTAL VERSION
-           **/
-          return GetOrthancJson(content, context_, ResourceType_Series, query);
-        }
-        else
-        {
-          /**
-           * VERSION IN ORTHANC <= 1.12.4
-           **/
-          OrthancJsonVisitor visitor(context_, content, ResourceType_Series);
-          context_.Apply(visitor, query, ResourceType_Series, 0 /* since */, 0 /* no limit */);
-          return visitor.IsSuccess();
-        }
+        return GetOrthancJson(content, context_, ResourceType_Series, query);
       }
       else if (path.size() == 4 &&
                boost::ends_with(path[3], ".dcm"))
@@ -1768,30 +1515,15 @@
       
         mime = MimeType_Dicom;
 
-        if (true)
-        {
-          /**
-           * EXPERIMENTAL VERSION
-           **/
-          ResourceFinder finder(ResourceType_Instance, ResponseContentFlags_ID);
-          finder.SetDatabaseLookup(query);
-          finder.SetRetrieveMetadata(true);
-          finder.SetRetrieveAttachments(true);
+        ResourceFinder finder(ResourceType_Instance, ResponseContentFlags_ID, context_.GetFindStorageAccessMode(), context_.GetIndex().HasFindSupport());
+        finder.SetDatabaseLookup(query);
+        finder.SetRetrieveMetadata(true);
+        finder.SetRetrieveAttachments(true);
 
-          DicomFileVisitorV2 visitor(context_, content, modificationTime);
-          finder.Execute(visitor, context_);
+        DicomFileVisitorV2 visitor(context_, content, modificationTime);
+        finder.Execute(visitor, context_);
 
-          return visitor.IsSuccess();
-        }
-        else
-        {
-          /**
-           * VERSION IN ORTHANC <= 1.12.4
-           **/
-          DicomFileVisitor visitor(context_, content, modificationTime);
-          context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 0 /* no limit */);
-          return visitor.IsSuccess();
-        }
+        return visitor.IsSuccess();
       }
       else
       {
@@ -1913,7 +1645,7 @@
 
         DicomDeleteVisitor visitor(context_, level);
 
-        ResourceFinder finder(level, ResponseContentFlags_ID);
+        ResourceFinder finder(level, ResponseContentFlags_ID, context_.GetFindStorageAccessMode(), context_.GetIndex().HasFindSupport());
         finder.SetDatabaseLookup(query);
         finder.Execute(visitor, context_);
         return true;
--- a/OrthancServer/Sources/OrthancWebDav.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/OrthancWebDav.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -38,9 +38,7 @@
     typedef std::map<ResourceType, std::string>  Templates;
 
     class DicomDeleteVisitor;
-    class DicomFileVisitor;
     class DicomFileVisitorV2;
-    class DicomIdentifiersVisitor;  
     class DicomIdentifiersVisitorV2;
     class InstancesOfSeries;
     class InternalNode;
--- a/OrthancServer/Sources/PrecompiledHeadersServer.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/PrecompiledHeadersServer.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/PrecompiledHeadersServer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/PrecompiledHeadersServer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/QueryRetrieveHandler.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/QueryRetrieveHandler.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -79,7 +79,7 @@
           params.SetTimeout(timeout_);
         }
         
-        DicomControlUserConnection connection(params);
+        DicomControlUserConnection connection(params, static_cast<ScuOperationFlags>(ScuOperationFlags_Find));
         connection.Find(answers_, level_, fixed, findNormalized_);
       }
 
--- a/OrthancServer/Sources/QueryRetrieveHandler.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/QueryRetrieveHandler.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ResourceFinder.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ResourceFinder.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -56,7 +56,7 @@
     if (request_.GetLevel() == parentLevel)
     {
       requestedComputedTags_.insert(tag);
-      request_.GetChildrenSpecification(childLevel).SetRetrieveIdentifiers(true);
+      request_.GetChildrenSpecification(childLevel).SetRetrieveCount(true);
     }
   }
 
@@ -68,8 +68,7 @@
   {
     if (IsRequestedComputedTag(tag))
     {
-      const std::set<std::string>& children = resource.GetChildrenIdentifiers(level);
-      requestedTags.SetValue(tag, boost::lexical_cast<std::string>(children.size()), false);
+      requestedTags.SetValue(tag, boost::lexical_cast<std::string>(resource.GetChildrenCount(level)), false);
     }
   }
 
@@ -326,7 +325,8 @@
         if (responseContent_ & ResponseContentFlags_AttachmentsLegacy)
         {
           FileInfo info;
-          if (resource.LookupAttachment(info, FileContentType_Dicom))
+          int64_t revision;
+          if (resource.LookupAttachment(info, revision, FileContentType_Dicom))
           {
             target["FileSize"] = static_cast<Json::UInt64>(info.GetUncompressedSize());
             target["FileUuid"] = info.GetUuid();
@@ -438,13 +438,13 @@
 
     if (responseContent_ & ResponseContentFlags_Metadata)  // new in Orthanc 1.12.4
     {
-      const std::map<MetadataType, std::string>& m = resource.GetMetadata(resource.GetLevel());
+      const std::map<MetadataType, FindResponse::MetadataContent>& m = resource.GetMetadata(resource.GetLevel());
 
       Json::Value metadata = Json::objectValue;
 
-      for (std::map<MetadataType, std::string>::const_iterator it = m.begin(); it != m.end(); ++it)
+      for (std::map<MetadataType, FindResponse::MetadataContent>::const_iterator it = m.begin(); it != m.end(); ++it)
       {
-        metadata[EnumerationToString(it->first)] = it->second;
+        metadata[EnumerationToString(it->first)] = it->second.GetValue();
       }
 
       target["Metadata"] = metadata;
@@ -472,66 +472,92 @@
   }
 
 
-  void ResourceFinder::UpdateRequestLimits()
+  void ResourceFinder::UpdateRequestLimits(ServerContext& context)
   {
-    // By default, use manual paging
-    pagingMode_ = PagingMode_FullManual;
+    if (context.GetIndex().HasFindSupport())  // in this case, limits are fully implemented in DB
+    {
+      pagingMode_ = PagingMode_FullDatabase;
 
-    if (databaseLimits_ != 0)
-    {
-      request_.SetLimits(0, databaseLimits_ + 1);
+      if (hasLimitsSince_ || hasLimitsCount_)
+      {
+        pagingMode_ = PagingMode_FullDatabase;
+        if (databaseLimits_ != 0 && limitsCount_ > databaseLimits_)
+        {
+          LOG(WARNING) << "ResourceFinder: \"Limit\" is larger than LimitFindResults/LimitFindInstances configurations, using limit from the configuration file";
+          limitsCount_ = databaseLimits_;
+        }
+
+        request_.SetLimits(limitsSince_, limitsCount_);
+      }
+      else if (databaseLimits_ != 0)
+      {
+        request_.SetLimits(0, databaseLimits_);
+      }
+
     }
     else
     {
-      request_.ClearLimits();
-    }
-
-    if (lookup_.get() == NULL &&
-        (hasLimitsSince_ || hasLimitsCount_))
-    {
-      pagingMode_ = PagingMode_FullDatabase;
-      request_.SetLimits(limitsSince_, limitsCount_);
-    }
+      // By default, use manual paging
+      pagingMode_ = PagingMode_FullManual;
 
-    if (lookup_.get() != NULL &&
-        isSimpleLookup_ &&
-        (hasLimitsSince_ || hasLimitsCount_))
-    {
-      /**
-       * TODO-FIND: "IDatabaseWrapper::ApplyLookupResources()" only
-       * accept the "limit" argument.  The "since" must be implemented
-       * manually.
-       **/
-
-      if (hasLimitsSince_ &&
-          limitsSince_ != 0)
+      if (databaseLimits_ != 0)
       {
-        pagingMode_ = PagingMode_ManualSkip;
-        request_.SetLimits(0, limitsCount_ + limitsSince_);
+        request_.SetLimits(0, databaseLimits_ + 1);
       }
       else
       {
+        request_.ClearLimits();
+      }
+
+      if (lookup_.get() == NULL &&
+          (hasLimitsSince_ || hasLimitsCount_))
+      {
         pagingMode_ = PagingMode_FullDatabase;
-        request_.SetLimits(0, limitsCount_);
+        request_.SetLimits(limitsSince_, limitsCount_);
+      }
+
+      if (lookup_.get() != NULL &&
+          canBeFullyPerformedInDb_ &&
+          (hasLimitsSince_ || hasLimitsCount_))
+      {
+        /**
+         * TODO-FIND: "IDatabaseWrapper::ApplyLookupResources()" only
+         * accept the "limit" argument.  The "since" must be implemented
+         * manually.
+         **/
+
+        if (hasLimitsSince_ &&
+            limitsSince_ != 0)
+        {
+          pagingMode_ = PagingMode_ManualSkip;
+          request_.SetLimits(0, limitsCount_ + limitsSince_);
+        }
+        else
+        {
+          pagingMode_ = PagingMode_FullDatabase;
+          request_.SetLimits(0, limitsCount_);
+        }
       }
     }
-
-    // TODO-FIND: More cases could be added, depending on "GetDatabaseCapabilities()"
   }
 
 
   ResourceFinder::ResourceFinder(ResourceType level,
-                                 ResponseContentFlags responseContent) :
+                                 ResponseContentFlags responseContent,
+                                 FindStorageAccessMode storageAccessMode,
+                                 bool supportsChildExistQueries) :
     request_(level),
     databaseLimits_(0),
     isSimpleLookup_(true),
+    canBeFullyPerformedInDb_(true),
     pagingMode_(PagingMode_FullManual),
     hasLimitsSince_(false),
     hasLimitsCount_(false),
     limitsSince_(0),
     limitsCount_(0),
     responseContent_(responseContent),
-    allowStorageAccess_(true),
+    storageAccessMode_(storageAccessMode),
+    supportsChildExistQueries_(supportsChildExistQueries),
     isWarning002Enabled_(false),
     isWarning004Enabled_(false),
     isWarning005Enabled_(false)
@@ -543,8 +569,6 @@
       isWarning005Enabled_ = lock.GetConfiguration().IsWarningEnabled(Warnings_005_RequestingTagFromLowerResourceLevel);
     }
 
-    UpdateRequestLimits();
-
     request_.SetRetrieveMainDicomTags(responseContent_ & ResponseContentFlags_MainDicomTags);
     request_.SetRetrieveMetadata((responseContent_ & ResponseContentFlags_Metadata) || (responseContent_ & ResponseContentFlags_MetadataLegacy));
     request_.SetRetrieveLabels(responseContent_ & ResponseContentFlags_Labels);
@@ -587,7 +611,6 @@
   void ResourceFinder::SetDatabaseLimits(uint64_t limits)
   {
     databaseLimits_ = limits;
-    UpdateRequestLimits();
   }
 
 
@@ -601,7 +624,6 @@
     {
       hasLimitsSince_ = true;
       limitsSince_ = since;
-      UpdateRequestLimits();
     }
   }
 
@@ -616,7 +638,6 @@
     {
       hasLimitsCount_ = true;
       limitsCount_ = count;
-      UpdateRequestLimits();
     }
   }
 
@@ -646,7 +667,7 @@
       }
     }
 
-    isSimpleLookup_ = registry.NormalizeLookup(request_.GetDicomTagConstraints(), lookup, request_.GetLevel());
+    isSimpleLookup_ = registry.NormalizeLookup(canBeFullyPerformedInDb_, request_.GetDicomTagConstraints(), lookup, request_.GetLevel(), supportsChildExistQueries_);
 
     // "request_.GetDicomTagConstraints()" only contains constraints on main DICOM tags
 
@@ -663,19 +684,20 @@
       }
       else
       {
-        LOG(WARNING) << "Executing a database lookup at level " << EnumerationToString(request_.GetLevel())
-                     << " on main DICOM tag " << constraint.GetTag().Format() << " from an inferior level ("
-                     << EnumerationToString(constraint.GetLevel()) << "), this will return no result";
+        if (!supportsChildExistQueries_ || (constraint.GetLevel() != ResourceType_Series && constraint.GetTag() != DICOM_TAG_MODALITIES_IN_STUDY))
+        {
+          LOG(WARNING) << "Executing a database lookup at level " << EnumerationToString(request_.GetLevel())
+                      << " on main DICOM tag " << constraint.GetTag().Format() << " from an inferior level ("
+                      << EnumerationToString(constraint.GetLevel()) << "), this will return no result";
+        }
       }
 
-      if (IsComputedTag(constraint.GetTag()))
+      if (IsComputedTag(constraint.GetTag()) && constraint.GetTag() != DICOM_TAG_MODALITIES_IN_STUDY)
       {
         // Sanity check
         throw OrthancException(ErrorCode_InternalError);
       }
     }
-
-    UpdateRequestLimits();
   }
 
 
@@ -827,7 +849,8 @@
 
       if (request_.GetLevel() != ResourceType_Instance)
       {
-        request_.SetRetrieveOneInstanceMetadataAndAttachments(true);
+        // only retrieve the instance attachments and metadata if we have allowed access to the storage
+        request_.SetRetrieveOneInstanceMetadataAndAttachments(IsStorageAccessAllowed());
       }
     }
   }
@@ -891,6 +914,19 @@
   }
 
 
+  static void ConvertMetadata(std::map<MetadataType, std::string>& converted,
+                              const FindResponse::Resource& resource)
+  {
+    const std::map<MetadataType, FindResponse::MetadataContent> metadata = resource.GetMetadata(ResourceType_Instance);
+
+    for (std::map<MetadataType, FindResponse::MetadataContent>::const_iterator
+           it = metadata.begin(); it != metadata.end(); ++it)
+    {
+      converted[it->first] = it->second.GetValue();
+    }
+  }
+
+
   static void ReadMissingTagsFromStorageArea(DicomMap& requestedTags,
                                              ServerContext& context,
                                              const FindRequest& request,
@@ -920,7 +956,10 @@
     {
       LOG(INFO) << "Will retrieve missing DICOM tags from instance: " << resource.GetIdentifier();
 
-      context.ReadDicomAsJson(tmpDicomAsJson, resource.GetIdentifier(), resource.GetMetadata(ResourceType_Instance),
+      std::map<MetadataType, std::string> converted;
+      ConvertMetadata(converted, resource);
+
+      context.ReadDicomAsJson(tmpDicomAsJson, resource.GetIdentifier(), converted,
                               resource.GetAttachments(), missingTags /* ignoreTagLength */);
     }
     else if (request.GetLevel() != ResourceType_Instance &&
@@ -963,7 +1002,10 @@
 
         if (request.GetLevel() == ResourceType_Instance)
         {
-          context.ReadDicomAsJson(tmpDicomAsJson, response.GetIdentifier(), response.GetMetadata(ResourceType_Instance),
+          std::map<MetadataType, std::string> converted;
+          ConvertMetadata(converted, resource);
+
+          context.ReadDicomAsJson(tmpDicomAsJson, response.GetIdentifier(), converted,
                                   response.GetAttachments(), missingTags /* ignoreTagLength */);
         }
         else
@@ -991,17 +1033,43 @@
     }
   }
 
+  uint64_t ResourceFinder::Count(ServerContext& context) const
+  {
+    if (!canBeFullyPerformedInDb_)
+    {
+      throw OrthancException(ErrorCode_BadRequest,
+                            "Unable to count resources when querying tags that are not stored as MainDicomTags in the Database or when using case sensitive queries.");
+    }
+
+    uint64_t count = 0;
+    context.GetIndex().ExecuteCount(count, request_);
+    return count;
+  }
+
 
   void ResourceFinder::Execute(IVisitor& visitor,
-                               ServerContext& context) const
+                               ServerContext& context)
   {
+    UpdateRequestLimits(context);
+
+    if ((request_.HasLimits() && request_.GetLimitsSince() > 0) &&
+        !canBeFullyPerformedInDb_)
+    {
+      throw OrthancException(ErrorCode_BadRequest,
+                             "nable to use 'Since' when finding resources when querying against Dicom Tags that are not in the MainDicomTags or when using CaseSenstive queries.");
+    }
+
     bool isWarning002Enabled = false;
     bool isWarning004Enabled = false;
+    bool isWarning006Enabled = false;
+    bool isWarning007Enabled = false;
 
     {
       OrthancConfiguration::ReaderLock lock;
       isWarning002Enabled = lock.GetConfiguration().IsWarningEnabled(Warnings_002_InconsistentDicomTagsInDb);
       isWarning004Enabled = lock.GetConfiguration().IsWarningEnabled(Warnings_004_NoMainDicomTagsSignature);
+      isWarning006Enabled = lock.GetConfiguration().IsWarningEnabled(Warnings_006_RequestingTagFromMetaHeader);
+      isWarning007Enabled = lock.GetConfiguration().IsWarningEnabled(Warnings_007_MissingRequestedTagsNotReadFromDisk);
     }
 
     FindResponse response;
@@ -1059,18 +1127,43 @@
         InjectRequestedTags(outRequestedTags, remainingRequestedTags, resource, ResourceType_Series);
         InjectRequestedTags(outRequestedTags, remainingRequestedTags, resource, ResourceType_Instance);
 
+        if (DicomMap::HasMetaInformationTags(remainingRequestedTags)) // we are not able to retrieve meta information in RequestedTags
+        {
+          std::set<DicomTag> metaTagsToRemove;
+          for (std::set<DicomTag>::const_iterator it = remainingRequestedTags.begin(); it != remainingRequestedTags.end(); ++it)
+          {
+            if (it->GetGroup() == 0x0002)
+            {
+              metaTagsToRemove.insert(*it);
+            }
+          }
+
+          if (isWarning006Enabled)
+          {
+            std::string joinedMetaTags;
+            FromDcmtkBridge::FormatListOfTags(joinedMetaTags, metaTagsToRemove);
+            LOG(WARNING) << "W006: Unable to include tags from the Meta Header in \"RequestedTags\".  Skipping them: " << joinedMetaTags;
+          }
+
+          Toolbox::RemoveSets(remainingRequestedTags, metaTagsToRemove);
+        }
+
+
         if (!remainingRequestedTags.empty() && 
             !DicomMap::HasOnlyComputedTags(remainingRequestedTags)) // if the only remaining tags are computed tags, it is worthless to read them from disk
         {
-          if (!allowStorageAccess_)
-          {
-            throw OrthancException(ErrorCode_BadSequenceOfCalls,
-                                   "Cannot add missing requested tags, as access to file storage is disallowed");
-          }
-          else
+          // If a lookup tag is not available from DB, it is included in remainingRequestedTags and it will always be included in the answer too
+          // -> from 1.12.5, "StorageAccessOnFind": "Always" is actually equivalent to "StorageAccessOnFind": "Answers"
+          if (IsStorageAccessAllowed())
           {
             ReadMissingTagsFromStorageArea(outRequestedTags, context, request_, resource, remainingRequestedTags);
           }
+          else if (isWarning007Enabled)
+          {
+            std::string joinedTags;
+            FromDcmtkBridge::FormatListOfTags(joinedTags, remainingRequestedTags);
+            LOG(WARNING) << "W007: Unable to include requested tags since \"StorageAccessOnFind\" does not allow accessing the storage to build answers: " << joinedTags;
+          }
         }
 
         std::string mainDicomTagsSignature;
@@ -1142,11 +1235,24 @@
     }
   }
 
+  bool ResourceFinder::IsStorageAccessAllowed()
+  {
+    switch (storageAccessMode_)
+    {
+      case FindStorageAccessMode_DiskOnAnswer:
+      case FindStorageAccessMode_DiskOnLookupAndAnswer:
+        return true;
+      case FindStorageAccessMode_DatabaseOnly:
+        return false;
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
 
   void ResourceFinder::Execute(Json::Value& target,
                                ServerContext& context,
                                DicomToJsonFormat format,
-                               bool includeAllMetadata) const
+                               bool includeAllMetadata)
   {
     class Visitor : public IVisitor
     {
@@ -1202,6 +1308,8 @@
       }
     };
 
+    UpdateRequestLimits(context);
+
     target = Json::arrayValue;
 
     Visitor visitor(*this, context.GetIndex(), target, format, HasRequestedTags(), includeAllMetadata);
@@ -1212,7 +1320,7 @@
   bool ResourceFinder::ExecuteOneResource(Json::Value& target,
                                           ServerContext& context,
                                           DicomToJsonFormat format,
-                                          bool includeAllMetadata) const
+                                          bool includeAllMetadata)
   {
     Json::Value answer;
     Execute(answer, context, format, includeAllMetadata);
--- a/OrthancServer/Sources/ResourceFinder.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ResourceFinder.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -60,13 +60,15 @@
     uint64_t                         databaseLimits_;
     std::unique_ptr<DatabaseLookup>  lookup_;
     bool                             isSimpleLookup_;
+    bool                             canBeFullyPerformedInDb_;
     PagingMode                       pagingMode_;
     bool                             hasLimitsSince_;
     bool                             hasLimitsCount_;
     uint64_t                         limitsSince_;
     uint64_t                         limitsCount_;
     ResponseContentFlags             responseContent_;
-    bool                             allowStorageAccess_;
+    FindStorageAccessMode            storageAccessMode_;
+    bool                             supportsChildExistQueries_;
     std::set<DicomTag>               requestedTags_;
     std::set<DicomTag>               requestedComputedTags_;
 
@@ -94,29 +96,23 @@
     void InjectComputedTags(DicomMap& requestedTags,
                             const FindResponse::Resource& resource) const;
 
-    void UpdateRequestLimits();
+    void UpdateRequestLimits(ServerContext& context);
 
     bool HasRequestedTags() const
     {
       return requestedTags_.size() > 0;
     }
 
+    bool IsStorageAccessAllowed();
+
   public:
     ResourceFinder(ResourceType level,
-                   ResponseContentFlags responseContent);
+                   ResponseContentFlags responseContent,
+                   FindStorageAccessMode storageAccessMode,
+                   bool supportsChildExistQueries);
 
     void SetDatabaseLimits(uint64_t limits);
 
-    bool IsAllowStorageAccess() const
-    {
-      return allowStorageAccess_;
-    }
-
-    void SetAllowStorageAccess(bool allow)
-    {
-      allowStorageAccess_ = allow;
-    }
-
     void SetOrthancId(ResourceType level,
                       const std::string& id)
     {
@@ -134,15 +130,17 @@
     void AddRequestedTags(const std::set<DicomTag>& tags);
 
     void AddOrdering(const DicomTag& tag,
+                     FindRequest::OrderingCast cast,
                      FindRequest::OrderingDirection direction)
     {
-      request_.AddOrdering(tag, direction);
+      request_.AddOrdering(tag, cast, direction);
     }
 
     void AddOrdering(MetadataType metadataType,
+                     FindRequest::OrderingCast cast,
                      FindRequest::OrderingDirection direction)
     {
-      request_.AddOrdering(metadataType, direction);
+      request_.AddOrdering(metadataType, cast, direction);
     }
 
     void AddMetadataConstraint(DatabaseMetadataConstraint* constraint)
@@ -187,16 +185,23 @@
                 DicomToJsonFormat format) const;
 
     void Execute(IVisitor& visitor,
-                 ServerContext& context) const;
+                 ServerContext& context);
 
     void Execute(Json::Value& target,
                  ServerContext& context,
                  DicomToJsonFormat format,
-                 bool includeAllMetadata) const;
+                 bool includeAllMetadata);
 
     bool ExecuteOneResource(Json::Value& target,
                             ServerContext& context,
                             DicomToJsonFormat format,
-                            bool includeAllMetadata) const;
+                            bool includeAllMetadata);
+
+    uint64_t Count(ServerContext& context) const;
+
+    bool CanBeFullyPerformedInDb() const
+    {
+      return canBeFullyPerformedInDb_;
+    }
   };
 }
--- a/OrthancServer/Sources/Search/DatabaseDicomTagConstraint.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraint.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Search/DatabaseDicomTagConstraint.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraint.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Search/DatabaseDicomTagConstraints.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraints.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Search/DatabaseDicomTagConstraints.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraints.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Search/DatabaseLookup.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/DatabaseLookup.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -282,6 +282,14 @@
 
   bool DatabaseLookup::HasOnlyMainDicomTags() const
   {
+    std::set<DicomTag> notUsed;
+    
+    return HasOnlyMainDicomTags(notUsed);
+  }
+
+
+  bool DatabaseLookup::HasOnlyMainDicomTags(std::set<DicomTag>& /* out*/ nonMainDicomTags) const
+  {
     std::set<DicomTag> allMainTags;
     DicomMap::GetAllMainDicomTags(allMainTags);
 
@@ -292,11 +300,11 @@
       if (allMainTags.find(constraints_[i]->GetTag()) == allMainTags.end())
       {
         // This is not a main DICOM tag
-        return false;
+        nonMainDicomTags.insert(constraints_[i]->GetTag());
       }
     }
 
-    return true;
+    return nonMainDicomTags.size() == 0;
   }
 
 
--- a/OrthancServer/Sources/Search/DatabaseLookup.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/DatabaseLookup.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -84,6 +84,8 @@
 
     bool HasOnlyMainDicomTags() const;
 
+    bool HasOnlyMainDicomTags(std::set<DicomTag>& /* out*/ nonMainDicomTags) const;
+
     std::string Format() const;
 
     bool HasTag(const DicomTag& tag) const;
--- a/OrthancServer/Sources/Search/DatabaseMetadataConstraint.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/DatabaseMetadataConstraint.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Search/DatabaseMetadataConstraint.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/DatabaseMetadataConstraint.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Search/DicomTagConstraint.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/DicomTagConstraint.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Search/DicomTagConstraint.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/DicomTagConstraint.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Search/HierarchicalMatcher.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/HierarchicalMatcher.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Search/HierarchicalMatcher.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/HierarchicalMatcher.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Search/IDatabaseConstraint.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/IDatabaseConstraint.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -766,7 +766,17 @@
         // first filter by 0/1 and then by the column value itself
         orderByField += "order" + boost::lexical_cast<std::string>(counter) + ".value IS NULL, ";
 #endif
-        orderByField += "order" + boost::lexical_cast<std::string>(counter) + ".value";
+        switch ((*it)->GetCast())
+        {
+          case FindRequest::OrderingCast_Int:
+            orderByField += "CAST(order" + boost::lexical_cast<std::string>(counter) + ".value AS INTEGER)";
+            break;
+          case FindRequest::OrderingCast_Float:
+            orderByField += "CAST(order" + boost::lexical_cast<std::string>(counter) + ".value AS REAL)";
+            break;
+          default:
+            orderByField += "order" + boost::lexical_cast<std::string>(counter) + ".value";
+        }
 
         if ((*it)->GetDirection() == FindRequest::OrderingDirection_Ascending)
         {
@@ -846,13 +856,25 @@
       {
         std::string join;
         FormatJoin(join, constraint, count);
-        joins += join;
+
+        if (constraint.GetLevel() <= queryLevel)
+        {
+          joins += join;
+        }
+        else if (constraint.GetLevel() == queryLevel + 1 && !comparison.empty())
+        {
+          // new in v 1.12.6, the constraints on child tags are actually looking for one child with this value
+          comparison = " EXISTS (SELECT 1 FROM Resources AS " + FormatLevel(static_cast<ResourceType>(queryLevel + 1)) + 
+                       join + 
+                       " WHERE " + comparison + " AND " + 
+                       FormatLevel(static_cast<ResourceType>(queryLevel + 1)) + ".parentId = " + FormatLevel(static_cast<ResourceType>(queryLevel)) + ".internalId) ";
+        }
 
         if (!comparison.empty())
         {
           comparisons += " AND " + comparison;
         }
-        
+
         count ++;
       }
     }
@@ -884,13 +906,14 @@
               FormatLevel(static_cast<ResourceType>(level + 1)) + ".parentId");
     }
       
-    for (int level = queryLevel + 1; level <= lowerLevel; level++)
-    {
-      sql += (" INNER JOIN Resources " +
-              FormatLevel(static_cast<ResourceType>(level)) + " ON " +
-              FormatLevel(static_cast<ResourceType>(level - 1)) + ".internalId=" +
-              FormatLevel(static_cast<ResourceType>(level)) + ".parentId");
-    }
+    // disabled in v 1.12.6 now that the child levels are considered as "is there at least one child that meets this constraint"
+    // for (int level = queryLevel + 1; level <= lowerLevel; level++)
+    // {
+    //   sql += (" INNER JOIN Resources " +
+    //           FormatLevel(static_cast<ResourceType>(level)) + " ON " +
+    //           FormatLevel(static_cast<ResourceType>(level - 1)) + ".internalId=" +
+    //           FormatLevel(static_cast<ResourceType>(level)) + ".parentId");
+    // }
 
     std::list<std::string> where;
     where.push_back(strQueryLevel + ".resourceType = " +
--- a/OrthancServer/Sources/Search/ISqlLookupFormatter.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -26,6 +26,7 @@
 #include "../../../OrthancFramework/Sources/Enumerations.h"
 
 #include <boost/noncopyable.hpp>
+#include <stdint.h>
 #include <vector>
 
 namespace Orthanc
--- a/OrthancServer/Sources/ServerContext.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerContext.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -50,6 +50,9 @@
 
 #include <dcmtk/dcmdata/dcfilefo.h>
 #include <dcmtk/dcmnet/dimse.h>
+#include <dcmtk/dcmdata/dcuid.h>        /* for variable dcmAllStorageSOPClassUIDs */
+
+#include <boost/regex.hpp>
 
 #if HAVE_MALLOC_TRIM == 1
 #  include <malloc.h>
@@ -69,12 +72,6 @@
 
 namespace Orthanc
 {
-  static void ComputeStudyTags(ExpandedResource& resource,
-                               ServerContext& context,
-                               const std::string& studyPublicId,
-                               const std::set<DicomTag>& requestedTags);
-
-
   static bool IsUncompressedTransferSyntax(DicomTransferSyntax transferSyntax)
   {
     return (transferSyntax == DicomTransferSyntax_LittleEndianImplicit ||
@@ -362,7 +359,8 @@
                                IStorageArea& area,
                                bool unitTesting,
                                size_t maxCompletedJobs,
-                               bool readOnly) :
+                               bool readOnly,
+                               unsigned int maxConcurrentDcmtkTranscoder) :
     index_(*this, database, (unitTesting ? 20 : 500), readOnly),
     area_(area),
     compressionEnabled_(false),
@@ -384,7 +382,7 @@
     isExecuteLuaEnabled_(false),
     isRestApiWriteToFileSystemEnabled_(false),
     overwriteInstances_(false),
-    dcmtkTranscoder_(new DcmtkTranscoder),
+    dcmtkTranscoder_(new DcmtkTranscoder(maxConcurrentDcmtkTranscoder)),
     isIngestTranscoding_(false),
     ingestTranscodingOfUncompressed_(true),
     ingestTranscodingOfCompressed_(true),
@@ -495,6 +493,15 @@
         lock.GetConfiguration().GetAcceptedTransferSyntaxes(acceptedTransferSyntaxes_);
 
         isUnknownSopClassAccepted_ = lock.GetConfiguration().GetBooleanParameter("UnknownSopClassAccepted", false);
+
+        // New options in Orthanc 1.12.6
+        std::list<std::string> acceptedSopClasses;
+        std::set<std::string> rejectedSopClasses;
+        lock.GetConfiguration().GetListOfStringsParameter(acceptedSopClasses, "AcceptedSopClasses");
+        lock.GetConfiguration().GetSetOfStringsParameter(rejectedSopClasses, "RejectSopClasses");
+        SetAcceptedSopClasses(acceptedSopClasses, rejectedSopClasses);
+
+        defaultDicomRetrieveMethod_ = StringToRetrieveMethod(lock.GetConfiguration().GetStringParameter("DicomDefaultRetrieveMethod", "C-MOVE"));
       }
 
       jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200);
@@ -951,10 +958,20 @@
         source.SetExternalBuffer(dicom->GetBufferData(), dicom->GetBufferSize());
         
         IDicomTranscoder::DicomImage transcoded;
+        
         if (Transcode(transcoded, source, syntaxes, true /* allow new SOP instance UID */))
         {
           std::unique_ptr<ParsedDicomFile> tmp(transcoded.ReleaseAsParsedDicomFile());
 
+          if (isReconstruct)
+          {
+            // when reconstructing, we always want to keep the DICOM IDs untouched even if the transcoding has generated a new SOPInstanceUID.
+            const Orthanc::ParsedDicomFile& sourceDicom = dicom->GetParsedDicomFile();
+            std::string sopInstanceUid;
+            sourceDicom.GetTagValue(sopInstanceUid, DICOM_TAG_SOP_INSTANCE_UID);
+            tmp->ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUid);
+          }
+
           std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromParsedDicomFile(*tmp));
           toStore->SetOrigin(dicom->GetOrigin());
           toStore->CopyMetadata(dicom->GetMetadata());
@@ -975,25 +992,15 @@
 
   
   void ServerContext::AnswerAttachment(RestApiOutput& output,
-                                       const std::string& resourceId,
-                                       FileContentType content)
+                                       const FileInfo& attachment)
   {
-    FileInfo attachment;
-    int64_t revision;
-    if (!index_.LookupAttachment(attachment, revision, resourceId, content))
-    {
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
-    else
-    {
-      StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry());
-      accessor.AnswerFile(output, attachment, GetFileContentMime(content));
-    }
+    StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry());
+    accessor.AnswerFile(output, attachment, GetFileContentMime(attachment.GetContentType()));
   }
 
 
-  void ServerContext::ChangeAttachmentCompression(const std::string& resourceId,
-                                                  ResourceType resourceType,
+  void ServerContext::ChangeAttachmentCompression(ResourceType level,
+                                                  const std::string& resourceId,
                                                   FileContentType attachmentType,
                                                   CompressionType compression)
   {
@@ -1004,7 +1011,7 @@
 
     FileInfo attachment;
     int64_t revision;
-    if (!index_.LookupAttachment(attachment, revision, resourceId, attachmentType))
+    if (!index_.LookupAttachment(attachment, revision, level, resourceId, attachmentType))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
@@ -1033,9 +1040,8 @@
     // }
     // else
     {
-      ResourceType resourceType = ResourceType_Instance; //TODO_CUSTOM_DATA: get it from above in the stack
-      modified = accessor.WriteAttachment(newCustomData, resourceId, resourceType, content.empty() ? NULL : content.c_str(),
-                                       content.size(), attachmentType, compression, storeMD5_, newUuid);
+      modified = accessor.WriteAttachment(newCustomData, resourceId, level, content.empty() ? NULL : content.c_str(),
+                                          content.size(), attachmentType, compression, storeMD5_, newUuid);
     }
 
 
@@ -1123,23 +1129,23 @@
     FileInfo attachment;
     int64_t revision;  // Ignored
 
-    if (index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_Dicom))
+    if (index_.LookupAttachment(attachment, revision, ResourceType_Instance, instancePublicId, FileContentType_Dicom))
     {
       attachments[FileContentType_Dicom] = attachment;
     }
 
-    if (index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_DicomUntilPixelData))
+    if (index_.LookupAttachment(attachment, revision, ResourceType_Instance, instancePublicId, FileContentType_DicomUntilPixelData))
     {
       attachments[FileContentType_DicomUntilPixelData] = attachment;
     }
 
-    if (index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_DicomAsJson))
+    if (index_.LookupAttachment(attachment, revision, ResourceType_Instance, instancePublicId, FileContentType_DicomAsJson))
     {
       attachments[FileContentType_DicomAsJson] = attachment;
     }
 
     std::string s;
-    if (index_.LookupMetadata(s, revision, instancePublicId, ResourceType_Instance,
+    if (index_.LookupMetadata(s, instancePublicId, ResourceType_Instance,
                               MetadataType_Instance_PixelDataOffset))
     {
       metadata[MetadataType_Instance_PixelDataOffset] = s;
@@ -1325,8 +1331,20 @@
                                 std::string& attachmentId,
                                 const std::string& instancePublicId)
   {
+    FileInfo attachment;
     int64_t revision;
-    ReadAttachment(dicom, revision, attachmentId, instancePublicId, FileContentType_Dicom, true /* uncompress */);
+
+    if (!index_.LookupAttachment(attachment, revision, ResourceType_Instance, instancePublicId, FileContentType_Dicom))
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             "Unable to read attachment " + EnumerationToString(FileContentType_Dicom) +
+                             " of instance " + instancePublicId);
+    }
+
+    assert(attachment.GetContentType() == FileContentType_Dicom);
+    attachmentId = attachment.GetUuid();
+
+    ReadAttachment(dicom, attachment, true /* uncompress */);
   }
 
 
@@ -1351,7 +1369,7 @@
   {
     FileInfo attachment;
     int64_t revision;  // Ignored
-    if (index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_DicomUntilPixelData))
+    if (index_.LookupAttachment(attachment, revision, ResourceType_Instance, instancePublicId, FileContentType_DicomUntilPixelData))
     {
       StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry());
 
@@ -1366,7 +1384,7 @@
       return false;
     }
     
-    if (!index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_Dicom))
+    if (!index_.LookupAttachment(attachment, revision, ResourceType_Instance, instancePublicId, FileContentType_Dicom))
     {
       throw OrthancException(ErrorCode_InternalError,
                              "Unable to read the DICOM file of instance " + instancePublicId);
@@ -1375,7 +1393,7 @@
     std::string s;
 
     if (attachment.GetCompressionType() == CompressionType_None &&
-        index_.LookupMetadata(s, revision, instancePublicId, ResourceType_Instance,
+        index_.LookupMetadata(s, instancePublicId, ResourceType_Instance,
                               MetadataType_Instance_PixelDataOffset) &&
         !s.empty())
     {
@@ -1401,47 +1419,40 @@
   
 
   void ServerContext::ReadAttachment(std::string& result,
-                                     int64_t& revision,
-                                     std::string& attachmentId,
-                                     const std::string& instancePublicId,
-                                     FileContentType content,
+                                     const FileInfo& attachment,
                                      bool uncompressIfNeeded,
                                      bool skipCache)
   {
-    FileInfo attachment;
-    if (!index_.LookupAttachment(attachment, revision, instancePublicId, content))
+    std::unique_ptr<StorageAccessor> accessor;
+      
+    if (skipCache)
     {
-      throw OrthancException(ErrorCode_InternalError,
-                             "Unable to read attachment " + EnumerationToString(content) +
-                             " of instance " + instancePublicId);
+      accessor.reset(new StorageAccessor(area_, GetMetricsRegistry()));
+    }
+    else
+    {
+      accessor.reset(new StorageAccessor(area_, storageCache_, GetMetricsRegistry()));
     }
 
-    assert(attachment.GetContentType() == content);
-    attachmentId = attachment.GetUuid();
-    
+    if (uncompressIfNeeded)
+    {
+      accessor->Read(result, attachment);
+    }
+    else
     {
-      std::unique_ptr<StorageAccessor> accessor;
-      
-      if (skipCache)
-      {
-        accessor.reset(new StorageAccessor(area_, GetMetricsRegistry()));
-      }
-      else
-      {
-        accessor.reset(new StorageAccessor(area_, storageCache_, GetMetricsRegistry()));
-      }
+      // Do not uncompress the content of the storage area, return the
+      // raw data
+      accessor->ReadRaw(result, attachment);
+    }
+  }
 
-      if (uncompressIfNeeded)
-      {
-        accessor->Read(result, attachment);
-      }
-      else
-      {
-        // Do not uncompress the content of the storage area, return the
-        // raw data
-        accessor->ReadRaw(result, attachment);
-      }
-    }
+  void ServerContext::ReadAttachmentRange(std::string &result,
+                                          const FileInfo &attachment,
+                                          const StorageAccessor::Range &range,
+                                          bool uncompressIfNeeded)
+  {
+    StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry());
+    accessor.ReadRange(result, attachment, range, uncompressIfNeeded);
   }
 
 
@@ -1661,188 +1672,6 @@
   }
 
 
-  void ServerContext::Apply(ILookupVisitor& visitor,
-                            const DatabaseLookup& lookup,
-                            ResourceType queryLevel,
-                            const std::set<std::string>& labels,
-                            LabelsConstraint labelsConstraint,
-                            size_t since,
-                            size_t limit)
-  {    
-    const uint64_t databaseLimit = GetDatabaseLimits(queryLevel);
-      
-    std::vector<std::string> resources, instances;
-    const DicomTagConstraint* dicomModalitiesConstraint = NULL;
-
-    bool hasModalitiesInStudyLookup = (queryLevel == ResourceType_Study &&
-          lookup.GetConstraint(dicomModalitiesConstraint, DICOM_TAG_MODALITIES_IN_STUDY) &&
-          ((dicomModalitiesConstraint->GetConstraintType() == ConstraintType_Equal && !dicomModalitiesConstraint->GetValue().empty()) ||
-          (dicomModalitiesConstraint->GetConstraintType() == ConstraintType_List && !dicomModalitiesConstraint->GetValues().empty())));
-
-    std::unique_ptr<DatabaseLookup> fastLookup(lookup.Clone());
-    
-    if (hasModalitiesInStudyLookup)
-    {
-      fastLookup->RemoveConstraint(DICOM_TAG_MODALITIES_IN_STUDY);
-    }
-
-    const size_t lookupLimit = (databaseLimit == 0 ? 0 : databaseLimit + 1);
-    GetIndex().ApplyLookupResources(resources, &instances, *fastLookup, queryLevel, labels, labelsConstraint, lookupLimit);
-
-    bool complete = (databaseLimit == 0 ||
-                     resources.size() <= databaseLimit);
-
-    LOG(INFO) << "Number of candidate resources after fast DB filtering on main DICOM tags: " << resources.size();
-
-    /**
-     * "resources" contains the Orthanc ID of the resource at level
-     * "queryLevel", "instances" contains one the Orthanc ID of one
-     * sample instance from this resource.
-     **/
-    assert(resources.size() == instances.size());
-
-    size_t countResults = 0;
-    size_t skipped = 0;
-
-    const bool isDicomAsJsonNeeded = visitor.IsDicomAsJsonNeeded();
-    
-    for (size_t i = 0; i < instances.size(); i++)
-    {
-      // Optimization in Orthanc 1.5.1 - Don't read the full JSON from
-      // the disk if only "main DICOM tags" are to be returned
-
-      boost::shared_ptr<Json::Value> dicomAsJson;
-
-      bool hasOnlyMainDicomTags;
-      DicomMap dicom;
-      DicomMap allMainDicomTagsFromDB;
-      
-      if (!IsStorageAccessAllowedForAnswers(findStorageAccessMode_) 
-          || fastLookup->HasOnlyMainDicomTags())
-      {
-        // Case (1): The main DICOM tags, as stored in the database,
-        // are sufficient to look for match
-
-        if (!GetIndex().GetAllMainDicomTags(allMainDicomTagsFromDB, instances[i]))
-        {
-          // The instance has been removed during the execution of the
-          // lookup, ignore it
-          continue;
-        }
-
-        // New in Orthanc 1.6.0: Only keep the main DICOM tags at the
-        // level of interest for the query
-        switch (queryLevel)
-        {
-          // WARNING: Don't reorder cases below, and don't add "break"
-          case ResourceType_Instance:
-            dicom.MergeMainDicomTags(allMainDicomTagsFromDB, ResourceType_Instance);
-
-          case ResourceType_Series:
-            dicom.MergeMainDicomTags(allMainDicomTagsFromDB, ResourceType_Series);
-
-          case ResourceType_Study:
-            dicom.MergeMainDicomTags(allMainDicomTagsFromDB, ResourceType_Study);
-            
-          case ResourceType_Patient:
-            dicom.MergeMainDicomTags(allMainDicomTagsFromDB, ResourceType_Patient);
-            break;
-
-          default:
-            throw OrthancException(ErrorCode_InternalError);
-        }
-        
-        hasOnlyMainDicomTags = true;
-      }
-      else
-      {
-        // Case (2): Need to read the "DICOM-as-JSON" attachment from
-        // the storage area
-        dicomAsJson.reset(new Json::Value);
-        ReadDicomAsJson(*dicomAsJson, instances[i]);
-
-        dicom.FromDicomAsJson(*dicomAsJson);
-
-        // This map contains the entire JSON, i.e. more than the main DICOM tags
-        hasOnlyMainDicomTags = false;   
-      }
-      
-      if (fastLookup->IsMatch(dicom))
-      {
-        bool isMatch = true;
-
-        if (hasModalitiesInStudyLookup)
-        {
-          std::set<DicomTag> requestedTags;
-          requestedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY);
-          ExpandedResource resource;
-          ComputeStudyTags(resource, *this, resources[i], requestedTags);
-
-          std::vector<std::string> modalities;
-          Toolbox::TokenizeString(modalities, resource.GetMainDicomTags().GetValue(DICOM_TAG_MODALITIES_IN_STUDY).GetContent(), '\\');
-          bool hasAtLeastOneModalityMatching = false;
-          for (size_t m = 0; m < modalities.size(); m++)
-          {
-            hasAtLeastOneModalityMatching |= dicomModalitiesConstraint->IsMatch(modalities[m]);
-          }
-
-          isMatch = isMatch && hasAtLeastOneModalityMatching;
-          // copy the value of ModalitiesInStudy such that it can be reused to build the answer
-          allMainDicomTagsFromDB.SetValue(DICOM_TAG_MODALITIES_IN_STUDY, resource.GetMainDicomTags().GetValue(DICOM_TAG_MODALITIES_IN_STUDY));
-        }
-
-        if (isMatch)
-        {
-          if (skipped < since)
-          {
-            skipped++;
-          }
-          else if (limit != 0 &&
-                  countResults >= limit)
-          {
-            // Too many results, don't mark as complete
-            complete = false;
-            break;
-          }
-          else
-          {
-            if (IsStorageAccessAllowedForAnswers(findStorageAccessMode_) &&
-                dicomAsJson.get() == NULL &&
-                isDicomAsJsonNeeded)
-            {
-              dicomAsJson.reset(new Json::Value);
-              ReadDicomAsJson(*dicomAsJson, instances[i]);
-            }
-
-            if (hasOnlyMainDicomTags)
-            {
-              // This is Case (1): The variable "dicom" only contains the main DICOM tags
-              visitor.Visit(resources[i], instances[i], allMainDicomTagsFromDB, dicomAsJson.get());
-            }
-            else
-            {
-              // Remove the non-main DICOM tags from "dicom" if Case (2)
-              // was used, for consistency with Case (1)
-
-              DicomMap mainDicomTags;
-              mainDicomTags.ExtractMainDicomTags(dicom);
-              visitor.Visit(resources[i], instances[i], mainDicomTags, dicomAsJson.get());            
-            }
-              
-            countResults ++;
-          }
-        }
-      }
-    }
-
-    if (complete)
-    {
-      visitor.MarkAsComplete();
-    }
-
-    LOG(INFO) << "Number of matching resources: " << countResults;
-  }
-
   bool ServerContext::LookupOrReconstructMetadata(std::string& target,
                                                   const std::string& publicId,
                                                   ResourceType level,
@@ -1855,8 +1684,7 @@
     if (metadata == MetadataType_Instance_SopClassUid ||
         metadata == MetadataType_Instance_TransferSyntax)
     {
-      int64_t revision;  // Ignored
-      if (index_.LookupMetadata(target, revision, publicId, level, metadata))
+      if (index_.LookupMetadata(target, publicId, level, metadata))
       {
         return true;
       }
@@ -1911,8 +1739,7 @@
     else
     {
       // No backward
-      int64_t revision;  // Ignored
-      return index_.LookupMetadata(target, revision, publicId, level, metadata);
+      return index_.LookupMetadata(target, publicId, level, metadata);
     }
   }
 
@@ -2224,8 +2051,118 @@
     }
   }
 
+  void ServerContext::SetAcceptedSopClasses(const std::list<std::string>& acceptedSopClasses,
+                                            const std::set<std::string>& rejectedSopClasses)
+  {
+    boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
+    acceptedSopClasses_.clear();
 
-  void ServerContext::GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& syntaxes)
+    size_t count = 0;
+    std::set<std::string> allDcmtkSopClassUids;
+    std::set<std::string> shortDcmtkSopClassUids;
+
+    // we actually take a list of default 120 most common storage SOP classes defined in DCMTK
+    while (dcmLongSCUStorageSOPClassUIDs[count] != NULL)
+    {
+      shortDcmtkSopClassUids.insert(dcmLongSCUStorageSOPClassUIDs[count++]);
+    }
+
+    count = 0;
+    while (dcmAllStorageSOPClassUIDs[count] != NULL)
+    {
+      allDcmtkSopClassUids.insert(dcmAllStorageSOPClassUIDs[count++]);
+    }
+
+    if (acceptedSopClasses.size() == 0)
+    {
+      // by default, include the short list first and then all the others
+      for (std::set<std::string>::const_iterator it = shortDcmtkSopClassUids.begin(); it != shortDcmtkSopClassUids.end(); ++it)
+      {
+        acceptedSopClasses_.push_back(*it);
+      }
+
+      for (std::set<std::string>::const_iterator it = allDcmtkSopClassUids.begin(); it != allDcmtkSopClassUids.end(); ++it)
+      {
+        if (shortDcmtkSopClassUids.find(*it) == shortDcmtkSopClassUids.end()) // don't add the classes that we have already added
+        {
+          acceptedSopClasses_.push_back(*it);
+        }
+      }
+    }
+    else
+    {
+      std::set<std::string> addedSopClasses;
+
+      for (std::list<std::string>::const_iterator it = acceptedSopClasses.begin(); it != acceptedSopClasses.end(); ++it)
+      {
+        if (it->find('*') != std::string::npos || it->find('?') != std::string::npos)
+        {
+          // if it contains wildcard, add all the matching SOP classes known by DCMTK
+          boost::regex pattern(Toolbox::WildcardToRegularExpression(*it));
+
+          for (std::set<std::string>::const_iterator itall = allDcmtkSopClassUids.begin(); itall != allDcmtkSopClassUids.end(); ++itall)
+          {
+            if (regex_match(*itall, pattern) && addedSopClasses.find(*itall) == addedSopClasses.end())
+            {
+              acceptedSopClasses_.push_back(*itall);
+              addedSopClasses.insert(*itall);
+            }
+          }
+        }
+        else
+        {
+          // if it is a SOP Class UID, add it without checking if it is known by DCMTK
+          acceptedSopClasses_.push_back(*it);
+          addedSopClasses.insert(*it);
+        }
+      }
+    }
+    
+    // now remove all rejected syntaxes
+    if (rejectedSopClasses.size() > 0)
+    {
+      for (std::set<std::string>::const_iterator it = rejectedSopClasses.begin(); it != rejectedSopClasses.end(); ++it)
+      {
+        if (it->find('*') != std::string::npos || it->find('?') != std::string::npos)
+        {
+          // if it contains wildcard, get all the matching SOP classes known by DCMTK
+          boost::regex pattern(Toolbox::WildcardToRegularExpression(*it));
+
+          for (std::set<std::string>::const_iterator itall = allDcmtkSopClassUids.begin(); itall != allDcmtkSopClassUids.end(); ++itall)
+          {
+            if (regex_match(*itall, pattern))
+            {
+              acceptedSopClasses_.remove(*itall);
+            }
+          }
+        }
+        else
+        {
+          // if it is a SOP Class UID, remove it without checking if it is known by DCMTK
+          acceptedSopClasses_.remove(*it);
+        }
+      }
+    }
+  }
+
+  void ServerContext::GetAcceptedSopClasses(std::set<std::string>& sopClasses, size_t maxCount) const
+  {
+    sopClasses.clear();
+
+    boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
+
+    size_t count = 0;
+    std::list<std::string>::const_iterator it = acceptedSopClasses_.begin();
+
+    while (it != acceptedSopClasses_.end() && (maxCount == 0 || count < maxCount))
+    {
+      sopClasses.insert(*it);
+      count++;
+      ++it;
+    }
+  }
+
+  void ServerContext::GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& syntaxes) const
   {
     boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
     syntaxes = acceptedTransferSyntaxes_;
@@ -2239,7 +2176,25 @@
   }
 
 
-  bool ServerContext::IsUnknownSopClassAccepted()
+  void ServerContext::GetProposedStorageTransferSyntaxes(std::list<DicomTransferSyntax>& syntaxes) const
+  {
+    boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
+    
+    // // TODO: investigate: actually, neither Orthanc 1.12.4 nor DCM4CHEE will accept to send a LittleEndianExplicit file
+    // //                    while e.g., Jpeg-LS has been presented (and accepted) as the preferred TS for the C-Store SCP.
+    // // if we have defined IngestTranscoding, let's propose this TS first to avoid any unnecessary transcoding
+    // if (isIngestTranscoding_)
+    // {
+    //   syntaxes.push_back(ingestTransferSyntax_);
+    // }
+    
+    // then, propose the default ones
+    syntaxes.push_back(DicomTransferSyntax_LittleEndianExplicit);
+    syntaxes.push_back(DicomTransferSyntax_LittleEndianImplicit);
+  }
+  
+
+  bool ServerContext::IsUnknownSopClassAccepted() const
   {
     boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
     return isUnknownSopClassAccepted_;
@@ -2252,600 +2207,6 @@
     isUnknownSopClassAccepted_ = accepted;
   }
 
-
-  static void SerializeExpandedResource(Json::Value& target,
-                                        const ExpandedResource& resource,
-                                        DicomToJsonFormat format,
-                                        const std::set<DicomTag>& requestedTags,
-                                        ExpandResourceFlags expandFlags)
-  {
-    target = Json::objectValue;
-
-    target["Type"] = GetResourceTypeText(resource.GetLevel(), false, true);
-    target["ID"] = resource.GetPublicId();
-
-    if (!resource.parentId_.empty())
-    {
-      switch (resource.GetLevel())
-      {
-        case ResourceType_Patient:
-          break;
-
-        case ResourceType_Study:
-          target["ParentPatient"] = resource.parentId_;
-          break;
-
-        case ResourceType_Series:
-          target["ParentStudy"] = resource.parentId_;
-          break;
-
-        case ResourceType_Instance:
-          target["ParentSeries"] = resource.parentId_;
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-
-    if ((expandFlags & ExpandResourceFlags_IncludeChildren) != 0)
-    {
-      switch (resource.GetLevel())
-      {
-        case ResourceType_Patient:
-        case ResourceType_Study:
-        case ResourceType_Series:
-        {
-          Json::Value c = Json::arrayValue;
-
-          for (std::list<std::string>::const_iterator
-                  it = resource.childrenIds_.begin(); it != resource.childrenIds_.end(); ++it)
-          {
-            c.append(*it);
-          }
-
-          if (resource.GetLevel() == ResourceType_Patient)
-          {
-            target["Studies"] = c;
-          }
-          else if (resource.GetLevel() == ResourceType_Study)
-          {
-            target["Series"] = c;
-          }
-          else
-          {
-            target["Instances"] = c;
-          }
-          break;
-        }
-
-        case ResourceType_Instance:
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-
-    if ((expandFlags & ExpandResourceFlags_IncludeMetadata) != 0)
-    {
-      switch (resource.GetLevel())
-      {
-        case ResourceType_Patient:
-        case ResourceType_Study:
-          break;
-
-        case ResourceType_Series:
-          if (resource.expectedNumberOfInstances_ < 0)
-          {
-            target["ExpectedNumberOfInstances"] = Json::nullValue;
-          }
-          else
-          {
-            target["ExpectedNumberOfInstances"] = resource.expectedNumberOfInstances_;
-          }
-          target["Status"] = resource.status_;
-          break;
-
-        case ResourceType_Instance:
-        {
-          target["FileSize"] = static_cast<unsigned int>(resource.fileSize_);
-          target["FileUuid"] = resource.fileUuid_;
-
-          if (resource.indexInSeries_ < 0)
-          {
-            target["IndexInSeries"] = Json::nullValue;
-          }
-          else
-          {
-            target["IndexInSeries"] = resource.indexInSeries_;
-          }
-
-          break;
-        }
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-    
-      if (!resource.anonymizedFrom_.empty())
-      {
-        target["AnonymizedFrom"] = resource.anonymizedFrom_;
-      }
-      
-      if (!resource.modifiedFrom_.empty())
-      {
-        target["ModifiedFrom"] = resource.modifiedFrom_;
-      }
-    }
-
-    if (resource.GetLevel() == ResourceType_Patient ||
-        resource.GetLevel() == ResourceType_Study ||
-        resource.GetLevel() == ResourceType_Series)
-    {
-      if ((expandFlags & ExpandResourceFlags_IncludeIsStable) != 0)
-      {
-        target["IsStable"] = resource.isStable_;
-      }
-
-      if (!resource.lastUpdate_.empty())
-      {
-        target["LastUpdate"] = resource.lastUpdate_;
-      }
-    }
-
-    if ((expandFlags & ExpandResourceFlags_IncludeMainDicomTags) != 0)
-    {
-      // serialize tags
-
-      static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
-      static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags";
-
-      DicomMap mainDicomTags;
-      resource.GetMainDicomTags().ExtractResourceInformation(mainDicomTags, resource.GetLevel());
-
-      target[MAIN_DICOM_TAGS] = Json::objectValue;
-      FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], mainDicomTags, format);
-      
-      if (resource.GetLevel() == ResourceType_Study)
-      {
-        DicomMap patientMainDicomTags;
-        resource.GetMainDicomTags().ExtractPatientInformation(patientMainDicomTags);
-
-        target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue;
-        FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format);
-      }
-
-      if (requestedTags.size() > 0)
-      {
-        static const char* const REQUESTED_TAGS = "RequestedTags";
-
-        DicomMap tags;
-        resource.GetMainDicomTags().ExtractTags(tags, requestedTags);
-
-        target[REQUESTED_TAGS] = Json::objectValue;
-        FromDcmtkBridge::ToJson(target[REQUESTED_TAGS], tags, format);
-
-      }
-    }
-
-    if ((expandFlags & ExpandResourceFlags_IncludeLabels) != 0)
-    {
-      Json::Value labels = Json::arrayValue;
-
-      for (std::set<std::string>::const_iterator it = resource.labels_.begin(); it != resource.labels_.end(); ++it)
-      {
-        labels.append(*it);
-      }
-
-      target["Labels"] = labels;
-    }
-
-    // new in Orthanc 1.12.4
-    if ((expandFlags & ExpandResourceFlags_IncludeAllMetadata) != 0)
-    {
-      Json::Value metadata = Json::objectValue;
-
-      for (std::map<MetadataType, std::string>::const_iterator it = resource.metadata_.begin(); it != resource.metadata_.end(); ++it)
-      {
-        metadata[EnumerationToString(it->first)] = it->second;
-      }
-
-      target["Metadata"] = metadata;
-    }
-  }
-
-
-  static void ComputeInstanceTags(ExpandedResource& resource,
-                                  ServerContext& context,
-                                  const std::string& instancePublicId,
-                                  const std::set<DicomTag>& requestedTags)
-  {
-    if (requestedTags.count(DICOM_TAG_INSTANCE_AVAILABILITY) > 0)
-    {
-      resource.GetMainDicomTags().SetValue(DICOM_TAG_INSTANCE_AVAILABILITY, "ONLINE", false);
-      resource.missingRequestedTags_.erase(DICOM_TAG_INSTANCE_AVAILABILITY);
-    }
-  }
-
-
-  static void ComputeSeriesTags(ExpandedResource& resource,
-                                ServerContext& context,
-                                const std::string& seriesPublicId,
-                                const std::set<DicomTag>& requestedTags)
-  {
-    if (requestedTags.count(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES) > 0)
-    {
-      ServerIndex& index = context.GetIndex();
-      std::list<std::string> instances;
-
-      index.GetChildren(instances, seriesPublicId);
-
-      resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES,
-                              boost::lexical_cast<std::string>(instances.size()), false);
-      resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES);
-    }
-  }
-
-  static void ComputeStudyTags(ExpandedResource& resource,
-                               ServerContext& context,
-                               const std::string& studyPublicId,
-                               const std::set<DicomTag>& requestedTags)
-  {
-    ServerIndex& index = context.GetIndex();
-    std::list<std::string> series;
-    std::list<std::string> instances;
-
-    bool hasNbRelatedSeries = requestedTags.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) > 0;
-    bool hasNbRelatedInstances = requestedTags.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) > 0;
-    bool hasModalitiesInStudy = requestedTags.count(DICOM_TAG_MODALITIES_IN_STUDY) > 0;
-    bool hasSopClassesInStudy = requestedTags.count(DICOM_TAG_SOP_CLASSES_IN_STUDY) > 0;
-
-    index.GetChildren(series, studyPublicId);
-
-    if (hasModalitiesInStudy)
-    {
-      std::set<std::string> values;
-
-      for (std::list<std::string>::const_iterator
-            it = series.begin(); it != series.end(); ++it)
-      {
-        DicomMap tags;
-        index.GetMainDicomTags(tags, *it, ResourceType_Series, ResourceType_Series);
-
-        const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY);
-
-        if (value != NULL &&
-            !value->IsNull() &&
-            !value->IsBinary())
-        {
-          values.insert(value->GetContent());
-        }
-      }
-
-      std::string modalities;
-      Toolbox::JoinStrings(modalities, values, "\\");
-
-      resource.GetMainDicomTags().SetValue(DICOM_TAG_MODALITIES_IN_STUDY, modalities, false);
-      resource.missingRequestedTags_.erase(DICOM_TAG_MODALITIES_IN_STUDY);
-    }
-
-    if (hasNbRelatedSeries)
-    {
-      resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES,
-                              boost::lexical_cast<std::string>(series.size()), false);
-      resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES);
-    }
-
-    if (hasNbRelatedInstances || hasSopClassesInStudy)
-    {
-      for (std::list<std::string>::const_iterator
-            it = series.begin(); it != series.end(); ++it)
-      {
-        std::list<std::string> seriesInstancesIds;
-        index.GetChildren(seriesInstancesIds, *it);
-
-        instances.splice(instances.end(), seriesInstancesIds);
-      }
-
-      if (hasNbRelatedInstances)
-      {
-        resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES,
-                                boost::lexical_cast<std::string>(instances.size()), false);      
-        resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES);
-      }
-
-      if (hasSopClassesInStudy)
-      {
-        std::set<std::string> values;
-
-        for (std::list<std::string>::const_iterator
-              it = instances.begin(); it != instances.end(); ++it)
-        {
-          std::string value;
-
-          if (context.LookupOrReconstructMetadata(value, *it, ResourceType_Instance, MetadataType_Instance_SopClassUid))
-          {
-            values.insert(value);
-          }
-        }
-
-        if (values.size() > 0)
-        {
-          std::string sopClassUids;
-          Toolbox::JoinStrings(sopClassUids, values, "\\");
-          resource.GetMainDicomTags().SetValue(DICOM_TAG_SOP_CLASSES_IN_STUDY, sopClassUids, false);
-        }
-
-        resource.missingRequestedTags_.erase(DICOM_TAG_SOP_CLASSES_IN_STUDY);
-      }
-    }
-  }
-
-  static void ComputePatientTags(ExpandedResource& resource,
-                                 ServerContext& context,
-                                 const std::string& patientPublicId,
-                                 const std::set<DicomTag>& requestedTags)
-  {
-    ServerIndex& index = context.GetIndex();
-
-    std::list<std::string> studies;
-    std::list<std::string> series;
-    std::list<std::string> instances;
-
-    bool hasNbRelatedStudies = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) > 0;
-    bool hasNbRelatedSeries = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) > 0;
-    bool hasNbRelatedInstances = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES) > 0;
-
-    index.GetChildren(studies, patientPublicId);
-
-    if (hasNbRelatedStudies)
-    {
-      resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES,
-                              boost::lexical_cast<std::string>(studies.size()), false);
-      resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES);
-    }
-
-    if (hasNbRelatedSeries || hasNbRelatedInstances)
-    {
-      for (std::list<std::string>::const_iterator
-            it = studies.begin(); it != studies.end(); ++it)
-      {
-        std::list<std::string> thisSeriesIds;
-        index.GetChildren(thisSeriesIds, *it);
-        series.splice(series.end(), thisSeriesIds);
-      }
-
-      if (hasNbRelatedSeries)
-      {
-        resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES,
-                                boost::lexical_cast<std::string>(series.size()), false);
-        resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES);
-      }
-    }
-
-    if (hasNbRelatedInstances)
-    {
-      for (std::list<std::string>::const_iterator
-            it = series.begin(); it != series.end(); ++it)
-      {
-        std::list<std::string> thisInstancesIds;
-        index.GetChildren(thisInstancesIds, *it);
-        instances.splice(instances.end(), thisInstancesIds);
-      }
-
-      resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES,
-                              boost::lexical_cast<std::string>(instances.size()), false);
-      resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES);
-    }
-  }
-
-
-  static void ComputeTags(ExpandedResource& resource,
-                          ServerContext& context,
-                          const std::string& resourceId,
-                          ResourceType level,
-                          const std::set<DicomTag>& requestedTags)
-  {
-    if (level == ResourceType_Patient 
-        && DicomMap::HasComputedTags(resource.missingRequestedTags_, ResourceType_Patient))
-    {
-      ComputePatientTags(resource, context, resourceId, requestedTags);
-    }
-
-    if (level == ResourceType_Study 
-        && DicomMap::HasComputedTags(resource.missingRequestedTags_, ResourceType_Study))
-    {
-      ComputeStudyTags(resource, context, resourceId, requestedTags);
-    }
-
-    if (level == ResourceType_Series 
-        && DicomMap::HasComputedTags(resource.missingRequestedTags_, ResourceType_Series))
-    {
-      ComputeSeriesTags(resource, context, resourceId, requestedTags);
-    }
-
-    if (level == ResourceType_Instance 
-        && DicomMap::HasComputedTags(resource.missingRequestedTags_, ResourceType_Instance))
-    {
-      ComputeInstanceTags(resource, context, resourceId, requestedTags);
-    }
-  }
-
-  bool ServerContext::ExpandResource(Json::Value& target,
-                                     const std::string& publicId,
-                                     ResourceType level,
-                                     DicomToJsonFormat format,
-                                     const std::set<DicomTag>& requestedTags,
-                                     bool allowStorageAccess)
-  {
-    std::string unusedInstanceId;
-    Json::Value* unusedDicomAsJson = NULL;
-    DicomMap unusedMainDicomTags;
-
-    return ExpandResource(target, publicId, unusedMainDicomTags, unusedInstanceId, unusedDicomAsJson, level, format, requestedTags, allowStorageAccess);
-  }
-
-  bool ServerContext::ExpandResource(Json::Value& target,
-                                     const std::string& publicId,
-                                     const DicomMap& mainDicomTags,    // optional: the main dicom tags for the resource (if already available)
-                                     const std::string& instanceId,    // optional: the id of an instance for the resource (if already available)
-                                     const Json::Value* dicomAsJson,   // optional: the dicom-as-json for the resource (if already available)
-                                     ResourceType level,
-                                     DicomToJsonFormat format,
-                                     const std::set<DicomTag>& requestedTags,
-                                     bool allowStorageAccess)
-  {
-    ExpandedResource resource;
-
-    if (ExpandResource(resource, publicId, mainDicomTags, instanceId, dicomAsJson, level, requestedTags, ExpandResourceFlags_DefaultExtract, allowStorageAccess))
-    {
-      SerializeExpandedResource(target, resource, format, requestedTags, ExpandResourceFlags_DefaultOutput);
-      return true;
-    }
-
-    return false;
-  }
-  
-  bool ServerContext::ExpandResource(ExpandedResource& resource,
-                                     const std::string& publicId,
-                                     const DicomMap& mainDicomTags,    // optional: the main dicom tags for the resource (if already available)
-                                     const std::string& instanceId,    // optional: the id of an instance for the resource (if already available)
-                                     const Json::Value* dicomAsJson,   // optional: the dicom-as-json for the resource (if already available)
-                                     ResourceType level,
-                                     const std::set<DicomTag>& requestedTags,
-                                     ExpandResourceFlags expandFlags,
-                                     bool allowStorageAccess)
-  {
-    // first try to get the tags from what is already available
-    
-    if ((expandFlags & ExpandResourceFlags_IncludeMainDicomTags) &&
-        mainDicomTags.GetSize() > 0 &&
-        dicomAsJson != NULL)
-    {
-      
-      resource.GetMainDicomTags().Merge(mainDicomTags);
-
-      if (dicomAsJson->isObject())
-      {
-        resource.GetMainDicomTags().FromDicomAsJson(*dicomAsJson);
-      }
-
-      std::set<DicomTag> retrievedTags;
-      std::set<DicomTag> missingTags;
-      resource.GetMainDicomTags().GetTags(retrievedTags);
-
-      Toolbox::GetMissingsFromSet(missingTags, requestedTags, retrievedTags);
-
-      // if all possible tags have been read, no need to get them from DB anymore
-      if (missingTags.size() > 0 && DicomMap::HasOnlyComputedTags(missingTags))
-      {
-        resource.missingRequestedTags_ = missingTags;
-        ComputeTags(resource, *this, publicId, level, requestedTags);
-        return true;
-      }
-      else if (missingTags.size() == 0)
-      {
-        expandFlags = static_cast<ExpandResourceFlags>(expandFlags & ~ExpandResourceFlags_IncludeMainDicomTags);
-      }
-
-      if (missingTags.size() == 0 && expandFlags == ExpandResourceFlags_None)  // we have already retrieved anything we need
-      {
-        return true;
-      }
-    }
-
-    if (expandFlags != ExpandResourceFlags_None &&
-        GetIndex().ExpandResource(resource, publicId, level, requestedTags,
-                                  static_cast<ExpandResourceFlags>(expandFlags | ExpandResourceFlags_IncludeMetadata)))  // we always need the metadata to get the mainDicomTagsSignature
-    {
-      // check the main dicom tags list has not changed since the resource was stored
-      if (resource.mainDicomTagsSignature_ != DicomMap::GetMainDicomTagsSignature(resource.GetLevel()))
-      {
-        OrthancConfiguration::ReaderLock lock;
-        if (lock.GetConfiguration().IsWarningEnabled(Warnings_002_InconsistentDicomTagsInDb))
-        {
-          LOG(WARNING) << "W002: " << Orthanc::GetResourceTypeText(resource.GetLevel(), false , false)
-                       << " has been stored with another version of Main Dicom Tags list, you should POST to /"
-                       << Orthanc::GetResourceTypeText(resource.GetLevel(), true, false)
-                       << "/" << resource.GetPublicId()
-                       << "/reconstruct to update the list of tags saved in DB.  Some MainDicomTags might be missing from this answer.";
-        }
-      }
-
-      // possibly merge missing requested tags from dicom-as-json
-      if (allowStorageAccess &&
-          !resource.missingRequestedTags_.empty() &&
-          !DicomMap::HasOnlyComputedTags(resource.missingRequestedTags_))
-      {
-        OrthancConfiguration::ReaderLock lock;
-        if (lock.GetConfiguration().IsWarningEnabled(Warnings_001_TagsBeingReadFromStorage))
-        {
-          std::set<DicomTag> missingTags;
-          Toolbox::AppendSets(missingTags, resource.missingRequestedTags_);
-          for (std::set<DicomTag>::const_iterator it = resource.missingRequestedTags_.begin(); it != resource.missingRequestedTags_.end(); ++it)
-          {
-            if (DicomMap::IsComputedTag(*it))
-            {
-              missingTags.erase(*it);
-            }
-          }
-
-          std::string missings;
-          FromDcmtkBridge::FormatListOfTags(missings, missingTags);
-
-          LOG(WARNING) << "W001: Accessing Dicom tags from storage when accessing "
-                       << Orthanc::GetResourceTypeText(resource.GetLevel(), false, false)
-                       << " : " << missings;
-        }
-
-
-        std::string instanceId_ = instanceId;
-        DicomMap tagsFromJson;
-
-        if (dicomAsJson == NULL)
-        {
-          if (instanceId_.empty())
-          {
-            if (level == ResourceType_Instance)
-            {
-              instanceId_ = publicId;
-            }
-            else
-            {
-              std::list<std::string> instancesIds;
-              GetIndex().GetChildInstances(instancesIds, publicId);
-              if (instancesIds.size() < 1)
-              {
-                throw OrthancException(ErrorCode_InternalError, "ExpandResource: no instances found");
-              }
-              instanceId_ = instancesIds.front();
-            }
-          }
-  
-          Json::Value tmpDicomAsJson;
-          ReadDicomAsJson(tmpDicomAsJson, instanceId_, resource.missingRequestedTags_ /* ignoreTagLength */);  // read all tags from DICOM and avoid cropping requested tags
-          tagsFromJson.FromDicomAsJson(tmpDicomAsJson, false /* append */, true /* parseSequences*/);
-        }
-        else
-        {
-          tagsFromJson.FromDicomAsJson(*dicomAsJson, false /* append */, true /* parseSequences*/);
-        }
-
-        resource.GetMainDicomTags().Merge(tagsFromJson);
-      }
-
-      // compute the requested tags
-      ComputeTags(resource, *this, publicId, level, requestedTags);
-    }
-    else
-    {
-      return false;
-    }
-
-    return true;
-  }
-
   int64_t ServerContext::GetServerUpTime() const
   {
     boost::posix_time::ptime nowUtc = boost::posix_time::second_clock::universal_time();
--- a/OrthancServer/Sources/ServerContext.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerContext.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -38,6 +38,8 @@
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 
+#include "../../OrthancFramework/Sources/FileStorage/StorageAccessor.h"
+
 namespace Orthanc
 {
   class DicomInstanceToStore;
@@ -66,25 +68,6 @@
     friend class ServerIndex;  // To access "RemoveFile()"
     
   public:
-    class ILookupVisitor : public boost::noncopyable
-    {
-    public:
-      virtual ~ILookupVisitor()
-      {
-      }
-
-      virtual bool IsDicomAsJsonNeeded() const = 0;
-      
-      virtual void MarkAsComplete() = 0;
-
-      // NB: "dicomAsJson" must *not* be deleted, and can be NULL if
-      // "!IsDicomAsJsonNeeded()"
-      virtual void Visit(const std::string& publicId,
-                         const std::string& instanceId,
-                         const DicomMap& mainDicomTags,
-                         const Json::Value* dicomAsJson) = 0;
-    };
-    
     struct StoreResult
     {
     private:
@@ -250,6 +233,7 @@
         
     std::unique_ptr<SharedArchive>  queryRetrieveArchive_;
     std::string defaultLocalAet_;
+    RetrieveMethod defaultDicomRetrieveMethod_;
     OrthancHttpHandler  httpHandler_;
     bool saveJobs_;
     FindStorageAccessMode findStorageAccessMode_;
@@ -274,9 +258,10 @@
 
     // New in Orthanc 1.9.0
     DicomTransferSyntax preferredTransferSyntax_;
-    boost::mutex dynamicOptionsMutex_;
+    mutable boost::mutex dynamicOptionsMutex_;
     bool isUnknownSopClassAccepted_;
     std::set<DicomTransferSyntax>  acceptedTransferSyntaxes_;
+    std::list<std::string>         acceptedSopClasses_;  // ordered; the most 120 common ones first
     bool readOnly_;
 
     StoreResult StoreAfterTranscoding(std::string& resultPublicId,
@@ -324,7 +309,8 @@
                   IStorageArea& area,
                   bool unitTesting,
                   size_t maxCompletedJobs,
-                  bool readOnly);
+                  bool readOnly,
+                  unsigned int maxConcurrentDcmtkTranscoder);
 
     ~ServerContext();
 
@@ -377,11 +363,10 @@
                                   bool isReconstruct = false);
 
     void AnswerAttachment(RestApiOutput& output,
-                          const std::string& resourceId,
-                          FileContentType content);
+                          const FileInfo& fileInfo);
 
-    void ChangeAttachmentCompression(const std::string& resourceId,
-                                     ResourceType resourceType,
+    void ChangeAttachmentCompression(ResourceType level,
+                                     const std::string& resourceId,
                                      FileContentType attachmentType,
                                      CompressionType compression);
 
@@ -413,13 +398,15 @@
 
     // This method is for low-level operations on "/instances/.../attachments/..."
     void ReadAttachment(std::string& result,
-                        int64_t& revision,
-                        std::string& attachmentId,
-                        const std::string& instancePublicId,
-                        FileContentType content,
+                        const FileInfo& attachment,
                         bool uncompressIfNeeded,
                         bool skipCache = false);
 
+    void ReadAttachmentRange(std::string& result,
+                             const FileInfo& attachment,
+                             const StorageAccessor::Range& range,
+                             bool uncompressIfNeeded);
+
     void SetStoreMD5ForAttachments(bool storeMD5);
 
     bool IsStoreMD5ForAttachments() const
@@ -453,6 +440,11 @@
       return defaultLocalAet_;
     }
 
+    RetrieveMethod GetDefaultDicomRetrieveMethod() const
+    {
+      return defaultDicomRetrieveMethod_;
+    }
+
     LuaScripting& GetLuaScripting()
     {
       return mainLua_;
@@ -470,23 +462,6 @@
       return (level == ResourceType_Instance ? limitFindInstances_ : limitFindResults_);
     }
 
-    void Apply(ILookupVisitor& visitor,
-               const DatabaseLookup& lookup,
-               ResourceType queryLevel,
-               const std::set<std::string>& labels,
-               LabelsConstraint labelsConstraint,
-               size_t since,
-               size_t limit);
-
-    void Apply(ILookupVisitor& visitor,
-               const DatabaseLookup& lookup,
-               ResourceType queryLevel,
-               size_t since,
-               size_t limit)
-    {
-      Apply(visitor, lookup, queryLevel, std::set<std::string>(), LabelsConstraint_All, since, limit);
-    }
-
     bool LookupOrReconstructMetadata(std::string& target,
                                      const std::string& publicId,
                                      ResourceType level,
@@ -617,41 +592,21 @@
 
     const std::string& GetDeidentifiedContent(const DicomElement& element) const;
 
-    void GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& syntaxes);
+    void GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& syntaxes) const;
 
     void SetAcceptedTransferSyntaxes(const std::set<DicomTransferSyntax>& syntaxes);
 
-    bool IsUnknownSopClassAccepted();
+    void GetProposedStorageTransferSyntaxes(std::list<DicomTransferSyntax>& syntaxes) const;
+
+    void SetAcceptedSopClasses(const std::list<std::string>& acceptedSopClasses,
+                               const std::set<std::string>& rejectedSopClasses);
+
+    void GetAcceptedSopClasses(std::set<std::string>& sopClasses, size_t maxCount) const;
+
+    bool IsUnknownSopClassAccepted() const;
 
     void SetUnknownSopClassAccepted(bool accepted);
 
-    bool ExpandResource(Json::Value& target,
-                        const std::string& publicId,
-                        ResourceType level,
-                        DicomToJsonFormat format,
-                        const std::set<DicomTag>& requestedTags,
-                        bool allowStorageAccess);
-
-    bool ExpandResource(Json::Value& target,
-                        const std::string& publicId,
-                        const DicomMap& mainDicomTags,    // optional: the main dicom tags for the resource (if already available)
-                        const std::string& instanceId,    // optional: the id of an instance for the resource
-                        const Json::Value* dicomAsJson,   // optional: the dicom-as-json for the resource
-                        ResourceType level,
-                        DicomToJsonFormat format,
-                        const std::set<DicomTag>& requestedTags,
-                        bool allowStorageAccess);
-
-    bool ExpandResource(ExpandedResource& target,
-                        const std::string& publicId,
-                        const DicomMap& mainDicomTags,    // optional: the main dicom tags for the resource (if already available)
-                        const std::string& instanceId,    // optional: the id of an instance for the resource
-                        const Json::Value* dicomAsJson,   // optional: the dicom-as-json for the resource
-                        ResourceType level,
-                        const std::set<DicomTag>& requestedTags,
-                        ExpandResourceFlags expandFlags,
-                        bool allowStorageAccess);
-
     FindStorageAccessMode GetFindStorageAccessMode() const
     {
       return findStorageAccessMode_;
--- a/OrthancServer/Sources/ServerEnumerations.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerEnumerations.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -623,6 +623,10 @@
     {
       return ResponseContentFlags_MainDicomTags;
     }
+    else if (value == "RequestedTags")
+    {
+      return ResponseContentFlags_RequestedTags;
+    }
     else if (value == "Metadata")
     {
       return ResponseContentFlags_Metadata;
--- a/OrthancServer/Sources/ServerEnumerations.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerEnumerations.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -137,6 +137,8 @@
     ResponseContentFlags_Labels               = (1 << 11),
     ResponseContentFlags_IsStable             = (1 << 12),
 
+    ResponseContentFlags_INTERNAL_CountResources = (1 << 30),
+    
     // Some predefined combinations
     ResponseContentFlags_ExpandTrue  = (ResponseContentFlags_ID |
                                         ResponseContentFlags_Type |
@@ -250,9 +252,11 @@
     Warnings_None,
     Warnings_001_TagsBeingReadFromStorage,
     Warnings_002_InconsistentDicomTagsInDb,
-    Warnings_003_DecoderFailure,              // new in Orthanc 1.12.5
-    Warnings_004_NoMainDicomTagsSignature,    // new in Orthanc 1.12.5
-    Warnings_005_RequestingTagFromLowerResourceLevel    // new in Orthanc 1.12.5
+    Warnings_003_DecoderFailure,                           // new in Orthanc 1.12.5
+    Warnings_004_NoMainDicomTagsSignature,                 // new in Orthanc 1.12.5
+    Warnings_005_RequestingTagFromLowerResourceLevel,      // new in Orthanc 1.12.5
+    Warnings_006_RequestingTagFromMetaHeader,              // new in Orthanc 1.12.5
+    Warnings_007_MissingRequestedTagsNotReadFromDisk       // new in Orthanc 1.12.5
   };
 
 
--- a/OrthancServer/Sources/ServerIndex.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerIndex.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -273,40 +273,6 @@
     }
   };
 
-  void ServerIndex::UpdateStatisticsThread(ServerIndex* that,
-                                           unsigned int threadSleepGranularityMilliseconds)
-  {
-    Logging::SetCurrentThreadName("DB-STATS");
-
-    static const unsigned int SLEEP_SECONDS = 10;
-
-    if (threadSleepGranularityMilliseconds > 1000)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    LOG(INFO) << "Starting the update statistics thread (sleep = " << SLEEP_SECONDS << " seconds)";
-
-    unsigned int count = 0;
-    unsigned int countThreshold = (1000 * SLEEP_SECONDS) / threadSleepGranularityMilliseconds;
-
-    while (!that->done_)
-    {
-      boost::this_thread::sleep(boost::posix_time::milliseconds(threadSleepGranularityMilliseconds));
-      count++;
-      
-      if (count >= countThreshold)
-      {
-        uint64_t diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances;
-        that->GetGlobalStatistics(diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances);
-        
-        count = 0;
-      }
-    }
-
-    LOG(INFO) << "Stopping the update statistics thread";
-  }
-
   void ServerIndex::FlushThread(ServerIndex* that,
                                 unsigned int threadSleepGranularityMilliseconds)
   {
@@ -384,21 +350,6 @@
       }
     }
 
-    // For some DB plugins that implements the UpdateAndGetStatistics function, updating 
-    // the statistics can take quite some time if you have not done it for a long time 
-    // -> make sure they are updated at regular interval
-    if (GetDatabaseCapabilities().HasUpdateAndGetStatistics())
-    {
-      if (readOnly)
-      {
-        LOG(WARNING) << "READ-ONLY SYSTEM: not starting the UpdateStatisticsThread";
-      }
-      else
-      {
-        updateStatisticsThread_ = boost::thread(UpdateStatisticsThread, this, threadSleepGranularityMilliseconds);
-      }
-    }
-
     if (readOnly)
     {
       LOG(WARNING) << "READ-ONLY SYSTEM: not starting the unstable resources monitor thread";
@@ -432,11 +383,6 @@
         flushThread_.join();
       }
 
-      if (updateStatisticsThread_.joinable())
-      {
-        updateStatisticsThread_.join();
-      }
-
       if (unstableResourcesMonitorThread_.joinable())
       {
         unstableResourcesMonitorThread_.join();
--- a/OrthancServer/Sources/ServerIndex.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerIndex.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -42,7 +42,6 @@
     bool done_;
     boost::mutex monitoringMutex_;
     boost::thread flushThread_;
-    boost::thread updateStatisticsThread_;
     boost::thread unstableResourcesMonitorThread_;
 
     LeastRecentlyUsedIndex<std::pair<ResourceType, int64_t>, UnstableResourcePayload>  unstableResources_;
@@ -55,9 +54,6 @@
     static void FlushThread(ServerIndex* that,
                             unsigned int threadSleep);
 
-    static void UpdateStatisticsThread(ServerIndex* that,
-                                       unsigned int threadSleep);
-
     static void UnstableResourcesMonitorThread(ServerIndex* that,
                                                unsigned int threadSleep);
 
--- a/OrthancServer/Sources/ServerIndexChange.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerIndexChange.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -31,8 +31,10 @@
 #include "../../../OrthancFramework/Sources/Logging.h"
 #include "../../../OrthancFramework/Sources/OrthancException.h"
 #include "../../../OrthancFramework/Sources/MultiThreading/Semaphore.h"
+#include "../../../OrthancFramework/Sources/SerializationToolbox.h"
 #include "../OrthancConfiguration.h"
 #include "../ServerContext.h"
+#include "../SimpleInstanceOrdering.h"
 
 #include <stdio.h>
 #include <boost/range/algorithm/count.hpp>
@@ -98,7 +100,7 @@
     {
     }
 
-    virtual void PrepareDicom(const std::string& instanceId)
+    virtual void PrepareDicom(const std::string& instanceId, const FileInfo& fileInfo)
     {
     }
 
@@ -127,7 +129,7 @@
       return false;
     }
 
-    virtual void GetDicom(std::string& dicom, const std::string& instanceId) = 0;
+    virtual void GetDicom(std::string& dicom, const std::string& instanceId, const FileInfo& fileInfo) = 0;
 
     virtual void Clear()
     {
@@ -142,9 +144,9 @@
     {
     }
 
-    virtual void GetDicom(std::string& dicom, const std::string& instanceId) ORTHANC_OVERRIDE
+    virtual void GetDicom(std::string& dicom, const std::string& instanceId, const FileInfo& fileInfo) ORTHANC_OVERRIDE
     {
-      context_.ReadDicom(dicom, instanceId);
+      context_.ReadAttachment(dicom, fileInfo, true);
 
       if (transcode_)
       {
@@ -158,21 +160,25 @@
     }
   };
 
-  class InstanceId : public Orthanc::IDynamicObject
+  class InstanceToPreload : public Orthanc::IDynamicObject
   {
   private:
     std::string id_;
+    FileInfo    fileInfo_;
 
   public:
-    explicit InstanceId(const std::string& id) : id_(id)
+    explicit InstanceToPreload(const std::string& id, const FileInfo& fileInfo) : 
+      id_(id),
+      fileInfo_(fileInfo)
     {
     }
 
-    virtual ~InstanceId() ORTHANC_OVERRIDE
+    virtual ~InstanceToPreload() ORTHANC_OVERRIDE
     {
     }
 
-    std::string GetId() const {return id_;};
+    const std::string& GetId() const {return id_;};
+    const FileInfo& GetFileInfo() const {return fileInfo_;};
   };
 
   class ArchiveJob::ThreadedInstanceLoader : public ArchiveJob::InstanceLoader
@@ -229,8 +235,8 @@
 
       while (true)
       {
-        std::unique_ptr<InstanceId> instanceId(dynamic_cast<InstanceId*>(that->instancesToPreload_.Dequeue(0)));
-        if (instanceId.get() == NULL)  // that's the signal to exit the thread
+        std::unique_ptr<InstanceToPreload> instanceToPreload(dynamic_cast<InstanceToPreload*>(that->instancesToPreload_.Dequeue(0)));
+        if (instanceToPreload.get() == NULL)  // that's the signal to exit the thread
         {
           return;
         }
@@ -241,12 +247,12 @@
         try
         {
           boost::shared_ptr<std::string> dicomContent(new std::string());
-          that->context_.ReadDicom(*dicomContent, instanceId->GetId());
+          that->context_.ReadAttachment(*dicomContent, instanceToPreload->GetFileInfo(), true);
 
           if (that->transcode_)
           {
             boost::shared_ptr<std::string> transcodedDicom(new std::string());
-            if (that->TranscodeDicom(*transcodedDicom, *dicomContent, instanceId->GetId()))
+            if (that->TranscodeDicom(*transcodedDicom, *dicomContent, instanceToPreload->GetId()))
             {
               dicomContent = transcodedDicom;
             }
@@ -254,7 +260,7 @@
 
           {
             boost::mutex::scoped_lock lock(that->availableInstancesMutex_);
-            that->availableInstances_[instanceId->GetId()] = dicomContent;
+            that->availableInstances_[instanceToPreload->GetId()] = dicomContent;
           }
 
           that->availableInstancesSemaphore_.Release();
@@ -263,18 +269,18 @@
         {
           boost::mutex::scoped_lock lock(that->availableInstancesMutex_);
           // store a NULL result to notify that we could not read the instance
-          that->availableInstances_[instanceId->GetId()] = boost::shared_ptr<std::string>(); 
+          that->availableInstances_[instanceToPreload->GetId()] = boost::shared_ptr<std::string>(); 
           that->availableInstancesSemaphore_.Release();
         }
       }
     }
 
-    virtual void PrepareDicom(const std::string& instanceId) ORTHANC_OVERRIDE
+    virtual void PrepareDicom(const std::string& instanceId, const FileInfo& fileInfo) ORTHANC_OVERRIDE
     {
-      instancesToPreload_.Enqueue(new InstanceId(instanceId));
+      instancesToPreload_.Enqueue(new InstanceToPreload(instanceId, fileInfo));
     }
 
-    virtual void GetDicom(std::string& dicom, const std::string& instanceId) ORTHANC_OVERRIDE
+    virtual void GetDicom(std::string& dicom, const std::string& instanceId, const FileInfo& fileInfo) ORTHANC_OVERRIDE
     {
       while (true)
       {
@@ -284,6 +290,8 @@
 
         boost::shared_ptr<std::string> dicomContent;
         {
+          boost::mutex::scoped_lock lock(availableInstancesMutex_);
+
           if (availableInstances_.find(instanceId) != availableInstances_.end())
           {
             // this is the instance we were waiting for
@@ -366,7 +374,7 @@
     {
       case ResourceType_Patient:
         return ArchiveResourceType_Patient;
-      case ArchiveResourceType_Study:
+      case ResourceType_Study:
        return ArchiveResourceType_PatientInfoFromStudy;
       case ResourceType_Series:
         return ArchiveResourceType_Series;
@@ -506,7 +514,8 @@
     virtual void Close() = 0;
 
     virtual void AddInstance(const std::string& instanceId,
-                             uint64_t uncompressedSize) = 0;
+                             uint32_t index,
+                             const FileInfo& fileInfo) = 0;
   };
 
 
@@ -516,12 +525,15 @@
     struct Instance
     {
       std::string  id_;
-      uint64_t     uncompressedSize_;
+      uint32_t     index_;
+      FileInfo     fileInfo_;
 
       Instance(const std::string& id,
-               uint64_t uncompressedSize) : 
+               uint32_t index,
+               const FileInfo& fileInfo) :
         id_(id),
-        uncompressedSize_(uncompressedSize)
+        index_(index),
+        fileInfo_(fileInfo)
       {
       }
     };
@@ -534,22 +546,18 @@
     std::list<Instance>  instances_;   // Only at instance level
 
 
-    void AddResourceToExpand(ServerIndex& index,
-                             const std::string& id)
+    void AddResourceToExpand(const std::string& id)
     {
-      if (level_ == ArchiveResourceType_Instance)
-      {
-        FileInfo tmp;
-        int64_t revision;  // ignored
-        if (index.LookupAttachment(tmp, revision, id, FileContentType_Dicom))
-        {
-          instances_.push_back(Instance(id, tmp.GetUncompressedSize()));
-        }
-      }
-      else
-      {
-        resources_[id] = NULL;
-      }
+      assert(level_ != ArchiveResourceType_Instance);
+      resources_[id] = NULL;
+    }
+
+    void AddInstance(const std::string& id,
+                     uint32_t indexInSeries,
+                     const FileInfo& fileInfo)
+    {
+      assert(level_ == ArchiveResourceType_Instance);
+      instances_.push_back(Instance(id, indexInSeries, fileInfo));
     }
 
 
@@ -577,7 +585,20 @@
 
       if (level_ == ArchiveResourceType_Instance)
       {
-        AddResourceToExpand(index, id);
+        std::string strIndexInSeries;
+        uint32_t indexInSeries = 0;
+        FileInfo fileInfo;
+        int64_t revisionNotUsed;
+        
+        if (index.LookupMetadata(strIndexInSeries, id, ResourceType_Instance, MetadataType_Instance_IndexInSeries))
+        {
+          SerializationToolbox::ParseUnsignedInteger32(indexInSeries, strIndexInSeries);
+        }
+
+        if (index.LookupAttachment(fileInfo, revisionNotUsed, ResourceType_Instance, id, FileContentType_Dicom))
+        {
+          AddInstance(id, indexInSeries, fileInfo);
+        }
       }
       else if (resource.GetLevel() == GetResourceLevel(level_))
       {
@@ -620,16 +641,31 @@
       {
         if (it->second == NULL)
         {
-          // This is resource is marked for expansion
-          std::list<std::string> children;
-          index.GetChildren(children, it->first);
-
+          // This resource is marked for expansion
           std::unique_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_)));
 
-          for (std::list<std::string>::const_iterator 
-                 it2 = children.begin(); it2 != children.end(); ++it2)
+          if (level_ == ArchiveResourceType_Series)
           {
-            child->AddResourceToExpand(index, *it2);
+            // Instances ordering is important !  
+            // From 1.12.6, when possible, the id in the filename will match the index in series.
+            // Only if there are duplicate index in series, we'll use a simple counter
+            SimpleInstanceOrdering orderedInstances(index, it->first);
+
+            for (size_t i = 0; i < orderedInstances.GetInstancesCount(); ++i)
+            {
+              child->AddInstance(orderedInstances.GetInstanceId(i), orderedInstances.GetInstanceIndexInSeries(i), orderedInstances.GetInstanceFileInfo(i));
+            }
+          }
+          else
+          {
+            std::list<std::string> children;
+            index.GetChildren(children, GetResourceLevel(level_), it->first);
+
+            for (std::list<std::string>::const_iterator 
+                  it2 = children.begin(); it2 != children.end(); ++it2)
+            {
+              child->AddResourceToExpand(*it2);
+            }
           }
 
           it->second = child.release();
@@ -648,7 +684,7 @@
         for (std::list<Instance>::const_iterator 
                it = instances_.begin(); it != instances_.end(); ++it)
         {
-          visitor.AddInstance(it->id_, it->uncompressedSize_);
+          visitor.AddInstance(it->id_,  it->index_, it->fileInfo_);
         }          
       }
       else
@@ -683,6 +719,7 @@
       Type          type_;
       std::string   filename_;
       std::string   instanceId_;
+      FileInfo      fileInfo_;
 
     public:
       explicit Command(Type type) :
@@ -701,10 +738,12 @@
         
       Command(Type type,
               const std::string& filename,
-              const std::string& instanceId) :
+              const std::string& instanceId,
+              const FileInfo& fileInfo) :
         type_(type),
         filename_(filename),
-        instanceId_(instanceId)
+        instanceId_(instanceId),
+        fileInfo_(fileInfo)
       {
         assert(type_ == Type_WriteInstance);
       }
@@ -733,7 +772,7 @@
 
             try
             {
-              instanceLoader.GetDicom(content, instanceId_);
+              instanceLoader.GetDicom(content, instanceId_, fileInfo_);
             }
             catch (OrthancException& e)
             {
@@ -858,12 +897,12 @@
 
     void AddWriteInstance(const std::string& filename,
                           const std::string& instanceId,
-                          uint64_t uncompressedSize)
+                          const FileInfo& fileInfo)
     {
-      instanceLoader_.PrepareDicom(instanceId);
-      commands_.push_back(new Command(Type_WriteInstance, filename, instanceId));
+      instanceLoader_.PrepareDicom(instanceId, fileInfo);
+      commands_.push_back(new Command(Type_WriteInstance, filename, instanceId, fileInfo));
       instancesCount_ ++;
-      uncompressedSize_ += uncompressedSize;
+      uncompressedSize_ += fileInfo.GetUncompressedSize();
     }
 
     bool IsZip64() const
@@ -982,13 +1021,13 @@
     }
 
     virtual void AddInstance(const std::string& instanceId,
-                             uint64_t uncompressedSize) ORTHANC_OVERRIDE
+                             uint32_t index,
+                             const FileInfo& fileInfo) ORTHANC_OVERRIDE
     {
       char filename[24];
-      snprintf(filename, sizeof(filename) - 1, instanceFormat_, counter_);
-      counter_ ++;
+      snprintf(filename, sizeof(filename) - 1, instanceFormat_, index);
 
-      commands_.AddWriteInstance(filename, instanceId, uncompressedSize);
+      commands_.AddWriteInstance(filename, instanceId, fileInfo);
     }
   };
 
@@ -1016,13 +1055,14 @@
     }
 
     virtual void AddInstance(const std::string& instanceId,
-                             uint64_t uncompressedSize) ORTHANC_OVERRIDE
+                             uint32_t indexNotUsed,
+                             const FileInfo& fileInfo) ORTHANC_OVERRIDE
     {
       // "DICOM restricts the filenames on DICOM media to 8
       // characters (some systems wrongly use 8.3, but this does not
       // conform to the standard)."
       std::string filename = "IM" + boost::lexical_cast<std::string>(counter_);
-      commands_.AddWriteInstance(filename, instanceId, uncompressedSize);
+      commands_.AddWriteInstance(filename, instanceId, fileInfo);
 
       counter_ ++;
     }
@@ -1478,7 +1518,7 @@
   }
 
 
-  float ArchiveJob::GetProgress()
+  float ArchiveJob::GetProgress() const
   {
     if (writer_.get() == NULL ||
         writer_->GetStepsCount() == 0)
@@ -1493,7 +1533,7 @@
   }
 
     
-  void ArchiveJob::GetJobType(std::string& target)
+  void ArchiveJob::GetJobType(std::string& target) const
   {
     if (isMedia_)
     {
@@ -1506,7 +1546,7 @@
   }
 
 
-  void ArchiveJob::GetPublicContent(Json::Value& value)
+  void ArchiveJob::GetPublicContent(Json::Value& value) const
   {
     value = Json::objectValue;
     value[KEY_DESCRIPTION] = description_;
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -107,13 +107,13 @@
 
     virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
 
-    virtual float GetProgress() ORTHANC_OVERRIDE;
+    virtual float GetProgress() const ORTHANC_OVERRIDE;
 
-    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE;
+    virtual void GetJobType(std::string& target) const ORTHANC_OVERRIDE;
     
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE
+    virtual bool Serialize(Json::Value& value) const ORTHANC_OVERRIDE
     {
       return false;  // Cannot serialize this kind of job
     }
--- a/OrthancServer/Sources/ServerJobs/CleaningInstancesJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/CleaningInstancesJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -83,7 +83,7 @@
   }
 
   
-  bool CleaningInstancesJob::Serialize(Json::Value& target)
+  bool CleaningInstancesJob::Serialize(Json::Value& target) const
   {
     if (!SetOfInstancesJob::Serialize(target))
     {
--- a/OrthancServer/Sources/ServerJobs/CleaningInstancesJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/CleaningInstancesJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -62,7 +62,7 @@
     
     void SetKeepSource(bool keep);
 
-    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& target) const ORTHANC_OVERRIDE;
 
     virtual void Start() ORTHANC_OVERRIDE;
   };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,121 @@
+/**
+ * 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/>.
+ **/
+
+
+#include "DicomGetScuJob.h"
+
+#include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
+#include "../../../OrthancFramework/Sources/SerializationToolbox.h"
+#include "../ServerContext.h"
+#include <dcmtk/dcmnet/dimse.h>
+#include <algorithm>
+
+static const char* const LOCAL_AET = "LocalAet";
+static const char* const QUERY = "Query";
+static const char* const QUERY_FORMAT = "QueryFormat";  // New in 1.9.5
+static const char* const REMOTE = "Remote";
+static const char* const TIMEOUT = "Timeout";
+
+namespace Orthanc
+{
+
+
+  static uint16_t InstanceReceivedHandler(void* callbackContext,
+                                          DcmDataset& dataset,
+                                          const std::string& remoteAet,
+                                          const std::string& remoteIp,
+                                          const std::string& calledAet)
+  {
+    // this code is equivalent to OrthancStoreRequestHandler
+    ServerContext* context = reinterpret_cast<ServerContext*>(callbackContext);
+
+    std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromDcmDataset(dataset));
+    
+    if (toStore->GetBufferSize() > 0)
+    {
+      toStore->SetOrigin(DicomInstanceOrigin::FromDicomProtocol
+                         (remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str()));
+
+      std::string id;
+      ServerContext::StoreResult result = context->Store(id, *toStore, StoreInstanceMode_Default);
+      return result.GetCStoreStatusCode();
+    }
+
+    return STATUS_STORE_Error_CannotUnderstand;
+  }
+
+  void DicomGetScuJob::Retrieve(const DicomMap& findAnswer)
+  {
+    if (connection_.get() == NULL)
+    {
+      std::set<std::string> sopClassesToPropose;
+      std::set<std::string> acceptedSopClasses;
+      std::list<DicomTransferSyntax> proposedTransferSyntaxes;
+
+      if (sopClassesFromResourcesToRetrieve_.size() > 0)
+      {
+        context_.GetAcceptedSopClasses(acceptedSopClasses, 0); 
+
+        // keep the sop classes from the resources to retrieve only if they are accepted by Orthanc
+        Toolbox::GetIntersection(sopClassesToPropose, sopClassesFromResourcesToRetrieve_, acceptedSopClasses);
+      }
+      else
+      {
+        // when we don't know what SOP Classes to use, we include the 120 most common SOP Classes because 
+        // there are only 128 presentation contexts available
+        context_.GetAcceptedSopClasses(sopClassesToPropose, 120); 
+      }
+
+      if (sopClassesToPropose.size() == 0)
+      {
+        throw OrthancException(ErrorCode_NoPresentationContext, "Cannot perform C-Get, no SOPClassUID have been accepted by Orthanc.");        
+      }
+
+      context_.GetProposedStorageTransferSyntaxes(proposedTransferSyntaxes);
+
+      connection_.reset(new DicomControlUserConnection(parameters_, 
+                                                       ScuOperationFlags_Get, 
+                                                       sopClassesToPropose,
+                                                       proposedTransferSyntaxes));
+    }
+
+    connection_->SetProgressListener(this);
+    connection_->Get(findAnswer, InstanceReceivedHandler, &context_);
+  }
+
+  void DicomGetScuJob::AddFindAnswer(const DicomMap& answer)
+  {
+    DicomRetrieveScuBaseJob::AddFindAnswer(answer);
+
+    std::set<std::string> sopClassesInStudy;
+    if (answer.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY) 
+        && answer.LookupStringValues(sopClassesInStudy, DICOM_TAG_SOP_CLASSES_IN_STUDY, false))
+    {
+      for (std::set<std::string>::const_iterator it = sopClassesInStudy.begin(); it != sopClassesInStudy.end(); ++it)
+      {
+        sopClassesFromResourcesToRetrieve_.insert(*it);
+      }
+    }
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/ServerJobs/DicomGetScuJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,60 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "../../../OrthancFramework/Sources/Compatibility.h"
+#include "../../../OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h"
+#include "DicomRetrieveScuBaseJob.h"
+#include "../QueryRetrieveHandler.h"
+
+namespace Orthanc
+{
+  class ServerContext;
+  
+  class DicomGetScuJob : public DicomRetrieveScuBaseJob
+  {
+  private:
+    std::set<std::string> sopClassesFromResourcesToRetrieve_;
+
+    virtual void Retrieve(const DicomMap& findAnswer) ORTHANC_OVERRIDE;
+    
+  public:
+    explicit DicomGetScuJob(ServerContext& context) :
+      DicomRetrieveScuBaseJob(context)
+    {
+    }
+
+    DicomGetScuJob(ServerContext& context,
+                   const Json::Value& serialized);
+
+
+    virtual void AddFindAnswer(const DicomMap &answer) ORTHANC_OVERRIDE;
+
+
+    virtual void GetJobType(std::string& target) const ORTHANC_OVERRIDE
+    {
+      target = "DicomGetScu";
+    }
+  };
+}
--- a/OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -242,7 +242,7 @@
   }
   
 
-  void DicomModalityStoreJob::GetPublicContent(Json::Value& value)
+  void DicomModalityStoreJob::GetPublicContent(Json::Value& value) const
   {
     SetOfInstancesJob::GetPublicContent(value);
     
@@ -281,7 +281,7 @@
   }
 
 
-  bool DicomModalityStoreJob::Serialize(Json::Value& target)
+  bool DicomModalityStoreJob::Serialize(Json::Value& target) const
   {
     if (!SetOfInstancesJob::Serialize(target))
     {
--- a/OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -88,14 +88,14 @@
 
     virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
 
-    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& target) const ORTHANC_OVERRIDE
     {
       target = "DicomModalityStore";
     }
 
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& target) const ORTHANC_OVERRIDE;
 
     virtual void Reset() ORTHANC_OVERRIDE;
 
--- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -36,123 +36,21 @@
 
 namespace Orthanc
 {
-  class DicomMoveScuJob::Command : public SetOfCommandsJob::ICommand
-  {
-  private:
-    DicomMoveScuJob&           that_;
-    std::unique_ptr<DicomMap>  findAnswer_;
 
-  public:
-    Command(DicomMoveScuJob& that,
-            const DicomMap&  findAnswer) :
-      that_(that),
-      findAnswer_(findAnswer.Clone())
-    {
-    }
-
-    virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE
-    {
-      that_.Retrieve(*findAnswer_);
-      return true;
-    }
-
-    virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE
-    {
-      findAnswer_->Serialize(target);
-    }
-  };
-
-
-  class DicomMoveScuJob::Unserializer :
-    public SetOfCommandsJob::ICommandUnserializer
-  {
-  private:
-    DicomMoveScuJob&   that_;
-
-  public:
-    explicit Unserializer(DicomMoveScuJob&  that) :
-      that_(that)
-    {
-    }
-
-    virtual ICommand* Unserialize(const Json::Value& source) const ORTHANC_OVERRIDE
-    {
-      DicomMap findAnswer;
-      findAnswer.Unserialize(source);
-      return new Command(that_, findAnswer);
-    }
-  };
 
 
   void DicomMoveScuJob::Retrieve(const DicomMap& findAnswer)
   {
     if (connection_.get() == NULL)
     {
-      connection_.reset(new DicomControlUserConnection(parameters_));
+      connection_.reset(new DicomControlUserConnection(parameters_, ScuOperationFlags_Move));
     }
     
+    connection_->SetProgressListener(this);
     connection_->Move(targetAet_, findAnswer);
   }
 
 
-  static void AddToQuery(DicomFindAnswers& query,
-                         const DicomMap& item)
-  {
-    query.Add(item);
-
-    /**
-     * Compatibility with Orthanc <= 1.9.4: Remove the
-     * "SpecificCharacterSet" (0008,0005) tag that is automatically
-     * added if creating a ParsedDicomFile object from a DicomMap.
-     **/
-    query.GetAnswer(query.GetSize() - 1).Remove(DICOM_TAG_SPECIFIC_CHARACTER_SET);
-  }
-
-  // this method is used to implement the retrieve part of a Q&R 
-  // it keeps only the main dicom tags from the C-Find answer
-  void DicomMoveScuJob::AddFindAnswer(const DicomMap& answer)
-  {
-    DicomMap item;
-    item.CopyTagIfExists(answer, DICOM_TAG_QUERY_RETRIEVE_LEVEL);
-    item.CopyTagIfExists(answer, DICOM_TAG_PATIENT_ID);
-    item.CopyTagIfExists(answer, DICOM_TAG_STUDY_INSTANCE_UID);
-    item.CopyTagIfExists(answer, DICOM_TAG_SERIES_INSTANCE_UID);
-    item.CopyTagIfExists(answer, DICOM_TAG_SOP_INSTANCE_UID);
-    item.CopyTagIfExists(answer, DICOM_TAG_ACCESSION_NUMBER);
-    AddToQuery(query_, item);
-    
-    AddCommand(new Command(*this, answer));
-  }
-
-  // this method is used to implement a C-Move
-  // it keeps all tags from the C-Move query
-  void DicomMoveScuJob::AddQuery(const DicomMap& query)
-  {
-    AddToQuery(query_, query);
-    AddCommand(new Command(*this, query));
-  }
-  
-  void DicomMoveScuJob::AddFindAnswer(QueryRetrieveHandler& query,
-                                      size_t i)
-  {
-    DicomMap answer;
-    query.GetAnswer(answer, i);
-    AddFindAnswer(answer);
-  }    
-
-
-  void DicomMoveScuJob::SetLocalAet(const std::string& aet)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      parameters_.SetLocalApplicationEntityTitle(aet);
-    }
-  }
-
   
   void DicomMoveScuJob::SetTargetAet(const std::string& aet)
   {
@@ -167,109 +65,33 @@
   }
 
   
-  void DicomMoveScuJob::SetRemoteModality(const RemoteModalityParameters& remote)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      parameters_.SetRemoteModality(remote);
-    }
-  }
 
 
-  void DicomMoveScuJob::SetTimeout(uint32_t seconds)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      parameters_.SetTimeout(seconds);
-    }
-  }
-
-  
-  void DicomMoveScuJob::Stop(JobStopReason reason)
-  {
-    connection_.reset();
-  }
-  
-
-  void DicomMoveScuJob::SetQueryFormat(DicomToJsonFormat format)
+  void DicomMoveScuJob::GetPublicContent(Json::Value& value) const
   {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      queryFormat_ = format;
-    }
-  }
+    DicomRetrieveScuBaseJob::GetPublicContent(value);
 
-
-  void DicomMoveScuJob::GetPublicContent(Json::Value& value)
-  {
-    SetOfCommandsJob::GetPublicContent(value);
-
-    value[LOCAL_AET] = parameters_.GetLocalApplicationEntityTitle();
-    value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle();
-
-    value[QUERY] = Json::objectValue;
-    query_.ToJson(value[QUERY], queryFormat_);
+    value[TARGET_AET] = targetAet_;
   }
 
 
   DicomMoveScuJob::DicomMoveScuJob(ServerContext& context,
                                    const Json::Value& serialized) :
-    SetOfCommandsJob(new Unserializer(*this), serialized),
-    context_(context),
-    parameters_(DicomAssociationParameters::UnserializeJob(serialized)),
-    targetAet_(SerializationToolbox::ReadString(serialized, TARGET_AET)),
-    query_(true),
-    queryFormat_(DicomToJsonFormat_Short)
+    DicomRetrieveScuBaseJob(context, serialized),
+    targetAet_(SerializationToolbox::ReadString(serialized, TARGET_AET))
   {
-    if (serialized.isMember(QUERY))
-    {
-      const Json::Value& query = serialized[QUERY];
-      if (query.type() == Json::arrayValue)
-      {
-        for (Json::Value::ArrayIndex i = 0; i < query.size(); i++)
-        {
-          DicomMap item;
-          FromDcmtkBridge::FromJson(item, query[i]);
-          AddToQuery(query_, item);
-        }
-      }
-    }
-
-    if (serialized.isMember(QUERY_FORMAT))
-    {
-      queryFormat_ = StringToDicomToJsonFormat(SerializationToolbox::ReadString(serialized, QUERY_FORMAT));
-    }
   }
 
   
-  bool DicomMoveScuJob::Serialize(Json::Value& target)
+  bool DicomMoveScuJob::Serialize(Json::Value& target) const
   {
-    if (!SetOfCommandsJob::Serialize(target))
+    if (!DicomRetrieveScuBaseJob::Serialize(target))
     {
       return false;
     }
     else
     {
-      parameters_.SerializeJob(target);
       target[TARGET_AET] = targetAet_;
-
-      // "Short" is for compatibility with Orthanc <= 1.9.4
-      target[QUERY] = Json::objectValue;
-      query_.ToJson(target[QUERY], DicomToJsonFormat_Short);
-
-      target[QUERY_FORMAT] = EnumerationToString(queryFormat_);
       
       return true;
     }
--- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -25,7 +25,8 @@
 
 #include "../../../OrthancFramework/Sources/Compatibility.h"
 #include "../../../OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h"
-#include "../../../OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h"
+// #include "../../../OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h"
+#include "DicomRetrieveScuBaseJob.h"
 
 #include "../QueryRetrieveHandler.h"
 
@@ -33,51 +34,22 @@
 {
   class ServerContext;
   
-  class DicomMoveScuJob : public SetOfCommandsJob
+  class DicomMoveScuJob : public DicomRetrieveScuBaseJob
   {
   private:
-    class Command;
-    class Unserializer;
+    std::string                 targetAet_;
     
-    ServerContext&              context_;
-    DicomAssociationParameters  parameters_;
-    std::string                 targetAet_;
-    DicomFindAnswers            query_;
-    DicomToJsonFormat           queryFormat_;  // New in 1.9.5
-
-    std::unique_ptr<DicomControlUserConnection>  connection_;
-    
-    void Retrieve(const DicomMap& findAnswer);
+    virtual void Retrieve(const DicomMap& findAnswer) ORTHANC_OVERRIDE;
     
   public:
     explicit DicomMoveScuJob(ServerContext& context) :
-      context_(context),
-      query_(false  /* this is not for worklists */),
-      queryFormat_(DicomToJsonFormat_Short)
+      DicomRetrieveScuBaseJob(context)
     {
     }
 
     DicomMoveScuJob(ServerContext& context,
                     const Json::Value& serialized);
 
-    void AddFindAnswer(const DicomMap& answer);
-    
-    void AddQuery(const DicomMap& query);
-
-    void AddFindAnswer(QueryRetrieveHandler& query,
-                       size_t i);
-
-    const DicomAssociationParameters& GetParameters() const
-    {
-      return parameters_;
-    }
-    
-    void SetLocalAet(const std::string& aet);
-
-    void SetRemoteModality(const RemoteModalityParameters& remote);
-
-    void SetTimeout(uint32_t timeout);
-
     const std::string& GetTargetAet() const
     {
       return targetAet_;
@@ -85,22 +57,13 @@
     
     void SetTargetAet(const std::string& aet);
 
-    void SetQueryFormat(DicomToJsonFormat format);
-
-    DicomToJsonFormat GetQueryFormat() const
-    {
-      return queryFormat_;
-    }
-
-    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
-
-    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& target) const ORTHANC_OVERRIDE
     {
       target = "DicomMoveScu";
     }
 
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& target) const ORTHANC_OVERRIDE;
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,248 @@
+/**
+ * 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/>.
+ **/
+
+
+#include "DicomGetScuJob.h"
+
+#include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
+#include "../../../OrthancFramework/Sources/SerializationToolbox.h"
+#include "../ServerContext.h"
+#include <dcmtk/dcmnet/dimse.h>
+#include <algorithm>
+#include "../../../OrthancFramework/Sources/Logging.h"
+
+static const char* const LOCAL_AET = "LocalAet";
+static const char* const QUERY = "Query";
+static const char* const QUERY_FORMAT = "QueryFormat";  // New in 1.9.5
+static const char* const REMOTE = "Remote";
+static const char* const TIMEOUT = "Timeout";
+
+namespace Orthanc
+{
+
+  static void AddToQuery(DicomFindAnswers& query,
+                         const DicomMap& item)
+  {
+    query.Add(item);
+
+    /**
+     * Compatibility with Orthanc <= 1.9.4: Remove the
+     * "SpecificCharacterSet" (0008,0005) tag that is automatically
+     * added if creating a ParsedDicomFile object from a DicomMap.
+     **/
+    query.GetAnswer(query.GetSize() - 1).Remove(DICOM_TAG_SPECIFIC_CHARACTER_SET);
+  }
+
+  // this method is used to implement the retrieve part of a Q&R 
+  // it keeps only the main dicom tags from the C-Find answer
+  void DicomRetrieveScuBaseJob::AddFindAnswer(const DicomMap& answer)
+  {
+    DicomMap item;
+    item.CopyTagIfExists(answer, DICOM_TAG_QUERY_RETRIEVE_LEVEL);
+    item.CopyTagIfExists(answer, DICOM_TAG_PATIENT_ID);
+    item.CopyTagIfExists(answer, DICOM_TAG_STUDY_INSTANCE_UID);
+    item.CopyTagIfExists(answer, DICOM_TAG_SERIES_INSTANCE_UID);
+    item.CopyTagIfExists(answer, DICOM_TAG_SOP_INSTANCE_UID);
+    item.CopyTagIfExists(answer, DICOM_TAG_ACCESSION_NUMBER);
+    AddToQuery(query_, item);
+    
+    AddCommand(new Command(*this, answer));
+  }
+
+  void DicomRetrieveScuBaseJob::AddFindAnswer(QueryRetrieveHandler& query,
+                                              size_t i)
+  {
+    DicomMap answer;
+    query.GetAnswer(answer, i);
+    AddFindAnswer(answer);
+  }    
+
+  // this method is used to implement a C-Move
+  // it keeps all tags from the C-Move query
+  void DicomRetrieveScuBaseJob::AddQuery(const DicomMap& query)
+  {
+    AddToQuery(query_, query);
+    AddCommand(new Command(*this, query));
+  }
+ 
+
+  void DicomRetrieveScuBaseJob::SetLocalAet(const std::string& aet)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      parameters_.SetLocalApplicationEntityTitle(aet);
+    }
+  }
+
+  
+  void DicomRetrieveScuBaseJob::SetRemoteModality(const RemoteModalityParameters& remote)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      parameters_.SetRemoteModality(remote);
+    }
+  }
+
+
+  void DicomRetrieveScuBaseJob::SetTimeout(uint32_t seconds)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      parameters_.SetTimeout(seconds);
+    }
+  }
+
+  
+  void DicomRetrieveScuBaseJob::Stop(JobStopReason reason)
+  {
+    connection_.reset();
+  }
+  
+
+  void DicomRetrieveScuBaseJob::SetQueryFormat(DicomToJsonFormat format)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      queryFormat_ = format;
+    }
+  }
+
+
+  void DicomRetrieveScuBaseJob::GetPublicContent(Json::Value& value) const
+  {
+    SetOfCommandsJob::GetPublicContent(value);
+
+    value[LOCAL_AET] = parameters_.GetLocalApplicationEntityTitle();
+    value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle();
+
+    value[QUERY] = Json::objectValue;
+    query_.ToJson(value[QUERY], queryFormat_);
+  }
+
+
+  DicomRetrieveScuBaseJob::DicomRetrieveScuBaseJob(ServerContext &context) :
+    context_(context),
+    query_(false /* this is not for worklists */),
+    queryFormat_(DicomToJsonFormat_Short),
+    nbRemainingSubOperations_(0),
+    nbCompletedSubOperations_(0),
+    nbFailedSubOperations_(0),
+    nbWarningSubOperations_(0)
+  {
+  }
+
+
+  DicomRetrieveScuBaseJob::DicomRetrieveScuBaseJob(ServerContext& context,
+                                                   const Json::Value& serialized) :
+    SetOfCommandsJob(new Unserializer(*this), serialized),
+    context_(context),
+    parameters_(DicomAssociationParameters::UnserializeJob(serialized)),
+    query_(true),
+    queryFormat_(DicomToJsonFormat_Short),
+    nbRemainingSubOperations_(0),
+    nbCompletedSubOperations_(0),
+    nbFailedSubOperations_(0),
+    nbWarningSubOperations_(0)  
+  {
+    if (serialized.isMember(QUERY))
+    {
+      const Json::Value& query = serialized[QUERY];
+      if (query.type() == Json::arrayValue)
+      {
+        for (Json::Value::ArrayIndex i = 0; i < query.size(); i++)
+        {
+          DicomMap item;
+          FromDcmtkBridge::FromJson(item, query[i]);
+          AddToQuery(query_, item);
+        }
+      }
+    }
+
+    if (serialized.isMember(QUERY_FORMAT))
+    {
+      queryFormat_ = StringToDicomToJsonFormat(SerializationToolbox::ReadString(serialized, QUERY_FORMAT));
+    }
+  }
+
+  
+  bool DicomRetrieveScuBaseJob::Serialize(Json::Value& target) const
+  {
+    if (!SetOfCommandsJob::Serialize(target))
+    {
+      return false;
+    }
+    else
+    {
+      parameters_.SerializeJob(target);
+
+      // "Short" is for compatibility with Orthanc <= 1.9.4
+      target[QUERY] = Json::objectValue;
+      query_.ToJson(target[QUERY], DicomToJsonFormat_Short);
+
+      target[QUERY_FORMAT] = EnumerationToString(queryFormat_);
+      
+      return true;
+    }
+  }
+
+  void DicomRetrieveScuBaseJob::OnProgressUpdated(uint16_t nbRemainingSubOperations,
+                                                  uint16_t nbCompletedSubOperations,
+                                                  uint16_t nbFailedSubOperations,
+                                                  uint16_t nbWarningSubOperations)
+  {
+    boost::mutex::scoped_lock lock(progressMutex_);
+
+    nbRemainingSubOperations_ = nbRemainingSubOperations;
+    nbCompletedSubOperations_ = nbCompletedSubOperations;
+    nbFailedSubOperations_ = nbFailedSubOperations;
+    nbWarningSubOperations_ = nbWarningSubOperations;
+  }
+
+  float DicomRetrieveScuBaseJob::GetProgress() const
+  {
+    boost::mutex::scoped_lock lock(progressMutex_);
+    
+    uint32_t totalOperations = nbRemainingSubOperations_ + nbCompletedSubOperations_ + nbFailedSubOperations_ + nbWarningSubOperations_;
+    if (totalOperations == 0)
+    {
+      return 0.0f;
+    }
+
+    return float(nbCompletedSubOperations_ + nbFailedSubOperations_ + nbWarningSubOperations_) / float(totalOperations);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,149 @@
+/**
+ * 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/>.
+ **/
+
+#pragma once
+
+#include "../../../OrthancFramework/Sources/Compatibility.h"
+#include "../../../OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h"
+#include "../../../OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h"
+#include <boost/thread/mutex.hpp>
+
+#include "../QueryRetrieveHandler.h"
+
+namespace Orthanc
+{
+  class ServerContext;
+
+  class DicomRetrieveScuBaseJob : public SetOfCommandsJob, public DicomControlUserConnection::IProgressListener
+  {
+  protected:
+    class Command : public SetOfCommandsJob::ICommand
+    {
+    private:
+      DicomRetrieveScuBaseJob &that_;
+      std::unique_ptr<DicomMap> findAnswer_;
+
+    public:
+      Command(DicomRetrieveScuBaseJob &that,
+              const DicomMap &findAnswer) :
+      that_(that),
+      findAnswer_(findAnswer.Clone())
+      {
+      }
+
+      virtual bool Execute(const std::string &jobId) ORTHANC_OVERRIDE
+      {
+        that_.Retrieve(*findAnswer_);
+        return true;
+      }
+
+      virtual void Serialize(Json::Value &target) const ORTHANC_OVERRIDE
+      {
+        findAnswer_->Serialize(target);
+      }
+    };
+
+    class Unserializer : public SetOfCommandsJob::ICommandUnserializer
+    {
+    protected:
+      DicomRetrieveScuBaseJob &that_;
+
+    public:
+      explicit Unserializer(DicomRetrieveScuBaseJob &that) : 
+      that_(that)
+      {
+      }
+
+      virtual ICommand *Unserialize(const Json::Value &source) const ORTHANC_OVERRIDE
+      {
+        DicomMap findAnswer;
+        findAnswer.Unserialize(source);
+        return new Command(that_, findAnswer);
+      }
+    };
+
+    ServerContext &context_;
+    DicomAssociationParameters parameters_;
+    DicomFindAnswers query_;
+    DicomToJsonFormat queryFormat_; // New in 1.9.5
+
+    std::unique_ptr<DicomControlUserConnection> connection_;
+
+    mutable boost::mutex progressMutex_;
+    uint16_t nbRemainingSubOperations_;
+    uint16_t nbCompletedSubOperations_;
+    uint16_t nbFailedSubOperations_;
+    uint16_t nbWarningSubOperations_;
+
+    virtual void Retrieve(const DicomMap &findAnswer) = 0;
+
+    explicit DicomRetrieveScuBaseJob(ServerContext &context);
+
+    DicomRetrieveScuBaseJob(ServerContext &context,
+                            const Json::Value &serialized);
+
+  public:
+    virtual void AddFindAnswer(const DicomMap &answer);
+
+    void AddQuery(const DicomMap& query);
+
+    void AddFindAnswer(QueryRetrieveHandler &query,
+                       size_t i);
+
+    const DicomAssociationParameters &GetParameters() const
+    {
+      return parameters_;
+    }
+
+    void SetLocalAet(const std::string &aet);
+
+    void SetRemoteModality(const RemoteModalityParameters &remote);
+
+    void SetTimeout(uint32_t timeout);
+
+    void SetQueryFormat(DicomToJsonFormat format);
+
+    DicomToJsonFormat GetQueryFormat() const
+    {
+      return queryFormat_;
+    }
+
+    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
+
+    virtual void GetPublicContent(Json::Value &value) const ORTHANC_OVERRIDE;
+
+    virtual bool Serialize(Json::Value &target) const ORTHANC_OVERRIDE;
+
+    virtual void OnProgressUpdated(uint16_t nbRemainingSubOperations,
+                                    uint16_t nbCompletedSubOperations,
+                                    uint16_t nbFailedSubOperations,
+                                    uint16_t nbWarningSubOperations) ORTHANC_OVERRIDE;
+
+    virtual float GetProgress() const ORTHANC_OVERRIDE;
+
+    virtual bool NeedsProgressUpdateBetweenSteps() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
+
+  };
+}
--- a/OrthancServer/Sources/ServerJobs/IStorageCommitmentFactory.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/IStorageCommitmentFactory.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/LuaJobManager.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/LuaJobManager.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/LuaJobManager.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/LuaJobManager.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/MergeStudyJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/MergeStudyJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -49,7 +49,7 @@
 
     // Add all the instances of the series as to be processed
     std::list<std::string> instances;
-    GetContext().GetIndex().GetChildren(instances, series);
+    GetContext().GetIndex().GetChildren(instances, ResourceType_Series, series);
 
     for (std::list<std::string>::const_iterator
            it = instances.begin(); it != instances.end(); ++it)
@@ -69,7 +69,7 @@
     else
     {
       std::list<std::string> series;
-      GetContext().GetIndex().GetChildren(series, study);
+      GetContext().GetIndex().GetChildren(series, ResourceType_Study, study);
 
       for (std::list<std::string>::const_iterator
              it = series.begin(); it != series.end(); ++it)
@@ -190,7 +190,7 @@
     DicomTag::AddTagsForModule(removals_, DicomModule_Study);
     
     std::list<std::string> instances;
-    GetContext().GetIndex().GetChildInstances(instances, targetStudy);
+    GetContext().GetIndex().GetChildInstances(instances, targetStudy, ResourceType_Study);
     
     if (instances.empty())
     {
@@ -353,7 +353,7 @@
   }
   
 
-  void MergeStudyJob::GetPublicContent(Json::Value& value)
+  void MergeStudyJob::GetPublicContent(Json::Value& value) const
   {
     CleaningInstancesJob::GetPublicContent(value);
     value["TargetStudy"] = targetStudy_;
@@ -386,7 +386,7 @@
   }
 
   
-  bool MergeStudyJob::Serialize(Json::Value& target)
+  bool MergeStudyJob::Serialize(Json::Value& target) const
   {
     if (!CleaningInstancesJob::Serialize(target))
     {
--- a/OrthancServer/Sources/ServerJobs/MergeStudyJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/MergeStudyJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -89,13 +89,13 @@
     {
     }
 
-    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& target) const ORTHANC_OVERRIDE
     {
       target = "MergeStudy";
     }
 
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& target) const ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancServer/Sources/ServerJobs/Operations/DeleteResourceOperation.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/DeleteResourceOperation.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/Operations/DeleteResourceOperation.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/DeleteResourceOperation.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/Operations/DicomInstanceOperationValue.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/DicomInstanceOperationValue.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/Operations/DicomInstanceOperationValue.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/DicomInstanceOperationValue.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/Operations/ModifyInstanceOperation.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/ModifyInstanceOperation.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/Operations/ModifyInstanceOperation.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/ModifyInstanceOperation.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/Operations/StorePeerOperation.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/StorePeerOperation.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/Operations/StorePeerOperation.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/StorePeerOperation.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/Operations/StoreScuOperation.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/StoreScuOperation.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/Operations/StoreScuOperation.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/StoreScuOperation.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/Operations/SystemCallOperation.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/SystemCallOperation.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/Operations/SystemCallOperation.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/SystemCallOperation.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/OrthancJobUnserializer.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/OrthancJobUnserializer.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/OrthancJobUnserializer.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/OrthancJobUnserializer.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerJobs/OrthancPeerStoreJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/OrthancPeerStoreJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -220,7 +220,7 @@
   }
 
 
-  void OrthancPeerStoreJob::GetPublicContent(Json::Value& value)
+  void OrthancPeerStoreJob::GetPublicContent(Json::Value& value) const
   {
     SetOfInstancesJob::GetPublicContent(value);
     
@@ -285,7 +285,7 @@
   }
 
 
-  bool OrthancPeerStoreJob::Serialize(Json::Value& target)
+  bool OrthancPeerStoreJob::Serialize(Json::Value& target) const
   {
     if (!SetOfInstancesJob::Serialize(target))
     {
--- a/OrthancServer/Sources/ServerJobs/OrthancPeerStoreJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/OrthancPeerStoreJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -92,13 +92,13 @@
 
     virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;   // For pausing jobs
 
-    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& target) const ORTHANC_OVERRIDE
     {
       target = "OrthancPeerStore";
     }
 
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& target) const ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -542,7 +542,7 @@
   }
 
 
-  void ResourceModificationJob::GetPublicContent(Json::Value& value)
+  void ResourceModificationJob::GetPublicContent(Json::Value& value) const
   {
     boost::recursive_mutex::scoped_lock lock(outputMutex_);
 
@@ -636,7 +636,7 @@
     }
   }
   
-  bool ResourceModificationJob::Serialize(Json::Value& value)
+  bool ResourceModificationJob::Serialize(Json::Value& value) const
   {
     if (modification_.get() == NULL)
     {
--- a/OrthancServer/Sources/ServerJobs/ResourceModificationJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/ResourceModificationJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -116,14 +116,14 @@
     // Only possible if "IsSingleResourceModification()"
     ResourceType GetOutputLevel() const;
 
-    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& target) const ORTHANC_OVERRIDE
     {
       target = "ResourceModification";
     }
 
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
     
-    virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& value) const ORTHANC_OVERRIDE;
 
     virtual void Reset() ORTHANC_OVERRIDE;
 
--- a/OrthancServer/Sources/ServerJobs/SplitStudyJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/SplitStudyJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -213,7 +213,7 @@
 
       // Add all the instances of the series as to be processed
       std::list<std::string> instances;
-      GetContext().GetIndex().GetChildren(instances, series);
+      GetContext().GetIndex().GetChildren(instances, ResourceType_Series, series);
 
       for (std::list<std::string>::const_iterator
              it = instances.begin(); it != instances.end(); ++it)
@@ -306,7 +306,7 @@
   }
   
     
-  void SplitStudyJob::GetPublicContent(Json::Value& value)
+  void SplitStudyJob::GetPublicContent(Json::Value& value) const
   {
     CleaningInstancesJob::GetPublicContent(value);
 
@@ -351,7 +351,7 @@
   }
 
   
-  bool SplitStudyJob::Serialize(Json::Value& target)
+  bool SplitStudyJob::Serialize(Json::Value& target) const
   {
     if (!CleaningInstancesJob::Serialize(target))
     {
--- a/OrthancServer/Sources/ServerJobs/SplitStudyJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/SplitStudyJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -113,13 +113,13 @@
     {
     }
 
-    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& target) const ORTHANC_OVERRIDE
     {
       target = "SplitStudy";
     }
 
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& target) const ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -404,7 +404,7 @@
   }
 
 
-  void StorageCommitmentScpJob::GetPublicContent(Json::Value& value)
+  void StorageCommitmentScpJob::GetPublicContent(Json::Value& value) const
   {
     SetOfCommandsJob::GetPublicContent(value);
       
@@ -434,7 +434,7 @@
   }
   
 
-  bool StorageCommitmentScpJob::Serialize(Json::Value& target)
+  bool StorageCommitmentScpJob::Serialize(Json::Value& target) const
   {
     if (!SetOfCommandsJob::Serialize(target))
     {
--- a/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -89,13 +89,13 @@
     {
     }
 
-    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& target) const ORTHANC_OVERRIDE
     {
       target = "StorageCommitmentScp";
     }
 
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& target) const ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -376,7 +376,7 @@
   static const char* KEY_WORKERS_COUNT = "WorkersCount";
 
 
-  void ThreadedSetOfInstancesJob::GetPublicContent(Json::Value& target)
+  void ThreadedSetOfInstancesJob::GetPublicContent(Json::Value& target) const
   {
     boost::recursive_mutex::scoped_lock lock(mutex_);
 
@@ -391,7 +391,7 @@
   }
 
 
-  bool ThreadedSetOfInstancesJob::Serialize(Json::Value& target)
+  bool ThreadedSetOfInstancesJob::Serialize(Json::Value& target) const
   {
     boost::recursive_mutex::scoped_lock lock(mutex_);
 
@@ -478,7 +478,7 @@
     return keepSource_;
   }
 
-  float ThreadedSetOfInstancesJob::GetProgress()
+  float ThreadedSetOfInstancesJob::GetProgress() const
   {
     boost::recursive_mutex::scoped_lock lock(mutex_);
 
--- a/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -140,15 +140,15 @@
     
     virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
 
-    virtual float GetProgress() ORTHANC_OVERRIDE;
+    virtual float GetProgress() const ORTHANC_OVERRIDE;
 
     bool IsStarted() const;
 
     virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE;
     
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE;
     
-    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
+    virtual bool Serialize(Json::Value& target) const ORTHANC_OVERRIDE;
 
     virtual bool GetOutput(std::string& output,
                            MimeType& mime,
--- a/OrthancServer/Sources/ServerToolbox.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerToolbox.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/ServerToolbox.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/ServerToolbox.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/SimpleInstanceOrdering.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,159 @@
+/**
+ * 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/>.
+ **/
+
+
+#include "PrecompiledHeadersServer.h"
+#include "SimpleInstanceOrdering.h"
+
+#include "../../OrthancFramework/Sources/Logging.h"
+#include "../../OrthancFramework/Sources/Toolbox.h"
+#include "../../OrthancFramework/Sources/SerializationToolbox.h"
+#include "ServerEnumerations.h"
+#include "ServerIndex.h"
+#include "ResourceFinder.h"
+
+#include <algorithm>
+#include <boost/lexical_cast.hpp>
+#include <boost/noncopyable.hpp>
+
+
+namespace Orthanc
+{
+  struct SimpleInstanceOrdering::Instance : public boost::noncopyable
+  {
+  private:
+    std::string   instanceId_;
+    uint32_t      indexInSeries_;
+    FileInfo      fileInfo_;
+
+
+  public:
+    Instance(const std::string& instanceId,
+             uint32_t indexInSeries,
+             const FileInfo& fileInfo) :
+      instanceId_(instanceId),
+      indexInSeries_(indexInSeries),
+      fileInfo_(fileInfo)
+    {
+    }
+
+    const std::string& GetIdentifier() const
+    {
+      return instanceId_;
+    }
+
+    uint32_t GetIndexInSeries() const
+    {
+      return indexInSeries_;
+    }
+ 
+    const FileInfo& GetFileInfo() const
+    {
+      return fileInfo_;
+    }
+  };
+
+  bool SimpleInstanceOrdering::IndexInSeriesComparator(const SimpleInstanceOrdering::Instance* a,
+                                                       const SimpleInstanceOrdering::Instance* b)
+  {
+    return a->GetIndexInSeries() < b->GetIndexInSeries();
+  }  
+
+
+  SimpleInstanceOrdering::SimpleInstanceOrdering(ServerIndex& index,
+                                                 const std::string& seriesId) :
+    hasDuplicateIndexInSeries_(false)
+  {
+    FindRequest request(ResourceType_Instance);
+    request.SetOrthancSeriesId(seriesId);
+    request.SetRetrieveMetadata(true);
+    request.SetRetrieveAttachments(true);
+
+    FindResponse response;
+    index.ExecuteFind(response, request);
+
+    std::set<uint32_t> allIndexInSeries;
+
+    for (size_t i = 0; i < response.GetSize(); ++i)
+    {
+      const FindResponse::Resource& resource = response.GetResourceByIndex(i);
+      std::string instanceId = resource.GetIdentifier();
+
+      std::string strIndexInSeries;
+      uint32_t indexInSeries = 0;
+      FileInfo fileInfo;
+      int64_t revisionNotUsed;
+			
+      if (resource.LookupMetadata(strIndexInSeries, ResourceType_Instance, MetadataType_Instance_IndexInSeries))
+      {
+        SerializationToolbox::ParseUnsignedInteger32(indexInSeries, strIndexInSeries);
+      }
+
+      if (resource.LookupAttachment(fileInfo, revisionNotUsed, FileContentType_Dicom))
+      {
+        allIndexInSeries.insert(indexInSeries);
+        instances_.push_back(new SimpleInstanceOrdering::Instance(instanceId, indexInSeries, fileInfo));
+      }
+    }
+
+    // if there are duplicates, the set will be smaller than the vector
+    hasDuplicateIndexInSeries_ = allIndexInSeries.size() != instances_.size(); 
+
+    std::sort(instances_.begin(), instances_.end(), IndexInSeriesComparator);
+  }
+
+  SimpleInstanceOrdering::~SimpleInstanceOrdering()
+  {
+    for (std::vector<Instance*>::iterator
+           it = instances_.begin(); it != instances_.end(); ++it)
+    {
+      if (*it != NULL)
+      {
+        delete *it;
+      }
+    }
+  }
+
+  const std::string& SimpleInstanceOrdering::GetInstanceId(size_t index) const
+  {
+    return instances_[index]->GetIdentifier();
+  }
+
+  uint32_t SimpleInstanceOrdering::GetInstanceIndexInSeries(size_t index) const
+  {
+    if (hasDuplicateIndexInSeries_)
+    {
+      // if there are duplicates, we count the instances from 0
+      return index; 
+    }
+    else
+    {
+      // if there are no duplicates, we return the real index in series (that may not start at 0 but that is more human friendly)
+      return instances_[index]->GetIndexInSeries();
+    }
+  }
+
+  const FileInfo& SimpleInstanceOrdering::GetInstanceFileInfo(size_t index) const
+  {
+    return instances_[index]->GetFileInfo();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/SimpleInstanceOrdering.h	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,61 @@
+/**
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "../../OrthancFramework/Sources/DicomFormat/DicomMap.h"
+#include "../../OrthancFramework/Sources/FileStorage/FileInfo.h"
+
+namespace Orthanc
+{
+  class ServerIndex;
+
+  class SimpleInstanceOrdering
+  {
+  private:
+    struct Instance;
+
+    std::vector<Instance*>   instances_;
+    bool                     hasDuplicateIndexInSeries_;
+
+  public:
+    SimpleInstanceOrdering(ServerIndex& index,
+                           const std::string& seriesId);
+    ~SimpleInstanceOrdering();
+
+    size_t GetInstancesCount() const
+    {
+      return instances_.size();
+    }
+
+    const std::string& GetInstanceId(size_t index) const;
+
+    uint32_t GetInstanceIndexInSeries(size_t index) const;
+
+    const FileInfo& GetInstanceFileInfo(size_t index) const;
+
+  private:
+    static bool IndexInSeriesComparator(const SimpleInstanceOrdering::Instance* a,
+                                        const SimpleInstanceOrdering::Instance* b);
+  };
+}
--- a/OrthancServer/Sources/SliceOrdering.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/SliceOrdering.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -198,8 +198,7 @@
 
       try
       {
-        int64_t revision;  // Ignored
-        if (index.LookupMetadata(s, revision, instanceId, ResourceType_Instance, MetadataType_Instance_IndexInSeries))
+        if (index.LookupMetadata(s, instanceId, ResourceType_Instance, MetadataType_Instance_IndexInSeries))
         {
           indexInSeries_ = boost::lexical_cast<size_t>(Toolbox::StripSpaces(s));
           hasIndexInSeries_ = true;
@@ -297,7 +296,7 @@
   void SliceOrdering::CreateInstances()
   {
     std::list<std::string> instancesId;
-    index_.GetChildren(instancesId, seriesId_);
+    index_.GetChildren(instancesId, ResourceType_Series, seriesId_);
 
     instances_.reserve(instancesId.size());
     for (std::list<std::string>::const_iterator
--- a/OrthancServer/Sources/SliceOrdering.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/SliceOrdering.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/StorageCommitmentReports.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/StorageCommitmentReports.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/StorageCommitmentReports.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/StorageCommitmentReports.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/Sources/main.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/Sources/main.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -63,6 +63,8 @@
 static const char* const KEY_DICOM_TLS_MINIMUM_PROTOCOL_VERSION = "DicomTlsMinimumProtocolVersion";
 static const char* const KEY_DICOM_TLS_ACCEPTED_CIPHERS = "DicomTlsCiphersAccepted";
 static const char* const KEY_MAXIMUM_PDU_LENGTH = "MaximumPduLength";
+static const char* const KEY_READ_ONLY = "ReadOnly";
+static const char* const KEY_MAXIMUM_CONCURRENT_DCMTK_TRANSCODERS = "MaximumConcurrentDcmtkTranscoders";
 
 
 class OrthancStoreRequestHandler : public IStoreRequestHandler
@@ -494,6 +496,13 @@
     context_.GetAcceptedTransferSyntaxes(target);
   }
 
+  virtual void GetProposedStorageTransferSyntaxes(std::list<DicomTransferSyntax>& target,
+                                                  const std::string& remoteIp,
+                                                  const std::string& remoteAet,
+                                                  const std::string& calledAet) ORTHANC_OVERRIDE
+  {
+    context_.GetProposedStorageTransferSyntaxes(target);
+  }
   
   virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp,
                                          const std::string& remoteAet,
@@ -501,6 +510,12 @@
   {
     return context_.IsUnknownSopClassAccepted();
   }
+
+  virtual void GetAcceptedSopClasses(std::set<std::string>& sopClasses,
+                                     size_t maxCount) ORTHANC_OVERRIDE
+  {
+    context_.GetAcceptedSopClasses(sopClasses, maxCount);
+  }
 };
 
 
@@ -746,8 +761,8 @@
     << path << " " << ORTHANC_VERSION << std::endl
     << "Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics Department, University Hospital of Liege (Belgium)" << std::endl
     << "Copyright (C) 2017-2023 Osimis S.A. (Belgium)" << std::endl
-    << "Copyright (C) 2024-2024 Orthanc Team SRL (Belgium)" << std::endl
-    << "Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain (Belgium)" << std::endl
+    << "Copyright (C) 2024-2025 Orthanc Team SRL (Belgium)" << std::endl
+    << "Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain (Belgium)" << std::endl
     << "Licensing GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>." << std::endl
     << "This is free software: you are free to change and redistribute it." << std::endl
     << "There is NO WARRANTY, to the extent permitted by law." << std::endl
@@ -887,6 +902,7 @@
     PrintErrorCode(ErrorCode_AlreadyExistingTag, "Cannot override the value of a tag that already exists");
     PrintErrorCode(ErrorCode_NoStorageCommitmentHandler, "No request handler factory for DICOM N-ACTION SCP (storage commitment)");
     PrintErrorCode(ErrorCode_NoCGetHandler, "No request handler factory for DICOM C-GET SCP");
+    PrintErrorCode(ErrorCode_DicomGetUnavailable, "DicomUserConnection: The C-GET command is not supported by the remote SCP");
     PrintErrorCode(ErrorCode_UnsupportedMediaType, "Unsupported media type");
   }
 
@@ -1519,16 +1535,23 @@
 {
   size_t maxCompletedJobs;
   bool readOnly;
-  
+  unsigned int maxDcmtkConcurrentTranscoders;
+
   {
     OrthancConfiguration::ReaderLock lock;
 
     // These configuration options must be set before creating the
     // ServerContext, otherwise the possible Lua scripts will not be
     // able to properly issue HTTP/HTTPS queries
+
+    std::string httpsCaCertificates = lock.GetConfiguration().GetStringParameter("HttpsCACertificates", "");
+    if (!httpsCaCertificates.empty())
+    {
+      httpsCaCertificates = lock.GetConfiguration().InterpretStringParameterAsPath(httpsCaCertificates);
+    }
+
     HttpClient::ConfigureSsl(lock.GetConfiguration().GetBooleanParameter("HttpsVerifyPeers", true),
-                             lock.GetConfiguration().InterpretStringParameterAsPath
-                             (lock.GetConfiguration().GetStringParameter("HttpsCACertificates", "")));
+                             httpsCaCertificates);
     HttpClient::SetDefaultVerbose(lock.GetConfiguration().GetBooleanParameter("HttpVerbose", false));
 
     // The value "0" below makes the class HttpClient use its default
@@ -1547,8 +1570,15 @@
     }
 
     // New option in Orthanc 1.12.5
-    readOnly = lock.GetConfiguration().GetBooleanParameter("ReadOnly", false);
+    readOnly = lock.GetConfiguration().GetBooleanParameter(KEY_READ_ONLY, false);
     
+    // New option in Orthanc 1.12.6
+    maxDcmtkConcurrentTranscoders = lock.GetConfiguration().GetUnsignedIntegerParameter(KEY_MAXIMUM_CONCURRENT_DCMTK_TRANSCODERS, 0);
+    if (maxDcmtkConcurrentTranscoders == 0)
+    {
+      maxDcmtkConcurrentTranscoders = static_cast<unsigned int>(boost::thread::hardware_concurrency());
+    }
+
     // Configuration of DICOM TLS for Orthanc SCU (since Orthanc 1.9.0)
     DicomAssociationParameters::SetDefaultOwnCertificatePath(
       lock.GetConfiguration().GetStringParameter(KEY_DICOM_TLS_PRIVATE_KEY, ""),
@@ -1563,7 +1593,7 @@
       lock.GetConfiguration().GetBooleanParameter(KEY_DICOM_TLS_REMOTE_CERTIFICATE_REQUIRED, true));
   }
   
-  ServerContext context(database, storageArea, false /* not running unit tests */, maxCompletedJobs, readOnly);
+  ServerContext context(database, storageArea, false /* not running unit tests */, maxCompletedJobs, readOnly, maxDcmtkConcurrentTranscoders);
 
   {
     OrthancConfiguration::ReaderLock lock;
@@ -1968,7 +1998,7 @@
           SQLiteDatabaseWrapper inMemoryDatabase;
           inMemoryDatabase.Open();
           MemoryStorageArea inMemoryStorage;
-          ServerContext context(inMemoryDatabase, inMemoryStorage, true /* unit testing */, 0 /* max completed jobs */, false /* readonly */);
+          ServerContext context(inMemoryDatabase, inMemoryStorage, true /* unit testing */, 0 /* max completed jobs */, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
           OrthancRestApi restApi(context, false /* no Orthanc Explorer */);
           restApi.GenerateOpenApiDocumentation(openapi);
           context.Stop();
@@ -2019,7 +2049,7 @@
           SQLiteDatabaseWrapper inMemoryDatabase;
           inMemoryDatabase.Open();
           MemoryStorageArea inMemoryStorage;
-          ServerContext context(inMemoryDatabase, inMemoryStorage, true /* unit testing */, 0 /* max completed jobs */, false /* readonly */);
+          ServerContext context(inMemoryDatabase, inMemoryStorage, true /* unit testing */, 0 /* max completed jobs */, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
           OrthancRestApi restApi(context, false /* no Orthanc Explorer */);
           restApi.GenerateReStructuredTextCheatSheet(cheatsheet, "https://orthanc.uclouvain.be/api/index.html");
           context.Stop();
--- a/OrthancServer/UnitTestsSources/DatabaseLookupTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/UnitTestsSources/DatabaseLookupTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/UnitTestsSources/LuaServerTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/UnitTestsSources/LuaServerTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -138,38 +138,43 @@
 TEST(Lua, Http)
 {
   Orthanc::LuaContext lua;
-
-#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1
-  // The "http://www.orthanc-server.com/downloads/third-party/" does
-  // not automatically redirect to HTTPS, so we use it even if the
-  // OpenSSL/HTTPS support is disabled in curl
-  const std::string BASE = "http://www.orthanc-server.com/downloads/third-party/";
-
-#if LUA_VERSION_NUM >= 502
-  // Since Lua >= 5.2.0, the function "loadstring" has been replaced by "load"
-  lua.Execute("JSON = load(HttpGet('" + BASE + "JSON.lua')) ()");
-#else
-  lua.Execute("JSON = loadstring(HttpGet('" + BASE + "JSON.lua')) ()");
-#endif
-
-  const std::string url(BASE + "Product.json");
-#endif
+  ASSERT_TRUE(lua.IsHttpsVerifyPeers());
+  lua.SetHttpsVerifyPeers(false);
+  ASSERT_FALSE(lua.IsHttpsVerifyPeers());
 
   std::string s;
   lua.Execute(s, "print(HttpGet({}))");
   ASSERT_EQ("nil", Orthanc::Toolbox::StripSpaces(s));
 
-#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1  
-  lua.Execute(s, "print(string.len(HttpGet(\"" + url + "\")))");
-  ASSERT_LE(100, boost::lexical_cast<int>(Orthanc::Toolbox::StripSpaces(s)));
+#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1
+  // The "http://httpbin.org/get" URL does not automatically redirect
+  // to HTTPS, so we can use it even if the OpenSSL/HTTPS support is
+  // disabled in curl
 
-  // Parse a JSON file
-  lua.Execute(s, "print(JSON:decode(HttpGet(\"" + url + "\")) ['Product'])");
-  ASSERT_EQ("OrthancClient", Orthanc::Toolbox::StripSpaces(s));
+  const std::string URL = "http://httpbin.org/get";
+  lua.Execute(s, "print(HttpGet(\"" + URL + "\"))");
+
+  Json::Value json;
+  Orthanc::Toolbox::ReadJson(json, s);
+
+  ASSERT_TRUE(json.type() == Json::objectValue);
+  ASSERT_TRUE(json.isMember("url"));
+  ASSERT_EQ(URL, json["url"].asString());
 
 #if 0
   // This part of the test can only be executed if one instance of
-  // Orthanc is running on the localhost
+  // Orthanc is running on the localhost, with configuration option
+  // "ExecuteLuaEnabled" equals to "true"
+
+  const std::string JSON_LUA_TOOLBOX = "https://regex.info/code/JSON.lua";
+  lua.Execute("HttpGet('" + JSON_LUA_TOOLBOX + "')");
+
+#if LUA_VERSION_NUM >= 502
+  // Since Lua >= 5.2.0, the function "loadstring" has been replaced by "load"
+  lua.Execute("JSON = load(HttpGet('" + JSON_LUA_TOOLBOX + "')) ()");
+#else
+  lua.Execute("JSON = loadstring(HttpGet('" + JSON_LUA_TOOLBOX + "')) ()");
+#endif
 
   lua.Execute("modality = {}");
   lua.Execute("table.insert(modality, 'ORTHANC')");
--- a/OrthancServer/UnitTestsSources/PluginsTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/UnitTestsSources/PluginsTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/UnitTestsSources/PrecompiledHeadersUnitTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/UnitTestsSources/PrecompiledHeadersUnitTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/UnitTestsSources/PrecompiledHeadersUnitTests.h	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/UnitTestsSources/PrecompiledHeadersUnitTests.h	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/UnitTestsSources/ServerConfigTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -0,0 +1,176 @@
+/**
+ * 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include <gtest/gtest.h>
+
+#include "../../OrthancFramework/Sources/Compatibility.h"
+#include "../../OrthancFramework/Sources/FileStorage/MemoryStorageArea.h"
+#include "../../OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h"
+#include "../../OrthancFramework/Sources/Logging.h"
+#include "../../OrthancFramework/Sources/SerializationToolbox.h"
+
+#include "../Sources/Database/SQLiteDatabaseWrapper.h"
+#include "../Sources/ServerContext.h"
+
+using namespace Orthanc;
+
+TEST(ServerConfig, AcceptedSopClasses)
+{
+  const std::string path = "UnitTestsStorage";
+
+  MemoryStorageArea storage;
+  SQLiteDatabaseWrapper db;   // The SQLite DB is in memory
+  db.Open();
+  ServerContext context(db, storage, true /* running unit tests */, 10, false, 1);
+
+  { // default config -> all SOP Classes should be accepted
+    std::set<std::string> s;
+
+    context.GetAcceptedSopClasses(s, 0);
+    ASSERT_LE(100u, s.size());
+
+    ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.4") != s.end());
+    ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.12.1.1") != s.end());
+
+    context.GetAcceptedSopClasses(s, 1);
+    ASSERT_EQ(1u, s.size());
+  }
+
+  {
+    std::list<std::string> acceptedStorageClasses;
+    std::set<std::string> rejectedStorageClasses;
+
+    std::set<std::string> s;
+    context.GetAcceptedSopClasses(s, 0);
+    size_t allSize = s.size();
+
+    { // default config but reject one class
+      acceptedStorageClasses.clear();
+      rejectedStorageClasses.clear();
+      rejectedStorageClasses.insert("1.2.840.10008.5.1.4.1.1.4");
+
+      context.SetAcceptedSopClasses(acceptedStorageClasses, rejectedStorageClasses);
+
+      context.GetAcceptedSopClasses(s, 0);
+      ASSERT_EQ(allSize - 1, s.size());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.4") == s.end());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.12.1.1") != s.end());
+
+      context.GetAcceptedSopClasses(s, 1);
+      ASSERT_EQ(1u, s.size());
+    }
+
+    { // default config but reject one regex
+      acceptedStorageClasses.clear();
+      rejectedStorageClasses.clear();
+      rejectedStorageClasses.insert("1.2.840.10008.5.1.4.1.*");
+      context.SetAcceptedSopClasses(acceptedStorageClasses, rejectedStorageClasses);
+
+      context.GetAcceptedSopClasses(s, 0);
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.4") == s.end());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.12.1.1") == s.end());
+    }
+
+    { // accept a single - no rejection
+      acceptedStorageClasses.clear();
+      acceptedStorageClasses.push_back("1.2.840.10008.5.1.4.1.1.4");
+      rejectedStorageClasses.clear();
+      context.SetAcceptedSopClasses(acceptedStorageClasses, rejectedStorageClasses);
+
+      context.GetAcceptedSopClasses(s, 0);
+      ASSERT_EQ(1, s.size());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.4") != s.end());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.12.1.1") == s.end());
+    }
+
+    { // accept from regex - reject one
+      acceptedStorageClasses.clear();
+      acceptedStorageClasses.push_back("1.2.840.10008.5.1.4.1.*");
+      rejectedStorageClasses.clear();
+      rejectedStorageClasses.insert("1.2.840.10008.5.1.4.1.1.12.1.1");
+      context.SetAcceptedSopClasses(acceptedStorageClasses, rejectedStorageClasses);
+
+      context.GetAcceptedSopClasses(s, 0);
+      ASSERT_LE(10, s.size());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.4") != s.end());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.12.1.1") == s.end());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.12.2.1") != s.end());
+    }
+
+    { // accept from regex - reject from regex
+      acceptedStorageClasses.clear();
+      acceptedStorageClasses.push_back("1.2.840.10008.5.1.4.1.*");
+      rejectedStorageClasses.clear();
+      rejectedStorageClasses.insert("1.2.840.10008.5.1.4.1.1.12.1.*");
+      context.SetAcceptedSopClasses(acceptedStorageClasses, rejectedStorageClasses);
+
+      context.GetAcceptedSopClasses(s, 0);
+      ASSERT_LE(10, s.size());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.4") != s.end());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.12.1.1") == s.end());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.12.2.1") != s.end());
+    }
+
+    { // accept one that is unknown form DCMTK
+      acceptedStorageClasses.clear();
+      acceptedStorageClasses.push_back("1.2.3.4");
+      rejectedStorageClasses.clear();
+      context.SetAcceptedSopClasses(acceptedStorageClasses, rejectedStorageClasses);
+
+      context.GetAcceptedSopClasses(s, 0);
+      ASSERT_EQ(1, s.size());
+      ASSERT_TRUE(s.find("1.2.3.4") != s.end());
+    }
+
+    { // accept the default ones + a custom one
+      acceptedStorageClasses.clear();
+      acceptedStorageClasses.push_back("1.2.840.*");
+      acceptedStorageClasses.push_back("1.2.3.4");
+      rejectedStorageClasses.clear();
+      context.SetAcceptedSopClasses(acceptedStorageClasses, rejectedStorageClasses);
+
+      context.GetAcceptedSopClasses(s, 0);
+      ASSERT_LE(100u, s.size());
+      ASSERT_TRUE(s.find("1.2.3.4") != s.end());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.12.2.1") != s.end());
+    }
+
+    { // test the ? in regex to replace a single char
+      acceptedStorageClasses.clear();
+      acceptedStorageClasses.push_back("1.2.840.10008.5.1.4.1.1.12.2.?");
+      acceptedStorageClasses.push_back("1.2.3.4");
+      rejectedStorageClasses.clear();
+      context.SetAcceptedSopClasses(acceptedStorageClasses, rejectedStorageClasses);
+
+      context.GetAcceptedSopClasses(s, 0);
+      ASSERT_EQ(2u, s.size());
+      ASSERT_TRUE(s.find("1.2.3.4") != s.end());
+      ASSERT_TRUE(s.find("1.2.840.10008.5.1.4.1.1.12.2.1") != s.end());
+    }
+
+  }
+
+  context.Stop();
+  db.Close();
+}
--- a/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -620,7 +620,7 @@
   FilesystemStorage storage(path);
   SQLiteDatabaseWrapper db;   // The SQLite DB is in memory
   db.Open();
-  ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */);
+  ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
   context.SetupJobsEngine(true, false);
 
   ServerIndex& index = context.GetIndex();
@@ -702,7 +702,7 @@
   FilesystemStorage storage(path);
   SQLiteDatabaseWrapper db;   // The SQLite DB is in memory
   db.Open();
-  ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */);
+  ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
   context.SetupJobsEngine(true, false);
   ServerIndex& index = context.GetIndex();
 
@@ -819,7 +819,7 @@
     MemoryStorageArea storage;
     SQLiteDatabaseWrapper db;   // The SQLite DB is in memory
     db.Open();
-    ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */);
+    ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
     context.SetupJobsEngine(true, false);
     context.SetCompressionEnabled(true);
 
@@ -864,15 +864,15 @@
     {
       FileInfo nope;
       int64_t revision;
-      ASSERT_FALSE(context.GetIndex().LookupAttachment(nope, revision, id, FileContentType_DicomAsJson));
+      ASSERT_FALSE(context.GetIndex().LookupAttachment(nope, revision, ResourceType_Instance, id, FileContentType_DicomAsJson));
     }
 
     FileInfo dicom1, pixelData1;
-    int64_t revision;
-    ASSERT_TRUE(context.GetIndex().LookupAttachment(dicom1, revision, id, FileContentType_Dicom));
+    int64_t revision = -1;
+    ASSERT_TRUE(context.GetIndex().LookupAttachment(dicom1, revision, ResourceType_Instance, id, FileContentType_Dicom));
     ASSERT_EQ(0, revision);
     revision = -1;
-    ASSERT_TRUE(context.GetIndex().LookupAttachment(pixelData1, revision, id, FileContentType_DicomUntilPixelData));
+    ASSERT_TRUE(context.GetIndex().LookupAttachment(pixelData1, revision, ResourceType_Instance, id, FileContentType_DicomUntilPixelData));
     ASSERT_EQ(0, revision);
 
     context.GetIndex().GetGlobalStatistics(diskSize, uncompressedSize, countPatients, 
@@ -914,14 +914,14 @@
     {
       FileInfo nope;
       int64_t revision;
-      ASSERT_FALSE(context.GetIndex().LookupAttachment(nope, revision, id, FileContentType_DicomAsJson));
+      ASSERT_FALSE(context.GetIndex().LookupAttachment(nope, revision, ResourceType_Instance, id, FileContentType_DicomAsJson));
     }
 
     FileInfo dicom2, pixelData2;
-    ASSERT_TRUE(context.GetIndex().LookupAttachment(dicom2, revision, id, FileContentType_Dicom));
+    ASSERT_TRUE(context.GetIndex().LookupAttachment(dicom2, revision, ResourceType_Instance, id, FileContentType_Dicom));
     ASSERT_EQ(0, revision);
     revision = -1;
-    ASSERT_TRUE(context.GetIndex().LookupAttachment(pixelData2, revision, id, FileContentType_DicomUntilPixelData));
+    ASSERT_TRUE(context.GetIndex().LookupAttachment(pixelData2, revision, ResourceType_Instance, id, FileContentType_DicomUntilPixelData));
     ASSERT_EQ(0, revision);
 
     context.GetIndex().GetGlobalStatistics(diskSize, uncompressedSize, countPatients, 
@@ -984,7 +984,7 @@
     MemoryStorageArea storage;
     SQLiteDatabaseWrapper db;   // The SQLite DB is in memory
     db.Open();
-    ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */);
+    ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
     context.SetupJobsEngine(true, false);
     context.SetCompressionEnabled(compression);
 
--- a/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -106,24 +106,24 @@
     {
     }
 
-    virtual float GetProgress() ORTHANC_OVERRIDE
+    virtual float GetProgress() const ORTHANC_OVERRIDE
     {
       return static_cast<float>(count_) / static_cast<float>(steps_ - 1);
     }
 
-    virtual void GetJobType(std::string& type) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& type) const ORTHANC_OVERRIDE
     {
       type = "DummyJob";
     }
 
-    virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE
+    virtual bool Serialize(Json::Value& value) const ORTHANC_OVERRIDE
     {
       value = Json::objectValue;
       value["Type"] = "DummyJob";
       return true;
     }
 
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE
+    virtual void GetPublicContent(Json::Value& value) const ORTHANC_OVERRIDE
     {
       value["hello"] = "world";
     }
@@ -202,7 +202,7 @@
     {
     }
 
-    virtual void GetJobType(std::string& s) ORTHANC_OVERRIDE
+    virtual void GetJobType(std::string& s) const ORTHANC_OVERRIDE
     {
       s = "DummyInstancesJob";
     }
@@ -536,7 +536,7 @@
     OrthancJobsSerialization()
     {
       db_.Open();
-      context_.reset(new ServerContext(db_, storage_, true /* running unit tests */, 10, false /* readonly */));
+      context_.reset(new ServerContext(db_, storage_, true /* running unit tests */, 10, false /* readonly */, 1 /* DCMTK concurrent transcoders */));
       context_->SetupJobsEngine(true, false);
     }
 
--- a/OrthancServer/UnitTestsSources/SizeOfTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/UnitTestsSources/SizeOfTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/UnitTestsSources/UnitTestsMain.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/UnitTestsSources/UnitTestsMain.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
--- a/OrthancServer/UnitTestsSources/VersionsTests.cpp	Wed Oct 09 11:06:20 2024 +0200
+++ b/OrthancServer/UnitTestsSources/VersionsTests.cpp	Thu Jan 30 17:41:33 2025 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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
@@ -113,7 +113,7 @@
 
 TEST(Versions, BoostStatic)
 {
-  ASSERT_TRUE(std::string(BOOST_LIB_VERSION) == "1_85" ||
+  ASSERT_TRUE(std::string(BOOST_LIB_VERSION) == "1_86" ||
               std::string(BOOST_LIB_VERSION) == "1_69" /* if USE_LEGACY_BOOST */);
 }
 
--- a/README	Wed Oct 09 11:06:20 2024 +0200
+++ b/README	Thu Jan 30 17:41:33 2025 +0100
@@ -49,9 +49,7 @@
 Licensing
 ---------
 
-Orthanc is licensed under the GPLv3 license, with the OpenSSL
-exception:
-http://people.gnome.org/~markmc/openssl-and-the-gpl.html
+Orthanc is licensed under the GPLv3 license.
 
 Full information about the licensing of the Orthanc ecosystem is
 available in the Orthanc Book:
--- a/TODO	Wed Oct 09 11:06:20 2024 +0200
+++ b/TODO	Thu Jan 30 17:41:33 2025 +0100
@@ -65,6 +65,11 @@
 * Add configurations to enable/disable warnings:
   - Modifying an instance while keeping its original SOPInstanceUID: This should be avoided!
   - Modifying a study while keeping its original StudyInstanceUID: This should be avoided!
+  In order to be able to disable/enable warnings in both the server and the framework, we should add a map of
+  enabled warnings in the logging classes directly and have something like:
+  LOG_WARNING_IF_ENABLED("Warning_ID") << ...
+  ENABLE_WARNING("Warning_ID", true)
+  Warnings from Framework should have a separate range like W999_ or WF001_ ...
 * Store the job registry in a dedicatd table in DB ?
   https://discourse.orthanc-server.org/t/performance-issue-when-adding-a-lot-of-jobs-in-the-queue/3915/2
   Note: that might also be the right time to have a central jobs registry when working
@@ -206,7 +211,12 @@
 Mid-term
 --------
 
-* Support C-GET SCU (note that C-GET SCP was introduced in Orthanc 1.7.0)
+* Check how Orthanc shall behave wrt to AcceptedSopClasses in these situations (consider Orthanc
+  accepts CT but not PT)
+  - What shall we log/warn if an external modality tries to send a PT/CT
+  - What shall we log/warn if we try to C-GET a PT/CT
+  Should the rejected files be logged as Failed, Warning, Refused, ...
+  Note: some tentative work has been initiated in the get-scu-test branch.
 * Support "Retrieve AE Title" (0008,0054) in C-FIND:
   - On SCP side: done by https://orthanc.uclouvain.be/hg/orthanc/rev/1ec3e1e18f50
   - On SCU side:
@@ -216,9 +226,6 @@
 * Strict hierarchical C-FIND:
   https://groups.google.com/d/msg/orthanc-users/VBHpeGVSNKM/tkaVvjWFBwAJ
 * report DIMSE error codes in Rest API and job status for /store /query /move /retrieve
-* report progress report of C-Move operation in jop progress.  There are 
-  progress callbacks available in DIMSE_moveUser
-  https://groups.google.com/g/orthanc-users/c/c8cGnA7FzsE/m/BSs66D8wBwAJ
 * Log outgoing C-Find queries
 * Support other Transfer Syntaxes in the Worklist plugin:
   https://discourse.orthanc-server.org/t/could-you-please-create-an-option-to-set-the-transfer-syntax-in-the-worklist-plugin-currently-little-endian-explicit-is-fixed/4871
@@ -257,12 +264,6 @@
 
 * (2) DicomMap: create a cache to the main DICOM tags index
 * (3) Check out rapidjson: https://github.com/miloyip/nativejson-benchmark
-* For C-Find results: we could store the computed tags
-    in metadata on some events like NewSeries + DeletedSeries (same for other computer tags).
-    OtherTags that could be saved in Metadata as well:
-    - ModalitiesInStudy
-    - all computed counters at series/study/patient level
-    - RequestAttributesSequence (sequence that must be included in all DicomWeb QIDO-RS for series)
 
 * Long-shot & not sure it is even feasible at all: try to reduce memory usage by implementing streaming
   when receiving DICOM instances from the Rest API or from DICOM and store files directly to disk as they
@@ -279,6 +280,9 @@
     to Orthanc and/or the Storage plugin (instead of passing a memory buffer) -> the object-storage plugin could 
     "stream" the file to the storage.  The HTTP server could also "stream" its response from file handles.
     Transcoding should be "file based" too.
+  Alternative option 4: Catch out-of-memory exceptions at quite high level in HTTPHandlers and DICOM receivers
+    and implement retries.  After a few retries, fail for good and return "out-of-resources".
+    Would be interesting to log these errors and count them in the prometheus metrics.
 * To investigate: usage of mapped_file (not only in the indexer plugin): 
   https://discourse.orthanc-server.org/t/patch-for-orthanc-indexer-plugin-crashing-on-big-non-dicom-files/3849/7
 
@@ -315,8 +319,22 @@
   https://groups.google.com/g/orthanc-users/c/ymtaAmgSs6Q/m/PqVBactQAQAJ
 * Add an index on the UUID column in the DelayedDeletion plugin:
   https://discourse.orthanc-server.org/t/delayeddeletion-improvement-unique-index-on-pending-uuid-column/4032
+* Orthanc shall refuse to start if one registers 2 storage plugins.
+  Right now, this is not possible because OrthancPluginRegisterStorageArea2 does not return any value
+  and it can not throw an Exception because that's a core function called from a plugin -> the Exception
+  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.
 
 
+-----------
+Housekeeper
+-----------
+
+* The Housekeeper should just refuse to start if you are using a lossy transfer syntax because that would generate 
+  new orthanc ids as well and there is a risk of messing up things.  tools/reconstruct shall return a 400 as well.
+  https://discourse.orthanc-server.org/t/crashes-on-housekeeper-transcode-storing-in-s3/5330/2
+
 ----------------
 Ideas of plugins
 ----------------