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