Mercurial > hg > orthanc
changeset 2728:cdf13d06cf40
integration jobs->mainline
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 13 Jul 2018 10:07:45 +0200 |
parents | 76af9465673d (current diff) 815e30657dad (diff) |
children | cb1b26a7db98 |
files | Core/DicomNetworking/ReusableDicomUserConnection.cpp Core/DicomNetworking/ReusableDicomUserConnection.h Core/MultiThreading/BagOfTasks.h Core/MultiThreading/BagOfTasksProcessor.cpp Core/MultiThreading/BagOfTasksProcessor.h Core/MultiThreading/ILockable.h Core/MultiThreading/Locker.h Core/MultiThreading/Mutex.cpp Core/MultiThreading/Mutex.h Core/MultiThreading/ReaderWriterLock.cpp Core/MultiThreading/ReaderWriterLock.h Core/MultiThreading/Semaphore.cpp Core/MultiThreading/Semaphore.h OrthancServer/Scheduler/CallSystemCommand.cpp OrthancServer/Scheduler/CallSystemCommand.h OrthancServer/Scheduler/DeleteInstanceCommand.cpp OrthancServer/Scheduler/DeleteInstanceCommand.h OrthancServer/Scheduler/IServerCommand.h OrthancServer/Scheduler/ModifyInstanceCommand.cpp OrthancServer/Scheduler/ModifyInstanceCommand.h OrthancServer/Scheduler/ServerCommandInstance.cpp OrthancServer/Scheduler/ServerCommandInstance.h OrthancServer/Scheduler/ServerJob.cpp OrthancServer/Scheduler/ServerJob.h OrthancServer/Scheduler/ServerScheduler.cpp OrthancServer/Scheduler/ServerScheduler.h OrthancServer/Scheduler/StorePeerCommand.cpp OrthancServer/Scheduler/StorePeerCommand.h OrthancServer/Scheduler/StoreScuCommand.cpp OrthancServer/Scheduler/StoreScuCommand.h Plugins/Include/orthanc/OrthancCppDatabasePlugin.h |
diffstat | 199 files changed, 16887 insertions(+), 7335 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Mon Jul 09 11:37:17 2018 +0200 +++ b/CMakeLists.txt Fri Jul 13 10:07:45 2018 +0200 @@ -1,518 +1,522 @@ -cmake_minimum_required(VERSION 2.8) - -project(Orthanc) - - -##################################################################### -## Generic parameters of the Orthanc framework -##################################################################### - -include(${CMAKE_SOURCE_DIR}/Resources/CMake/OrthancFrameworkParameters.cmake) - -# Enable all the optional components of the Orthanc framework -set(ENABLE_CRYPTO_OPTIONS ON) -set(ENABLE_DCMTK ON) -set(ENABLE_DCMTK_NETWORKING ON) -set(ENABLE_GOOGLE_TEST ON) -set(ENABLE_JPEG ON) -set(ENABLE_LOCALE ON) -set(ENABLE_LUA ON) -set(ENABLE_PNG ON) -set(ENABLE_PUGIXML ON) -set(ENABLE_SQLITE ON) -set(ENABLE_WEB_CLIENT ON) -set(ENABLE_WEB_SERVER ON) -set(ENABLE_ZLIB ON) - -set(HAS_EMBEDDED_RESOURCES ON) - - -##################################################################### -## CMake parameters tunable at the command line to configure the -## plugins, the companion tools, and the unit tests -##################################################################### - -# Parameters of the build -SET(BUILD_MODALITY_WORKLISTS ON CACHE BOOL "Whether to build the sample plugin to serve modality worklists") -SET(BUILD_RECOVER_COMPRESSED_FILE ON CACHE BOOL "Whether to build the companion tool to recover files compressed using Orthanc") -SET(BUILD_SERVE_FOLDERS ON CACHE BOOL "Whether to build the ServeFolders plugin") -SET(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins") -SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests") - - -##################################################################### -## Configuration of the Orthanc framework -##################################################################### - -include(${CMAKE_SOURCE_DIR}/Resources/CMake/VisualStudioPrecompiledHeaders.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/OrthancFrameworkConfiguration.cmake) - - -##################################################################### -## List of source files -##################################################################### - -set(ORTHANC_SERVER_SOURCES - OrthancServer/DatabaseWrapper.cpp - OrthancServer/DatabaseWrapperBase.cpp - OrthancServer/DicomInstanceToStore.cpp - OrthancServer/ExportedResource.cpp - OrthancServer/LuaScripting.cpp - OrthancServer/OrthancFindRequestHandler.cpp - OrthancServer/OrthancHttpHandler.cpp - OrthancServer/OrthancInitialization.cpp - OrthancServer/OrthancMoveRequestHandler.cpp - OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp - OrthancServer/OrthancRestApi/OrthancRestApi.cpp - OrthancServer/OrthancRestApi/OrthancRestArchive.cpp - OrthancServer/OrthancRestApi/OrthancRestChanges.cpp - OrthancServer/OrthancRestApi/OrthancRestModalities.cpp - OrthancServer/OrthancRestApi/OrthancRestResources.cpp - OrthancServer/OrthancRestApi/OrthancRestSystem.cpp - OrthancServer/QueryRetrieveHandler.cpp - OrthancServer/Scheduler/CallSystemCommand.cpp - OrthancServer/Scheduler/DeleteInstanceCommand.cpp - OrthancServer/Scheduler/ModifyInstanceCommand.cpp - OrthancServer/Scheduler/ServerCommandInstance.cpp - OrthancServer/Scheduler/ServerJob.cpp - OrthancServer/Scheduler/ServerScheduler.cpp - OrthancServer/Scheduler/StorePeerCommand.cpp - OrthancServer/Scheduler/StoreScuCommand.cpp - OrthancServer/Search/HierarchicalMatcher.cpp - OrthancServer/Search/IFindConstraint.cpp - OrthancServer/Search/ListConstraint.cpp - OrthancServer/Search/LookupIdentifierQuery.cpp - OrthancServer/Search/LookupResource.cpp - OrthancServer/Search/RangeConstraint.cpp - OrthancServer/Search/SetOfResources.cpp - OrthancServer/Search/ValueConstraint.cpp - OrthancServer/Search/WildcardConstraint.cpp - OrthancServer/ServerContext.cpp - OrthancServer/ServerEnumerations.cpp - OrthancServer/ServerIndex.cpp - OrthancServer/ServerToolbox.cpp - OrthancServer/SliceOrdering.cpp - ) - - -set(ORTHANC_UNIT_TESTS_SOURCES - UnitTestsSources/DicomMapTests.cpp - UnitTestsSources/FileStorageTests.cpp - UnitTestsSources/FromDcmtkTests.cpp - UnitTestsSources/MemoryCacheTests.cpp - UnitTestsSources/ImageTests.cpp - UnitTestsSources/RestApiTests.cpp - UnitTestsSources/SQLiteTests.cpp - UnitTestsSources/SQLiteChromiumTests.cpp - UnitTestsSources/ServerIndexTests.cpp - UnitTestsSources/VersionsTests.cpp - UnitTestsSources/ZipTests.cpp - UnitTestsSources/LuaTests.cpp - UnitTestsSources/MultiThreadingTests.cpp - UnitTestsSources/UnitTestsMain.cpp - UnitTestsSources/ImageProcessingTests.cpp - UnitTestsSources/JpegLosslessTests.cpp - UnitTestsSources/StreamTests.cpp - ) - - -if (ENABLE_PLUGINS) - list(APPEND ORTHANC_SERVER_SOURCES - Plugins/Engine/OrthancPluginDatabase.cpp - Plugins/Engine/OrthancPlugins.cpp - Plugins/Engine/PluginsEnumerations.cpp - Plugins/Engine/PluginsErrorDictionary.cpp - Plugins/Engine/PluginsManager.cpp - ) - - list(APPEND ORTHANC_UNIT_TESTS_SOURCES - UnitTestsSources/PluginsTests.cpp - ) -endif() - - -if (CMAKE_COMPILER_IS_GNUCXX - AND NOT CMAKE_CROSSCOMPILING - AND USE_DCMTK_360) - # Add the "-pedantic" flag only on the Orthanc sources, and only if - # cross-compiling DCMTK 3.6.0 - set(ORTHANC_ALL_SOURCES - ${ORTHANC_CORE_SOURCES_INTERNAL} - ${ORTHANC_DICOM_SOURCES_INTERNAL} - ${ORTHANC_SERVER_SOURCES} - ${ORTHANC_UNIT_TESTS_SOURCES} - Plugins/Samples/ServeFolders/Plugin.cpp - Plugins/Samples/ModalityWorklists/Plugin.cpp - OrthancServer/main.cpp - ) - - set_source_files_properties(${ORTHANC_ALL_SOURCES} - PROPERTIES COMPILE_FLAGS -pedantic - ) -endif() - - -##################################################################### -## Autogeneration of files -##################################################################### - -set(ORTHANC_EMBEDDED_FILES - PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql - UPGRADE_DATABASE_3_TO_4 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade3To4.sql - UPGRADE_DATABASE_4_TO_5 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade4To5.sql - CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json - DICOM_CONFORMANCE_STATEMENT ${CMAKE_CURRENT_SOURCE_DIR}/Resources/DicomConformanceStatement.txt - LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua - FONT_UBUNTU_MONO_BOLD_16 ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/UbuntuMonoBold-16.json - ) - -if (STANDALONE_BUILD) - # We embed all the resources in the binaries for standalone builds - add_definitions(-DORTHANC_STANDALONE=1) - EmbedResources( - ${ORTHANC_EMBEDDED_FILES} - ORTHANC_EXPLORER ${CMAKE_CURRENT_SOURCE_DIR}/OrthancExplorer - ${DCMTK_DICTIONARIES} - ) -else() - add_definitions( - -DORTHANC_STANDALONE=0 - -DORTHANC_PATH=\"${CMAKE_SOURCE_DIR}\" - ) - EmbedResources( - ${ORTHANC_EMBEDDED_FILES} - ) -endif() - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - execute_process( - COMMAND - ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py - ${ORTHANC_VERSION} Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging" - ERROR_VARIABLE Failure - OUTPUT_FILE ${AUTOGENERATED_DIR}/Orthanc.rc - ) - - if (Failure) - message(FATAL_ERROR "Error while computing the version information: ${Failure}") - endif() - - list(APPEND ORTHANC_RESOURCES ${AUTOGENERATED_DIR}/Orthanc.rc) -endif() - - - -##################################################################### -## Configuration of the C/C++ macros -##################################################################### - -if (ENABLE_PLUGINS) - add_definitions(-DORTHANC_ENABLE_PLUGINS=1) -else() - add_definitions(-DORTHANC_ENABLE_PLUGINS=0) -endif() - - -if (UNIT_TESTS_WITH_HTTP_CONNEXIONS) - add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1) -else() - add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=0) -endif() - - -include_directories(${CMAKE_SOURCE_DIR}/Plugins/Include) - -add_definitions( - -DORTHANC_BUILD_UNIT_TESTS=1 - - # Macros for the plugins - -DHAS_ORTHANC_EXCEPTION=0 - -DMODALITY_WORKLISTS_VERSION="${ORTHANC_VERSION}" - -DSERVE_FOLDERS_VERSION="${ORTHANC_VERSION}" - ) - - -# Setup precompiled headers for Microsoft Visual Studio - -# WARNING: There must be NO MORE "add_definitions()", "include()" or -# "include_directories()" below, otherwise the generated precompiled -# headers might get broken! - -if (MSVC) - add_definitions(-DORTHANC_USE_PRECOMPILED_HEADERS=1) - - set(TMP - ${ORTHANC_CORE_SOURCES_INTERNAL} - ${ORTHANC_DICOM_SOURCES_INTERNAL} - ) - - ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( - "PrecompiledHeaders.h" "Core/PrecompiledHeaders.cpp" - TMP ORTHANC_CORE_PCH) - - ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( - "PrecompiledHeadersServer.h" "OrthancServer/PrecompiledHeadersServer.cpp" - ORTHANC_SERVER_SOURCES ORTHANC_SERVER_PCH) - - ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( - "PrecompiledHeadersUnitTests.h" "UnitTestsSources/PrecompiledHeadersUnitTests.cpp" - ORTHANC_UNIT_TESTS_SOURCES ORTHANC_UNIT_TESTS_PCH) -endif() - - - -##################################################################### -## Build the core of Orthanc -##################################################################### - -# "CoreLibrary" contains all the third-party dependencies and the -# content of the "Core" folder -add_library(CoreLibrary - STATIC - ${ORTHANC_CORE_PCH} - ${ORTHANC_CORE_SOURCES} - ${ORTHANC_DICOM_SOURCES} - ${AUTOGENERATED_SOURCES} - ) - - -##################################################################### -## Build the Orthanc server -##################################################################### - -add_library(ServerLibrary - STATIC - ${ORTHANC_SERVER_PCH} - ${ORTHANC_SERVER_SOURCES} - ) - -# Ensure autogenerated code is built before building ServerLibrary -add_dependencies(ServerLibrary CoreLibrary) - -add_executable(Orthanc - OrthancServer/main.cpp - ${ORTHANC_RESOURCES} - ) - -target_link_libraries(Orthanc ServerLibrary CoreLibrary ${DCMTK_LIBRARIES}) - -install( - TARGETS Orthanc - RUNTIME DESTINATION sbin - ) - - -##################################################################### -## Build the unit tests -##################################################################### - -add_executable(UnitTests - ${GOOGLE_TEST_SOURCES} - ${ORTHANC_UNIT_TESTS_PCH} - ${ORTHANC_UNIT_TESTS_SOURCES} - ) - -target_link_libraries(UnitTests - ServerLibrary - CoreLibrary - ${DCMTK_LIBRARIES} - ${GOOGLE_TEST_LIBRARIES} - ) - - -##################################################################### -## Build the "ServeFolders" plugin -##################################################################### - -if (ENABLE_PLUGINS AND BUILD_SERVE_FOLDERS) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - execute_process( - COMMAND - ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py - ${ORTHANC_VERSION} ServeFolders ServeFolders.dll "Orthanc plugin to serve additional folders" - ERROR_VARIABLE Failure - OUTPUT_FILE ${AUTOGENERATED_DIR}/ServeFolders.rc - ) - - if (Failure) - message(FATAL_ERROR "Error while computing the version information: ${Failure}") - endif() - - list(APPEND SERVE_FOLDERS_RESOURCES ${AUTOGENERATED_DIR}/ServeFolders.rc) - endif() - - add_library(ServeFolders SHARED - ${BOOST_SOURCES} - ${JSONCPP_SOURCES} - ${LIBICONV_SOURCES} - Plugins/Samples/ServeFolders/Plugin.cpp - Plugins/Samples/Common/OrthancPluginCppWrapper.cpp - ${SERVE_FOLDERS_RESOURCES} - ) - - set_target_properties( - ServeFolders PROPERTIES - VERSION ${ORTHANC_VERSION} - SOVERSION ${ORTHANC_VERSION} - ) - - install( - TARGETS ServeFolders - RUNTIME DESTINATION lib # Destination for Windows - LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux - ) -endif() - - - -##################################################################### -## Build the "ModalityWorklists" plugin -##################################################################### - -if (ENABLE_PLUGINS AND BUILD_MODALITY_WORKLISTS) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - execute_process( - COMMAND - ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py - ${ORTHANC_VERSION} ModalityWorklists ModalityWorklists.dll "Sample Orthanc plugin to serve modality worklists" - ERROR_VARIABLE Failure - OUTPUT_FILE ${AUTOGENERATED_DIR}/ModalityWorklists.rc - ) - - if (Failure) - message(FATAL_ERROR "Error while computing the version information: ${Failure}") - endif() - - list(APPEND MODALITY_WORKLISTS_RESOURCES ${AUTOGENERATED_DIR}/ModalityWorklists.rc) - endif() - - add_library(ModalityWorklists SHARED - ${BOOST_SOURCES} - ${JSONCPP_SOURCES} - ${LIBICONV_SOURCES} - Plugins/Samples/Common/OrthancPluginCppWrapper.cpp - Plugins/Samples/ModalityWorklists/Plugin.cpp - ${MODALITY_WORKLISTS_RESOURCES} - ) - - set_target_properties( - ModalityWorklists PROPERTIES - VERSION ${ORTHANC_VERSION} - SOVERSION ${ORTHANC_VERSION} - ) - - install( - TARGETS ModalityWorklists - RUNTIME DESTINATION lib # Destination for Windows - LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux - ) -endif() - - - -##################################################################### -## Build the companion tool to recover files compressed using Orthanc -##################################################################### - -if (BUILD_RECOVER_COMPRESSED_FILE) - set(RECOVER_COMPRESSED_SOURCES - Resources/Samples/Tools/RecoverCompressedFile.cpp - ) - - if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - execute_process( - COMMAND - ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py - ${ORTHANC_VERSION} OrthancRecoverCompressedFile OrthancRecoverCompressedFile.exe - "Lightweight, RESTful DICOM server for medical imaging" - ERROR_VARIABLE Failure - OUTPUT_FILE ${AUTOGENERATED_DIR}/OrthancRecoverCompressedFile.rc - ) - - if (Failure) - message(FATAL_ERROR "Error while computing the version information: ${Failure}") - endif() - - list(APPEND RECOVER_COMPRESSED_SOURCES - ${AUTOGENERATED_DIR}/OrthancRecoverCompressedFile.rc - ) - endif() - - add_executable(OrthancRecoverCompressedFile ${RECOVER_COMPRESSED_SOURCES}) - - target_link_libraries(OrthancRecoverCompressedFile CoreLibrary) - - install( - TARGETS OrthancRecoverCompressedFile - RUNTIME DESTINATION bin - ) -endif() - - - -##################################################################### -## Generate the documentation if Doxygen is present -##################################################################### - -find_package(Doxygen) -if (DOXYGEN_FOUND) - configure_file( - ${CMAKE_SOURCE_DIR}/Resources/Orthanc.doxygen - ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen - @ONLY) - - configure_file( - ${CMAKE_SOURCE_DIR}/Resources/OrthancPlugin.doxygen - ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen - @ONLY) - - add_custom_target(doc - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen - COMMENT "Generating internal documentation with Doxygen" VERBATIM - ) - - add_custom_command(TARGET Orthanc - POST_BUILD - COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating plugin documentation with Doxygen" VERBATIM - ) - - install( - DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/OrthancPluginDocumentation/doc/ - DESTINATION share/doc/orthanc/OrthancPlugin - ) -else() - message("Doxygen not found. The documentation will not be built.") -endif() - - - -##################################################################### -## Install the plugin SDK -##################################################################### - -if (ENABLE_PLUGINS) - install( - FILES - Plugins/Include/orthanc/OrthancCPlugin.h - Plugins/Include/orthanc/OrthancCDatabasePlugin.h - Plugins/Include/orthanc/OrthancCppDatabasePlugin.h - DESTINATION include/orthanc - ) -endif() - - - -##################################################################### -## Prepare the "uninstall" target -## http://www.cmake.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F -##################################################################### - -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/Resources/CMake/Uninstall.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" - IMMEDIATE @ONLY) - -add_custom_target(uninstall - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) +cmake_minimum_required(VERSION 2.8) + +project(Orthanc) + + +##################################################################### +## Generic parameters of the Orthanc framework +##################################################################### + +include(${CMAKE_SOURCE_DIR}/Resources/CMake/OrthancFrameworkParameters.cmake) + +# Enable all the optional components of the Orthanc framework +set(ENABLE_CRYPTO_OPTIONS ON) +set(ENABLE_DCMTK ON) +set(ENABLE_DCMTK_NETWORKING ON) +set(ENABLE_GOOGLE_TEST ON) +set(ENABLE_JPEG ON) +set(ENABLE_LOCALE ON) +set(ENABLE_LUA ON) +set(ENABLE_PNG ON) +set(ENABLE_PUGIXML ON) +set(ENABLE_SQLITE ON) +set(ENABLE_WEB_CLIENT ON) +set(ENABLE_WEB_SERVER ON) +set(ENABLE_ZLIB ON) + +set(HAS_EMBEDDED_RESOURCES ON) + + +##################################################################### +## CMake parameters tunable at the command line to configure the +## plugins, the companion tools, and the unit tests +##################################################################### + +# Parameters of the build +SET(BUILD_MODALITY_WORKLISTS ON CACHE BOOL "Whether to build the sample plugin to serve modality worklists") +SET(BUILD_RECOVER_COMPRESSED_FILE ON CACHE BOOL "Whether to build the companion tool to recover files compressed using Orthanc") +SET(BUILD_SERVE_FOLDERS ON CACHE BOOL "Whether to build the ServeFolders plugin") +SET(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins") +SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests") + + +##################################################################### +## Configuration of the Orthanc framework +##################################################################### + +include(${CMAKE_SOURCE_DIR}/Resources/CMake/VisualStudioPrecompiledHeaders.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/OrthancFrameworkConfiguration.cmake) + + +##################################################################### +## List of source files +##################################################################### + +set(ORTHANC_SERVER_SOURCES + OrthancServer/DatabaseWrapper.cpp + OrthancServer/DatabaseWrapperBase.cpp + OrthancServer/DicomInstanceOrigin.cpp + OrthancServer/DicomInstanceToStore.cpp + OrthancServer/ExportedResource.cpp + OrthancServer/LuaScripting.cpp + OrthancServer/OrthancFindRequestHandler.cpp + OrthancServer/OrthancHttpHandler.cpp + OrthancServer/OrthancInitialization.cpp + OrthancServer/OrthancMoveRequestHandler.cpp + OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp + OrthancServer/OrthancRestApi/OrthancRestApi.cpp + OrthancServer/OrthancRestApi/OrthancRestArchive.cpp + OrthancServer/OrthancRestApi/OrthancRestChanges.cpp + OrthancServer/OrthancRestApi/OrthancRestModalities.cpp + OrthancServer/OrthancRestApi/OrthancRestResources.cpp + OrthancServer/OrthancRestApi/OrthancRestSystem.cpp + OrthancServer/QueryRetrieveHandler.cpp + OrthancServer/Search/HierarchicalMatcher.cpp + OrthancServer/Search/IFindConstraint.cpp + OrthancServer/Search/ListConstraint.cpp + OrthancServer/Search/LookupIdentifierQuery.cpp + OrthancServer/Search/LookupResource.cpp + OrthancServer/Search/RangeConstraint.cpp + OrthancServer/Search/SetOfResources.cpp + OrthancServer/Search/ValueConstraint.cpp + OrthancServer/Search/WildcardConstraint.cpp + OrthancServer/ServerContext.cpp + OrthancServer/ServerEnumerations.cpp + OrthancServer/ServerIndex.cpp + OrthancServer/ServerJobs/ArchiveJob.cpp + OrthancServer/ServerJobs/DicomModalityStoreJob.cpp + OrthancServer/ServerJobs/LuaJobManager.cpp + OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp + OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp + OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp + OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp + OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp + OrthancServer/ServerJobs/OrthancJobUnserializer.cpp + OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp + OrthancServer/ServerJobs/ResourceModificationJob.cpp + OrthancServer/ServerToolbox.cpp + OrthancServer/SliceOrdering.cpp + ) + + +set(ORTHANC_UNIT_TESTS_SOURCES + UnitTestsSources/DicomMapTests.cpp + UnitTestsSources/FileStorageTests.cpp + UnitTestsSources/FromDcmtkTests.cpp + UnitTestsSources/MemoryCacheTests.cpp + UnitTestsSources/ImageTests.cpp + UnitTestsSources/RestApiTests.cpp + UnitTestsSources/SQLiteTests.cpp + UnitTestsSources/SQLiteChromiumTests.cpp + UnitTestsSources/ServerIndexTests.cpp + UnitTestsSources/VersionsTests.cpp + UnitTestsSources/ZipTests.cpp + UnitTestsSources/LuaTests.cpp + UnitTestsSources/MultiThreadingTests.cpp + UnitTestsSources/UnitTestsMain.cpp + UnitTestsSources/ImageProcessingTests.cpp + UnitTestsSources/JpegLosslessTests.cpp + UnitTestsSources/StreamTests.cpp + ) + + +if (ENABLE_PLUGINS) + list(APPEND ORTHANC_SERVER_SOURCES + Plugins/Engine/OrthancPluginDatabase.cpp + Plugins/Engine/OrthancPlugins.cpp + Plugins/Engine/PluginsEnumerations.cpp + Plugins/Engine/PluginsErrorDictionary.cpp + Plugins/Engine/PluginsManager.cpp + ) + + list(APPEND ORTHANC_UNIT_TESTS_SOURCES + UnitTestsSources/PluginsTests.cpp + ) +endif() + + +if (CMAKE_COMPILER_IS_GNUCXX + AND NOT CMAKE_CROSSCOMPILING + AND USE_DCMTK_360) + # Add the "-pedantic" flag only on the Orthanc sources, and only if + # cross-compiling DCMTK 3.6.0 + set(ORTHANC_ALL_SOURCES + ${ORTHANC_CORE_SOURCES_INTERNAL} + ${ORTHANC_DICOM_SOURCES_INTERNAL} + ${ORTHANC_SERVER_SOURCES} + ${ORTHANC_UNIT_TESTS_SOURCES} + Plugins/Samples/ServeFolders/Plugin.cpp + Plugins/Samples/ModalityWorklists/Plugin.cpp + OrthancServer/main.cpp + ) + + set_source_files_properties(${ORTHANC_ALL_SOURCES} + PROPERTIES COMPILE_FLAGS -pedantic + ) +endif() + + +##################################################################### +## Autogeneration of files +##################################################################### + +set(ORTHANC_EMBEDDED_FILES + PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql + UPGRADE_DATABASE_3_TO_4 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade3To4.sql + UPGRADE_DATABASE_4_TO_5 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade4To5.sql + CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json + DICOM_CONFORMANCE_STATEMENT ${CMAKE_CURRENT_SOURCE_DIR}/Resources/DicomConformanceStatement.txt + LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua + FONT_UBUNTU_MONO_BOLD_16 ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/UbuntuMonoBold-16.json + ) + +if (STANDALONE_BUILD) + # We embed all the resources in the binaries for standalone builds + add_definitions(-DORTHANC_STANDALONE=1) + EmbedResources( + ${ORTHANC_EMBEDDED_FILES} + ORTHANC_EXPLORER ${CMAKE_CURRENT_SOURCE_DIR}/OrthancExplorer + ${DCMTK_DICTIONARIES} + ) +else() + add_definitions( + -DORTHANC_STANDALONE=0 + -DORTHANC_PATH=\"${CMAKE_SOURCE_DIR}\" + ) + EmbedResources( + ${ORTHANC_EMBEDDED_FILES} + ) +endif() + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_VERSION} Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/Orthanc.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND ORTHANC_RESOURCES ${AUTOGENERATED_DIR}/Orthanc.rc) +endif() + + + +##################################################################### +## Configuration of the C/C++ macros +##################################################################### + +if (ENABLE_PLUGINS) + add_definitions(-DORTHANC_ENABLE_PLUGINS=1) +else() + add_definitions(-DORTHANC_ENABLE_PLUGINS=0) +endif() + + +if (UNIT_TESTS_WITH_HTTP_CONNEXIONS) + add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1) +else() + add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=0) +endif() + + +include_directories(${CMAKE_SOURCE_DIR}/Plugins/Include) + +add_definitions( + -DORTHANC_BUILD_UNIT_TESTS=1 + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + + # Macros for the plugins + -DHAS_ORTHANC_EXCEPTION=0 + -DMODALITY_WORKLISTS_VERSION="${ORTHANC_VERSION}" + -DSERVE_FOLDERS_VERSION="${ORTHANC_VERSION}" + ) + + +# Setup precompiled headers for Microsoft Visual Studio + +# WARNING: There must be NO MORE "add_definitions()", "include()" or +# "include_directories()" below, otherwise the generated precompiled +# headers might get broken! + +if (MSVC) + add_definitions(-DORTHANC_USE_PRECOMPILED_HEADERS=1) + + set(TMP + ${ORTHANC_CORE_SOURCES_INTERNAL} + ${ORTHANC_DICOM_SOURCES_INTERNAL} + ) + + ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( + "PrecompiledHeaders.h" "Core/PrecompiledHeaders.cpp" + TMP ORTHANC_CORE_PCH) + + ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( + "PrecompiledHeadersServer.h" "OrthancServer/PrecompiledHeadersServer.cpp" + ORTHANC_SERVER_SOURCES ORTHANC_SERVER_PCH) + + ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( + "PrecompiledHeadersUnitTests.h" "UnitTestsSources/PrecompiledHeadersUnitTests.cpp" + ORTHANC_UNIT_TESTS_SOURCES ORTHANC_UNIT_TESTS_PCH) +endif() + + + +##################################################################### +## Build the core of Orthanc +##################################################################### + +# "CoreLibrary" contains all the third-party dependencies and the +# content of the "Core" folder +add_library(CoreLibrary + STATIC + ${ORTHANC_CORE_PCH} + ${ORTHANC_CORE_SOURCES} + ${ORTHANC_DICOM_SOURCES} + ${AUTOGENERATED_SOURCES} + ) + + +##################################################################### +## Build the Orthanc server +##################################################################### + +add_library(ServerLibrary + STATIC + ${ORTHANC_SERVER_PCH} + ${ORTHANC_SERVER_SOURCES} + ) + +# Ensure autogenerated code is built before building ServerLibrary +add_dependencies(ServerLibrary CoreLibrary) + +add_executable(Orthanc + OrthancServer/main.cpp + ${ORTHANC_RESOURCES} + ) + +target_link_libraries(Orthanc ServerLibrary CoreLibrary ${DCMTK_LIBRARIES}) + +install( + TARGETS Orthanc + RUNTIME DESTINATION sbin + ) + + +##################################################################### +## Build the unit tests +##################################################################### + +add_executable(UnitTests + ${GOOGLE_TEST_SOURCES} + ${ORTHANC_UNIT_TESTS_PCH} + ${ORTHANC_UNIT_TESTS_SOURCES} + ) + +target_link_libraries(UnitTests + ServerLibrary + CoreLibrary + ${DCMTK_LIBRARIES} + ${GOOGLE_TEST_LIBRARIES} + ) + + +##################################################################### +## Build the "ServeFolders" plugin +##################################################################### + +if (ENABLE_PLUGINS AND BUILD_SERVE_FOLDERS) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_VERSION} ServeFolders ServeFolders.dll "Orthanc plugin to serve additional folders" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/ServeFolders.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND SERVE_FOLDERS_RESOURCES ${AUTOGENERATED_DIR}/ServeFolders.rc) + endif() + + add_library(ServeFolders SHARED + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + ${LIBICONV_SOURCES} + Plugins/Samples/ServeFolders/Plugin.cpp + Plugins/Samples/Common/OrthancPluginCppWrapper.cpp + ${SERVE_FOLDERS_RESOURCES} + ) + + set_target_properties( + ServeFolders PROPERTIES + VERSION ${ORTHANC_VERSION} + SOVERSION ${ORTHANC_VERSION} + ) + + install( + TARGETS ServeFolders + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + ) +endif() + + + +##################################################################### +## Build the "ModalityWorklists" plugin +##################################################################### + +if (ENABLE_PLUGINS AND BUILD_MODALITY_WORKLISTS) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_VERSION} ModalityWorklists ModalityWorklists.dll "Sample Orthanc plugin to serve modality worklists" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/ModalityWorklists.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND MODALITY_WORKLISTS_RESOURCES ${AUTOGENERATED_DIR}/ModalityWorklists.rc) + endif() + + add_library(ModalityWorklists SHARED + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + ${LIBICONV_SOURCES} + Plugins/Samples/Common/OrthancPluginCppWrapper.cpp + Plugins/Samples/ModalityWorklists/Plugin.cpp + ${MODALITY_WORKLISTS_RESOURCES} + ) + + set_target_properties( + ModalityWorklists PROPERTIES + VERSION ${ORTHANC_VERSION} + SOVERSION ${ORTHANC_VERSION} + ) + + install( + TARGETS ModalityWorklists + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + ) +endif() + + + +##################################################################### +## Build the companion tool to recover files compressed using Orthanc +##################################################################### + +if (BUILD_RECOVER_COMPRESSED_FILE) + set(RECOVER_COMPRESSED_SOURCES + Resources/Samples/Tools/RecoverCompressedFile.cpp + ) + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_VERSION} OrthancRecoverCompressedFile OrthancRecoverCompressedFile.exe + "Lightweight, RESTful DICOM server for medical imaging" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/OrthancRecoverCompressedFile.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND RECOVER_COMPRESSED_SOURCES + ${AUTOGENERATED_DIR}/OrthancRecoverCompressedFile.rc + ) + endif() + + add_executable(OrthancRecoverCompressedFile ${RECOVER_COMPRESSED_SOURCES}) + + target_link_libraries(OrthancRecoverCompressedFile CoreLibrary) + + install( + TARGETS OrthancRecoverCompressedFile + RUNTIME DESTINATION bin + ) +endif() + + + +##################################################################### +## Generate the documentation if Doxygen is present +##################################################################### + +find_package(Doxygen) +if (DOXYGEN_FOUND) + configure_file( + ${CMAKE_SOURCE_DIR}/Resources/Orthanc.doxygen + ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen + @ONLY) + + configure_file( + ${CMAKE_SOURCE_DIR}/Resources/OrthancPlugin.doxygen + ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen + @ONLY) + + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen + COMMENT "Generating internal documentation with Doxygen" VERBATIM + ) + + add_custom_command(TARGET Orthanc + POST_BUILD + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating plugin documentation with Doxygen" VERBATIM + ) + + install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/OrthancPluginDocumentation/doc/ + DESTINATION share/doc/orthanc/OrthancPlugin + ) +else() + message("Doxygen not found. The documentation will not be built.") +endif() + + + +##################################################################### +## Install the plugin SDK +##################################################################### + +if (ENABLE_PLUGINS) + install( + FILES + Plugins/Include/orthanc/OrthancCPlugin.h + Plugins/Include/orthanc/OrthancCDatabasePlugin.h + DESTINATION include/orthanc + ) +endif() + + + +##################################################################### +## Prepare the "uninstall" target +## http://www.cmake.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F +##################################################################### + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/Resources/CMake/Uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
--- a/Core/Cache/MemoryCache.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/Cache/MemoryCache.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -80,9 +80,12 @@ void MemoryCache::Invalidate(const std::string& id) { - if (index_.Contains(id)) + Page* p = NULL; + if (index_.Contains(id, p)) { VLOG(1) << "Invalidating a cache page"; + assert(p != NULL); + delete p; index_.Invalidate(id); } }
--- a/Core/DicomFormat/DicomTag.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/DicomFormat/DicomTag.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -39,9 +39,32 @@ #include <iostream> #include <iomanip> #include <stdio.h> +#include <string.h> namespace Orthanc -{ +{ + static inline uint16_t GetCharValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; + } + + + static inline uint16_t GetTagValue(const char* c) + { + return ((GetCharValue(c[0]) << 12) + + (GetCharValue(c[1]) << 8) + + (GetCharValue(c[2]) << 4) + + GetCharValue(c[3])); + } + + bool DicomTag::operator< (const DicomTag& other) const { if (group_ < other.group_) @@ -74,6 +97,49 @@ } + bool DicomTag::ParseHexadecimal(DicomTag& tag, + const char* value) + { + size_t length = strlen(value); + + if (length == 9 && + isxdigit(value[0]) && + isxdigit(value[1]) && + isxdigit(value[2]) && + isxdigit(value[3]) && + (value[4] == '-' || value[4] == ',') && + isxdigit(value[5]) && + isxdigit(value[6]) && + isxdigit(value[7]) && + isxdigit(value[8])) + { + uint16_t group = GetTagValue(value); + uint16_t element = GetTagValue(value + 5); + tag = DicomTag(group, element); + return true; + } + else if (length == 8 && + isxdigit(value[0]) && + isxdigit(value[1]) && + isxdigit(value[2]) && + isxdigit(value[3]) && + isxdigit(value[4]) && + isxdigit(value[5]) && + isxdigit(value[6]) && + isxdigit(value[7])) + { + uint16_t group = GetTagValue(value); + uint16_t element = GetTagValue(value + 4); + tag = DicomTag(group, element); + return true; + } + else + { + return false; + } + } + + const char* DicomTag::GetMainTagsName() const { if (*this == DICOM_TAG_ACCESSION_NUMBER)
--- a/Core/DicomFormat/DicomTag.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/DicomFormat/DicomTag.h Fri Jul 13 10:07:45 2018 +0200 @@ -88,6 +88,9 @@ std::string Format() const; + static bool ParseHexadecimal(DicomTag& tag, + const char* value); + friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag); static void AddTagsForModule(std::set<DicomTag>& target,
--- a/Core/DicomNetworking/DicomUserConnection.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/DicomNetworking/DicomUserConnection.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -264,8 +264,6 @@ const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { - CheckIsOpen(); - DcmFileFormat dcmff; Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); @@ -284,23 +282,36 @@ bool isGeneric = IsGenericTransferSyntax(syntax); bool renegotiate; - if (isGeneric) + + if (!IsOpen()) + { + renegotiate = true; + } + else if (isGeneric) { // Are we making a generic-to-specific or specific-to-generic change of // the transfer syntax? If this is the case, renegotiate the connection. renegotiate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()); + + if (renegotiate) + { + LOG(INFO) << "Use of non-generic transfer syntax: the C-Store associated must be renegotiated"; + } } else { // We are using a specific transfer syntax. Renegotiate if the // current connection does not match this transfer syntax. renegotiate = (syntax != connection.GetPreferredTransferSyntax()); + + if (renegotiate) + { + LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; + } } if (renegotiate) { - LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; - if (isGeneric) { connection.ResetPreferredTransferSyntax(); @@ -313,7 +324,6 @@ if (!connection.IsOpen()) { - LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters"; connection.Open(); } @@ -785,13 +795,12 @@ } - DicomUserConnection::DicomUserConnection() : - pimpl_(new PImpl), - preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX), - localAet_("STORESCU"), - remoteAet_("ANY-SCP"), - remoteHost_("127.0.0.1") + void DicomUserConnection::DefaultSetup() { + preferredTransferSyntax_ = DEFAULT_PREFERRED_TRANSFER_SYNTAX; + localAet_ = "STORESCU"; + remoteAet_ = "ANY-SCP"; + remoteHost_ = "127.0.0.1"; remotePort_ = 104; manufacturer_ = ModalityManufacturer_Generic; @@ -809,6 +818,24 @@ ResetStorageSOPClasses(); } + + + DicomUserConnection::DicomUserConnection() : + pimpl_(new PImpl) + { + DefaultSetup(); + } + + + DicomUserConnection::DicomUserConnection(const std::string& localAet, + const RemoteModalityParameters& remote) : + pimpl_(new PImpl) + { + DefaultSetup(); + SetLocalApplicationEntityTitle(localAet); + SetRemoteModality(remote); + } + DicomUserConnection::~DicomUserConnection() { @@ -820,7 +847,7 @@ { SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle()); SetRemoteHost(parameters.GetHost()); - SetRemotePort(parameters.GetPort()); + SetRemotePort(parameters.GetPortNumber()); SetRemoteManufacturer(parameters.GetManufacturer()); } @@ -1217,4 +1244,15 @@ << seconds << " seconds (0 = no timeout)"; defaultTimeout_ = seconds; } + + + bool DicomUserConnection::IsSameAssociation(const std::string& localAet, + const RemoteModalityParameters& remote) const + { + return (localAet_ == localAet && + remoteAet_ == remote.GetApplicationEntityTitle() && + remoteHost_ == remote.GetHost() && + remotePort_ == remote.GetPortNumber() && + manufacturer_ == remote.GetManufacturer()); + } }
--- a/Core/DicomNetworking/DicomUserConnection.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/DicomNetworking/DicomUserConnection.h Fri Jul 13 10:07:45 2018 +0200 @@ -77,11 +77,18 @@ void CheckStorageSOPClassesInvariant() const; + void DefaultSetup(); + public: DicomUserConnection(); ~DicomUserConnection(); + // This constructor corresponds to behavior of the old class + // "ReusableDicomUserConnection", without the call to "Open()" + DicomUserConnection(const std::string& localAet, + const RemoteModalityParameters& remote); + void SetRemoteModality(const RemoteModalityParameters& parameters); void SetLocalApplicationEntityTitle(const std::string& aet); @@ -201,5 +208,8 @@ ParsedDicomFile& query); static void SetDefaultTimeout(uint32_t seconds); + + bool IsSameAssociation(const std::string& localAet, + const RemoteModalityParameters& remote) const; }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IDicomConnectionManager.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,82 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_DCMTK_NETWORKING) +# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be defined +#endif + +#if ORTHANC_ENABLE_DCMTK_NETWORKING == 0 + +namespace Orthanc +{ + // DICOM networking is disabled, this is just a void class + class IDicomConnectionManager : public boost::noncopyable + { + public: + virtual ~IDicomConnectionManager() + { + } + }; +} + +#else + +#include "DicomUserConnection.h" + +namespace Orthanc +{ + class IDicomConnectionManager : public boost::noncopyable + { + public: + virtual ~IDicomConnectionManager() + { + } + + class IResource : public boost::noncopyable + { + public: + virtual ~IResource() + { + } + + virtual DicomUserConnection& GetConnection() = 0; + }; + + virtual IResource* AcquireConnection(const std::string& localAet, + const RemoteModalityParameters& remote) = 0; + }; +} + +#endif
--- a/Core/DicomNetworking/Internals/MoveScp.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/DicomNetworking/Internals/MoveScp.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -93,6 +93,15 @@ #include <boost/lexical_cast.hpp> +/** + * Macro specifying whether to apply the patch suggested in issue 66: + * "Orthanc responses C-MOVE with zero Move Originator Message ID" + * https://bitbucket.org/sjodogne/orthanc/issues/66/ + **/ + +#define APPLY_FIX_ISSUE_66 1 + + namespace Orthanc { namespace @@ -112,7 +121,7 @@ }; - +#if APPLY_FIX_ISSUE_66 != 1 static uint16_t GetMessageId(const DicomMap& message) { /** @@ -145,7 +154,7 @@ return 0; } - +#endif void MoveScpCallback( @@ -172,8 +181,15 @@ try { +#if APPLY_FIX_ISSUE_66 == 1 + uint16_t messageId = request->MessageID; +#else + // The line below was the implementation for Orthanc <= 1.3.2 + uint16_t messageId = GetMessageId(input); +#endif + data.iterator_.reset(data.handler_->Handle(data.target_, input, *data.remoteIp_, *data.remoteAet_, - *data.calledAet_, GetMessageId(input))); + *data.calledAet_, messageId)); if (data.iterator_.get() == NULL) {
--- a/Core/DicomNetworking/RemoteModalityParameters.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/DicomNetworking/RemoteModalityParameters.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -36,6 +36,7 @@ #include "../Logging.h" #include "../OrthancException.h" +#include "../SerializationToolbox.h" #include <boost/lexical_cast.hpp> #include <stdexcept> @@ -57,7 +58,7 @@ { SetApplicationEntityTitle(aet); SetHost(host); - SetPort(port); + SetPortNumber(port); SetManufacturer(manufacturer); } @@ -83,13 +84,13 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } - SetPort(static_cast<uint16_t>(tmp)); + SetPortNumber(static_cast<uint16_t>(tmp)); } catch (std::runtime_error /* error inside JsonCpp */) { try { - SetPort(boost::lexical_cast<uint16_t>(portValue.asString())); + SetPortNumber(boost::lexical_cast<uint16_t>(portValue.asString())); } catch (boost::bad_lexical_cast) { @@ -117,12 +118,34 @@ } } + void RemoteModalityParameters::ToJson(Json::Value& value) const { value = Json::arrayValue; value.append(GetApplicationEntityTitle()); value.append(GetHost()); - value.append(GetPort()); + value.append(GetPortNumber()); value.append(EnumerationToString(GetManufacturer())); } + + + void RemoteModalityParameters::Serialize(Json::Value& target) const + { + target = Json::objectValue; + target["AET"] = aet_; + target["Host"] = host_; + target["Port"] = port_; + target["Manufacturer"] = EnumerationToString(manufacturer_); + } + + + RemoteModalityParameters::RemoteModalityParameters(const Json::Value& serialized) + { + aet_ = SerializationToolbox::ReadString(serialized, "AET"); + host_ = SerializationToolbox::ReadString(serialized, "Host"); + port_ = static_cast<uint16_t> + (SerializationToolbox::ReadUnsignedInteger(serialized, "Port")); + manufacturer_ = StringToModalityManufacturer + (SerializationToolbox::ReadString(serialized, "Manufacturer")); + } }
--- a/Core/DicomNetworking/RemoteModalityParameters.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/DicomNetworking/RemoteModalityParameters.h Fri Jul 13 10:07:45 2018 +0200 @@ -52,6 +52,8 @@ public: RemoteModalityParameters(); + RemoteModalityParameters(const Json::Value& serialized); + RemoteModalityParameters(const std::string& aet, const std::string& host, uint16_t port, @@ -77,12 +79,12 @@ host_ = host; } - uint16_t GetPort() const + uint16_t GetPortNumber() const { return port_; } - void SetPort(uint16_t port) + void SetPortNumber(uint16_t port) { port_ = port; } @@ -105,5 +107,7 @@ void FromJson(const Json::Value& modality); void ToJson(Json::Value& value) const; + + void Serialize(Json::Value& target) const; }; }
--- a/Core/DicomNetworking/ReusableDicomUserConnection.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,188 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeaders.h" -#include "ReusableDicomUserConnection.h" - -#include "../Logging.h" -#include "../OrthancException.h" - -namespace Orthanc -{ - static boost::posix_time::ptime Now() - { - return boost::posix_time::microsec_clock::local_time(); - } - - void ReusableDicomUserConnection::Open(const std::string& localAet, - const RemoteModalityParameters& remote) - { - if (connection_ != NULL && - connection_->GetLocalApplicationEntityTitle() == localAet && - connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() && - connection_->GetRemoteHost() == remote.GetHost() && - connection_->GetRemotePort() == remote.GetPort() && - connection_->GetRemoteManufacturer() == remote.GetManufacturer()) - { - // The current connection can be reused - LOG(INFO) << "Reusing the previous SCU connection"; - return; - } - - Close(); - - connection_ = new DicomUserConnection(); - connection_->SetLocalApplicationEntityTitle(localAet); - connection_->SetRemoteModality(remote); - connection_->Open(); - } - - void ReusableDicomUserConnection::Close() - { - if (connection_ != NULL) - { - delete connection_; - connection_ = NULL; - } - } - - void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that) - { - for (;;) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - if (!that->continue_) - { - //LOG(INFO) << "Finishing the thread watching the global SCU connection"; - return; - } - - { - boost::mutex::scoped_lock lock(that->mutex_); - if (that->connection_ != NULL && - Now() >= that->lastUse_ + that->timeBeforeClose_) - { - LOG(INFO) << "Closing the global SCU connection after timeout"; - that->Close(); - } - } - } - } - - - ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that, - const std::string& localAet, - const RemoteModalityParameters& remote) : - ::Orthanc::Locker(that) - { - that.Open(localAet, remote); - connection_ = that.connection_; - } - - - DicomUserConnection& ReusableDicomUserConnection::Locker::GetConnection() - { - if (connection_ == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - return *connection_; - } - - ReusableDicomUserConnection::ReusableDicomUserConnection() : - connection_(NULL), - timeBeforeClose_(boost::posix_time::seconds(5)) // By default, close connection after 5 seconds - { - lastUse_ = Now(); - continue_ = true; - closeThread_ = boost::thread(CloseThread, this); - } - - ReusableDicomUserConnection::~ReusableDicomUserConnection() - { - if (continue_) - { - LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!"; - Finalize(); - } - } - - void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms) - { - boost::mutex::scoped_lock lock(mutex_); - - if (ms == 0) - { - ms = 1; - } - - timeBeforeClose_ = boost::posix_time::milliseconds(ms); - } - - void ReusableDicomUserConnection::Lock() - { - mutex_.lock(); - } - - void ReusableDicomUserConnection::Unlock() - { - if (connection_ != NULL && - connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp) - { - // "storescp" from DCMTK has problems when reusing a - // connection. Always close. - Close(); - } - - lastUse_ = Now(); - mutex_.unlock(); - } - - - void ReusableDicomUserConnection::Finalize() - { - if (continue_) - { - continue_ = false; - - if (closeThread_.joinable()) - { - closeThread_.join(); - } - - Close(); - } - } -} -
--- a/Core/DicomNetworking/ReusableDicomUserConnection.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "DicomUserConnection.h" -#include "../../Core/MultiThreading/Locker.h" - -#include <boost/thread.hpp> -#include <boost/date_time/posix_time/posix_time.hpp> - -namespace Orthanc -{ - class ReusableDicomUserConnection : public ILockable - { - private: - boost::mutex mutex_; - DicomUserConnection* connection_; - bool continue_; - boost::posix_time::time_duration timeBeforeClose_; - boost::posix_time::ptime lastUse_; - boost::thread closeThread_; - - void Open(const std::string& localAet, - const RemoteModalityParameters& remote); - - void Close(); - - static void CloseThread(ReusableDicomUserConnection* that); - - protected: - virtual void Lock(); - - virtual void Unlock(); - - public: - class Locker : public ::Orthanc::Locker - { - private: - DicomUserConnection* connection_; - - public: - Locker(ReusableDicomUserConnection& that, - const std::string& localAet, - const RemoteModalityParameters& remote); - - DicomUserConnection& GetConnection(); - }; - - ReusableDicomUserConnection(); - - virtual ~ReusableDicomUserConnection(); - - void SetMillisecondsBeforeClose(uint64_t ms); - - void Finalize(); - }; -} -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,134 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "TimeoutDicomConnectionManager.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +namespace Orthanc +{ + static boost::posix_time::ptime GetNow() + { + return boost::posix_time::microsec_clock::universal_time(); + } + + class TimeoutDicomConnectionManager::Resource : public IDicomConnectionManager::IResource + { + private: + TimeoutDicomConnectionManager& that_; + + public: + Resource(TimeoutDicomConnectionManager& that) : + that_(that) + { + if (that_.connection_.get() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + ~Resource() + { + that_.Touch(); + } + + DicomUserConnection& GetConnection() + { + assert(that_.connection_.get() != NULL); + return *that_.connection_; + } + }; + + + void TimeoutDicomConnectionManager::Touch() + { + lastUse_ = GetNow(); + } + + + void TimeoutDicomConnectionManager::CheckTimeoutInternal() + { + if (connection_.get() != NULL && + (GetNow() - lastUse_) >= timeout_) + { + Close(); + } + } + + + void TimeoutDicomConnectionManager::SetTimeout(unsigned int timeout) + { + timeout_ = boost::posix_time::milliseconds(timeout); + CheckTimeoutInternal(); + } + + + unsigned int TimeoutDicomConnectionManager::GetTimeout() + { + return timeout_.total_milliseconds(); + } + + + void TimeoutDicomConnectionManager::Close() + { + if (connection_.get() != NULL) + { + LOG(INFO) << "Closing inactive DICOM association with modality: " + << connection_->GetRemoteApplicationEntityTitle(); + + connection_.reset(NULL); + } + } + + + void TimeoutDicomConnectionManager::CheckTimeout() + { + CheckTimeoutInternal(); + } + + + IDicomConnectionManager::IResource* + TimeoutDicomConnectionManager::AcquireConnection(const std::string& localAet, + const RemoteModalityParameters& remote) + { + if (connection_.get() == NULL || + !connection_->IsSameAssociation(localAet, remote)) + { + connection_.reset(new DicomUserConnection(localAet, remote)); + } + + return new Resource(*this); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,102 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IDicomConnectionManager.h" + +#if ORTHANC_ENABLE_DCMTK_NETWORKING == 0 + +namespace Orthanc +{ + class TimeoutDicomConnectionManager : public IDicomConnectionManager + { + public: + void SetTimeout(unsigned int timeout) + { + } + + unsigned int GetTimeout() + { + return 0; + } + + void Close() + { + } + + void CheckTimeout() + { + } + }; +} + +#else + +#include <boost/date_time/posix_time/posix_time.hpp> + +namespace Orthanc +{ + class TimeoutDicomConnectionManager : public IDicomConnectionManager + { + private: + class Resource; + + std::auto_ptr<DicomUserConnection> connection_; + boost::posix_time::ptime lastUse_; + boost::posix_time::time_duration timeout_; + + void Touch(); + + void CheckTimeoutInternal(); + + public: + TimeoutDicomConnectionManager() : + timeout_(boost::posix_time::milliseconds(1000)) + { + } + + void SetTimeout(unsigned int timeout); + + unsigned int GetTimeout(); + + void Close(); + + void CheckTimeout(); + + virtual IResource* AcquireConnection(const std::string& localAet, + const RemoteModalityParameters& remote); + }; +} + +#endif
--- a/Core/DicomParsing/DicomModification.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/DicomParsing/DicomModification.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -36,6 +36,7 @@ #include "../Logging.h" #include "../OrthancException.h" +#include "../SerializationToolbox.h" #include "FromDcmtkBridge.h" #include "ITagVisitor.h" @@ -315,6 +316,7 @@ keepStudyInstanceUid_(false), keepSeriesInstanceUid_(false), updateReferencedRelationships_(true), + isAnonymization_(false), identifierGenerator_(NULL) { } @@ -819,6 +821,8 @@ void DicomModification::SetupAnonymization(DicomVersion version) { + isAnonymization_ = true; + removals_.clear(); clearings_.clear(); ClearReplacements(); @@ -1029,7 +1033,8 @@ MapDicomTags(toModify, ResourceType_Instance); } - // (6) Update the "referenced" relationships + // (6) Update the "referenced" relationships in the case of an anonymization + if (isAnonymization_) { RelationshipsVisitor visitor(*this); @@ -1237,4 +1242,169 @@ patientNameReplaced = (IsReplaced(DICOM_TAG_PATIENT_NAME) && GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName); } + + + + + static const char* REMOVE_PRIVATE_TAGS = "RemovePrivateTags"; + static const char* LEVEL = "Level"; + static const char* ALLOW_MANUAL_IDENTIFIERS = "AllowManualIdentifiers"; + static const char* KEEP_STUDY_INSTANCE_UID = "KeepStudyInstanceUID"; + static const char* KEEP_SERIES_INSTANCE_UID = "KeepSeriesInstanceUID"; + static const char* UPDATE_REFERENCED_RELATIONSHIPS = "UpdateReferencedRelationships"; + static const char* IS_ANONYMIZATION = "IsAnonymization"; + static const char* REMOVALS = "Removals"; + static const char* CLEARINGS = "Clearings"; + static const char* PRIVATE_TAGS_TO_KEEP = "PrivateTagsToKeep"; + static const char* REPLACEMENTS = "Replacements"; + static const char* MAP_PATIENTS = "MapPatients"; + static const char* MAP_STUDIES = "MapStudies"; + static const char* MAP_SERIES = "MapSeries"; + static const char* MAP_INSTANCES = "MapInstances"; + + void DicomModification::Serialize(Json::Value& value) const + { + if (identifierGenerator_ != NULL) + { + LOG(ERROR) << "Cannot serialize a DicomModification with a custom identifier generator"; + throw OrthancException(ErrorCode_InternalError); + } + + value = Json::objectValue; + value[REMOVE_PRIVATE_TAGS] = removePrivateTags_; + value[LEVEL] = EnumerationToString(level_); + value[ALLOW_MANUAL_IDENTIFIERS] = allowManualIdentifiers_; + value[KEEP_STUDY_INSTANCE_UID] = keepStudyInstanceUid_; + value[KEEP_SERIES_INSTANCE_UID] = keepSeriesInstanceUid_; + value[UPDATE_REFERENCED_RELATIONSHIPS] = updateReferencedRelationships_; + value[IS_ANONYMIZATION] = isAnonymization_; + + SerializationToolbox::WriteSetOfTags(value, removals_, REMOVALS); + SerializationToolbox::WriteSetOfTags(value, clearings_, CLEARINGS); + SerializationToolbox::WriteSetOfTags(value, privateTagsToKeep_, PRIVATE_TAGS_TO_KEEP); + + Json::Value& tmp = value[REPLACEMENTS]; + + tmp = Json::objectValue; + + for (Replacements::const_iterator it = replacements_.begin(); + it != replacements_.end(); ++it) + { + assert(it->second != NULL); + tmp[it->first.Format()] = *it->second; + } + + Json::Value& mapPatients = value[MAP_PATIENTS]; + Json::Value& mapStudies = value[MAP_STUDIES]; + Json::Value& mapSeries = value[MAP_SERIES]; + Json::Value& mapInstances = value[MAP_INSTANCES]; + + mapPatients = Json::objectValue; + mapStudies = Json::objectValue; + mapSeries = Json::objectValue; + mapInstances = Json::objectValue; + + for (UidMap::const_iterator it = uidMap_.begin(); it != uidMap_.end(); ++it) + { + Json::Value* tmp = NULL; + + switch (it->first.first) + { + case ResourceType_Patient: + tmp = &mapPatients; + break; + + case ResourceType_Study: + tmp = &mapStudies; + break; + + case ResourceType_Series: + tmp = &mapSeries; + break; + + case ResourceType_Instance: + tmp = &mapInstances; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + assert(tmp != NULL); + (*tmp) [it->first.second] = it->second; + } + } + + + void DicomModification::UnserializeUidMap(ResourceType level, + const Json::Value& serialized, + const char* field) + { + if (!serialized.isMember(field) || + serialized[field].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value::Members names = serialized[field].getMemberNames(); + + for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) + { + const Json::Value& value = serialized[field][*it]; + + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + uidMap_[std::make_pair(level, *it)] = value.asString(); + } + } + } + + + DicomModification::DicomModification(const Json::Value& serialized) : + identifierGenerator_(NULL) + { + removePrivateTags_ = SerializationToolbox::ReadBoolean(serialized, REMOVE_PRIVATE_TAGS); + level_ = StringToResourceType(SerializationToolbox::ReadString(serialized, LEVEL).c_str()); + allowManualIdentifiers_ = SerializationToolbox::ReadBoolean(serialized, ALLOW_MANUAL_IDENTIFIERS); + keepStudyInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_STUDY_INSTANCE_UID); + keepSeriesInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SERIES_INSTANCE_UID); + updateReferencedRelationships_ = SerializationToolbox::ReadBoolean + (serialized, UPDATE_REFERENCED_RELATIONSHIPS); + isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION); + + SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); + SerializationToolbox::ReadSetOfTags(clearings_, serialized, CLEARINGS); + SerializationToolbox::ReadSetOfTags(privateTagsToKeep_, serialized, PRIVATE_TAGS_TO_KEEP); + + if (!serialized.isMember(REPLACEMENTS) || + serialized[REPLACEMENTS].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value::Members names = serialized[REPLACEMENTS].getMemberNames(); + + for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) + { + DicomTag tag(0, 0); + if (!DicomTag::ParseHexadecimal(tag, it->c_str())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + const Json::Value& value = serialized[REPLACEMENTS][*it]; + replacements_.insert(std::make_pair(tag, new Json::Value(value))); + } + } + + UnserializeUidMap(ResourceType_Patient, serialized, MAP_PATIENTS); + UnserializeUidMap(ResourceType_Study, serialized, MAP_STUDIES); + UnserializeUidMap(ResourceType_Series, serialized, MAP_SERIES); + UnserializeUidMap(ResourceType_Instance, serialized, MAP_INSTANCES); + } }
--- a/Core/DicomParsing/DicomModification.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/DicomParsing/DicomModification.h Fri Jul 13 10:07:45 2018 +0200 @@ -84,6 +84,7 @@ bool keepStudyInstanceUid_; bool keepSeriesInstanceUid_; bool updateReferencedRelationships_; + bool isAnonymization_; DicomMap currentSource_; IDicomIdentifierGenerator* identifierGenerator_; @@ -107,9 +108,15 @@ void SetupAnonymization2017c(); + void UnserializeUidMap(ResourceType level, + const Json::Value& serialized, + const char* field); + public: DicomModification(); + DicomModification(const Json::Value& serialized); + ~DicomModification(); void Keep(const DicomTag& tag); @@ -172,5 +179,7 @@ { identifierGenerator_ = &generator; } + + void Serialize(Json::Value& value) const; }; }
--- a/Core/DicomParsing/FromDcmtkBridge.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -106,27 +106,6 @@ namespace Orthanc { - static inline uint16_t GetCharValue(char c) - { - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - else - return 0; - } - - static inline uint16_t GetTagValue(const char* c) - { - return ((GetCharValue(c[0]) << 12) + - (GetCharValue(c[1]) << 8) + - (GetCharValue(c[2]) << 4) + - GetCharValue(c[3])); - } - - #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, EmbeddedResources::FileResourceId resource) @@ -1061,35 +1040,10 @@ DicomTag FromDcmtkBridge::ParseTag(const char* name) { - if (strlen(name) == 9 && - isxdigit(name[0]) && - isxdigit(name[1]) && - isxdigit(name[2]) && - isxdigit(name[3]) && - (name[4] == '-' || name[4] == ',') && - isxdigit(name[5]) && - isxdigit(name[6]) && - isxdigit(name[7]) && - isxdigit(name[8])) + DicomTag parsed(0, 0); + if (DicomTag::ParseHexadecimal(parsed, name)) { - uint16_t group = GetTagValue(name); - uint16_t element = GetTagValue(name + 5); - return DicomTag(group, element); - } - - if (strlen(name) == 8 && - isxdigit(name[0]) && - isxdigit(name[1]) && - isxdigit(name[2]) && - isxdigit(name[3]) && - isxdigit(name[4]) && - isxdigit(name[5]) && - isxdigit(name[6]) && - isxdigit(name[7])) - { - uint16_t group = GetTagValue(name); - uint16_t element = GetTagValue(name + 4); - return DicomTag(group, element); + return parsed; } #if 0
--- a/Core/EnumerationDictionary.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/EnumerationDictionary.h Fri Jul 13 10:07:45 2018 +0200 @@ -84,10 +84,12 @@ Enumeration Translate(const std::string& str) const { - int value; - if (boost::conversion::try_lexical_convert<int>(str, value)) + try { - return static_cast<Enumeration>(value); + return static_cast<Enumeration>(boost::lexical_cast<int>(str)); + } + catch (boost::bad_lexical_cast&) + { } typename StringToEnumeration::const_iterator
--- a/Core/Enumerations.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/Enumerations.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -164,6 +164,9 @@ case ErrorCode_DatabaseUnavailable: return "The database is currently not available (probably a transient situation)"; + case ErrorCode_CanceledJob: + return "This job was canceled"; + case ErrorCode_SQLiteNotOpened: return "SQLite: The database is not opened"; @@ -987,6 +990,34 @@ } + const char* EnumerationToString(JobState state) + { + switch (state) + { + case JobState_Pending: + return "Pending"; + + case JobState_Running: + return "Running"; + + case JobState_Success: + return "Success"; + + case JobState_Failure: + return "Failure"; + + case JobState_Paused: + return "Paused"; + + case JobState_Retry: + return "Retry"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + Encoding StringToEncoding(const char* encoding) { std::string s(encoding); @@ -1436,6 +1467,68 @@ } + JobState StringToJobState(const std::string& state) + { + if (state == "Pending") + { + return JobState_Pending; + } + else if (state == "Running") + { + return JobState_Running; + } + else if (state == "Success") + { + return JobState_Success; + } + else if (state == "Failure") + { + return JobState_Failure; + } + else if (state == "Paused") + { + return JobState_Paused; + } + else if (state == "Retry") + { + return JobState_Retry; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + RequestOrigin StringToRequestOrigin(const std::string& origin) + { + if (origin == "Unknown") + { + return RequestOrigin_Unknown; + } + else if (origin == "DicomProtocol") + { + return RequestOrigin_DicomProtocol; + } + else if (origin == "RestApi") + { + return RequestOrigin_RestApi; + } + else if (origin == "Plugins") + { + return RequestOrigin_Plugins; + } + else if (origin == "Lua") + { + return RequestOrigin_Lua; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + unsigned int GetBytesPerPixel(PixelFormat format) { switch (format)
--- a/Core/Enumerations.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/Enumerations.h Fri Jul 13 10:07:45 2018 +0200 @@ -96,6 +96,7 @@ ErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, ErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */, ErrorCode_DatabaseUnavailable = 36 /*!< The database is currently not available (probably a transient situation) */, + ErrorCode_CanceledJob = 37 /*!< This job was canceled */, ErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, ErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, ErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, @@ -548,6 +549,24 @@ TransferSyntax_Rle }; + enum JobState + { + JobState_Pending, + JobState_Running, + JobState_Success, + JobState_Failure, + JobState_Paused, + JobState_Retry + }; + + enum JobStepCode + { + JobStepCode_Success, + JobStepCode_Failure, + JobStepCode_Continue, + JobStepCode_Retry + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -628,6 +647,8 @@ const char* EnumerationToString(ValueRepresentation vr); + const char* EnumerationToString(JobState state); + Encoding StringToEncoding(const char* encoding); ResourceType StringToResourceType(const char* type); @@ -644,6 +665,10 @@ ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); DicomVersion StringToDicomVersion(const std::string& version); + + JobState StringToJobState(const std::string& state); + + RequestOrigin StringToRequestOrigin(const std::string& origin); unsigned int GetBytesPerPixel(PixelFormat format);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileStorage/MemoryStorageArea.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,119 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "MemoryStorageArea.h" + +#include "../OrthancException.h" + +namespace Orthanc +{ + MemoryStorageArea::~MemoryStorageArea() + { + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) + { + if (it->second != NULL) + { + delete it->second; + } + } + } + + void MemoryStorageArea::Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType type) + { + boost::mutex::scoped_lock lock(mutex_); + + if (size != 0 && + content == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else if (content_.find(uuid) != content_.end()) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + content_[uuid] = new std::string(reinterpret_cast<const char*>(content), size); + } + } + + + void MemoryStorageArea::Read(std::string& content, + const std::string& uuid, + FileContentType type) + { + boost::mutex::scoped_lock lock(mutex_); + + Content::const_iterator found = content_.find(uuid); + + if (found == content_.end()) + { + throw OrthancException(ErrorCode_InexistentFile); + } + else if (found->second == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + content.assign(*found->second); + } + } + + + void MemoryStorageArea::Remove(const std::string& uuid, + FileContentType type) + { + boost::mutex::scoped_lock lock(mutex_); + + Content::iterator found = content_.find(uuid); + + if (found == content_.end()) + { + // Ignore second removal + } + else if (found->second == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + delete found->second; + content_.erase(found); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileStorage/MemoryStorageArea.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,66 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IStorageArea.h" + +#include <boost/thread/mutex.hpp> +#include <map> + +namespace Orthanc +{ + class MemoryStorageArea : public IStorageArea + { + private: + typedef std::map<std::string, std::string*> Content; + + boost::mutex mutex_; + Content content_; + + public: + virtual ~MemoryStorageArea(); + + virtual void Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType type); + + virtual void Read(std::string& content, + const std::string& uuid, + FileContentType type); + + virtual void Remove(const std::string& uuid, + FileContentType type); + }; +}
--- a/Core/HttpClient.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/HttpClient.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -46,16 +46,6 @@ #include <boost/thread/mutex.hpp> -#if ORTHANC_ENABLE_SSL == 1 -// For OpenSSL initialization and finalization -# include <openssl/conf.h> -# include <openssl/engine.h> -# include <openssl/err.h> -# include <openssl/evp.h> -# include <openssl/ssl.h> -#endif - - #if ORTHANC_ENABLE_PKCS11 == 1 # include "Pkcs11.h" #endif @@ -812,34 +802,4 @@ throw OrthancException(ErrorCode_InternalError); #endif } - - - void HttpClient::InitializeOpenSsl() - { -#if ORTHANC_ENABLE_SSL == 1 - // https://wiki.openssl.org/index.php/Library_Initialization - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - ERR_load_crypto_strings(); -#endif - } - - - void HttpClient::FinalizeOpenSsl() - { -#if ORTHANC_ENABLE_SSL == 1 - // Finalize OpenSSL - // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup -#ifdef FIPS_mode_set - FIPS_mode_set(0); -#endif - ENGINE_cleanup(); - CONF_modules_unload(1); - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); - ERR_remove_state(0); - ERR_free_strings(); -#endif - } }
--- a/Core/HttpClient.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/HttpClient.h Fri Jul 13 10:07:45 2018 +0200 @@ -268,10 +268,6 @@ static void GlobalFinalize(); - static void InitializeOpenSsl(); - - static void FinalizeOpenSsl(); - static void InitializePkcs11(const std::string& module, const std::string& pin, bool verbose);
--- a/Core/HttpServer/IIncomingHttpRequestFilter.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/HttpServer/IIncomingHttpRequestFilter.h Fri Jul 13 10:07:45 2018 +0200 @@ -49,6 +49,6 @@ const char* ip, const char* username, const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::GetArguments& getArguments) const = 0; + const IHttpHandler::GetArguments& getArguments) = 0; }; }
--- a/Core/HttpServer/MongooseServer.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/HttpServer/MongooseServer.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -676,7 +676,7 @@ std::string username = GetAuthenticatedUsername(headers); - const IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); + IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); if (filter != NULL) { if (!filter->IsAllowed(method, request->uri, remoteIp,
--- a/Core/HttpServer/MongooseServer.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/HttpServer/MongooseServer.h Fri Jul 13 10:07:45 2018 +0200 @@ -162,7 +162,7 @@ void SetHttpCompressionEnabled(bool enabled); - const IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const + IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const { return filter_; }
--- a/Core/Images/ImageProcessing.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/Images/ImageProcessing.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -550,7 +550,8 @@ return; } - if (target.GetFormat() == PixelFormat_RGBA32 && + if ((target.GetFormat() == PixelFormat_RGBA32 || + target.GetFormat() == PixelFormat_BGRA32) && source.GetFormat() == PixelFormat_Grayscale8) { for (unsigned int y = 0; y < source.GetHeight(); y++)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/GenericJobUnserializer.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,99 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "GenericJobUnserializer.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../SerializationToolbox.h" + +#include "Operations/LogJobOperation.h" +#include "Operations/NullOperationValue.h" +#include "Operations/SequenceOfOperationsJob.h" +#include "Operations/StringOperationValue.h" + +namespace Orthanc +{ + IJob* GenericJobUnserializer::UnserializeJob(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + + if (type == "SequenceOfOperations") + { + return new SequenceOfOperationsJob(*this, source); + } + else + { + LOG(ERROR) << "Cannot unserialize job of type: " << type; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + IJobOperation* GenericJobUnserializer::UnserializeOperation(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + + if (type == "Log") + { + return new LogJobOperation; + } + else + { + LOG(ERROR) << "Cannot unserialize operation of type: " << type; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + JobOperationValue* GenericJobUnserializer::UnserializeValue(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + + if (type == "String") + { + return new StringOperationValue(SerializationToolbox::ReadString(source, "Content")); + } + else if (type == "Null") + { + return new NullOperationValue; + } + else + { + LOG(ERROR) << "Cannot unserialize value of type: " << type; + throw OrthancException(ErrorCode_BadFileFormat); + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/GenericJobUnserializer.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,49 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IJobUnserializer.h" + +namespace Orthanc +{ + class GenericJobUnserializer : public IJobUnserializer + { + public: + virtual IJob* UnserializeJob(const Json::Value& value); + + virtual IJobOperation* UnserializeOperation(const Json::Value& value); + + virtual JobOperationValue* UnserializeValue(const Json::Value& value); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/IJob.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,68 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "JobStepResult.h" + +#include <boost/noncopyable.hpp> +#include <json/value.h> + +namespace Orthanc +{ + class IJob : public boost::noncopyable + { + public: + virtual ~IJob() + { + } + + // Method called once the job enters the jobs engine + virtual void Start() = 0; + + virtual JobStepResult ExecuteStep() = 0; + + // Method called once the job is resubmitted after a failure + virtual void SignalResubmit() = 0; + + virtual void ReleaseResources() = 0; // For pausing/canceling jobs + + virtual float GetProgress() = 0; + + virtual void GetJobType(std::string& target) = 0; + + virtual void GetPublicContent(Json::Value& value) = 0; + + virtual bool Serialize(Json::Value& value) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/IJobUnserializer.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IJob.h" +#include "Operations/JobOperationValue.h" +#include "Operations/IJobOperation.h" + +#include <vector> + +namespace Orthanc +{ + class IJobUnserializer : public boost::noncopyable + { + public: + virtual ~IJobUnserializer() + { + } + + virtual IJob* UnserializeJob(const Json::Value& value) = 0; + + virtual IJobOperation* UnserializeOperation(const Json::Value& value) = 0; + + virtual JobOperationValue* UnserializeValue(const Json::Value& value) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobInfo.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,148 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "JobInfo.h" + +#include "../OrthancException.h" + +// This "include" is mandatory for Release builds using Linux Standard Base +#include <boost/math/special_functions/round.hpp> + +namespace Orthanc +{ + JobInfo::JobInfo(const std::string& id, + int priority, + JobState state, + const JobStatus& status, + const boost::posix_time::ptime& creationTime, + const boost::posix_time::ptime& lastStateChangeTime, + const boost::posix_time::time_duration& runtime) : + id_(id), + priority_(priority), + state_(state), + timestamp_(boost::posix_time::microsec_clock::universal_time()), + creationTime_(creationTime), + lastStateChangeTime_(lastStateChangeTime), + runtime_(runtime), + hasEta_(false), + status_(status) + { + if (state_ == JobState_Running) + { + float ms = static_cast<float>(runtime_.total_milliseconds()); + + if (status_.GetProgress() > 0.01f && + ms > 0.01f) + { + float ratio = static_cast<float>(1.0 - status_.GetProgress()); + long long remaining = boost::math::llround(ratio * ms); + eta_ = timestamp_ + boost::posix_time::milliseconds(remaining); + hasEta_ = true; + } + } + } + + + JobInfo::JobInfo() : + priority_(0), + state_(JobState_Failure), + timestamp_(boost::posix_time::microsec_clock::universal_time()), + creationTime_(timestamp_), + lastStateChangeTime_(timestamp_), + runtime_(boost::posix_time::milliseconds(0)), + hasEta_(false) + { + } + + + bool JobInfo::HasCompletionTime() const + { + return (state_ == JobState_Success || + state_ == JobState_Failure); + } + + + const boost::posix_time::ptime& JobInfo::GetEstimatedTimeOfArrival() const + { + if (hasEta_) + { + return eta_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + const boost::posix_time::ptime& JobInfo::GetCompletionTime() const + { + if (HasCompletionTime()) + { + return lastStateChangeTime_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + void JobInfo::Format(Json::Value& target) const + { + target = Json::objectValue; + target["ID"] = id_; + target["Priority"] = priority_; + target["ErrorCode"] = static_cast<int>(status_.GetErrorCode()); + target["ErrorDescription"] = EnumerationToString(status_.GetErrorCode()); + target["State"] = EnumerationToString(state_); + target["Timestamp"] = boost::posix_time::to_iso_string(timestamp_); + target["CreationTime"] = boost::posix_time::to_iso_string(creationTime_); + target["EffectiveRuntime"] = static_cast<double>(runtime_.total_milliseconds()) / 1000.0; + target["Progress"] = boost::math::iround(status_.GetProgress() * 100.0f); + + target["Type"] = status_.GetJobType(); + target["Content"] = status_.GetPublicContent(); + + if (HasEstimatedTimeOfArrival()) + { + target["EstimatedTimeOfArrival"] = boost::posix_time::to_iso_string(GetEstimatedTimeOfArrival()); + } + + if (HasCompletionTime()) + { + target["CompletionTime"] = boost::posix_time::to_iso_string(GetCompletionTime()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobInfo.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,120 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "JobStatus.h" + +#include <boost/date_time/posix_time/posix_time.hpp> + +namespace Orthanc +{ + class JobInfo + { + private: + std::string id_; + int priority_; + JobState state_; + boost::posix_time::ptime timestamp_; + boost::posix_time::ptime creationTime_; + boost::posix_time::ptime lastStateChangeTime_; + boost::posix_time::time_duration runtime_; + bool hasEta_; + boost::posix_time::ptime eta_; + JobStatus status_; + + public: + JobInfo(const std::string& id, + int priority, + JobState state, + const JobStatus& status, + const boost::posix_time::ptime& creationTime, + const boost::posix_time::ptime& lastStateChangeTime, + const boost::posix_time::time_duration& runtime); + + JobInfo(); + + const std::string& GetIdentifier() const + { + return id_; + } + + int GetPriority() const + { + return priority_; + } + + JobState GetState() const + { + return state_; + } + + const boost::posix_time::ptime& GetInfoTime() const + { + return timestamp_; + } + + const boost::posix_time::ptime& GetCreationTime() const + { + return creationTime_; + } + + const boost::posix_time::time_duration& GetRuntime() const + { + return runtime_; + } + + bool HasEstimatedTimeOfArrival() const + { + return hasEta_; + } + + bool HasCompletionTime() const; + + const boost::posix_time::ptime& GetEstimatedTimeOfArrival() const; + + const boost::posix_time::ptime& GetCompletionTime() const; + + const JobStatus& GetStatus() const + { + return status_; + } + + JobStatus& GetStatus() + { + return status_; + } + + void Format(Json::Value& target) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStatus.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,85 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "JobStatus.h" + +#include "../OrthancException.h" + +namespace Orthanc +{ + JobStatus::JobStatus() : + errorCode_(ErrorCode_InternalError), + progress_(0), + jobType_("Invalid"), + publicContent_(Json::objectValue), + hasSerialized_(false) + { + } + + + JobStatus::JobStatus(ErrorCode code, + IJob& job) : + errorCode_(code), + progress_(job.GetProgress()), + publicContent_(Json::objectValue) + { + if (progress_ < 0) + { + progress_ = 0; + } + + if (progress_ > 1) + { + progress_ = 1; + } + + job.GetJobType(jobType_); + job.GetPublicContent(publicContent_); + + hasSerialized_ = job.Serialize(serialized_); + } + + + const Json::Value& JobStatus::GetSerialized() const + { + if (!hasSerialized_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return serialized_; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStatus.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,88 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IJob.h" + +namespace Orthanc +{ + class JobStatus + { + private: + ErrorCode errorCode_; + float progress_; + std::string jobType_; + Json::Value publicContent_; + Json::Value serialized_; + bool hasSerialized_; + + public: + JobStatus(); + + JobStatus(ErrorCode code, + IJob& job); + + ErrorCode GetErrorCode() const + { + return errorCode_; + } + + void SetErrorCode(ErrorCode error) + { + errorCode_ = error; + } + + float GetProgress() const + { + return progress_; + } + + const std::string& GetJobType() const + { + return jobType_; + } + + const Json::Value& GetPublicContent() const + { + return publicContent_; + } + + const Json::Value& GetSerialized() const; + + bool HasSerialized() const + { + return hasSerialized_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStepResult.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,81 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "JobStepResult.h" + +#include "../OrthancException.h" + +namespace Orthanc +{ + JobStepResult JobStepResult::Retry(unsigned int timeout) + { + JobStepResult result(JobStepCode_Retry); + result.timeout_ = timeout; + return result; + } + + + JobStepResult JobStepResult::Failure(const ErrorCode& error) + { + JobStepResult result(JobStepCode_Failure); + result.error_ = error; + return result; + } + + + unsigned int JobStepResult::GetRetryTimeout() const + { + if (code_ == JobStepCode_Retry) + { + return timeout_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + ErrorCode JobStepResult::GetFailureCode() const + { + if (code_ == JobStepCode_Failure) + { + return error_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStepResult.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,85 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +namespace Orthanc +{ + class JobStepResult + { + private: + JobStepCode code_; + unsigned int timeout_; + ErrorCode error_; + + explicit JobStepResult(JobStepCode code) : + code_(code), + timeout_(0), + error_(ErrorCode_Success) + { + } + + public: + explicit JobStepResult() : + code_(JobStepCode_Failure), + timeout_(0), + error_(ErrorCode_InternalError) + { + } + + static JobStepResult Success() + { + return JobStepResult(JobStepCode_Success); + } + + static JobStepResult Continue() + { + return JobStepResult(JobStepCode_Continue); + } + + static JobStepResult Retry(unsigned int timeout); + + static JobStepResult Failure(const ErrorCode& error); + + JobStepCode GetCode() const + { + return code_; + } + + unsigned int GetRetryTimeout() const; + + ErrorCode GetFailureCode() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsEngine.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,324 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "JobsEngine.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +#include <json/reader.h> + +namespace Orthanc +{ + bool JobsEngine::IsRunning() + { + boost::mutex::scoped_lock lock(stateMutex_); + return (state_ == State_Running); + } + + + bool JobsEngine::ExecuteStep(JobsRegistry::RunningJob& running, + size_t workerIndex) + { + assert(running.IsValid()); + + if (running.IsPauseScheduled()) + { + running.GetJob().ReleaseResources(); + running.MarkPause(); + return false; + } + + if (running.IsCancelScheduled()) + { + running.GetJob().ReleaseResources(); + running.MarkCanceled(); + return false; + } + + JobStepResult result; + + try + { + result = running.GetJob().ExecuteStep(); + } + catch (OrthancException& e) + { + result = JobStepResult::Failure(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + result = JobStepResult::Failure(ErrorCode_BadFileFormat); + } + catch (...) + { + result = JobStepResult::Failure(ErrorCode_InternalError); + } + + switch (result.GetCode()) + { + case JobStepCode_Success: + running.UpdateStatus(ErrorCode_Success); + running.GetJob().ReleaseResources(); + running.MarkSuccess(); + return false; + + case JobStepCode_Failure: + running.GetJob().ReleaseResources(); + running.UpdateStatus(result.GetFailureCode()); + running.MarkFailure(); + return false; + + case JobStepCode_Retry: + running.GetJob().ReleaseResources(); + running.UpdateStatus(ErrorCode_Success); + running.MarkRetry(result.GetRetryTimeout()); + return false; + + case JobStepCode_Continue: + running.UpdateStatus(ErrorCode_Success); + return true; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + void JobsEngine::RetryHandler(JobsEngine* engine) + { + assert(engine != NULL); + + while (engine->IsRunning()) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(engine->threadSleep_)); + engine->GetRegistry().ScheduleRetries(); + } + } + + + void JobsEngine::Worker(JobsEngine* engine, + size_t workerIndex) + { + assert(engine != NULL); + + LOG(INFO) << "Worker thread " << workerIndex << " has started"; + + while (engine->IsRunning()) + { + JobsRegistry::RunningJob running(engine->GetRegistry(), engine->threadSleep_); + + if (running.IsValid()) + { + LOG(INFO) << "Executing job with priority " << running.GetPriority() + << " in worker thread " << workerIndex << ": " << running.GetId(); + + while (engine->IsRunning()) + { + if (!engine->ExecuteStep(running, workerIndex)) + { + break; + } + } + } + } + } + + + JobsEngine::JobsEngine() : + state_(State_Setup), + registry_(new JobsRegistry), + threadSleep_(200), + workers_(1) + { + } + + + JobsEngine::~JobsEngine() + { + if (state_ != State_Setup && + state_ != State_Done) + { + LOG(ERROR) << "INTERNAL ERROR: JobsEngine::Stop() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } + } + + + JobsRegistry& JobsEngine::GetRegistry() + { + if (registry_.get() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + return *registry_; + } + + + void JobsEngine::LoadRegistryFromJson(IJobUnserializer& unserializer, + const Json::Value& serialized) + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Setup) + { + // Can only be invoked before calling "Start()" + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + registry_.reset(new JobsRegistry(unserializer, serialized)); + } + + + void JobsEngine::LoadRegistryFromString(IJobUnserializer& unserializer, + const std::string& serialized) + { + Json::Value value; + Json::Reader reader; + if (reader.parse(serialized, value)) + { + LoadRegistryFromJson(unserializer, value); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + void JobsEngine::SetWorkersCount(size_t count) + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Setup) + { + // Can only be invoked before calling "Start()" + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + workers_.resize(count); + } + + + void JobsEngine::SetThreadSleep(unsigned int sleep) + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Setup) + { + // Can only be invoked before calling "Start()" + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + threadSleep_ = sleep; + } + + + void JobsEngine::Start() + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Setup) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + retryHandler_ = boost::thread(RetryHandler, this); + + if (workers_.size() == 0) + { + // Use all the available CPUs + size_t n = boost::thread::hardware_concurrency(); + + if (n == 0) + { + n = 1; + } + + workers_.resize(n); + } + + for (size_t i = 0; i < workers_.size(); i++) + { + assert(workers_[i] == NULL); + workers_[i] = new boost::thread(Worker, this, i); + } + + state_ = State_Running; + + LOG(WARNING) << "The jobs engine has started with " << workers_.size() << " threads"; + } + + + void JobsEngine::Stop() + { + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Running) + { + return; + } + + state_ = State_Stopping; + } + + LOG(INFO) << "Stopping the jobs engine"; + + if (retryHandler_.joinable()) + { + retryHandler_.join(); + } + + for (size_t i = 0; i < workers_.size(); i++) + { + assert(workers_[i] != NULL); + + if (workers_[i]->joinable()) + { + workers_[i]->join(); + } + + delete workers_[i]; + } + + { + boost::mutex::scoped_lock lock(stateMutex_); + state_ = State_Done; + } + + LOG(WARNING) << "The jobs engine has stopped"; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsEngine.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,91 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "JobsRegistry.h" + +#include <boost/thread.hpp> + +namespace Orthanc +{ + class JobsEngine + { + private: + enum State + { + State_Setup, + State_Running, + State_Stopping, + State_Done + }; + + boost::mutex stateMutex_; + State state_; + std::auto_ptr<JobsRegistry> registry_; + boost::thread retryHandler_; + unsigned int threadSleep_; + std::vector<boost::thread*> workers_; + + bool IsRunning(); + + bool ExecuteStep(JobsRegistry::RunningJob& running, + size_t workerIndex); + + static void RetryHandler(JobsEngine* engine); + + static void Worker(JobsEngine* engine, + size_t workerIndex); + + public: + JobsEngine(); + + ~JobsEngine(); + + JobsRegistry& GetRegistry(); + + void LoadRegistryFromJson(IJobUnserializer& unserializer, + const Json::Value& serialized); + + void LoadRegistryFromString(IJobUnserializer& unserializer, + const std::string& serialized); + + void SetWorkersCount(size_t count); + + void SetThreadSleep(unsigned int sleep); + + void Start(); + + void Stop(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsRegistry.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,1304 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "JobsRegistry.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../Toolbox.h" +#include "../SerializationToolbox.h" + +namespace Orthanc +{ + static const char* STATE = "State"; + static const char* TYPE = "Type"; + static const char* PRIORITY = "Priority"; + static const char* JOB = "Job"; + static const char* JOBS = "Jobs"; + static const char* JOBS_REGISTRY = "JobsRegistry"; + static const char* MAX_COMPLETED_JOBS = "MaxCompletedJobs"; + static const char* CREATION_TIME = "CreationTime"; + static const char* LAST_CHANGE_TIME = "LastChangeTime"; + static const char* RUNTIME = "Runtime"; + + + class JobsRegistry::JobHandler : public boost::noncopyable + { + private: + std::string id_; + JobState state_; + std::string jobType_; + std::auto_ptr<IJob> job_; + int priority_; // "+inf()" means highest priority + boost::posix_time::ptime creationTime_; + boost::posix_time::ptime lastStateChangeTime_; + boost::posix_time::time_duration runtime_; + boost::posix_time::ptime retryTime_; + bool pauseScheduled_; + bool cancelScheduled_; + JobStatus lastStatus_; + + void Touch() + { + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + + if (state_ == JobState_Running) + { + runtime_ += (now - lastStateChangeTime_); + } + + lastStateChangeTime_ = now; + } + + void SetStateInternal(JobState state) + { + state_ = state; + pauseScheduled_ = false; + cancelScheduled_ = false; + Touch(); + } + + public: + JobHandler(IJob* job, + int priority) : + id_(Toolbox::GenerateUuid()), + state_(JobState_Pending), + job_(job), + priority_(priority), + creationTime_(boost::posix_time::microsec_clock::universal_time()), + lastStateChangeTime_(creationTime_), + runtime_(boost::posix_time::milliseconds(0)), + retryTime_(creationTime_), + pauseScheduled_(false), + cancelScheduled_(false) + { + if (job == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + job->GetJobType(jobType_); + job->Start(); + + lastStatus_ = JobStatus(ErrorCode_Success, *job_); + } + + const std::string& GetId() const + { + return id_; + } + + IJob& GetJob() const + { + assert(job_.get() != NULL); + return *job_; + } + + void SetPriority(int priority) + { + priority_ = priority; + } + + int GetPriority() const + { + return priority_; + } + + JobState GetState() const + { + return state_; + } + + void SetState(JobState state) + { + if (state == JobState_Retry) + { + // Use "SetRetryState()" + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + SetStateInternal(state); + } + } + + void SetRetryState(unsigned int timeout) + { + if (state_ == JobState_Running) + { + SetStateInternal(JobState_Retry); + retryTime_ = (boost::posix_time::microsec_clock::universal_time() + + boost::posix_time::milliseconds(timeout)); + } + else + { + // Only valid for running jobs + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + void SchedulePause() + { + if (state_ == JobState_Running) + { + pauseScheduled_ = true; + } + else + { + // Only valid for running jobs + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + void ScheduleCancel() + { + if (state_ == JobState_Running) + { + cancelScheduled_ = true; + } + else + { + // Only valid for running jobs + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + bool IsPauseScheduled() + { + return pauseScheduled_; + } + + bool IsCancelScheduled() + { + return cancelScheduled_; + } + + bool IsRetryReady(const boost::posix_time::ptime& now) const + { + if (state_ != JobState_Retry) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return retryTime_ <= now; + } + } + + const boost::posix_time::ptime& GetCreationTime() const + { + return creationTime_; + } + + const boost::posix_time::ptime& GetLastStateChangeTime() const + { + return lastStateChangeTime_; + } + + void SetLastStateChangeTime(const boost::posix_time::ptime& time) + { + lastStateChangeTime_ = time; + } + + const boost::posix_time::time_duration& GetRuntime() const + { + return runtime_; + } + + const JobStatus& GetLastStatus() const + { + return lastStatus_; + } + + void SetLastStatus(const JobStatus& status) + { + lastStatus_ = status; + Touch(); + } + + void SetLastErrorCode(ErrorCode code) + { + lastStatus_.SetErrorCode(code); + } + + bool Serialize(Json::Value& target) const + { + target = Json::objectValue; + + bool ok; + + if (state_ == JobState_Running) + { + // WARNING: Cannot directly access the "job_" member, as long + // as a "RunningJob" instance is running. We do not use a + // mutex at the "JobHandler" level, as serialization would be + // blocked while a step in the job is running. Instead, we + // save a snapshot of the serialized job. + + if (lastStatus_.HasSerialized()) + { + target[JOB] = lastStatus_.GetSerialized(); + ok = true; + } + else + { + ok = false; + } + } + else + { + ok = job_->Serialize(target[JOB]); + } + + if (ok) + { + target[STATE] = EnumerationToString(state_); + target[PRIORITY] = priority_; + target[CREATION_TIME] = boost::posix_time::to_iso_string(creationTime_); + target[LAST_CHANGE_TIME] = boost::posix_time::to_iso_string(lastStateChangeTime_); + target[RUNTIME] = static_cast<unsigned int>(runtime_.total_milliseconds()); + return true; + } + else + { + LOG(INFO) << "Job backup is not supported for job of type: " << jobType_; + return false; + } + } + + JobHandler(IJobUnserializer& unserializer, + const Json::Value& serialized, + const std::string& id) : + id_(id), + pauseScheduled_(false), + cancelScheduled_(false) + { + state_ = StringToJobState(SerializationToolbox::ReadString(serialized, STATE)); + priority_ = SerializationToolbox::ReadInteger(serialized, PRIORITY); + creationTime_ = boost::posix_time::from_iso_string + (SerializationToolbox::ReadString(serialized, CREATION_TIME)); + lastStateChangeTime_ = boost::posix_time::from_iso_string + (SerializationToolbox::ReadString(serialized, LAST_CHANGE_TIME)); + runtime_ = boost::posix_time::milliseconds + (SerializationToolbox::ReadInteger(serialized, RUNTIME)); + + retryTime_ = creationTime_; + + job_.reset(unserializer.UnserializeJob(serialized[JOB])); + job_->GetJobType(jobType_); + job_->Start(); + + lastStatus_ = JobStatus(ErrorCode_Success, *job_); + } + }; + + + bool JobsRegistry::PriorityComparator::operator() (JobHandler*& a, + JobHandler*& b) const + { + return a->GetPriority() < b->GetPriority(); + } + + +#if defined(NDEBUG) + void JobsRegistry::CheckInvariants() const + { + } + +#else + bool JobsRegistry::IsPendingJob(const JobHandler& job) const + { + PendingJobs copy = pendingJobs_; + while (!copy.empty()) + { + if (copy.top() == &job) + { + return true; + } + + copy.pop(); + } + + return false; + } + + bool JobsRegistry::IsCompletedJob(JobHandler& job) const + { + for (CompletedJobs::const_iterator it = completedJobs_.begin(); + it != completedJobs_.end(); ++it) + { + if (*it == &job) + { + return true; + } + } + + return false; + } + + bool JobsRegistry::IsRetryJob(JobHandler& job) const + { + return retryJobs_.find(&job) != retryJobs_.end(); + } + + void JobsRegistry::CheckInvariants() const + { + { + PendingJobs copy = pendingJobs_; + while (!copy.empty()) + { + assert(copy.top()->GetState() == JobState_Pending); + copy.pop(); + } + } + + assert(completedJobs_.size() <= maxCompletedJobs_); + + for (CompletedJobs::const_iterator it = completedJobs_.begin(); + it != completedJobs_.end(); ++it) + { + assert((*it)->GetState() == JobState_Success || + (*it)->GetState() == JobState_Failure); + } + + for (RetryJobs::const_iterator it = retryJobs_.begin(); + it != retryJobs_.end(); ++it) + { + assert((*it)->GetState() == JobState_Retry); + } + + for (JobsIndex::const_iterator it = jobsIndex_.begin(); + it != jobsIndex_.end(); ++it) + { + JobHandler& job = *it->second; + + assert(job.GetId() == it->first); + + switch (job.GetState()) + { + case JobState_Pending: + assert(!IsRetryJob(job) && IsPendingJob(job) && !IsCompletedJob(job)); + break; + + case JobState_Success: + case JobState_Failure: + assert(!IsRetryJob(job) && !IsPendingJob(job) && IsCompletedJob(job)); + break; + + case JobState_Retry: + assert(IsRetryJob(job) && !IsPendingJob(job) && !IsCompletedJob(job)); + break; + + case JobState_Running: + case JobState_Paused: + assert(!IsRetryJob(job) && !IsPendingJob(job) && !IsCompletedJob(job)); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + } +#endif + + + void JobsRegistry::ForgetOldCompletedJobs() + { + if (maxCompletedJobs_ != 0) + { + while (completedJobs_.size() > maxCompletedJobs_) + { + assert(completedJobs_.front() != NULL); + + std::string id = completedJobs_.front()->GetId(); + assert(jobsIndex_.find(id) != jobsIndex_.end()); + + jobsIndex_.erase(id); + delete(completedJobs_.front()); + completedJobs_.pop_front(); + } + } + } + + + void JobsRegistry::SetCompletedJob(JobHandler& job, + bool success) + { + job.SetState(success ? JobState_Success : JobState_Failure); + + completedJobs_.push_back(&job); + ForgetOldCompletedJobs(); + + someJobComplete_.notify_all(); + } + + + void JobsRegistry::MarkRunningAsCompleted(JobHandler& job, + bool success) + { + LOG(INFO) << "Job has completed with " << (success ? "success" : "failure") + << ": " << job.GetId(); + + CheckInvariants(); + + assert(job.GetState() == JobState_Running); + SetCompletedJob(job, success); + + if (observer_ != NULL) + { + if (success) + { + observer_->SignalJobSuccess(job.GetId()); + } + else + { + observer_->SignalJobFailure(job.GetId()); + } + } + + CheckInvariants(); + } + + + void JobsRegistry::MarkRunningAsRetry(JobHandler& job, + unsigned int timeout) + { + LOG(INFO) << "Job scheduled for retry in " << timeout << "ms: " << job.GetId(); + + CheckInvariants(); + + assert(job.GetState() == JobState_Running && + retryJobs_.find(&job) == retryJobs_.end()); + + retryJobs_.insert(&job); + job.SetRetryState(timeout); + + CheckInvariants(); + } + + + void JobsRegistry::MarkRunningAsPaused(JobHandler& job) + { + LOG(INFO) << "Job paused: " << job.GetId(); + + CheckInvariants(); + assert(job.GetState() == JobState_Running); + + job.SetState(JobState_Paused); + + CheckInvariants(); + } + + + bool JobsRegistry::GetStateInternal(JobState& state, + const std::string& id) + { + CheckInvariants(); + + JobsIndex::const_iterator it = jobsIndex_.find(id); + if (it == jobsIndex_.end()) + { + return false; + } + else + { + state = it->second->GetState(); + return true; + } + } + + + JobsRegistry::~JobsRegistry() + { + for (JobsIndex::iterator it = jobsIndex_.begin(); it != jobsIndex_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + } + + + void JobsRegistry::SetMaxCompletedJobs(size_t n) + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + LOG(INFO) << "The size of the history of the jobs engine is set to: " << n << " job(s)"; + + maxCompletedJobs_ = n; + ForgetOldCompletedJobs(); + + CheckInvariants(); + } + + + void JobsRegistry::ListJobs(std::set<std::string>& target) + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + for (JobsIndex::const_iterator it = jobsIndex_.begin(); + it != jobsIndex_.end(); ++it) + { + target.insert(it->first); + } + } + + + bool JobsRegistry::GetJobInfo(JobInfo& target, + const std::string& id) + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::const_iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + return false; + } + else + { + const JobHandler& handler = *found->second; + target = JobInfo(handler.GetId(), + handler.GetPriority(), + handler.GetState(), + handler.GetLastStatus(), + handler.GetCreationTime(), + handler.GetLastStateChangeTime(), + handler.GetRuntime()); + return true; + } + } + + + void JobsRegistry::SubmitInternal(std::string& id, + JobHandler* handlerRaw, + bool keepLastChangeTime) + { + if (handlerRaw == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + std::auto_ptr<JobHandler> handler(handlerRaw); + + boost::posix_time::ptime lastChangeTime = handler->GetLastStateChangeTime(); + + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + id = handler->GetId(); + int priority = handler->GetPriority(); + + switch (handler->GetState()) + { + case JobState_Pending: + case JobState_Retry: + case JobState_Running: + handler->SetState(JobState_Pending); + pendingJobs_.push(handler.get()); + pendingJobAvailable_.notify_one(); + break; + + case JobState_Success: + SetCompletedJob(*handler, true); + break; + + case JobState_Failure: + SetCompletedJob(*handler, false); + break; + + case JobState_Paused: + break; + + default: + LOG(ERROR) << "A job should not be loaded from state: " + << EnumerationToString(handler->GetState()); + throw OrthancException(ErrorCode_InternalError); + } + + if (keepLastChangeTime) + { + handler->SetLastStateChangeTime(lastChangeTime); + } + + jobsIndex_.insert(std::make_pair(id, handler.release())); + + LOG(INFO) << "New job submitted with priority " << priority << ": " << id; + + if (observer_ != NULL) + { + observer_->SignalJobSubmitted(id); + } + + CheckInvariants(); + } + } + + + void JobsRegistry::Submit(std::string& id, + IJob* job, // Takes ownership + int priority) + { + SubmitInternal(id, new JobHandler(job, priority), false); + } + + + void JobsRegistry::Submit(IJob* job, // Takes ownership + int priority) + { + std::string id; + SubmitInternal(id, new JobHandler(job, priority), false); + } + + + bool JobsRegistry::SubmitAndWait(IJob* job, // Takes ownership + int priority) + { + std::string id; + Submit(id, job, priority); + + JobState state; + + { + boost::mutex::scoped_lock lock(mutex_); + + while (GetStateInternal(state, id) && + state != JobState_Success && + state != JobState_Failure) + { + someJobComplete_.wait(lock); + } + } + + return (state == JobState_Success); + } + + + bool JobsRegistry::SetPriority(const std::string& id, + int priority) + { + LOG(INFO) << "Changing priority to " << priority << " for job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else + { + found->second->SetPriority(priority); + + if (found->second->GetState() == JobState_Pending) + { + // If the job is pending, we need to reconstruct the + // priority queue, as the heap condition has changed + + PendingJobs copy; + std::swap(copy, pendingJobs_); + + assert(pendingJobs_.empty()); + while (!copy.empty()) + { + pendingJobs_.push(copy.top()); + copy.pop(); + } + } + + CheckInvariants(); + return true; + } + } + + + void JobsRegistry::RemovePendingJob(const std::string& id) + { + // If the job is pending, we need to reconstruct the priority + // queue to remove it + PendingJobs copy; + std::swap(copy, pendingJobs_); + + assert(pendingJobs_.empty()); + while (!copy.empty()) + { + if (copy.top()->GetId() != id) + { + pendingJobs_.push(copy.top()); + } + + copy.pop(); + } + } + + + void JobsRegistry::RemoveRetryJob(JobHandler* handler) + { + RetryJobs::iterator item = retryJobs_.find(handler); + assert(item != retryJobs_.end()); + retryJobs_.erase(item); + } + + + bool JobsRegistry::Pause(const std::string& id) + { + LOG(INFO) << "Pausing job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else + { + switch (found->second->GetState()) + { + case JobState_Pending: + RemovePendingJob(id); + found->second->SetState(JobState_Paused); + break; + + case JobState_Retry: + RemoveRetryJob(found->second); + found->second->SetState(JobState_Paused); + break; + + case JobState_Paused: + case JobState_Success: + case JobState_Failure: + // Nothing to be done + break; + + case JobState_Running: + found->second->SchedulePause(); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + CheckInvariants(); + return true; + } + } + + + bool JobsRegistry::Cancel(const std::string& id) + { + LOG(INFO) << "Canceling job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else + { + switch (found->second->GetState()) + { + case JobState_Pending: + RemovePendingJob(id); + SetCompletedJob(*found->second, false); + found->second->SetLastErrorCode(ErrorCode_CanceledJob); + break; + + case JobState_Retry: + RemoveRetryJob(found->second); + SetCompletedJob(*found->second, false); + found->second->SetLastErrorCode(ErrorCode_CanceledJob); + break; + + case JobState_Paused: + SetCompletedJob(*found->second, false); + found->second->SetLastErrorCode(ErrorCode_CanceledJob); + break; + + case JobState_Success: + case JobState_Failure: + // Nothing to be done + break; + + case JobState_Running: + found->second->ScheduleCancel(); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + CheckInvariants(); + return true; + } + } + + + bool JobsRegistry::Resume(const std::string& id) + { + LOG(INFO) << "Resuming job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else if (found->second->GetState() != JobState_Paused) + { + LOG(WARNING) << "Cannot resume a job that is not paused: " << id; + return false; + } + else + { + found->second->SetState(JobState_Pending); + pendingJobs_.push(found->second); + pendingJobAvailable_.notify_one(); + CheckInvariants(); + return true; + } + } + + + bool JobsRegistry::Resubmit(const std::string& id) + { + LOG(INFO) << "Resubmitting failed job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else if (found->second->GetState() != JobState_Failure) + { + LOG(WARNING) << "Cannot resubmit a job that has not failed: " << id; + return false; + } + else + { + found->second->GetJob().SignalResubmit(); + + bool ok = false; + for (CompletedJobs::iterator it = completedJobs_.begin(); + it != completedJobs_.end(); ++it) + { + if (*it == found->second) + { + ok = true; + completedJobs_.erase(it); + break; + } + } + + assert(ok); + + found->second->SetState(JobState_Pending); + pendingJobs_.push(found->second); + pendingJobAvailable_.notify_one(); + + CheckInvariants(); + return true; + } + } + + + void JobsRegistry::ScheduleRetries() + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + RetryJobs copy; + std::swap(copy, retryJobs_); + + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + + assert(retryJobs_.empty()); + for (RetryJobs::iterator it = copy.begin(); it != copy.end(); ++it) + { + if ((*it)->IsRetryReady(now)) + { + LOG(INFO) << "Retrying job: " << (*it)->GetId(); + (*it)->SetState(JobState_Pending); + pendingJobs_.push(*it); + pendingJobAvailable_.notify_one(); + } + else + { + retryJobs_.insert(*it); + } + } + + CheckInvariants(); + } + + + bool JobsRegistry::GetState(JobState& state, + const std::string& id) + { + boost::mutex::scoped_lock lock(mutex_); + return GetStateInternal(state, id); + } + + + void JobsRegistry::SetObserver(JobsRegistry::IObserver& observer) + { + boost::mutex::scoped_lock lock(mutex_); + observer_ = &observer; + } + + + void JobsRegistry::ResetObserver() + { + boost::mutex::scoped_lock lock(mutex_); + observer_ = NULL; + } + + + JobsRegistry::RunningJob::RunningJob(JobsRegistry& registry, + unsigned int timeout) : + registry_(registry), + handler_(NULL), + targetState_(JobState_Failure), + targetRetryTimeout_(0), + canceled_(false) + { + { + boost::mutex::scoped_lock lock(registry_.mutex_); + + while (registry_.pendingJobs_.empty()) + { + if (timeout == 0) + { + registry_.pendingJobAvailable_.wait(lock); + } + else + { + bool success = registry_.pendingJobAvailable_.timed_wait + (lock, boost::posix_time::milliseconds(timeout)); + if (!success) + { + // No pending job + return; + } + } + } + + handler_ = registry_.pendingJobs_.top(); + registry_.pendingJobs_.pop(); + + assert(handler_->GetState() == JobState_Pending); + handler_->SetState(JobState_Running); + handler_->SetLastErrorCode(ErrorCode_Success); + + job_ = &handler_->GetJob(); + id_ = handler_->GetId(); + priority_ = handler_->GetPriority(); + } + } + + + JobsRegistry::RunningJob::~RunningJob() + { + if (IsValid()) + { + boost::mutex::scoped_lock lock(registry_.mutex_); + + switch (targetState_) + { + case JobState_Failure: + registry_.MarkRunningAsCompleted(*handler_, false); + + if (canceled_) + { + handler_->SetLastErrorCode(ErrorCode_CanceledJob); + } + + break; + + case JobState_Success: + registry_.MarkRunningAsCompleted(*handler_, true); + break; + + case JobState_Paused: + registry_.MarkRunningAsPaused(*handler_); + break; + + case JobState_Retry: + registry_.MarkRunningAsRetry(*handler_, targetRetryTimeout_); + break; + + default: + assert(0); + } + } + } + + + bool JobsRegistry::RunningJob::IsValid() const + { + return (handler_ != NULL && + job_ != NULL); + } + + + const std::string& JobsRegistry::RunningJob::GetId() const + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return id_; + } + } + + + int JobsRegistry::RunningJob::GetPriority() const + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return priority_; + } + } + + + IJob& JobsRegistry::RunningJob::GetJob() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return *job_; + } + } + + + bool JobsRegistry::RunningJob::IsPauseScheduled() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + boost::mutex::scoped_lock lock(registry_.mutex_); + registry_.CheckInvariants(); + assert(handler_->GetState() == JobState_Running); + + return handler_->IsPauseScheduled(); + } + } + + + bool JobsRegistry::RunningJob::IsCancelScheduled() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + boost::mutex::scoped_lock lock(registry_.mutex_); + registry_.CheckInvariants(); + assert(handler_->GetState() == JobState_Running); + + return handler_->IsCancelScheduled(); + } + } + + + void JobsRegistry::RunningJob::MarkSuccess() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Success; + } + } + + + void JobsRegistry::RunningJob::MarkFailure() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Failure; + } + } + + + void JobsRegistry::RunningJob::MarkCanceled() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Failure; + canceled_ = true; + } + } + + + void JobsRegistry::RunningJob::MarkPause() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Paused; + } + } + + + void JobsRegistry::RunningJob::MarkRetry(unsigned int timeout) + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Retry; + targetRetryTimeout_ = timeout; + } + } + + + void JobsRegistry::RunningJob::UpdateStatus(ErrorCode code) + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + JobStatus status(code, *job_); + + boost::mutex::scoped_lock lock(registry_.mutex_); + registry_.CheckInvariants(); + assert(handler_->GetState() == JobState_Running); + + handler_->SetLastStatus(status); + } + } + + + + void JobsRegistry::Serialize(Json::Value& target) + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + target = Json::objectValue; + target[TYPE] = JOBS_REGISTRY; + target[MAX_COMPLETED_JOBS] = static_cast<unsigned int>(maxCompletedJobs_); + target[JOBS] = Json::objectValue; + + for (JobsIndex::const_iterator it = jobsIndex_.begin(); + it != jobsIndex_.end(); ++it) + { + Json::Value v; + if (it->second->Serialize(v)) + { + target[JOBS][it->first] = v; + } + } + } + + + JobsRegistry::JobsRegistry(IJobUnserializer& unserializer, + const Json::Value& s) : + observer_(NULL) + { + if (SerializationToolbox::ReadString(s, TYPE) != JOBS_REGISTRY || + !s.isMember(JOBS) || + s[JOBS].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + maxCompletedJobs_ = SerializationToolbox::ReadUnsignedInteger(s, MAX_COMPLETED_JOBS); + + Json::Value::Members members = s[JOBS].getMemberNames(); + + for (Json::Value::Members::const_iterator it = members.begin(); + it != members.end(); ++it) + { + std::auto_ptr<JobHandler> job(new JobHandler(unserializer, s[JOBS][*it], *it)); + + std::string id; + SubmitInternal(id, job.release(), true); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsRegistry.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,234 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if ORTHANC_SANDBOXED == 1 +# error The job engine cannot be used in sandboxed environments +#endif + +#include "JobInfo.h" +#include "IJobUnserializer.h" + +#include <list> +#include <set> +#include <queue> +#include <boost/thread/mutex.hpp> +#include <boost/thread/condition_variable.hpp> + +namespace Orthanc +{ + // This class handles the state machine of the jobs engine + class JobsRegistry : public boost::noncopyable + { + public: + class IObserver : public boost::noncopyable + { + public: + virtual ~IObserver() + { + } + + virtual void SignalJobSubmitted(const std::string& jobId) = 0; + + virtual void SignalJobSuccess(const std::string& jobId) = 0; + + virtual void SignalJobFailure(const std::string& jobId) = 0; + }; + + private: + class JobHandler; + + struct PriorityComparator + { + bool operator() (JobHandler*& a, + JobHandler*& b) const; + }; + + typedef std::map<std::string, JobHandler*> JobsIndex; + typedef std::list<JobHandler*> CompletedJobs; + typedef std::set<JobHandler*> RetryJobs; + typedef std::priority_queue<JobHandler*, + std::vector<JobHandler*>, // Could be a "std::deque" + PriorityComparator> PendingJobs; + + boost::mutex mutex_; + JobsIndex jobsIndex_; + PendingJobs pendingJobs_; + CompletedJobs completedJobs_; + RetryJobs retryJobs_; + + boost::condition_variable pendingJobAvailable_; + boost::condition_variable someJobComplete_; + size_t maxCompletedJobs_; + + IObserver* observer_; + + +#ifndef NDEBUG + bool IsPendingJob(const JobHandler& job) const; + + bool IsCompletedJob(JobHandler& job) const; + + bool IsRetryJob(JobHandler& job) const; +#endif + + void CheckInvariants() const; + + void ForgetOldCompletedJobs(); + + void SetCompletedJob(JobHandler& job, + bool success); + + void MarkRunningAsCompleted(JobHandler& job, + bool success); + + void MarkRunningAsRetry(JobHandler& job, + unsigned int timeout); + + void MarkRunningAsPaused(JobHandler& job); + + bool GetStateInternal(JobState& state, + const std::string& id); + + void RemovePendingJob(const std::string& id); + + void RemoveRetryJob(JobHandler* handler); + + void SubmitInternal(std::string& id, + JobHandler* handler, + bool keepLastChangeTime); + + public: + JobsRegistry() : + maxCompletedJobs_(10), + observer_(NULL) + { + } + + JobsRegistry(IJobUnserializer& unserializer, + const Json::Value& s); + + ~JobsRegistry(); + + void SetMaxCompletedJobs(size_t i); + + void ListJobs(std::set<std::string>& target); + + bool GetJobInfo(JobInfo& target, + const std::string& id); + + void Serialize(Json::Value& target); + + void Submit(std::string& id, + IJob* job, // Takes ownership + int priority); + + void Submit(IJob* job, // Takes ownership + int priority); + + bool SubmitAndWait(IJob* job, // Takes ownership + int priority); + + bool SetPriority(const std::string& id, + int priority); + + bool Pause(const std::string& id); + + bool Resume(const std::string& id); + + bool Resubmit(const std::string& id); + + bool Cancel(const std::string& id); + + void ScheduleRetries(); + + bool GetState(JobState& state, + const std::string& id); + + void SetObserver(IObserver& observer); + + void ResetObserver(); + + class RunningJob : public boost::noncopyable + { + private: + JobsRegistry& registry_; + JobHandler* handler_; // Can only be accessed if the + // registry mutex is locked! + IJob* job_; // Will by design be in mutual exclusion, + // because only one RunningJob can be + // executed at a time on a JobHandler + + std::string id_; + int priority_; + JobState targetState_; + unsigned int targetRetryTimeout_; + bool canceled_; + + public: + RunningJob(JobsRegistry& registry, + unsigned int timeout); + + ~RunningJob(); + + bool IsValid() const; + + const std::string& GetId() const; + + int GetPriority() const; + + IJob& GetJob(); + + bool IsPauseScheduled(); + + bool IsCancelScheduled(); + + void MarkSuccess(); + + void MarkFailure(); + + void MarkPause(); + + void MarkCanceled(); + + void MarkRetry(unsigned int timeout); + + void UpdateStatus(ErrorCode code); + }; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/IJobOperation.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "JobOperationValues.h" +#include "../../DicomNetworking/IDicomConnectionManager.h" + +namespace Orthanc +{ + class IJobOperation : public boost::noncopyable + { + public: + virtual ~IJobOperation() + { + } + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& dicomConnection) = 0; + + virtual void Serialize(Json::Value& result) const = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValue.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,74 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <json/value.h> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class JobOperationValue : public boost::noncopyable + { + public: + enum Type + { + Type_DicomInstance, + Type_Null, + Type_String + }; + + private: + Type type_; + + protected: + JobOperationValue(Type type) : + type_(type) + { + } + + public: + virtual ~JobOperationValue() + { + } + + Type GetType() const + { + return type_; + } + + virtual JobOperationValue* Clone() const = 0; + + virtual void Serialize(Json::Value& target) const = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValues.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,143 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../PrecompiledHeaders.h" +#include "JobOperationValues.h" + +#include "../IJobUnserializer.h" +#include "../../OrthancException.h" + +#include <cassert> +#include <memory> + +namespace Orthanc +{ + void JobOperationValues::Append(JobOperationValues& target, + bool clear) + { + target.Reserve(target.GetSize() + GetSize()); + + for (size_t i = 0; i < values_.size(); i++) + { + if (clear) + { + target.Append(values_[i]); + values_[i] = NULL; + } + else + { + target.Append(GetValue(i).Clone()); + } + } + + if (clear) + { + Clear(); + } + } + + + void JobOperationValues::Clear() + { + for (size_t i = 0; i < values_.size(); i++) + { + if (values_[i] != NULL) + { + delete values_[i]; + } + } + + values_.clear(); + } + + + void JobOperationValues::Append(JobOperationValue* value) // Takes ownership + { + if (value == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + values_.push_back(value); + } + } + + + JobOperationValue& JobOperationValues::GetValue(size_t index) const + { + if (index >= values_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + assert(values_[index] != NULL); + return *values_[index]; + } + } + + + void JobOperationValues::Serialize(Json::Value& target) const + { + target = Json::arrayValue; + + for (size_t i = 0; i < values_.size(); i++) + { + Json::Value tmp; + values_[i]->Serialize(tmp); + target.append(tmp); + } + } + + + JobOperationValues* JobOperationValues::Unserialize(IJobUnserializer& unserializer, + const Json::Value& source) + { + if (source.type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + std::auto_ptr<JobOperationValues> result(new JobOperationValues); + + result->Reserve(source.size()); + + for (Json::Value::ArrayIndex i = 0; i < source.size(); i++) + { + result->Append(unserializer.UnserializeValue(source[i])); + } + + return result.release(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValues.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,89 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "JobOperationValue.h" + +#include <vector> + +namespace Orthanc +{ + class IJobUnserializer; + + class JobOperationValues : public boost::noncopyable + { + private: + std::vector<JobOperationValue*> values_; + + void Append(JobOperationValues& target, + bool clear); + + public: + ~JobOperationValues() + { + Clear(); + } + + void Move(JobOperationValues& target) + { + return Append(target, true); + } + + void Copy(JobOperationValues& target) + { + return Append(target, false); + } + + void Clear(); + + void Reserve(size_t count) + { + values_.reserve(count); + } + + void Append(JobOperationValue* value); // Takes ownership + + size_t GetSize() const + { + return values_.size(); + } + + JobOperationValue& GetValue(size_t index) const; + + void Serialize(Json::Value& target) const; + + static JobOperationValues* Unserialize(IJobUnserializer& unserializer, + const Json::Value& source); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/LogJobOperation.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,64 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../PrecompiledHeaders.h" +#include "LogJobOperation.h" + +#include "../../Logging.h" +#include "StringOperationValue.h" + +namespace Orthanc +{ + void LogJobOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + switch (input.GetType()) + { + case JobOperationValue::Type_String: + LOG(INFO) << "Job value: " + << dynamic_cast<const StringOperationValue&>(input).GetContent(); + break; + + case JobOperationValue::Type_Null: + LOG(INFO) << "Job value: (null)"; + break; + + default: + LOG(INFO) << "Job value: (unsupport)"; + break; + } + + outputs.Append(input.Clone()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/LogJobOperation.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IJobOperation.h" + +namespace Orthanc +{ + class LogJobOperation : public IJobOperation + { + public: + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager); + + virtual void Serialize(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = "Log"; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/NullOperationValue.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,59 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "JobOperationValue.h" + +namespace Orthanc +{ + class NullOperationValue : public JobOperationValue + { + public: + NullOperationValue() : + JobOperationValue(Type_Null) + { + } + + virtual JobOperationValue* Clone() const + { + return new NullOperationValue; + } + + virtual void Serialize(Json::Value& target) const + { + target = Json::objectValue; + target["Type"] = "Null"; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,495 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../PrecompiledHeaders.h" +#include "SequenceOfOperationsJob.h" + +#include "../../Logging.h" +#include "../../OrthancException.h" +#include "../../SerializationToolbox.h" +#include "../IJobUnserializer.h" + +namespace Orthanc +{ + static const char* CURRENT = "Current"; + static const char* DESCRIPTION = "Description"; + static const char* DICOM_TIMEOUT = "DicomTimeout"; + static const char* NEXT_OPERATIONS = "Next"; + static const char* OPERATION = "Operation"; + static const char* OPERATIONS = "Operations"; + static const char* ORIGINAL_INPUTS = "OriginalInputs"; + static const char* TRAILING_TIMEOUT = "TrailingTimeout"; + static const char* TYPE = "Type"; + static const char* WORK_INPUTS = "WorkInputs"; + + + class SequenceOfOperationsJob::Operation : public boost::noncopyable + { + private: + size_t index_; + std::auto_ptr<IJobOperation> operation_; + std::auto_ptr<JobOperationValues> originalInputs_; + std::auto_ptr<JobOperationValues> workInputs_; + std::list<Operation*> nextOperations_; + size_t currentInput_; + + public: + Operation(size_t index, + IJobOperation* operation) : + index_(index), + operation_(operation), + originalInputs_(new JobOperationValues), + workInputs_(new JobOperationValues), + currentInput_(0) + { + if (operation == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + } + + void AddOriginalInput(const JobOperationValue& value) + { + if (currentInput_ != 0) + { + // Cannot add input after processing has started + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + originalInputs_->Append(value.Clone()); + } + } + + const JobOperationValues& GetOriginalInputs() const + { + return *originalInputs_; + } + + void Reset() + { + workInputs_->Clear(); + currentInput_ = 0; + } + + void AddNextOperation(Operation& other, + bool unserializing) + { + if (other.index_ <= index_) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (!unserializing && + currentInput_ != 0) + { + // Cannot add input after processing has started + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + nextOperations_.push_back(&other); + } + } + + bool IsDone() const + { + return currentInput_ >= originalInputs_->GetSize() + workInputs_->GetSize(); + } + + void Step(IDicomConnectionManager& connectionManager) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + const JobOperationValue* input; + + if (currentInput_ < originalInputs_->GetSize()) + { + input = &originalInputs_->GetValue(currentInput_); + } + else + { + input = &workInputs_->GetValue(currentInput_ - originalInputs_->GetSize()); + } + + JobOperationValues outputs; + operation_->Apply(outputs, *input, connectionManager); + + if (!nextOperations_.empty()) + { + std::list<Operation*>::iterator first = nextOperations_.begin(); + outputs.Move(*(*first)->workInputs_); + + std::list<Operation*>::iterator current = first; + ++current; + + while (current != nextOperations_.end()) + { + (*first)->workInputs_->Copy(*(*current)->workInputs_); + ++current; + } + } + + currentInput_ += 1; + } + + void Serialize(Json::Value& target) const + { + target = Json::objectValue; + target[CURRENT] = static_cast<unsigned int>(currentInput_); + operation_->Serialize(target[OPERATION]); + originalInputs_->Serialize(target[ORIGINAL_INPUTS]); + workInputs_->Serialize(target[WORK_INPUTS]); + + Json::Value tmp = Json::arrayValue; + for (std::list<Operation*>::const_iterator it = nextOperations_.begin(); + it != nextOperations_.end(); ++it) + { + tmp.append(static_cast<int>((*it)->index_)); + } + + target[NEXT_OPERATIONS] = tmp; + } + + Operation(IJobUnserializer& unserializer, + Json::Value::ArrayIndex index, + const Json::Value& serialized) : + index_(index) + { + if (serialized.type() != Json::objectValue || + !serialized.isMember(OPERATION) || + !serialized.isMember(ORIGINAL_INPUTS) || + !serialized.isMember(WORK_INPUTS)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + currentInput_ = SerializationToolbox::ReadUnsignedInteger(serialized, CURRENT); + operation_.reset(unserializer.UnserializeOperation(serialized[OPERATION])); + originalInputs_.reset(JobOperationValues::Unserialize + (unserializer, serialized[ORIGINAL_INPUTS])); + workInputs_.reset(JobOperationValues::Unserialize + (unserializer, serialized[WORK_INPUTS])); + } + }; + + + SequenceOfOperationsJob::SequenceOfOperationsJob() : + done_(false), + current_(0), + trailingTimeout_(boost::posix_time::milliseconds(1000)) + { + } + + + SequenceOfOperationsJob::~SequenceOfOperationsJob() + { + for (size_t i = 0; i < operations_.size(); i++) + { + if (operations_[i] != NULL) + { + delete operations_[i]; + } + } + } + + + void SequenceOfOperationsJob::SetDescription(const std::string& description) + { + boost::mutex::scoped_lock lock(mutex_); + description_ = description; + } + + + void SequenceOfOperationsJob::GetDescription(std::string& description) + { + boost::mutex::scoped_lock lock(mutex_); + description = description_; + } + + + void SequenceOfOperationsJob::Register(IObserver& observer) + { + boost::mutex::scoped_lock lock(mutex_); + observers_.push_back(&observer); + } + + + void SequenceOfOperationsJob::Lock::SetTrailingOperationTimeout(unsigned int timeout) + { + that_.trailingTimeout_ = boost::posix_time::milliseconds(timeout); + } + + + void SequenceOfOperationsJob::Lock::SetDicomAssociationTimeout(unsigned int timeout) + { + that_.connectionManager_.SetTimeout(timeout); + } + + + size_t SequenceOfOperationsJob::Lock::AddOperation(IJobOperation* operation) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + size_t index = that_.operations_.size(); + + that_.operations_.push_back(new Operation(index, operation)); + that_.operationAdded_.notify_one(); + + return index; + } + + + void SequenceOfOperationsJob::Lock::AddInput(size_t index, + const JobOperationValue& value) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (index >= that_.operations_.size() || + index < that_.current_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + that_.operations_[index]->AddOriginalInput(value); + } + } + + + void SequenceOfOperationsJob::Lock::Connect(size_t input, + size_t output) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (input >= output || + input >= that_.operations_.size() || + output >= that_.operations_.size() || + input < that_.current_ || + output < that_.current_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + Operation& a = *that_.operations_[input]; + Operation& b = *that_.operations_[output]; + a.AddNextOperation(b, false /* not unserializing */); + } + } + + + JobStepResult SequenceOfOperationsJob::ExecuteStep() + { + boost::mutex::scoped_lock lock(mutex_); + + if (current_ == operations_.size()) + { + LOG(INFO) << "Executing the trailing timeout in the sequence of operations"; + operationAdded_.timed_wait(lock, trailingTimeout_); + + if (current_ == operations_.size()) + { + // No operation was added during the trailing timeout: The + // job is over + LOG(INFO) << "The sequence of operations is over"; + done_ = true; + + for (std::list<IObserver*>::iterator it = observers_.begin(); + it != observers_.end(); ++it) + { + (*it)->SignalDone(*this); + } + + connectionManager_.Close(); + return JobStepResult::Success(); + } + else + { + LOG(INFO) << "New operation were added to the sequence of operations"; + } + } + + assert(current_ < operations_.size()); + + while (current_ < operations_.size() && + operations_[current_]->IsDone()) + { + current_++; + } + + if (current_ < operations_.size()) + { + operations_[current_]->Step(connectionManager_); + } + + connectionManager_.CheckTimeout(); + + return JobStepResult::Continue(); + } + + + void SequenceOfOperationsJob::SignalResubmit() + { + boost::mutex::scoped_lock lock(mutex_); + + current_ = 0; + done_ = false; + + for (size_t i = 0; i < operations_.size(); i++) + { + operations_[i]->Reset(); + } + } + + + void SequenceOfOperationsJob::ReleaseResources() + { + boost::mutex::scoped_lock lock(mutex_); + connectionManager_.Close(); + } + + + float SequenceOfOperationsJob::GetProgress() + { + boost::mutex::scoped_lock lock(mutex_); + + return (static_cast<float>(current_) / + static_cast<float>(operations_.size() + 1)); + } + + + void SequenceOfOperationsJob::GetPublicContent(Json::Value& value) + { + boost::mutex::scoped_lock lock(mutex_); + + value["CountOperations"] = static_cast<unsigned int>(operations_.size()); + value["Description"] = description_; + } + + + bool SequenceOfOperationsJob::Serialize(Json::Value& value) + { + boost::mutex::scoped_lock lock(mutex_); + + value = Json::objectValue; + + std::string jobType; + GetJobType(jobType); + value[TYPE] = jobType; + + value[DESCRIPTION] = description_; + value[TRAILING_TIMEOUT] = static_cast<unsigned int>(trailingTimeout_.total_milliseconds()); + value[DICOM_TIMEOUT] = connectionManager_.GetTimeout(); + value[CURRENT] = static_cast<unsigned int>(current_); + + Json::Value tmp = Json::arrayValue; + for (size_t i = 0; i < operations_.size(); i++) + { + Json::Value operation = Json::objectValue; + operations_[i]->Serialize(operation); + tmp.append(operation); + } + + value[OPERATIONS] = tmp; + + return true; + } + + + SequenceOfOperationsJob::SequenceOfOperationsJob(IJobUnserializer& unserializer, + const Json::Value& serialized) : + done_(false) + { + std::string jobType; + GetJobType(jobType); + + if (SerializationToolbox::ReadString(serialized, TYPE) != jobType || + !serialized.isMember(OPERATIONS) || + serialized[OPERATIONS].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + description_ = SerializationToolbox::ReadString(serialized, DESCRIPTION); + trailingTimeout_ = boost::posix_time::milliseconds + (SerializationToolbox::ReadUnsignedInteger(serialized, TRAILING_TIMEOUT)); + connectionManager_.SetTimeout + (SerializationToolbox::ReadUnsignedInteger(serialized, DICOM_TIMEOUT)); + current_ = SerializationToolbox::ReadUnsignedInteger(serialized, CURRENT); + + const Json::Value& ops = serialized[OPERATIONS]; + + // Unserialize the individual operations + operations_.reserve(ops.size()); + for (Json::Value::ArrayIndex i = 0; i < ops.size(); i++) + { + operations_.push_back(new Operation(unserializer, i, ops[i])); + } + + // Connect the next operations + for (Json::Value::ArrayIndex i = 0; i < ops.size(); i++) + { + if (!ops[i].isMember(NEXT_OPERATIONS) || + ops[i][NEXT_OPERATIONS].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const Json::Value& next = ops[i][NEXT_OPERATIONS]; + for (Json::Value::ArrayIndex j = 0; j < next.size(); j++) + { + if (next[j].type() != Json::intValue || + next[j].asInt() < 0 || + next[j].asUInt() >= operations_.size()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + operations_[i]->AddNextOperation(*operations_[next[j].asUInt()], true); + } + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,153 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../IJob.h" +#include "IJobOperation.h" + +#include "../../DicomNetworking/TimeoutDicomConnectionManager.h" + +#include <boost/thread/mutex.hpp> +#include <boost/thread/condition_variable.hpp> + +#include <list> + +namespace Orthanc +{ + class SequenceOfOperationsJob : public IJob + { + public: + class IObserver : public boost::noncopyable + { + public: + virtual ~IObserver() + { + } + + virtual void SignalDone(const SequenceOfOperationsJob& job) = 0; + }; + + private: + class Operation; + + std::string description_; + bool done_; + boost::mutex mutex_; + std::vector<Operation*> operations_; + size_t current_; + boost::condition_variable operationAdded_; + boost::posix_time::time_duration trailingTimeout_; + std::list<IObserver*> observers_; + TimeoutDicomConnectionManager connectionManager_; + + public: + SequenceOfOperationsJob(); + + SequenceOfOperationsJob(IJobUnserializer& unserializer, + const Json::Value& serialized); + + virtual ~SequenceOfOperationsJob(); + + void SetDescription(const std::string& description); + + void GetDescription(std::string& description); + + void Register(IObserver& observer); + + // This lock allows adding new operations to the end of the job, + // from another thread than the worker thread, after the job has + // been submitted for processing + class Lock : public boost::noncopyable + { + private: + SequenceOfOperationsJob& that_; + boost::mutex::scoped_lock lock_; + + public: + Lock(SequenceOfOperationsJob& that) : + that_(that), + lock_(that.mutex_) + { + } + + bool IsDone() const + { + return that_.done_; + } + + void SetTrailingOperationTimeout(unsigned int timeout); + + void SetDicomAssociationTimeout(unsigned int timeout); + + size_t AddOperation(IJobOperation* operation); + + size_t GetOperationsCount() const + { + return that_.operations_.size(); + } + + void AddInput(size_t index, + const JobOperationValue& value); + + void Connect(size_t input, + size_t output); + }; + + virtual void Start() + { + } + + virtual JobStepResult ExecuteStep(); + + virtual void SignalResubmit(); + + virtual void ReleaseResources(); + + virtual float GetProgress(); + + virtual void GetJobType(std::string& target) + { + target = "SequenceOfOperations"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& value); + + void AwakeTrailingSleep() + { + operationAdded_.notify_one(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/StringOperationValue.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,71 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "JobOperationValue.h" + +#include <string> + +namespace Orthanc +{ + class StringOperationValue : public JobOperationValue + { + private: + std::string content_; + + public: + StringOperationValue(const std::string& content) : + JobOperationValue(JobOperationValue::Type_String), + content_(content) + { + } + + virtual JobOperationValue* Clone() const + { + return new StringOperationValue(content_); + } + + const std::string& GetContent() const + { + return content_; + } + + virtual void Serialize(Json::Value& target) const + { + target = Json::objectValue; + target["Type"] = "String"; + target["Content"] = content_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/SetOfInstancesJob.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,236 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "SetOfInstancesJob.h" + +#include "../OrthancException.h" +#include "../SerializationToolbox.h" + +namespace Orthanc +{ + SetOfInstancesJob::SetOfInstancesJob() : + started_(false), + permissive_(false), + position_(0) + { + } + + + void SetOfInstancesJob::Reserve(size_t size) + { + if (started_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + instances_.reserve(size); + } + } + + + void SetOfInstancesJob::AddInstance(const std::string& instance) + { + if (started_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + instances_.push_back(instance); + } + } + + + void SetOfInstancesJob::SetPermissive(bool permissive) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + permissive_ = permissive; + } + } + + + void SetOfInstancesJob::SignalResubmit() + { + if (started_) + { + position_ = 0; + failedInstances_.clear(); + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + float SetOfInstancesJob::GetProgress() + { + if (instances_.size() == 0) + { + return 0; + } + else + { + return (static_cast<float>(position_) / + static_cast<float>(instances_.size())); + } + } + + + const std::string& SetOfInstancesJob::GetInstance(size_t index) const + { + if (index > instances_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return instances_[index]; + } + } + + + JobStepResult SetOfInstancesJob::ExecuteStep() + { + if (!started_) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (instances_.empty() && + position_ == 0) + { + // No instance to handle, we're done + position_ = 1; + return JobStepResult::Success(); + } + + if (position_ >= instances_.size()) + { + // Already done + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + const std::string currentInstance = instances_[position_]; + + bool ok; + + try + { + ok = HandleInstance(currentInstance); + + if (!ok && !permissive_) + { + return JobStepResult::Failure(ErrorCode_InternalError); + } + } + catch (OrthancException&) + { + if (permissive_) + { + ok = false; + } + else + { + throw; + } + } + + if (!ok) + { + failedInstances_.insert(currentInstance); + } + + position_ += 1; + + if (position_ == instances_.size()) + { + // We're done + return JobStepResult::Success(); + } + else + { + return JobStepResult::Continue(); + } + } + + + void SetOfInstancesJob::GetPublicContent(Json::Value& value) + { + value["Description"] = GetDescription(); + value["InstancesCount"] = static_cast<uint32_t>(instances_.size()); + value["FailedInstancesCount"] = static_cast<uint32_t>(failedInstances_.size()); + } + + + bool SetOfInstancesJob::Serialize(Json::Value& value) + { + value = Json::objectValue; + + std::string type; + GetJobType(type); + value["Type"] = type; + + value["Permissive"] = permissive_; + value["Position"] = static_cast<unsigned int>(position_); + value["Description"] = description_; + + SerializationToolbox::WriteArrayOfStrings(value, instances_, "Instances"); + SerializationToolbox::WriteSetOfStrings(value, failedInstances_, "FailedInstances"); + + return true; + } + + + SetOfInstancesJob::SetOfInstancesJob(const Json::Value& value) : + started_(false), + permissive_(SerializationToolbox::ReadBoolean(value, "Permissive")), + position_(SerializationToolbox::ReadUnsignedInteger(value, "Position")), + description_(SerializationToolbox::ReadString(value, "Description")) + { + SerializationToolbox::ReadArrayOfStrings(instances_, value, "Instances"); + SerializationToolbox::ReadSetOfStrings(failedInstances_, value, "FailedInstances"); + + if (position_ > instances_.size()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/SetOfInstancesJob.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,123 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IJob.h" + +#include <set> + +namespace Orthanc +{ + class SetOfInstancesJob : public IJob + { + private: + bool started_; + std::vector<std::string> instances_; + bool permissive_; + size_t position_; + std::set<std::string> failedInstances_; + std::string description_; + + protected: + virtual bool HandleInstance(const std::string& instance) = 0; + + public: + SetOfInstancesJob(); + + SetOfInstancesJob(const Json::Value& s); // Unserialization + + size_t GetPosition() const + { + return position_; + } + + void SetDescription(const std::string& description) + { + description_ = description; + } + + const std::string& GetDescription() const + { + return description_; + } + + void Reserve(size_t size); + + size_t GetInstancesCount() const + { + return instances_.size(); + } + + void AddInstance(const std::string& instance); + + bool IsPermissive() const + { + return permissive_; + } + + void SetPermissive(bool permissive); + + virtual void SignalResubmit(); + + virtual void Start() + { + started_ = true; + } + + virtual float GetProgress(); + + bool IsStarted() const + { + return started_; + } + + const std::string& GetInstance(size_t index) const; + + const std::set<std::string>& GetFailedInstances() const + { + return failedInstances_; + } + + bool IsFailedInstance(const std::string& instance) const + { + return failedInstances_.find(instance) != failedInstances_.end(); + } + + virtual JobStepResult ExecuteStep(); + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& value); + }; +}
--- a/Core/Lua/LuaContext.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/Lua/LuaContext.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -287,7 +287,17 @@ if (nArgs >= 2 && !lua_isnil(state, 2)) { - that.httpClient_.SetBody(lua_tostring(state, 2)); + size_t bodySize = 0; + const char* bodyData = lua_tolstring(state, 2, &bodySize); + + if (bodySize == 0) + { + that.httpClient_.GetBody().clear(); + } + else + { + that.httpClient_.GetBody().assign(bodyData, bodySize); + } } else {
--- a/Core/MultiThreading/BagOfTasks.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../ICommand.h" - -#include <list> -#include <cstddef> - -namespace Orthanc -{ - class BagOfTasks : public boost::noncopyable - { - private: - typedef std::list<ICommand*> Tasks; - - Tasks tasks_; - - public: - ~BagOfTasks() - { - for (Tasks::iterator it = tasks_.begin(); it != tasks_.end(); ++it) - { - delete *it; - } - } - - ICommand* Pop() - { - ICommand* task = tasks_.front(); - tasks_.pop_front(); - return task; - } - - void Push(ICommand* task) // Takes ownership - { - if (task != NULL) - { - tasks_.push_back(task); - } - } - - size_t GetSize() const - { - return tasks_.size(); - } - - bool IsEmpty() const - { - return tasks_.empty(); - } - }; -}
--- a/Core/MultiThreading/BagOfTasksProcessor.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,277 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeaders.h" -#include "BagOfTasksProcessor.h" - -#include "../Logging.h" -#include "../OrthancException.h" - -#include <stdio.h> - -namespace Orthanc -{ - class BagOfTasksProcessor::Task : public IDynamicObject - { - private: - uint64_t bag_; - std::auto_ptr<ICommand> command_; - - public: - Task(uint64_t bag, - ICommand* command) : - bag_(bag), - command_(command) - { - } - - bool Execute() - { - try - { - return command_->Execute(); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Exception while processing a bag of tasks: " << e.What(); - return false; - } - catch (std::runtime_error& e) - { - LOG(ERROR) << "Runtime exception while processing a bag of tasks: " << e.what(); - return false; - } - catch (...) - { - LOG(ERROR) << "Native exception while processing a bag of tasks"; - return false; - } - } - - uint64_t GetBag() - { - return bag_; - } - }; - - - void BagOfTasksProcessor::SignalProgress(Task& task, - Bag& bag) - { - assert(bag.done_ < bag.size_); - - bag.done_ += 1; - - if (bag.done_ == bag.size_) - { - exitStatus_[task.GetBag()] = (bag.status_ == BagStatus_Running); - bagFinished_.notify_all(); - } - } - - void BagOfTasksProcessor::Worker(BagOfTasksProcessor* that) - { - while (that->continue_) - { - std::auto_ptr<IDynamicObject> obj(that->queue_.Dequeue(100)); - if (obj.get() != NULL) - { - Task& task = *dynamic_cast<Task*>(obj.get()); - - { - boost::mutex::scoped_lock lock(that->mutex_); - - Bags::iterator bag = that->bags_.find(task.GetBag()); - assert(bag != that->bags_.end()); - assert(bag->second.done_ < bag->second.size_); - - if (bag->second.status_ != BagStatus_Running) - { - // Do not execute this task, as its parent bag of tasks - // has failed or is tagged as canceled - that->SignalProgress(task, bag->second); - continue; - } - } - - bool success = task.Execute(); - - { - boost::mutex::scoped_lock lock(that->mutex_); - - Bags::iterator bag = that->bags_.find(task.GetBag()); - assert(bag != that->bags_.end()); - - if (!success) - { - bag->second.status_ = BagStatus_Failed; - } - - that->SignalProgress(task, bag->second); - } - } - } - } - - - void BagOfTasksProcessor::Cancel(int64_t bag) - { - boost::mutex::scoped_lock lock(mutex_); - - Bags::iterator it = bags_.find(bag); - if (it != bags_.end()) - { - it->second.status_ = BagStatus_Canceled; - } - } - - - bool BagOfTasksProcessor::Join(int64_t bag) - { - boost::mutex::scoped_lock lock(mutex_); - - while (continue_) - { - ExitStatus::iterator it = exitStatus_.find(bag); - if (it == exitStatus_.end()) // The bag is still running - { - bagFinished_.wait(lock); - } - else - { - bool status = it->second; - exitStatus_.erase(it); - return status; - } - } - - return false; // The processor is stopping - } - - - float BagOfTasksProcessor::GetProgress(int64_t bag) - { - boost::mutex::scoped_lock lock(mutex_); - - Bags::const_iterator it = bags_.find(bag); - if (it == bags_.end()) - { - // The bag of tasks has finished - return 1.0f; - } - else - { - return (static_cast<float>(it->second.done_) / - static_cast<float>(it->second.size_)); - } - } - - - bool BagOfTasksProcessor::Handle::Join() - { - if (hasJoined_) - { - return status_; - } - else - { - status_ = that_.Join(bag_); - hasJoined_ = true; - return status_; - } - } - - - BagOfTasksProcessor::BagOfTasksProcessor(size_t countThreads) : - countBags_(0), - continue_(true) - { - if (countThreads == 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - threads_.resize(countThreads); - - for (size_t i = 0; i < threads_.size(); i++) - { - threads_[i] = new boost::thread(Worker, this); - } - } - - - BagOfTasksProcessor::~BagOfTasksProcessor() - { - continue_ = false; - - bagFinished_.notify_all(); // Wakes up all the pending "Join()" - - for (size_t i = 0; i < threads_.size(); i++) - { - if (threads_[i]) - { - if (threads_[i]->joinable()) - { - threads_[i]->join(); - } - - delete threads_[i]; - threads_[i] = NULL; - } - } - } - - - BagOfTasksProcessor::Handle* BagOfTasksProcessor::Submit(BagOfTasks& tasks) - { - if (tasks.GetSize() == 0) - { - return new Handle(*this, 0, true); - } - - boost::mutex::scoped_lock lock(mutex_); - - uint64_t id = countBags_; - countBags_ += 1; - - Bag bag(tasks.GetSize()); - bags_[id] = bag; - - while (!tasks.IsEmpty()) - { - queue_.Enqueue(new Task(id, tasks.Pop())); - } - - return new Handle(*this, id, false); - } -}
--- a/Core/MultiThreading/BagOfTasksProcessor.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,150 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "BagOfTasks.h" -#include "SharedMessageQueue.h" - -#include <stdint.h> -#include <map> - -namespace Orthanc -{ - class BagOfTasksProcessor : public boost::noncopyable - { - private: - enum BagStatus - { - BagStatus_Running, - BagStatus_Canceled, - BagStatus_Failed - }; - - - struct Bag - { - size_t size_; - size_t done_; - BagStatus status_; - - Bag() : - size_(0), - done_(0), - status_(BagStatus_Failed) - { - } - - explicit Bag(size_t size) : - size_(size), - done_(0), - status_(BagStatus_Running) - { - } - }; - - class Task; - - - typedef std::map<uint64_t, Bag> Bags; - typedef std::map<uint64_t, bool> ExitStatus; - - SharedMessageQueue queue_; - - boost::mutex mutex_; - uint64_t countBags_; - Bags bags_; - std::vector<boost::thread*> threads_; - ExitStatus exitStatus_; - bool continue_; - - boost::condition_variable bagFinished_; - - static void Worker(BagOfTasksProcessor* that); - - void Cancel(int64_t bag); - - bool Join(int64_t bag); - - float GetProgress(int64_t bag); - - void SignalProgress(Task& task, - Bag& bag); - - public: - class Handle : public boost::noncopyable - { - friend class BagOfTasksProcessor; - - private: - BagOfTasksProcessor& that_; - uint64_t bag_; - bool hasJoined_; - bool status_; - - Handle(BagOfTasksProcessor& that, - uint64_t bag, - bool empty) : - that_(that), - bag_(bag), - hasJoined_(empty) - { - } - - public: - ~Handle() - { - Join(); - } - - void Cancel() - { - that_.Cancel(bag_); - } - - bool Join(); - - float GetProgress() - { - return that_.GetProgress(bag_); - } - }; - - - explicit BagOfTasksProcessor(size_t countThreads); - - ~BagOfTasksProcessor(); - - Handle* Submit(BagOfTasks& tasks); - }; -}
--- a/Core/MultiThreading/ILockable.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <boost/noncopyable.hpp> - -namespace Orthanc -{ - class ILockable : public boost::noncopyable - { - friend class Locker; - - protected: - virtual void Lock() = 0; - - virtual void Unlock() = 0; - - public: - virtual ~ILockable() - { - } - }; -}
--- a/Core/MultiThreading/Locker.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ILockable.h" - -namespace Orthanc -{ - class Locker : public boost::noncopyable - { - private: - ILockable& lockable_; - - public: - Locker(ILockable& lockable) : lockable_(lockable) - { - lockable_.Lock(); - } - - virtual ~Locker() - { - lockable_.Unlock(); - } - }; -}
--- a/Core/MultiThreading/Mutex.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeaders.h" -#include "Mutex.h" - -#include "../OrthancException.h" - -#if defined(_WIN32) -#include <windows.h> -#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) -#include <pthread.h> -#else -#error Support your platform here -#endif - -namespace Orthanc -{ -#if defined (_WIN32) - - struct Mutex::PImpl - { - CRITICAL_SECTION criticalSection_; - }; - - Mutex::Mutex() - { - pimpl_ = new PImpl; - ::InitializeCriticalSection(&pimpl_->criticalSection_); - } - - Mutex::~Mutex() - { - ::DeleteCriticalSection(&pimpl_->criticalSection_); - delete pimpl_; - } - - void Mutex::Lock() - { - ::EnterCriticalSection(&pimpl_->criticalSection_); - } - - void Mutex::Unlock() - { - ::LeaveCriticalSection(&pimpl_->criticalSection_); - } - - -#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) - - struct Mutex::PImpl - { - pthread_mutex_t mutex_; - }; - - Mutex::Mutex() - { - pimpl_ = new PImpl; - - if (pthread_mutex_init(&pimpl_->mutex_, NULL) != 0) - { - delete pimpl_; - throw OrthancException(ErrorCode_InternalError); - } - } - - Mutex::~Mutex() - { - pthread_mutex_destroy(&pimpl_->mutex_); - delete pimpl_; - } - - void Mutex::Lock() - { - if (pthread_mutex_lock(&pimpl_->mutex_) != 0) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - void Mutex::Unlock() - { - if (pthread_mutex_unlock(&pimpl_->mutex_) != 0) - { - throw OrthancException(ErrorCode_InternalError); - } - } - -#else -#error Support your plateform here -#endif -}
--- a/Core/MultiThreading/Mutex.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ILockable.h" - -namespace Orthanc -{ - class Mutex : public ILockable - { - private: - struct PImpl; - - PImpl *pimpl_; - - protected: - virtual void Lock(); - - virtual void Unlock(); - - public: - Mutex(); - - ~Mutex(); - }; -}
--- a/Core/MultiThreading/ReaderWriterLock.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeaders.h" -#include "ReaderWriterLock.h" - -#include <boost/thread/shared_mutex.hpp> - -namespace Orthanc -{ - namespace - { - // Anonymous namespace to avoid clashes between compilation - // modules. - - class ReaderLockable : public ILockable - { - private: - boost::shared_mutex& lock_; - - protected: - virtual void Lock() - { - lock_.lock_shared(); - } - - virtual void Unlock() - { - lock_.unlock_shared(); - } - - public: - explicit ReaderLockable(boost::shared_mutex& lock) : lock_(lock) - { - } - }; - - - class WriterLockable : public ILockable - { - private: - boost::shared_mutex& lock_; - - protected: - virtual void Lock() - { - lock_.lock(); - } - - virtual void Unlock() - { - lock_.unlock(); - } - - public: - explicit WriterLockable(boost::shared_mutex& lock) : lock_(lock) - { - } - }; - } - - struct ReaderWriterLock::PImpl - { - boost::shared_mutex lock_; - ReaderLockable reader_; - WriterLockable writer_; - - PImpl() : reader_(lock_), writer_(lock_) - { - } - }; - - - ReaderWriterLock::ReaderWriterLock() - { - pimpl_ = new PImpl; - } - - - ReaderWriterLock::~ReaderWriterLock() - { - delete pimpl_; - } - - - ILockable& ReaderWriterLock::ForReader() - { - return pimpl_->reader_; - } - - - ILockable& ReaderWriterLock::ForWriter() - { - return pimpl_->writer_; - } -}
--- a/Core/MultiThreading/ReaderWriterLock.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ILockable.h" - -#include <boost/noncopyable.hpp> - -namespace Orthanc -{ - class ReaderWriterLock : public boost::noncopyable - { - private: - struct PImpl; - - PImpl *pimpl_; - - public: - ReaderWriterLock(); - - virtual ~ReaderWriterLock(); - - ILockable& ForReader(); - - ILockable& ForWriter(); - }; -}
--- a/Core/MultiThreading/Semaphore.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeaders.h" -#include "Semaphore.h" - -#include "../OrthancException.h" - - -namespace Orthanc -{ - Semaphore::Semaphore(unsigned int count) : count_(count) - { - } - - void Semaphore::Release() - { - boost::mutex::scoped_lock lock(mutex_); - - count_++; - condition_.notify_one(); - } - - void Semaphore::Acquire() - { - boost::mutex::scoped_lock lock(mutex_); - - while (count_ == 0) - { - condition_.wait(lock); - } - - count_++; - } -}
--- a/Core/MultiThreading/Semaphore.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <boost/noncopyable.hpp> -#include <boost/thread.hpp> - -namespace Orthanc -{ - class Semaphore : public boost::noncopyable - { - private: - unsigned int count_; - boost::mutex mutex_; - boost::condition_variable condition_; - - public: - explicit Semaphore(unsigned int count); - - void Release(); - - void Acquire(); - - class Locker : public boost::noncopyable - { - private: - Semaphore& that_; - - public: - explicit Locker(Semaphore& that) : - that_(that) - { - that_.Acquire(); - } - - ~Locker() - { - that_.Release(); - } - }; - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SerializationToolbox.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,262 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeaders.h" +#include "SerializationToolbox.h" + +#include "OrthancException.h" + +namespace Orthanc +{ + namespace SerializationToolbox + { + std::string ReadString(const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return value[field.c_str()].asString(); + } + } + + + int ReadInteger(const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + (value[field.c_str()].type() != Json::intValue && + value[field.c_str()].type() != Json::uintValue)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return value[field.c_str()].asInt(); + } + } + + + unsigned int ReadUnsignedInteger(const Json::Value& value, + const std::string& field) + { + int tmp = ReadInteger(value, field); + + if (tmp < 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return static_cast<unsigned int>(tmp); + } + } + + + bool ReadBoolean(const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::booleanValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return value[field.c_str()].asBool(); + } + } + + + void ReadArrayOfStrings(std::vector<std::string>& target, + const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const Json::Value& arr = value[field.c_str()]; + + target.resize(arr.size()); + + for (Json::Value::ArrayIndex i = 0; i < arr.size(); i++) + { + if (arr[i].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + target[i] = arr[i].asString(); + } + } + } + + + void ReadListOfStrings(std::list<std::string>& target, + const Json::Value& value, + const std::string& field) + { + std::vector<std::string> tmp; + ReadArrayOfStrings(tmp, value, field); + + target.clear(); + for (size_t i = 0; i < tmp.size(); i++) + { + target.push_back(tmp[i]); + } + } + + + void ReadSetOfStrings(std::set<std::string>& target, + const Json::Value& value, + const std::string& field) + { + std::vector<std::string> tmp; + ReadArrayOfStrings(tmp, value, field); + + target.clear(); + for (size_t i = 0; i < tmp.size(); i++) + { + target.insert(tmp[i]); + } + } + + + void ReadSetOfTags(std::set<DicomTag>& target, + const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const Json::Value& arr = value[field.c_str()]; + + target.clear(); + + for (Json::Value::ArrayIndex i = 0; i < arr.size(); i++) + { + DicomTag tag(0, 0); + + if (arr[i].type() != Json::stringValue || + !DicomTag::ParseHexadecimal(tag, arr[i].asCString())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + target.insert(tag); + } + } + } + + + void WriteArrayOfStrings(Json::Value& target, + const std::vector<std::string>& values, + const std::string& field) + { + if (target.type() != Json::objectValue || + target.isMember(field.c_str())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& value = target[field]; + + value = Json::arrayValue; + for (size_t i = 0; i < values.size(); i++) + { + value.append(values[i]); + } + } + + + void WriteSetOfStrings(Json::Value& target, + const std::set<std::string>& values, + const std::string& field) + { + if (target.type() != Json::objectValue || + target.isMember(field.c_str())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& value = target[field]; + + value = Json::arrayValue; + + for (std::set<std::string>::const_iterator it = values.begin(); + it != values.end(); ++it) + { + value.append(*it); + } + } + + + void WriteSetOfTags(Json::Value& target, + const std::set<DicomTag>& tags, + const std::string& field) + { + if (target.type() != Json::objectValue || + target.isMember(field.c_str())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& value = target[field]; + + value = Json::arrayValue; + + for (std::set<DicomTag>::const_iterator it = tags.begin(); + it != tags.end(); ++it) + { + value.append(it->Format()); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SerializationToolbox.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,85 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "DicomFormat/DicomTag.h" + +#include <json/value.h> +#include <list> + +namespace Orthanc +{ + namespace SerializationToolbox + { + std::string ReadString(const Json::Value& value, + const std::string& field); + + int ReadInteger(const Json::Value& value, + const std::string& field); + + unsigned int ReadUnsignedInteger(const Json::Value& value, + const std::string& field); + + bool ReadBoolean(const Json::Value& value, + const std::string& field); + + void ReadArrayOfStrings(std::vector<std::string>& target, + const Json::Value& value, + const std::string& field); + + void ReadListOfStrings(std::list<std::string>& target, + const Json::Value& value, + const std::string& field); + + void ReadSetOfStrings(std::set<std::string>& target, + const Json::Value& value, + const std::string& field); + + void ReadSetOfTags(std::set<DicomTag>& target, + const Json::Value& value, + const std::string& field); + + void WriteArrayOfStrings(Json::Value& target, + const std::vector<std::string>& values, + const std::string& field); + + void WriteSetOfStrings(Json::Value& target, + const std::set<std::string>& values, + const std::string& field); + + void WriteSetOfTags(Json::Value& target, + const std::set<DicomTag>& tags, + const std::string& field); + } +}
--- a/Core/Toolbox.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/Toolbox.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -62,6 +62,15 @@ # include <boost/locale.hpp> #endif +#if ORTHANC_ENABLE_SSL == 1 +// For OpenSSL initialization and finalization +# include <openssl/conf.h> +# include <openssl/engine.h> +# include <openssl/err.h> +# include <openssl/evp.h> +# include <openssl/ssl.h> +#endif + #if defined(_MSC_VER) && (_MSC_VER < 1800) // Patch for the missing "_strtoll" symbol when compiling with Visual Studio < 2013 @@ -1421,7 +1430,7 @@ globalLocale_.reset(); } - + std::string Toolbox::ToUpperCaseWithAccents(const std::string& source) { if (globalLocale_.get() == NULL) @@ -1464,6 +1473,36 @@ #endif + void Toolbox::InitializeOpenSsl() + { +#if ORTHANC_ENABLE_SSL == 1 + // https://wiki.openssl.org/index.php/Library_Initialization + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); +#endif + } + + + void Toolbox::FinalizeOpenSsl() + { +#if ORTHANC_ENABLE_SSL == 1 + // Finalize OpenSSL + // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup +#ifdef FIPS_mode_set + FIPS_mode_set(0); +#endif + ENGINE_cleanup(); + CONF_modules_unload(1); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + ERR_remove_state(0); + ERR_free_strings(); +#endif + } + + std::string Toolbox::GenerateUuid() { #ifdef WIN32
--- a/Core/Toolbox.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/Toolbox.h Fri Jul 13 10:07:45 2018 +0200 @@ -233,6 +233,10 @@ std::string ToUpperCaseWithAccents(const std::string& source); #endif + void InitializeOpenSsl(); + + void FinalizeOpenSsl(); + std::string GenerateUuid(); } }
--- a/Core/WebServiceParameters.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/WebServiceParameters.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -34,8 +34,9 @@ #include "PrecompiledHeaders.h" #include "WebServiceParameters.h" -#include "../Core/Logging.h" -#include "../Core/OrthancException.h" +#include "Logging.h" +#include "OrthancException.h" +#include "SerializationToolbox.h" #if ORTHANC_SANDBOXED == 0 # include "../Core/SystemToolbox.h" @@ -129,6 +130,11 @@ SetUsername(""); SetPassword(""); } + else if (peer.size() == 2) + { + LOG(ERROR) << "The HTTP password is not provided"; + throw OrthancException(ErrorCode_BadFileFormat); + } else if (peer.size() == 3) { SetUsername(peer.get(1u, "").asString()); @@ -177,12 +183,25 @@ SetUsername(GetStringMember(peer, "Username", "")); SetPassword(GetStringMember(peer, "Password", "")); + if (!username_.empty() && + !peer.isMember("Password")) + { + LOG(ERROR) << "The HTTP password is not provided"; + throw OrthancException(ErrorCode_BadFileFormat); + } + #if ORTHANC_SANDBOXED == 0 if (peer.isMember("CertificateFile")) { SetClientCertificate(GetStringMember(peer, "CertificateFile", ""), GetStringMember(peer, "CertificateKeyFile", ""), GetStringMember(peer, "CertificateKeyPassword", "")); + + if (!peer.isMember("CertificateKeyPassword")) + { + LOG(ERROR) << "The password for the HTTPS certificate is not provided"; + throw OrthancException(ErrorCode_BadFileFormat); + } } #endif @@ -228,7 +247,8 @@ } - void WebServiceParameters::ToJson(Json::Value& value) const + void WebServiceParameters::ToJson(Json::Value& value, + bool includePasswords) const { if (advancedFormat_) { @@ -239,7 +259,11 @@ !password_.empty()) { value["Username"] = username_; - value["Password"] = password_; + + if (includePasswords) + { + value["Password"] = password_; + } } if (!certificateFile_.empty()) @@ -252,7 +276,8 @@ value["CertificateKeyFile"] = certificateKeyFile_; } - if (!certificateKeyPassword_.empty()) + if (!certificateKeyPassword_.empty() && + includePasswords) { value["CertificateKeyPassword"] = certificateKeyPassword_; } @@ -266,8 +291,48 @@ !password_.empty()) { value.append(username_); - value.append(password_); + + if (includePasswords) + { + value.append(password_); + } } } } + + + void WebServiceParameters::Serialize(Json::Value& target) const + { + target = Json::objectValue; + target["URL"] = url_; + target["Username"] = username_; + target["Password"] = password_; + target["CertificateFile"] = certificateFile_; + target["CertificateKeyFile"] = certificateKeyFile_; + target["CertificateKeyPassword"] = certificateKeyPassword_; + target["PKCS11"] = pkcs11Enabled_; + target["AdvancedFormat"] = advancedFormat_; + } + + + WebServiceParameters::WebServiceParameters(const Json::Value& serialized) : + advancedFormat_(true) + { + url_ = SerializationToolbox::ReadString(serialized, "URL"); + username_ = SerializationToolbox::ReadString(serialized, "Username"); + password_ = SerializationToolbox::ReadString(serialized, "Password"); + + std::string a, b, c; + a = SerializationToolbox::ReadString(serialized, "CertificateFile"); + b = SerializationToolbox::ReadString(serialized, "CertificateKeyFile"); + c = SerializationToolbox::ReadString(serialized, "CertificateKeyPassword"); + + if (!a.empty()) + { + SetClientCertificate(a, b, c); + } + + pkcs11Enabled_ = SerializationToolbox::ReadBoolean(serialized, "PKCS11"); + advancedFormat_ = SerializationToolbox::ReadBoolean(serialized, "AdvancedFormat"); + } }
--- a/Core/WebServiceParameters.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Core/WebServiceParameters.h Fri Jul 13 10:07:45 2018 +0200 @@ -61,6 +61,8 @@ public: WebServiceParameters(); + WebServiceParameters(const Json::Value& serialized); + const std::string& GetUrl() const { return url_; @@ -126,6 +128,9 @@ void FromJson(const Json::Value& peer); - void ToJson(Json::Value& value) const; + void ToJson(Json::Value& value, + bool includePasswords) const; + + void Serialize(Json::Value& target) const; }; }
--- a/NEWS Mon Jul 09 11:37:17 2018 +0200 +++ b/NEWS Fri Jul 13 10:07:45 2018 +0200 @@ -1,6 +1,17 @@ Pending changes in the mainline =============================== +General +------- + +* New advanced job engine +* New configuration options: + - "ConcurrentJobs": Max number of jobs that are simultanously running + - "SynchronousCMove": Whether to run DICOM C-Move operations synchronously + - "JobsHistorySize": Max number of completed jobs that are kept in memory +* New metadata automatically computed at the instance level: + "RemoteIp", "CalledAet" and "HttpUsername" + Orthanc Explorer ---------------- @@ -9,6 +20,7 @@ REST API -------- +* "/jobs/..." to manage the jobs from the REST API * New option "?short" to list DICOM tags using their hexadecimal ID in: - "/instances/.../tags?short" - "/instances/.../header?short" @@ -25,11 +37,24 @@ * "/instances/.../frame/../image-uint8 and friends now accepts a "image/pam" MIME type to retrieve images in PAM format (https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format) +* New option "?expand" to "/instances/.../metadata" + +Plugins +------- + +* New primitive in database SDK: "lookupIdentifierRange" to speed up range searches +* New function in the SDK: "OrthancPluginCheckVersionAdvanced()" Maintenance ----------- +* Configuration option "LogExportedResources" is now "false" by default +* Header "OrthancCppDatabasePlugin.h" is now part of the "orthanc-databases" project * Fix generation of DICOMDIR if PatientID is empty +* Fix issue 25 (Deadlock with Lua scripts): The event queue is now implemented for Lua +* Fix issue 94 (Instance modification should not modify FrameOfReferenceUID) +* Fix issue 77 (Lua access to REST-API is null terminated) +* Fix memory leak introduced by changeset #99116ed6f38c in Orthanc 1.3.2 * Upgraded dependencies for static and Windows builds: - boost 1.67.0 - openssl 1.0.2o
--- a/OrthancExplorer/explorer.html Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancExplorer/explorer.html Fri Jul 13 10:07:45 2018 +0200 @@ -38,12 +38,13 @@ <div data-role="header" > <h1><span class="orthanc-name"></span>Find a patient</h1> <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#find-studies" data-icon="arrow-r" data-role="button" data-direction="reverse">Studies</a> <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> - <a href="#find-studies" data-icon="arrow-r" data-role="button" data-direction="reverse">Studies</a> </div> <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> <a href="#upload" data-icon="gear" data-role="button">Upload</a> <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> </div> </div> <div data-role="content"> @@ -56,12 +57,13 @@ <div data-role="header" > <h1><span class="orthanc-name"></span>Find a study</h1> <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#find-patients" data-icon="home" data-role="button" data-direction="reverse">Patients</a> <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> - <a href="#find-patients" data-icon="home" data-role="button" data-direction="reverse">Patients</a> </div> <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> <a href="#upload" data-icon="gear" data-role="button">Upload</a> <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> </div> </div> <div data-role="content"> @@ -109,6 +111,7 @@ <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> <a href="#upload" data-icon="gear" data-role="button">Upload</a> <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> </div> </div> <div data-role="content"> @@ -169,6 +172,7 @@ <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> <a href="#upload" data-icon="gear" data-role="button">Upload</a> <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> </div> </div> <div data-role="content"> @@ -223,6 +227,7 @@ <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> <a href="#upload" data-icon="gear" data-role="button">Upload</a> <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> </div> </div> <div data-role="content"> @@ -279,6 +284,7 @@ <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> <a href="#upload" data-icon="gear" data-role="button">Upload</a> <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> </div> </div> <div data-role="content"> @@ -468,6 +474,48 @@ </div> </div> + + <div data-role="page" id="jobs" > + <div data-role="header" > + <h1><span class="orthanc-name"></span>Jobs</h1> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#find-patients" data-icon="home" data-role="button" data-direction="reverse">Patients</a> + <a href="#find-studies" data-icon="arrow-r" data-role="button" data-direction="reverse">Studies</a> + </div> + </div> + <div data-role="content"> + <ul id="all-jobs" data-role="listview" data-inset="true" data-filter="true"> + </ul> + </div> + </div> + + <div data-role="page" id="job" > + <div data-role="header" > + <h1><span class="orthanc-name"></span>Job</h1> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#find-patients" data-icon="home" data-role="button" data-direction="reverse">Patients</a> + <a href="#find-studies" data-icon="arrow-r" data-role="button" data-direction="reverse">Studies</a> + </div> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> + </div> + </div> + <div data-role="content"> + <ul data-role="listview" data-inset="true" data-filter="true" id="job-info"> + </ul> + + <fieldset class="ui-grid-b"> + <div class="ui-block-a"></div> + <div class="ui-block-b"> + <button id="job-cancel" data-theme="b">Cancel job</button> + <button id="job-resubmit" data-theme="b">Resubmit job</button> + <button id="job-pause" data-theme="b">Pause job</button> + <button id="job-resume" data-theme="b">Resume job</button> + </div> + <div class="ui-block-c"></div> + </fieldset> + </div> + </div> <div id="peer-store" style="display:none;" class="ui-body-c"> <p align="center"><b>Sending to Orthanc peer...</b></p>
--- a/OrthancExplorer/explorer.js Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancExplorer/explorer.js Fri Jul 13 10:07:45 2018 +0200 @@ -1156,3 +1156,209 @@ } }); }); + + + +function ParseJobTime(s) +{ + var t = (s.substr(0, 4) + '-' + + s.substr(4, 2) + '-' + + s.substr(6, 5) + ':' + + s.substr(11, 2) + ':' + + s.substr(13)); + var utc = new Date(t); + + // Convert from UTC to local time + return new Date(utc.getTime() - utc.getTimezoneOffset() * 60000); +} + + +function AddJobField(target, description, field) +{ + if (!(typeof field === 'undefined')) { + target.append($('<p>') + .text(description) + .append($('<strong>').text(field))); + } +} + + +function AddJobDateField(target, description, field) +{ + if (!(typeof field === 'undefined')) { + target.append($('<p>') + .text(description) + .append($('<strong>').text(ParseJobTime(field)))); + } +} + + +$('#jobs').live('pagebeforeshow', function() { + $.ajax({ + url: '../jobs?expand', + dataType: 'json', + async: false, + cache: false, + success: function(jobs) { + var target = $('#all-jobs'); + $('li', target).remove(); + + var running = $('<li>') + .attr('data-role', 'list-divider') + .text('Currently running'); + + var pending = $('<li>') + .attr('data-role', 'list-divider') + .text('Pending jobs'); + + var inactive = $('<li>') + .attr('data-role', 'list-divider') + .text('Inactive jobs'); + + target.append(running); + target.append(pending); + target.append(inactive); + + jobs.map(function(job) { + var li = $('<li>'); + var item = $('<a>'); + li.append(item); + item.attr('href', '#job?uuid=' + job.ID); + item.append($('<h1>').text(job.Type)); + item.append($('<span>').addClass('ui-li-count').text(job.State)); + AddJobField(item, 'ID: ', job.ID); + AddJobField(item, 'Local AET: ', job.Content.LocalAet); + AddJobField(item, 'Remote AET: ', job.Content.RemoteAet); + AddJobDateField(item, 'Creation time: ', job.CreationTime); + AddJobDateField(item, 'Completion time: ', job.CompletionTime); + AddJobDateField(item, 'ETA: ', job.EstimatedTimeOfArrival); + + if (job.State == 'Running' || + job.State == 'Pending' || + job.State == 'Paused') { + AddJobField(item, 'Priority: ', job.Priority); + AddJobField(item, 'Progress: ', job.Progress); + } + + if (job.State == 'Running') { + li.insertAfter(running); + } else if (job.State == 'Pending' || + job.State == 'Paused') { + li.insertAfter(pending); + } else { + li.insertAfter(inactive); + } + }); + + target.listview('refresh'); + } + }); +}); + + +$('#job').live('pagebeforeshow', function() { + if ($.mobile.pageData) { + var pageData = DeepCopy($.mobile.pageData); + + $.ajax({ + url: '../jobs/' + pageData.uuid, + dataType: 'json', + async: false, + cache: false, + success: function(job) { + var target = $('#job-info'); + $('li', target).remove(); + + target.append($('<li>') + .attr('data-role', 'list-divider') + .text('General information about the job')); + + var block = $('<li>'); + for (var i in job) { + if (i == 'CreationTime' || + i == 'CompletionTime' || + i == 'EstimatedTimeOfArrival') { + AddJobDateField(block, i + ': ', job[i]); + } else if (i != 'InternalContent' && + i != 'Content' && + i != 'Timestamp') { + AddJobField(block, i + ': ', job[i]); + } + } + + target.append(block); + + target.append($('<li>') + .attr('data-role', 'list-divider') + .text('Detailed information')); + + var block = $('<li>'); + + for (var item in job.Content) { + var value = job.Content[item]; + if (typeof value !== 'string') { + value = JSON.stringify(value); + } + + AddJobField(block, item + ': ', value); + } + + target.append(block); + + target.listview('refresh'); + + $('#job-cancel').closest('.ui-btn').hide(); + $('#job-retry').closest('.ui-btn').hide(); + $('#job-resubmit').closest('.ui-btn').hide(); + $('#job-pause').closest('.ui-btn').hide(); + $('#job-resume').closest('.ui-btn').hide(); + + if (job.State == 'Running' || + job.State == 'Pending' || + job.State == 'Retry') { + $('#job-cancel').closest('.ui-btn').show(); + $('#job-pause').closest('.ui-btn').show(); + } + else if (job.State == 'Success') { + } + else if (job.State == 'Failure') { + $('#job-resubmit').closest('.ui-btn').show(); + } + else if (job.State == 'Paused') { + $('#job-resume').closest('.ui-btn').show(); + } + } + }); + } +}); + + + +function TriggerJobAction(action) +{ + $.ajax({ + url: '../jobs/' + $.mobile.pageData.uuid + '/' + action, + type: 'POST', + async: false, + cache: false, + complete: function(s) { + window.location.reload(); + } + }); +} + +$('#job-cancel').live('click', function() { + TriggerJobAction('cancel'); +}); + +$('#job-resubmit').live('click', function() { + TriggerJobAction('resubmit'); +}); + +$('#job-pause').live('click', function() { + TriggerJobAction('pause'); +}); + +$('#job-resume').live('click', function() { + TriggerJobAction('resume'); +});
--- a/OrthancServer/DatabaseWrapper.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/DatabaseWrapper.h Fri Jul 13 10:07:45 2018 +0200 @@ -331,6 +331,17 @@ base_.LookupIdentifier(result, level, tag, type, value); } + + virtual void LookupIdentifierRange(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end) + { + base_.LookupIdentifierRange(result, level, tag, start, end); + } + + virtual void GetAllMetadata(std::map<MetadataType, std::string>& target, int64_t id);
--- a/OrthancServer/DatabaseWrapperBase.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/DatabaseWrapperBase.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -728,4 +728,30 @@ target.push_back(s->ColumnInt64(0)); } } + + + void DatabaseWrapperBase::LookupIdentifierRange(std::list<int64_t>& target, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end) + { + SQLite::Statement statement(db_, SQLITE_FROM_HERE, + "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " + "d.id = r.internalId AND r.resourceType=? AND " + "d.tagGroup=? AND d.tagElement=? AND d.value>=? AND d.value<=?"); + + statement.BindInt(0, level); + statement.BindInt(1, tag.GetGroup()); + statement.BindInt(2, tag.GetElement()); + statement.BindString(3, start); + statement.BindString(4, end); + + target.clear(); + + while (statement.Step()) + { + target.push_back(statement.ColumnInt64(0)); + } + } }
--- a/OrthancServer/DatabaseWrapperBase.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/DatabaseWrapperBase.h Fri Jul 13 10:07:45 2018 +0200 @@ -199,6 +199,12 @@ const DicomTag& tag, IdentifierConstraintType type, const std::string& value); + + void LookupIdentifierRange(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomInstanceOrigin.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,211 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersServer.h" +#include "DicomInstanceOrigin.h" + +#include "../Core/OrthancException.h" +#include "../Core/SerializationToolbox.h" + + +namespace Orthanc +{ + void DicomInstanceOrigin::Format(Json::Value& result) const + { + result = Json::objectValue; + result["RequestOrigin"] = EnumerationToString(origin_); + + switch (origin_) + { + case RequestOrigin_Unknown: + { + // None of the methods "SetDicomProtocolOrigin()", "SetHttpOrigin()", + // "SetLuaOrigin()" or "SetPluginsOrigin()" was called! + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + case RequestOrigin_DicomProtocol: + { + result["RemoteIp"] = remoteIp_; + result["RemoteAet"] = dicomRemoteAet_; + result["CalledAet"] = dicomCalledAet_; + break; + } + + case RequestOrigin_RestApi: + { + result["RemoteIp"] = remoteIp_; + result["Username"] = httpUsername_; + break; + } + + case RequestOrigin_Lua: + case RequestOrigin_Plugins: + { + // No additional information available for these kinds of requests + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + DicomInstanceOrigin DicomInstanceOrigin::FromDicomProtocol(const char* remoteIp, + const char* remoteAet, + const char* calledAet) + { + DicomInstanceOrigin result(RequestOrigin_DicomProtocol); + result.remoteIp_ = remoteIp; + result.dicomRemoteAet_ = remoteAet; + result.dicomCalledAet_ = calledAet; + return result; + } + + DicomInstanceOrigin DicomInstanceOrigin::FromRest(const RestApiCall& call) + { + DicomInstanceOrigin result(call.GetRequestOrigin()); + + if (result.origin_ == RequestOrigin_RestApi) + { + result.remoteIp_ = call.GetRemoteIp(); + result.httpUsername_ = call.GetUsername(); + } + + return result; + } + + DicomInstanceOrigin DicomInstanceOrigin::FromHttp(const char* remoteIp, + const char* username) + { + DicomInstanceOrigin result(RequestOrigin_RestApi); + result.remoteIp_ = remoteIp; + result.httpUsername_ = username; + return result; + } + + const char* DicomInstanceOrigin::GetRemoteAetC() const + { + if (origin_ == RequestOrigin_DicomProtocol) + { + return dicomRemoteAet_.c_str(); + } + else + { + return ""; + } + } + + bool DicomInstanceOrigin::LookupRemoteAet(std::string& result) const + { + if (origin_ == RequestOrigin_DicomProtocol) + { + result = dicomRemoteAet_.c_str(); + return true; + } + else + { + return false; + } + } + + bool DicomInstanceOrigin::LookupRemoteIp(std::string& result) const + { + if (origin_ == RequestOrigin_DicomProtocol || + origin_ == RequestOrigin_RestApi) + { + result = remoteIp_; + return true; + } + else + { + return false; + } + } + + bool DicomInstanceOrigin::LookupCalledAet(std::string& result) const + { + if (origin_ == RequestOrigin_DicomProtocol) + { + result = dicomCalledAet_; + return true; + } + else + { + return false; + } + } + + bool DicomInstanceOrigin::LookupHttpUsername(std::string& result) const + { + if (origin_ == RequestOrigin_RestApi) + { + result = httpUsername_; + return true; + } + else + { + return false; + } + } + + + + static const char* ORIGIN = "Origin"; + static const char* REMOTE_IP = "RemoteIP"; + static const char* DICOM_REMOTE_AET = "RemoteAET"; + static const char* DICOM_CALLED_AET = "CalledAET"; + static const char* HTTP_USERNAME = "Username"; + + + DicomInstanceOrigin::DicomInstanceOrigin(const Json::Value& serialized) + { + origin_ = StringToRequestOrigin(SerializationToolbox::ReadString(serialized, ORIGIN)); + remoteIp_ = SerializationToolbox::ReadString(serialized, REMOTE_IP); + dicomRemoteAet_ = SerializationToolbox::ReadString(serialized, DICOM_REMOTE_AET); + dicomCalledAet_ = SerializationToolbox::ReadString(serialized, DICOM_CALLED_AET); + httpUsername_ = SerializationToolbox::ReadString(serialized, HTTP_USERNAME); + } + + + void DicomInstanceOrigin::Serialize(Json::Value& result) const + { + result = Json::objectValue; + result[ORIGIN] = EnumerationToString(origin_); + result[REMOTE_IP] = remoteIp_; + result[DICOM_REMOTE_AET] = dicomRemoteAet_; + result[DICOM_CALLED_AET] = dicomCalledAet_; + result[HTTP_USERNAME] = httpUsername_; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomInstanceOrigin.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,100 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Core/RestApi/RestApiCall.h" + +namespace Orthanc +{ + class DicomInstanceOrigin + { + private: + RequestOrigin origin_; + std::string remoteIp_; + std::string dicomRemoteAet_; + std::string dicomCalledAet_; + std::string httpUsername_; + + DicomInstanceOrigin(RequestOrigin origin) : + origin_(origin) + { + } + + public: + DicomInstanceOrigin() : + origin_(RequestOrigin_Unknown) + { + } + + DicomInstanceOrigin(const Json::Value& serialized); + + static DicomInstanceOrigin FromDicomProtocol(const char* remoteIp, + const char* remoteAet, + const char* calledAet); + + static DicomInstanceOrigin FromRest(const RestApiCall& call); + + static DicomInstanceOrigin FromHttp(const char* remoteIp, + const char* username); + + static DicomInstanceOrigin FromLua() + { + return DicomInstanceOrigin(RequestOrigin_Lua); + } + + static DicomInstanceOrigin FromPlugins() + { + return DicomInstanceOrigin(RequestOrigin_Plugins); + } + + RequestOrigin GetRequestOrigin() const + { + return origin_; + } + + const char* GetRemoteAetC() const; + + bool LookupRemoteAet(std::string& result) const; + + bool LookupRemoteIp(std::string& result) const; + + bool LookupCalledAet(std::string& result) const; + + bool LookupHttpUsername(std::string& result) const; + + void Format(Json::Value& result) const; + + void Serialize(Json::Value& result) const; + }; +}
--- a/OrthancServer/DicomInstanceToStore.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/DicomInstanceToStore.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -181,101 +181,6 @@ } - - void DicomInstanceToStore::GetOriginInformation(Json::Value& result) const - { - result = Json::objectValue; - result["RequestOrigin"] = EnumerationToString(origin_); - - switch (origin_) - { - case RequestOrigin_Unknown: - { - // None of the methods "SetDicomProtocolOrigin()", "SetHttpOrigin()", - // "SetLuaOrigin()" or "SetPluginsOrigin()" was called! - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - case RequestOrigin_DicomProtocol: - { - result["RemoteIp"] = remoteIp_; - result["RemoteAet"] = dicomRemoteAet_; - result["CalledAet"] = dicomCalledAet_; - break; - } - - case RequestOrigin_RestApi: - { - result["RemoteIp"] = remoteIp_; - result["Username"] = httpUsername_; - break; - } - - case RequestOrigin_Lua: - case RequestOrigin_Plugins: - { - // No additional information available for these kinds of requests - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - - void DicomInstanceToStore::SetDicomProtocolOrigin(const char* remoteIp, - const char* remoteAet, - const char* calledAet) - { - origin_ = RequestOrigin_DicomProtocol; - remoteIp_ = remoteIp; - dicomRemoteAet_ = remoteAet; - dicomCalledAet_ = calledAet; - } - - void DicomInstanceToStore::SetRestOrigin(const RestApiCall& call) - { - origin_ = call.GetRequestOrigin(); - - if (origin_ == RequestOrigin_RestApi) - { - remoteIp_ = call.GetRemoteIp(); - httpUsername_ = call.GetUsername(); - } - } - - void DicomInstanceToStore::SetHttpOrigin(const char* remoteIp, - const char* username) - { - origin_ = RequestOrigin_RestApi; - remoteIp_ = remoteIp; - httpUsername_ = username; - } - - void DicomInstanceToStore::SetLuaOrigin() - { - origin_ = RequestOrigin_Lua; - } - - void DicomInstanceToStore::SetPluginsOrigin() - { - origin_ = RequestOrigin_Plugins; - } - - const char* DicomInstanceToStore::GetRemoteAet() const - { - if (origin_ == RequestOrigin_DicomProtocol) - { - return dicomRemoteAet_.c_str(); - } - else - { - return ""; - } - } - - bool DicomInstanceToStore::LookupTransferSyntax(std::string& result) { ComputeMissingInformation();
--- a/OrthancServer/DicomInstanceToStore.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/DicomInstanceToStore.h Fri Jul 13 10:07:45 2018 +0200 @@ -34,9 +34,9 @@ #pragma once #include "../Core/DicomParsing/ParsedDicomFile.h" +#include "../Core/OrthancException.h" +#include "DicomInstanceOrigin.h" #include "ServerIndex.h" -#include "../Core/OrthancException.h" -#include "../Core/RestApi/RestApiCall.h" namespace Orthanc { @@ -139,46 +139,26 @@ } }; - - SmartContainer<std::string> buffer_; + DicomInstanceOrigin origin_; + SmartContainer<std::string> buffer_; SmartContainer<ParsedDicomFile> parsed_; - SmartContainer<DicomMap> summary_; - SmartContainer<Json::Value> json_; - - RequestOrigin origin_; - std::string remoteIp_; - std::string dicomRemoteAet_; - std::string dicomCalledAet_; - std::string httpUsername_; - ServerIndex::MetadataMap metadata_; + SmartContainer<DicomMap> summary_; + SmartContainer<Json::Value> json_; + ServerIndex::MetadataMap metadata_; void ComputeMissingInformation(); public: - DicomInstanceToStore() : origin_(RequestOrigin_Unknown) + void SetOrigin(const DicomInstanceOrigin& origin) { + origin_ = origin; } - - void SetDicomProtocolOrigin(const char* remoteIp, - const char* remoteAet, - const char* calledAet); - - void SetRestOrigin(const RestApiCall& call); - - void SetHttpOrigin(const char* remoteIp, - const char* username); - - void SetLuaOrigin(); - - void SetPluginsOrigin(); - - RequestOrigin GetRequestOrigin() const + + const DicomInstanceOrigin& GetOrigin() const { return origin_; } - - const char* GetRemoteAet() const; - + void SetBuffer(const std::string& dicom) { buffer_.SetConstReference(dicom); @@ -221,8 +201,6 @@ const Json::Value& GetJson(); - void GetOriginInformation(Json::Value& result) const; - bool LookupTransferSyntax(std::string& result); }; }
--- a/OrthancServer/IDatabaseWrapper.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/IDatabaseWrapper.h Fri Jul 13 10:07:45 2018 +0200 @@ -156,6 +156,12 @@ IdentifierConstraintType type, const std::string& value) = 0; + virtual void LookupIdentifierRange(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end) = 0; + virtual bool LookupMetadata(std::string& target, int64_t id, MetadataType type) = 0;
--- a/OrthancServer/IServerListener.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/IServerListener.h Fri Jul 13 10:07:45 2018 +0200 @@ -40,7 +40,7 @@ namespace Orthanc { - class IServerListener + class IServerListener : public boost::noncopyable { public: virtual ~IServerListener()
--- a/OrthancServer/LuaScripting.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/LuaScripting.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -34,24 +34,217 @@ #include "PrecompiledHeadersServer.h" #include "LuaScripting.h" +#include "OrthancInitialization.h" +#include "OrthancRestApi/OrthancRestApi.h" #include "ServerContext.h" -#include "OrthancInitialization.h" -#include "../Core/Lua/LuaFunctionCall.h" + #include "../Core/HttpServer/StringHttpOutput.h" #include "../Core/Logging.h" - -#include "Scheduler/DeleteInstanceCommand.h" -#include "Scheduler/StoreScuCommand.h" -#include "Scheduler/StorePeerCommand.h" -#include "Scheduler/ModifyInstanceCommand.h" -#include "Scheduler/CallSystemCommand.h" -#include "OrthancRestApi/OrthancRestApi.h" +#include "../Core/Lua/LuaFunctionCall.h" #include <EmbeddedResources.h> namespace Orthanc { + class LuaScripting::IEvent : public IDynamicObject + { + public: + virtual void Apply(LuaScripting& lock) = 0; + }; + + + class LuaScripting::OnStoredInstanceEvent : public LuaScripting::IEvent + { + private: + std::string instanceId_; + Json::Value simplifiedTags_; + Json::Value metadata_; + Json::Value origin_; + + public: + OnStoredInstanceEvent(const std::string& instanceId, + const Json::Value& simplifiedTags, + const Json::Value& metadata, + const DicomInstanceToStore& instance) : + instanceId_(instanceId), + simplifiedTags_(simplifiedTags), + metadata_(metadata) + { + instance.GetOrigin().Format(origin_); + } + + virtual void Apply(LuaScripting& that) + { + static const char* NAME = "OnStoredInstance"; + + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(NAME)) + { + that.InitializeJob(); + + LuaFunctionCall call(lock.GetLua(), NAME); + call.PushString(instanceId_); + call.PushJson(simplifiedTags_); + call.PushJson(metadata_); + call.PushJson(origin_); + call.Execute(); + + that.SubmitJob(); + } + } + }; + + + class LuaScripting::ExecuteEvent : public LuaScripting::IEvent + { + private: + std::string command_; + + public: + ExecuteEvent(const std::string& command) : + command_(command) + { + } + + virtual void Apply(LuaScripting& that) + { + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(command_.c_str())) + { + LuaFunctionCall call(lock.GetLua(), command_.c_str()); + call.Execute(); + } + } + }; + + + class LuaScripting::StableResourceEvent : public LuaScripting::IEvent + { + private: + ServerIndexChange change_; + + public: + StableResourceEvent(const ServerIndexChange& change) : + change_(change) + { + } + + virtual void Apply(LuaScripting& that) + { + const char* name; + + switch (change_.GetChangeType()) + { + case ChangeType_StablePatient: + name = "OnStablePatient"; + break; + + case ChangeType_StableStudy: + name = "OnStableStudy"; + break; + + case ChangeType_StableSeries: + name = "OnStableSeries"; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + { + // Avoid unnecessary calls to the database if there's no Lua callback + LuaScripting::Lock lock(that); + + if (!lock.GetLua().IsExistingFunction(name)) + { + return; + } + } + + Json::Value tags, metadata; + if (that.context_.GetIndex().LookupResource(tags, change_.GetPublicId(), change_.GetResourceType()) && + that.context_.GetIndex().GetMetadata(metadata, change_.GetPublicId())) + { + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(name)) + { + that.InitializeJob(); + + LuaFunctionCall call(lock.GetLua(), name); + call.PushString(change_.GetPublicId()); + call.PushJson(tags["MainDicomTags"]); + call.PushJson(metadata); + call.Execute(); + + that.SubmitJob(); + } + } + } + }; + + + class LuaScripting::JobEvent : public LuaScripting::IEvent + { + public: + enum Type + { + Type_Failure, + Type_Submitted, + Type_Success + }; + + private: + Type type_; + std::string jobId_; + + public: + JobEvent(Type type, + const std::string& jobId) : + type_(type), + jobId_(jobId) + { + } + + virtual void Apply(LuaScripting& that) + { + std::string functionName; + + switch (type_) + { + case Type_Failure: + functionName = "OnJobFailure"; + break; + + case Type_Submitted: + functionName = "OnJobSubmitted"; + break; + + case Type_Success: + functionName = "OnJobSuccess"; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + { + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(functionName.c_str())) + { + LuaFunctionCall call(lock.GetLua(), functionName.c_str()); + call.PushString(jobId_); + call.Execute(); + } + } + } + }; + + ServerContext* LuaScripting::GetServerContext(lua_State *state) { const void* value = LuaContext::GetGlobalVariable(state, "_ServerContext"); @@ -211,7 +404,7 @@ } LOG(ERROR) << "Lua: Error in RestApiDelete() for URI: " << uri; - lua_pushnil(state); + lua_pushnil(state); return 1; } @@ -229,13 +422,14 @@ } - IServerCommand* LuaScripting::ParseOperation(const std::string& operation, - const Json::Value& parameters) + size_t LuaScripting::ParseOperation(LuaJobManager::Lock& lock, + const std::string& operation, + const Json::Value& parameters) { if (operation == "delete") { LOG(INFO) << "Lua script to delete resource " << parameters["Resource"].asString(); - return new DeleteInstanceCommand(context_); + return lock.AddDeleteResourceOperation(context_); } if (operation == "store-scu") @@ -250,36 +444,29 @@ localAet = context_.GetDefaultLocalApplicationEntityTitle(); } - std::string modality = parameters["Modality"].asString(); - LOG(INFO) << "Lua script to send resource " << parameters["Resource"].asString() - << " to modality " << modality << " using Store-SCU"; + std::string name = parameters["Modality"].asString(); + RemoteModalityParameters modality = Configuration::GetModalityUsingSymbolicName(name); // This is not a C-MOVE: No need to call "StoreScuCommand::SetMoveOriginator()" - return new StoreScuCommand(context_, localAet, - Configuration::GetModalityUsingSymbolicName(modality), true); + return lock.AddStoreScuOperation(localAet, modality); } if (operation == "store-peer") { - std::string peer = parameters["Peer"].asString(); - LOG(INFO) << "Lua script to send resource " << parameters["Resource"].asString() - << " to peer " << peer << " using HTTP"; + std::string name = parameters["Peer"].asString(); - WebServiceParameters parameters; - Configuration::GetOrthancPeer(parameters, peer); - return new StorePeerCommand(context_, parameters, true); + WebServiceParameters peer; + Configuration::GetOrthancPeer(peer, name); + + return lock.AddStorePeerOperation(peer); } if (operation == "modify") { - LOG(INFO) << "Lua script to modify resource " << parameters["Resource"].asString(); std::auto_ptr<DicomModification> modification(new DicomModification); modification->ParseModifyRequest(parameters); - std::auto_ptr<ModifyInstanceCommand> command - (new ModifyInstanceCommand(context_, RequestOrigin_Lua, modification.release())); - - return command.release(); + return lock.AddModifyInstanceOperation(context_, modification.release()); } if (operation == "call-system") @@ -320,7 +507,10 @@ } } - return new CallSystemCommand(context_, parameters["Command"].asString(), args); + std::string command = parameters["Command"].asString(); + std::vector<std::string> postArgs; + + return lock.AddSystemCallOperation(command, args, postArgs); } throw OrthancException(ErrorCode_ParameterOutOfRange); @@ -333,7 +523,7 @@ } - void LuaScripting::SubmitJob(const std::string& description) + void LuaScripting::SubmitJob() { Json::Value operations; LuaFunctionCall call2(lua_, "_AccessJob"); @@ -344,8 +534,10 @@ throw OrthancException(ErrorCode_InternalError); } - ServerJob job; - ServerCommandInstance* previousCommand = NULL; + LuaJobManager::Lock lock(jobManager_, context_.GetJobsEngine()); + + bool isFirst = true; + size_t previous = 0; // Dummy initialization to avoid warning for (Json::Value::ArrayIndex i = 0; i < operations.size(); ++i) { @@ -356,34 +548,33 @@ } const Json::Value& parameters = operations[i]; - std::string operation = parameters["Operation"].asString(); - - ServerCommandInstance& command = job.AddCommand(ParseOperation(operation, operations[i])); - if (!parameters.isMember("Resource")) { throw OrthancException(ErrorCode_InternalError); } + std::string operation = parameters["Operation"].asString(); + size_t index = ParseOperation(lock, operation, operations[i]); + std::string resource = parameters["Resource"].asString(); - if (resource.empty()) + if (!resource.empty()) { - previousCommand->ConnectOutput(command); + lock.AddDicomInstanceInput(index, context_, resource); } - else + else if (!isFirst) { - command.AddInput(resource); + lock.Connect(previous, index); } - previousCommand = &command; + isFirst = false; + previous = index; } - - job.SetDescription(description); - context_.GetScheduler().Submit(job); } - LuaScripting::LuaScripting(ServerContext& context) : context_(context) + LuaScripting::LuaScripting(ServerContext& context) : + context_(context), + state_(State_Setup) { lua_.SetGlobalVariable("_ServerContext", &context); lua_.RegisterFunction("RestApiGet", RestApiGet); @@ -391,34 +582,88 @@ lua_.RegisterFunction("RestApiPut", RestApiPut); lua_.RegisterFunction("RestApiDelete", RestApiDelete); lua_.RegisterFunction("GetOrthancConfiguration", GetOrthancConfiguration); + } - lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); + + LuaScripting::~LuaScripting() + { + if (state_ == State_Running) + { + LOG(ERROR) << "INTERNAL ERROR: LuaScripting::Stop() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } } - void LuaScripting::ApplyOnStoredInstance(const std::string& instanceId, - const Json::Value& simplifiedTags, - const Json::Value& metadata, - const DicomInstanceToStore& instance) + void LuaScripting::EventThread(LuaScripting* that) { - static const char* NAME = "OnStoredInstance"; + for (;;) + { + std::auto_ptr<IDynamicObject> event(that->pendingEvents_.Dequeue(100)); + + if (event.get() == NULL) + { + // The event queue is empty, check whether we should stop + boost::recursive_mutex::scoped_lock lock(that->mutex_); - if (lua_.IsExistingFunction(NAME)) - { - InitializeJob(); + if (that->state_ != State_Running) + { + return; + } + } + else + { + try + { + dynamic_cast<IEvent&>(*event).Apply(*that); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Error while processing Lua events: " << e.What(); + } + } + } + } + + + void LuaScripting::Start() + { + boost::recursive_mutex::scoped_lock lock(mutex_); - LuaFunctionCall call(lua_, NAME); - call.PushString(instanceId); - call.PushJson(simplifiedTags); - call.PushJson(metadata); + if (state_ != State_Setup || + eventThread_.joinable() /* already started */) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + LOG(INFO) << "Starting the Lua engine"; + eventThread_ = boost::thread(EventThread, this); + state_ = State_Running; + } + } + - Json::Value origin; - instance.GetOriginInformation(origin); - call.PushJson(origin); + void LuaScripting::Stop() + { + { + boost::recursive_mutex::scoped_lock lock(mutex_); + + if (state_ != State_Running) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } - call.Execute(); + state_ = State_Done; + } + + jobManager_.AwakeTrailingSleep(); - SubmitJob(std::string("Lua script: ") + NAME); + if (eventThread_.joinable()) + { + LOG(INFO) << "Stopping the Lua engine"; + eventThread_.join(); + LOG(INFO) << "The Lua engine has stopped"; } } @@ -427,8 +672,6 @@ DicomInstanceToStore& instance, const Json::Value& simplifiedTags) { - boost::recursive_mutex::scoped_lock lock(mutex_); - Json::Value metadata = Json::objectValue; for (ServerIndex::MetadataMap::const_iterator @@ -441,72 +684,17 @@ } } - ApplyOnStoredInstance(publicId, simplifiedTags, metadata, instance); + pendingEvents_.Enqueue(new OnStoredInstanceEvent(publicId, simplifiedTags, metadata, instance)); } - - void LuaScripting::OnStableResource(const ServerIndexChange& change) - { - const char* name; - - switch (change.GetChangeType()) - { - case ChangeType_StablePatient: - name = "OnStablePatient"; - break; - - case ChangeType_StableStudy: - name = "OnStableStudy"; - break; - - case ChangeType_StableSeries: - name = "OnStableSeries"; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - { - // Avoid unnecessary calls to the database if there's no Lua callback - boost::recursive_mutex::scoped_lock lock(mutex_); - if (!lua_.IsExistingFunction(name)) - { - return; - } - } - - Json::Value tags, metadata; - if (context_.GetIndex().LookupResource(tags, change.GetPublicId(), change.GetResourceType()) && - context_.GetIndex().GetMetadata(metadata, change.GetPublicId())) - { - boost::recursive_mutex::scoped_lock lock(mutex_); - - if (lua_.IsExistingFunction(name)) - { - InitializeJob(); - - LuaFunctionCall call(lua_, name); - call.PushString(change.GetPublicId()); - call.PushJson(tags["MainDicomTags"]); - call.PushJson(metadata); - call.Execute(); - - SubmitJob(std::string("Lua script: ") + name); - } - } - } - - - void LuaScripting::SignalChange(const ServerIndexChange& change) { if (change.GetChangeType() == ChangeType_StablePatient || change.GetChangeType() == ChangeType_StableStudy || change.GetChangeType() == ChangeType_StableSeries) { - OnStableResource(change); + pendingEvents_.Enqueue(new StableResourceEvent(change)); } } @@ -524,7 +712,7 @@ call.PushJson(simplified); Json::Value origin; - instance.GetOriginInformation(origin); + instance.GetOrigin().Format(origin); call.PushJson(origin); if (!call.ExecutePredicate()) @@ -539,12 +727,46 @@ void LuaScripting::Execute(const std::string& command) { - LuaScripting::Locker locker(*this); - - if (locker.GetLua().IsExistingFunction(command.c_str())) + pendingEvents_.Enqueue(new ExecuteEvent(command)); + } + + + void LuaScripting::LoadGlobalConfiguration() + { + lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); + + std::list<std::string> luaScripts; + Configuration::GetGlobalListOfStringsParameter(luaScripts, "LuaScripts"); + + LuaScripting::Lock lock(*this); + + for (std::list<std::string>::const_iterator + it = luaScripts.begin(); it != luaScripts.end(); ++it) { - LuaFunctionCall call(locker.GetLua(), command.c_str()); - call.Execute(); + std::string path = Configuration::InterpretStringParameterAsPath(*it); + LOG(INFO) << "Installing the Lua scripts from: " << path; + std::string script; + SystemToolbox::ReadFile(script, path); + + lock.GetLua().Execute(script); } } + + + void LuaScripting::SignalJobSubmitted(const std::string& jobId) + { + pendingEvents_.Enqueue(new JobEvent(JobEvent::Type_Submitted, jobId)); + } + + + void LuaScripting::SignalJobSuccess(const std::string& jobId) + { + pendingEvents_.Enqueue(new JobEvent(JobEvent::Type_Success, jobId)); + } + + + void LuaScripting::SignalJobFailure(const std::string& jobId) + { + pendingEvents_.Enqueue(new JobEvent(JobEvent::Type_Failure, jobId)); + } }
--- a/OrthancServer/LuaScripting.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/LuaScripting.h Fri Jul 13 10:07:45 2018 +0200 @@ -34,8 +34,11 @@ #pragma once #include "IServerListener.h" + +#include "ServerJobs/LuaJobManager.h" + +#include "../Core/MultiThreading/SharedMessageQueue.h" #include "../Core/Lua/LuaContext.h" -#include "Scheduler/IServerCommand.h" namespace Orthanc { @@ -44,6 +47,19 @@ class LuaScripting : public IServerListener { private: + enum State + { + State_Setup, + State_Running, + State_Done + }; + + class ExecuteEvent; + class IEvent; + class OnStoredInstanceEvent; + class StableResourceEvent; + class JobEvent; + static ServerContext* GetServerContext(lua_State *state); static int RestApiPostOrPut(lua_State *state, @@ -54,33 +70,33 @@ static int RestApiDelete(lua_State *state); static int GetOrthancConfiguration(lua_State *state); - void ApplyOnStoredInstance(const std::string& instanceId, - const Json::Value& simplifiedDicom, - const Json::Value& metadata, - const DicomInstanceToStore& instance); - - IServerCommand* ParseOperation(const std::string& operation, - const Json::Value& parameters); + size_t ParseOperation(LuaJobManager::Lock& lock, + const std::string& operation, + const Json::Value& parameters); void InitializeJob(); - void SubmitJob(const std::string& description); - - void OnStableResource(const ServerIndexChange& change); + void SubmitJob(); - boost::recursive_mutex mutex_; - LuaContext lua_; - ServerContext& context_; + boost::recursive_mutex mutex_; + LuaContext lua_; + ServerContext& context_; + LuaJobManager jobManager_; + State state_; + boost::thread eventThread_; + SharedMessageQueue pendingEvents_; + + static void EventThread(LuaScripting* that); public: - class Locker : public boost::noncopyable + class Lock : public boost::noncopyable { private: - LuaScripting& that_; - boost::recursive_mutex::scoped_lock lock_; + LuaScripting& that_; + boost::recursive_mutex::scoped_lock lock_; public: - Locker(LuaScripting& that) : + explicit Lock(LuaScripting& that) : that_(that), lock_(that.mutex_) { @@ -93,6 +109,12 @@ }; LuaScripting(ServerContext& context); + + ~LuaScripting(); + + void Start(); + + void Stop(); virtual void SignalStoredInstance(const std::string& publicId, DicomInstanceToStore& instance, @@ -104,5 +126,13 @@ const Json::Value& simplifiedTags); void Execute(const std::string& command); + + void LoadGlobalConfiguration(); + + void SignalJobSubmitted(const std::string& jobId); + + void SignalJobSuccess(const std::string& jobId); + + void SignalJobFailure(const std::string& jobId); }; }
--- a/OrthancServer/OrthancFindRequestHandler.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -485,9 +485,10 @@ const std::string& calledAet) { static const char* LUA_CALLBACK = "IncomingFindRequestFilter"; + + LuaScripting::Lock lock(context_.GetLuaScripting()); - LuaScripting::Locker locker(context_.GetLua()); - if (!locker.GetLua().IsExistingFunction(LUA_CALLBACK)) + if (!lock.GetLua().IsExistingFunction(LUA_CALLBACK)) { return false; } @@ -498,7 +499,7 @@ origin["RemoteAet"] = remoteAet; origin["CalledAet"] = calledAet; - LuaFunctionCall call(locker.GetLua(), LUA_CALLBACK); + LuaFunctionCall call(lock.GetLua(), LUA_CALLBACK); call.PushDicom(source); call.PushJson(origin); FromDcmtkBridge::ExecuteToDicom(target, call); @@ -508,6 +509,14 @@ } + OrthancFindRequestHandler::OrthancFindRequestHandler(ServerContext& context) : + context_(context), + maxResults_(0), + maxInstances_(0) + { + } + + void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, const DicomMap& input, const std::list<DicomTag>& sequencesToReturn,
--- a/OrthancServer/OrthancFindRequestHandler.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/OrthancFindRequestHandler.h Fri Jul 13 10:07:45 2018 +0200 @@ -42,8 +42,8 @@ { private: ServerContext& context_; - unsigned int maxResults_; - unsigned int maxInstances_; + unsigned int maxResults_; + unsigned int maxInstances_; bool HasReachedLimit(const DicomFindAnswers& answers, ResourceType level) const; @@ -60,12 +60,7 @@ const std::string& calledAet); public: - OrthancFindRequestHandler(ServerContext& context) : - context_(context), - maxResults_(0), - maxInstances_(0) - { - } + OrthancFindRequestHandler(ServerContext& context); virtual void Handle(DicomFindAnswers& answers, const DicomMap& input,
--- a/OrthancServer/OrthancInitialization.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -458,7 +458,7 @@ { boost::recursive_mutex::scoped_lock lock(globalMutex_); - HttpClient::InitializeOpenSsl(); + Toolbox::InitializeOpenSsl(); InitializeServerEnumerations(); @@ -514,7 +514,7 @@ boost::recursive_mutex::scoped_lock lock(globalMutex_); HttpClient::GlobalFinalize(); FromDcmtkBridge::FinalizeCodecs(); - HttpClient::FinalizeOpenSsl(); + Toolbox::FinalizeOpenSsl(); Toolbox::FinalizeGlobalLocale(); } @@ -960,7 +960,7 @@ peers.removeMember(symbolicName); Json::Value v; - peer.ToJson(v); + peer.ToJson(v, true); peers[symbolicName] = v; }
--- a/OrthancServer/OrthancMoveRequestHandler.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -38,6 +38,7 @@ #include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/DicomFormat/DicomArray.h" #include "../Core/Logging.h" +#include "ServerJobs/DicomModalityStoreJob.h" namespace Orthanc { @@ -45,7 +46,7 @@ { // Anonymous namespace to avoid clashes between compilation modules - class OrthancMoveRequestIterator : public IMoveRequestIterator + class SynchronousMove : public IMoveRequestIterator { private: ServerContext& context_; @@ -55,20 +56,21 @@ RemoteModalityParameters remote_; std::string originatorAet_; uint16_t originatorId_; + std::auto_ptr<DicomUserConnection> connection_; public: - OrthancMoveRequestIterator(ServerContext& context, - const std::string& aet, - const std::string& publicId, - const std::string& originatorAet, - uint16_t originatorId) : + SynchronousMove(ServerContext& context, + const std::string& targetAet, + const std::string& publicId, + const std::string& originatorAet, + uint16_t originatorId) : context_(context), localAet_(context.GetDefaultLocalApplicationEntityTitle()), position_(0), originatorAet_(originatorAet), originatorId_(originatorId) { - LOG(INFO) << "Sending resource " << publicId << " to modality \"" << aet << "\""; + LOG(INFO) << "Sending resource " << publicId << " to modality \"" << targetAet << "\""; std::list<std::string> tmp; context_.GetIndex().GetChildInstances(tmp, publicId); @@ -79,7 +81,7 @@ instances_.push_back(*it); } - remote_ = Configuration::GetModalityUsingAet(aet); + remote_ = Configuration::GetModalityUsingAet(targetAet); } virtual unsigned int GetSubOperationCount() const @@ -99,15 +101,76 @@ std::string dicom; context_.ReadDicom(dicom, id); + if (connection_.get() == NULL) { - ReusableDicomUserConnection::Locker locker - (context_.GetReusableDicomUserConnection(), localAet_, remote_); - locker.GetConnection().Store(dicom, originatorAet_, originatorId_); + connection_.reset(new DicomUserConnection(localAet_, remote_)); } + connection_->Store(dicom, originatorAet_, originatorId_); + return Status_Success; } }; + + + class AsynchronousMove : public IMoveRequestIterator + { + private: + ServerContext& context_; + std::auto_ptr<DicomModalityStoreJob> job_; + size_t position_; + + public: + AsynchronousMove(ServerContext& context, + const std::string& targetAet, + const std::string& publicId, + const std::string& originatorAet, + uint16_t originatorId) : + context_(context), + job_(new DicomModalityStoreJob(context)), + position_(0) + { + LOG(INFO) << "Sending resource " << publicId << " to modality \"" << targetAet << "\""; + + job_->SetDescription("C-MOVE"); + job_->SetPermissive(true); + job_->SetLocalAet(context.GetDefaultLocalApplicationEntityTitle()); + job_->SetRemoteModality(Configuration::GetModalityUsingAet(targetAet)); + + if (originatorId != 0) + { + job_->SetMoveOriginator(originatorAet, originatorId); + } + + std::list<std::string> tmp; + context_.GetIndex().GetChildInstances(tmp, publicId); + + job_->Reserve(tmp.size()); + + for (std::list<std::string>::iterator it = tmp.begin(); it != tmp.end(); ++it) + { + job_->AddInstance(*it); + } + } + + virtual unsigned int GetSubOperationCount() const + { + return 1; + } + + virtual Status DoNext() + { + if (position_ == 0) + { + context_.GetJobsEngine().GetRegistry().Submit(job_.release(), 0 /* priority */); + return Status_Success; + } + else + { + return Status_Failure; + } + } + }; } @@ -169,6 +232,25 @@ } + static IMoveRequestIterator* CreateIterator(ServerContext& context, + const std::string& targetAet, + const std::string& publicId, + const std::string& originatorAet, + uint16_t originatorId) + { + bool synchronous = Configuration::GetGlobalBoolParameter("SynchronousCMove", false); + + if (synchronous) + { + return new SynchronousMove(context, targetAet, publicId, originatorAet, originatorId); + } + else + { + return new AsynchronousMove(context, targetAet, publicId, originatorAet, originatorId); + } + } + + IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& targetAet, const DicomMap& input, const std::string& originatorIp, @@ -191,7 +273,6 @@ } } - /** * Retrieve the query level. **/ @@ -215,7 +296,7 @@ LookupIdentifier(publicId, ResourceType_Study, input) || LookupIdentifier(publicId, ResourceType_Patient, input)) { - return new OrthancMoveRequestIterator(context_, targetAet, publicId, originatorAet, originatorId); + return CreateIterator(context_, targetAet, publicId, originatorAet, originatorId); } else { @@ -236,7 +317,7 @@ if (LookupIdentifier(publicId, level, input)) { - return new OrthancMoveRequestIterator(context_, targetAet, publicId, originatorAet, originatorId); + return CreateIterator(context_, targetAet, publicId, originatorAet, originatorId); } else {
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -34,10 +34,10 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Core/Logging.h" -#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../ServerContext.h" -#include "../OrthancInitialization.h" +#include "../ServerJobs/ResourceModificationJob.h" #include <boost/lexical_cast.hpp> #include <boost/algorithm/string/predicate.hpp> @@ -54,14 +54,39 @@ } + static int GetPriority(const Json::Value& request) + { + static const char* PRIORITY = "Priority"; + + if (request.isMember(PRIORITY)) + { + if (request[PRIORITY].type() == Json::intValue) + { + return request[PRIORITY].asInt(); + } + else + { + LOG(ERROR) << "Field \"" << PRIORITY << "\" of a modification request should be an integer"; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + else + { + return 0; // Default priority + } + } + + static void ParseModifyRequest(DicomModification& target, + int& priority, const RestApiPostCall& call) { - // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"}}' + // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"},"Priority":9}' Json::Value request; if (call.ParseJsonRequest(request)) { + priority = GetPriority(request); target.ParseModifyRequest(request); } else @@ -72,6 +97,7 @@ static void ParseAnonymizationRequest(DicomModification& target, + int& priority, RestApiPostCall& call) { // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": true,"Remove":["Modality"]}' > Anonymized.dcm @@ -80,6 +106,8 @@ if (call.ParseJsonRequest(request) && request.isObject()) { + priority = GetPriority(request); + bool patientNameReplaced; target.ParseAnonymizationRequest(patientNameReplaced, request); @@ -110,142 +138,36 @@ } - static void AnonymizeOrModifyResource(DicomModification& modification, - MetadataType metadataType, - ChangeType changeType, - ResourceType resourceType, - RestApiPostCall& call) + + static void SubmitJob(std::auto_ptr<DicomModification>& modification, + bool isAnonymization, + ResourceType level, + int priority, + RestApiPostCall& call) { - bool isFirst = true; - Json::Value result(Json::objectValue); - ServerContext& context = OrthancRestApi::GetContext(call); - typedef std::list<std::string> Instances; - Instances instances; - std::string id = call.GetUriComponent("id", ""); - context.GetIndex().GetChildInstances(instances, id); - - if (instances.empty()) - { - return; - } - - - /** - * Loop over all the instances of the resource. - **/ - - for (Instances::const_iterator it = instances.begin(); - it != instances.end(); ++it) - { - LOG(INFO) << "Modifying instance " << *it; - - std::auto_ptr<ServerContext::DicomCacheLocker> locker; - - try - { - locker.reset(new ServerContext::DicomCacheLocker(OrthancRestApi::GetContext(call), *it)); - } - catch (OrthancException&) - { - // This child instance has been removed in between - continue; - } - - - ParsedDicomFile& original = locker->GetDicom(); - DicomInstanceHasher originalHasher = original.GetHasher(); - - - /** - * Compute the resulting DICOM instance. - **/ - - std::auto_ptr<ParsedDicomFile> modified(original.Clone(true)); - modification.Apply(*modified); - - DicomInstanceToStore toStore; - toStore.SetRestOrigin(call); - toStore.SetParsedDicomFile(*modified); - - - /** - * Prepare the metadata information to associate with the - * resulting DICOM instance (AnonymizedFrom/ModifiedFrom). - **/ - - DicomInstanceHasher modifiedHasher = modified->GetHasher(); - - if (originalHasher.HashSeries() != modifiedHasher.HashSeries()) - { - toStore.AddMetadata(ResourceType_Series, metadataType, originalHasher.HashSeries()); - } + std::auto_ptr<ResourceModificationJob> job(new ResourceModificationJob(context)); + + boost::shared_ptr<ResourceModificationJob::Output> output(new ResourceModificationJob::Output(level)); + job->SetModification(modification.release(), isAnonymization); + job->SetOutput(output); + job->SetOrigin(call); + job->SetDescription("REST API"); - if (originalHasher.HashStudy() != modifiedHasher.HashStudy()) - { - toStore.AddMetadata(ResourceType_Study, metadataType, originalHasher.HashStudy()); - } - - if (originalHasher.HashPatient() != modifiedHasher.HashPatient()) - { - toStore.AddMetadata(ResourceType_Patient, metadataType, originalHasher.HashPatient()); - } - - assert(*it == originalHasher.HashInstance()); - toStore.AddMetadata(ResourceType_Instance, metadataType, *it); - - - /** - * Store the resulting DICOM instance into the Orthanc store. - **/ - - std::string modifiedInstance; - if (context.Store(modifiedInstance, toStore) != StoreStatus_Success) + context.AddChildInstances(*job, call.GetUriComponent("id", "")); + + if (context.GetJobsEngine().GetRegistry().SubmitAndWait(job.release(), priority)) + { + Json::Value json; + if (output->Format(json)) { - LOG(ERROR) << "Error while storing a modified instance " << *it; - throw OrthancException(ErrorCode_CannotStoreInstance); - } - - // Sanity checks in debug mode - assert(modifiedInstance == modifiedHasher.HashInstance()); - - - /** - * Compute the JSON object that is returned by the REST call. - **/ - - if (isFirst) - { - std::string newId; - - switch (resourceType) - { - case ResourceType_Series: - newId = modifiedHasher.HashSeries(); - break; - - case ResourceType_Study: - newId = modifiedHasher.HashStudy(); - break; - - case ResourceType_Patient: - newId = modifiedHasher.HashPatient(); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - result["Type"] = EnumerationToString(resourceType); - result["ID"] = newId; - result["Path"] = GetBasePath(resourceType, newId); - result["PatientID"] = modifiedHasher.HashPatient(); - isFirst = false; + call.GetOutput().AnswerJson(json); + return; } } - call.GetOutput().AnswerJson(result); + call.GetOutput().SignalError(HttpStatus_500_InternalServerError); } @@ -255,7 +177,8 @@ DicomModification modification; modification.SetAllowManualIdentifiers(true); - ParseModifyRequest(modification, call); + int priority; + ParseModifyRequest(modification, priority, call); if (modification.IsReplaced(DICOM_TAG_PATIENT_ID)) { @@ -283,36 +206,35 @@ DicomModification modification; modification.SetAllowManualIdentifiers(true); - ParseAnonymizationRequest(modification, call); + int priority; + ParseAnonymizationRequest(modification, priority, call); AnonymizeOrModifyInstance(modification, call); } - template <enum ChangeType changeType, - enum ResourceType resourceType> + template <enum ResourceType resourceType> static void ModifyResource(RestApiPostCall& call) { - DicomModification modification; - - ParseModifyRequest(modification, call); + std::auto_ptr<DicomModification> modification(new DicomModification); - modification.SetLevel(resourceType); - AnonymizeOrModifyResource(modification, MetadataType_ModifiedFrom, - changeType, resourceType, call); + int priority; + ParseModifyRequest(*modification, priority, call); + + modification->SetLevel(resourceType); + SubmitJob(modification, false, resourceType, priority, call); } - template <enum ChangeType changeType, - enum ResourceType resourceType> + template <enum ResourceType resourceType> static void AnonymizeResource(RestApiPostCall& call) { - DicomModification modification; + std::auto_ptr<DicomModification> modification(new DicomModification); - ParseAnonymizationRequest(modification, call); + int priority; + ParseAnonymizationRequest(*modification, priority, call); - AnonymizeOrModifyResource(modification, MetadataType_AnonymizedFrom, - changeType, resourceType, call); + SubmitJob(modification, true, resourceType, priority, call); } @@ -321,7 +243,7 @@ ParsedDicomFile& dicom) { DicomInstanceToStore toStore; - toStore.SetRestOrigin(call); + toStore.SetOrigin(DicomInstanceOrigin::FromRest(call)); toStore.SetParsedDicomFile(dicom); ServerContext& context = OrthancRestApi::GetContext(call); @@ -729,14 +651,14 @@ void OrthancRestApi::RegisterAnonymizeModify() { Register("/instances/{id}/modify", ModifyInstance); - Register("/series/{id}/modify", ModifyResource<ChangeType_ModifiedSeries, ResourceType_Series>); - Register("/studies/{id}/modify", ModifyResource<ChangeType_ModifiedStudy, ResourceType_Study>); - Register("/patients/{id}/modify", ModifyResource<ChangeType_ModifiedPatient, ResourceType_Patient>); + Register("/series/{id}/modify", ModifyResource<ResourceType_Series>); + Register("/studies/{id}/modify", ModifyResource<ResourceType_Study>); + Register("/patients/{id}/modify", ModifyResource<ResourceType_Patient>); Register("/instances/{id}/anonymize", AnonymizeInstance); - Register("/series/{id}/anonymize", AnonymizeResource<ChangeType_AnonymizedSeries, ResourceType_Series>); - Register("/studies/{id}/anonymize", AnonymizeResource<ChangeType_AnonymizedStudy, ResourceType_Study>); - Register("/patients/{id}/anonymize", AnonymizeResource<ChangeType_AnonymizedPatient, ResourceType_Patient>); + Register("/series/{id}/anonymize", AnonymizeResource<ResourceType_Series>); + Register("/studies/{id}/anonymize", AnonymizeResource<ResourceType_Study>); + Register("/patients/{id}/anonymize", AnonymizeResource<ResourceType_Patient>); Register("/tools/create-dicom", CreateDicom); }
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -93,7 +93,7 @@ std::string postData(call.GetBodyData(), call.GetBodySize()); DicomInstanceToStore toStore; - toStore.SetRestOrigin(call); + toStore.SetOrigin(DicomInstanceOrigin::FromRest(call)); toStore.SetBuffer(postData); std::string publicId;
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -34,651 +34,14 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" -#include "../../Core/DicomParsing/DicomDirWriter.h" -#include "../../Core/FileStorage/StorageAccessor.h" -#include "../../Core/Compression/HierarchicalZipWriter.h" #include "../../Core/HttpServer/FilesystemHttpSender.h" -#include "../../Core/Logging.h" -#include "../../Core/TemporaryFile.h" -#include "../ServerContext.h" - -#include <stdio.h> - -#if defined(_MSC_VER) -#define snprintf _snprintf -#endif - -static const uint64_t MEGA_BYTES = 1024 * 1024; -static const uint64_t GIGA_BYTES = 1024 * 1024 * 1024; +#include "../ServerJobs/ArchiveJob.h" namespace Orthanc { - // Download of ZIP files ---------------------------------------------------- - - static bool IsZip64Required(uint64_t uncompressedSize, - unsigned int countInstances) - { - static const uint64_t SAFETY_MARGIN = 64 * MEGA_BYTES; - - /** - * Determine whether ZIP64 is required. Original ZIP format can - * store up to 2GB of data (some implementation supporting up to - * 4GB of data), and up to 65535 files. - * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 - **/ - - const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES - SAFETY_MARGIN || - countInstances >= 65535); - - LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " - << (uncompressedSize / MEGA_BYTES) << "MB using the " - << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; - - return isZip64; - } - - - namespace - { - class ResourceIdentifiers - { - private: - ResourceType level_; - std::string patient_; - std::string study_; - std::string series_; - std::string instance_; - - static void GoToParent(ServerIndex& index, - std::string& current) - { - std::string tmp; - - if (index.LookupParent(tmp, current)) - { - current = tmp; - } - else - { - throw OrthancException(ErrorCode_UnknownResource); - } - } - - - public: - ResourceIdentifiers(ServerIndex& index, - const std::string& publicId) - { - if (!index.LookupResourceType(level_, publicId)) - { - throw OrthancException(ErrorCode_UnknownResource); - } - - std::string current = publicId;; - switch (level_) // Do not add "break" below! - { - case ResourceType_Instance: - instance_ = current; - GoToParent(index, current); - - case ResourceType_Series: - series_ = current; - GoToParent(index, current); - - case ResourceType_Study: - study_ = current; - GoToParent(index, current); - - case ResourceType_Patient: - patient_ = current; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - ResourceType GetLevel() const - { - return level_; - } - - const std::string& GetIdentifier(ResourceType level) const - { - // Some sanity check to ensure enumerations are not altered - assert(ResourceType_Patient < ResourceType_Study); - assert(ResourceType_Study < ResourceType_Series); - assert(ResourceType_Series < ResourceType_Instance); - - if (level > level_) - { - throw OrthancException(ErrorCode_InternalError); - } - - switch (level) - { - case ResourceType_Patient: - return patient_; - - case ResourceType_Study: - return study_; - - case ResourceType_Series: - return series_; - - case ResourceType_Instance: - return instance_; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - }; - - - class IArchiveVisitor : public boost::noncopyable - { - public: - virtual ~IArchiveVisitor() - { - } - - virtual void Open(ResourceType level, - const std::string& publicId) = 0; - - virtual void Close() = 0; - - virtual void AddInstance(const std::string& instanceId, - const FileInfo& dicom) = 0; - }; - - - class ArchiveIndex - { - private: - struct Instance - { - std::string id_; - FileInfo dicom_; - - Instance(const std::string& id, - const FileInfo& dicom) : - id_(id), dicom_(dicom) - { - } - }; - - // A "NULL" value for ArchiveIndex indicates a non-expanded node - typedef std::map<std::string, ArchiveIndex*> Resources; - - ResourceType level_; - Resources resources_; // Only at patient/study/series level - std::list<Instance> instances_; // Only at instance level - - - void AddResourceToExpand(ServerIndex& index, - const std::string& id) - { - if (level_ == ResourceType_Instance) - { - FileInfo tmp; - if (index.LookupAttachment(tmp, id, FileContentType_Dicom)) - { - instances_.push_back(Instance(id, tmp)); - } - } - else - { - resources_[id] = NULL; - } - } - - - public: - ArchiveIndex(ResourceType level) : - level_(level) - { - } - - ~ArchiveIndex() - { - for (Resources::iterator it = resources_.begin(); - it != resources_.end(); ++it) - { - delete it->second; - } - } - - - void Add(ServerIndex& index, - const ResourceIdentifiers& resource) - { - const std::string& id = resource.GetIdentifier(level_); - Resources::iterator previous = resources_.find(id); - - if (level_ == ResourceType_Instance) - { - AddResourceToExpand(index, id); - } - else if (resource.GetLevel() == level_) - { - // Mark this resource for further expansion - if (previous != resources_.end()) - { - delete previous->second; - } - - resources_[id] = NULL; - } - else if (previous == resources_.end()) - { - // This is the first time we meet this resource - std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); - child->Add(index, resource); - resources_[id] = child.release(); - } - else if (previous->second != NULL) - { - previous->second->Add(index, resource); - } - else - { - // Nothing to do: This item is marked for further expansion - } - } - - - void Expand(ServerIndex& index) - { - if (level_ == ResourceType_Instance) - { - // Expanding an instance node makes no sense - return; - } - - for (Resources::iterator it = resources_.begin(); - it != resources_.end(); ++it) - { - if (it->second == NULL) - { - // This is resource is marked for expansion - std::list<std::string> children; - index.GetChildren(children, it->first); - - std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); - - for (std::list<std::string>::const_iterator - it2 = children.begin(); it2 != children.end(); ++it2) - { - child->AddResourceToExpand(index, *it2); - } - - it->second = child.release(); - } - - assert(it->second != NULL); - it->second->Expand(index); - } - } - - - void Apply(IArchiveVisitor& visitor) const - { - if (level_ == ResourceType_Instance) - { - for (std::list<Instance>::const_iterator - it = instances_.begin(); it != instances_.end(); ++it) - { - visitor.AddInstance(it->id_, it->dicom_); - } - } - else - { - for (Resources::const_iterator it = resources_.begin(); - it != resources_.end(); ++it) - { - assert(it->second != NULL); // There must have been a call to "Expand()" - visitor.Open(level_, it->first); - it->second->Apply(visitor); - visitor.Close(); - } - } - } - }; - - - class StatisticsVisitor : public IArchiveVisitor - { - private: - uint64_t size_; - unsigned int instances_; - - public: - StatisticsVisitor() : size_(0), instances_(0) - { - } - - uint64_t GetUncompressedSize() const - { - return size_; - } - - unsigned int GetInstancesCount() const - { - return instances_; - } - - virtual void Open(ResourceType level, - const std::string& publicId) - { - } - - virtual void Close() - { - } - - virtual void AddInstance(const std::string& instanceId, - const FileInfo& dicom) - { - instances_ ++; - size_ += dicom.GetUncompressedSize(); - } - }; - - - class PrintVisitor : public IArchiveVisitor - { - private: - std::ostream& out_; - std::string indent_; - - public: - PrintVisitor(std::ostream& out) : out_(out) - { - } - - virtual void Open(ResourceType level, - const std::string& publicId) - { - switch (level) - { - case ResourceType_Patient: indent_ = ""; break; - case ResourceType_Study: indent_ = " "; break; - case ResourceType_Series: indent_ = " "; break; - default: - throw OrthancException(ErrorCode_InternalError); - } - - out_ << indent_ << publicId << std::endl; - } - - virtual void Close() - { - } - - virtual void AddInstance(const std::string& instanceId, - const FileInfo& dicom) - { - out_ << " " << instanceId << std::endl; - } - }; - - - class ArchiveWriterVisitor : public IArchiveVisitor - { - private: - HierarchicalZipWriter& writer_; - ServerContext& context_; - char instanceFormat_[24]; - unsigned int countInstances_; - - static std::string GetTag(const DicomMap& tags, - const DicomTag& tag) - { - const DicomValue* v = tags.TestAndGetValue(tag); - if (v != NULL && - !v->IsBinary() && - !v->IsNull()) - { - return v->GetContent(); - } - else - { - return ""; - } - } - - public: - ArchiveWriterVisitor(HierarchicalZipWriter& writer, - ServerContext& context) : - writer_(writer), - context_(context), - countInstances_(0) - { - snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); - } - - virtual void Open(ResourceType level, - const std::string& publicId) - { - std::string path; - - DicomMap tags; - if (context_.GetIndex().GetMainDicomTags(tags, publicId, level, level)) - { - switch (level) - { - case ResourceType_Patient: - path = GetTag(tags, DICOM_TAG_PATIENT_ID) + " " + GetTag(tags, DICOM_TAG_PATIENT_NAME); - break; - - case ResourceType_Study: - path = GetTag(tags, DICOM_TAG_ACCESSION_NUMBER) + " " + GetTag(tags, DICOM_TAG_STUDY_DESCRIPTION); - break; - - case ResourceType_Series: - { - std::string modality = GetTag(tags, DICOM_TAG_MODALITY); - path = modality + " " + GetTag(tags, DICOM_TAG_SERIES_DESCRIPTION); - - if (modality.size() == 0) - { - snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); - } - else if (modality.size() == 1) - { - snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%%07d.dcm", - toupper(modality[0])); - } - else if (modality.size() >= 2) - { - snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%c%%06d.dcm", - toupper(modality[0]), toupper(modality[1])); - } - - countInstances_ = 0; - - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - path = Toolbox::StripSpaces(Toolbox::ConvertToAscii(path)); - - if (path.empty()) - { - path = std::string("Unknown ") + EnumerationToString(level); - } - - writer_.OpenDirectory(path.c_str()); - } - - virtual void Close() - { - writer_.CloseDirectory(); - } - - virtual void AddInstance(const std::string& instanceId, - const FileInfo& dicom) - { - std::string content; - context_.ReadAttachment(content, dicom); - - char filename[24]; - snprintf(filename, sizeof(filename) - 1, instanceFormat_, countInstances_); - countInstances_ ++; - - writer_.OpenFile(filename); - writer_.Write(content); - } - - static void Apply(RestApiOutput& output, - ServerContext& context, - ArchiveIndex& archive, - const std::string& filename) - { - archive.Expand(context.GetIndex()); - - StatisticsVisitor stats; - archive.Apply(stats); - - const bool isZip64 = IsZip64Required(stats.GetUncompressedSize(), stats.GetInstancesCount()); - - // Create a RAII for the temporary file to manage the ZIP file - TemporaryFile tmp; - - { - // Create a ZIP writer - HierarchicalZipWriter writer(tmp.GetPath().c_str()); - writer.SetZip64(isZip64); - - ArchiveWriterVisitor v(writer, context); - archive.Apply(v); - } - - // Prepare the sending of the ZIP file - FilesystemHttpSender sender(tmp.GetPath()); - sender.SetContentType("application/zip"); - sender.SetContentFilename(filename); - - // Send the ZIP - output.AnswerStream(sender); - - // The temporary file is automatically removed thanks to the RAII - } - }; - - - class MediaWriterVisitor : public IArchiveVisitor - { - private: - HierarchicalZipWriter& writer_; - DicomDirWriter dicomDir_; - ServerContext& context_; - unsigned int countInstances_; - - public: - MediaWriterVisitor(HierarchicalZipWriter& writer, - ServerContext& context) : - writer_(writer), - context_(context), - countInstances_(0) - { - } - - void EncodeDicomDir(std::string& result) - { - dicomDir_.Encode(result); - } - - virtual void Open(ResourceType level, - const std::string& publicId) - { - } - - virtual void Close() - { - } - - virtual void AddInstance(const std::string& instanceId, - const FileInfo& dicom) - { - // "DICOM restricts the filenames on DICOM media to 8 - // characters (some systems wrongly use 8.3, but this does not - // conform to the standard)." - std::string filename = "IM" + boost::lexical_cast<std::string>(countInstances_); - writer_.OpenFile(filename.c_str()); - - std::string content; - context_.ReadAttachment(content, dicom); - writer_.Write(content); - - ParsedDicomFile parsed(content); - dicomDir_.Add("IMAGES", filename, parsed); - - countInstances_ ++; - } - - static void Apply(RestApiOutput& output, - ServerContext& context, - ArchiveIndex& archive, - const std::string& filename, - bool enableExtendedSopClass) - { - archive.Expand(context.GetIndex()); - - StatisticsVisitor stats; - archive.Apply(stats); - - const bool isZip64 = IsZip64Required(stats.GetUncompressedSize(), stats.GetInstancesCount()); - - // Create a RAII for the temporary file to manage the ZIP file - TemporaryFile tmp; - - { - // Create a ZIP writer - HierarchicalZipWriter writer(tmp.GetPath().c_str()); - writer.SetZip64(isZip64); - writer.OpenDirectory("IMAGES"); - - // Create a DICOMDIR writer - MediaWriterVisitor v(writer, context); - - // Request type-3 arguments to be added to the DICOMDIR - v.dicomDir_.EnableExtendedSopClass(enableExtendedSopClass); - - archive.Apply(v); - - // Add the DICOMDIR - writer.CloseDirectory(); - writer.OpenFile("DICOMDIR"); - std::string s; - v.EncodeDicomDir(s); - writer.Write(s); - } - - // Prepare the sending of the ZIP file - FilesystemHttpSender sender(tmp.GetPath()); - sender.SetContentType("application/zip"); - sender.SetContentFilename(filename); - - // Send the ZIP - output.AnswerStream(sender); - - // The temporary file is automatically removed thanks to the RAII - } - }; - } - - - static bool AddResourcesOfInterest(ArchiveIndex& archive, + static bool AddResourcesOfInterest(ArchiveJob& job, RestApiPostCall& call) { - ServerIndex& index = OrthancRestApi::GetIndex(call); - Json::Value resources; if (call.ParseJsonRequest(resources) && resources.type() == Json::arrayValue) @@ -690,8 +53,7 @@ return false; // Bad request } - ResourceIdentifiers resource(index, resources[i].asString()); - archive.Add(index, resource); + job.AddResource(resources[i].asString()); } return true; @@ -703,16 +65,46 @@ } + static void SubmitJob(RestApiCall& call, + boost::shared_ptr<TemporaryFile>& tmp, + ServerContext& context, + std::auto_ptr<ArchiveJob>& job, + const std::string& filename) + { + if (job.get() == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + job->SetDescription("REST API"); + + if (context.GetJobsEngine().GetRegistry().SubmitAndWait(job.release(), 0 /* TODO priority */)) + { + // The archive is now created: Prepare the sending of the ZIP file + FilesystemHttpSender sender(tmp->GetPath()); + sender.SetContentType("application/zip"); + sender.SetContentFilename(filename); + + // Send the ZIP + call.GetOutput().AnswerStream(sender); + } + else + { + call.GetOutput().SignalError(HttpStatus_500_InternalServerError); + } + } + + static void CreateBatchArchive(RestApiPostCall& call) { - ArchiveIndex archive(ResourceType_Patient); // root + ServerContext& context = OrthancRestApi::GetContext(call); - if (AddResourcesOfInterest(archive, call)) + boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); + std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, false, false)); + + if (AddResourcesOfInterest(*job, call)) { - ArchiveWriterVisitor::Apply(call.GetOutput(), - OrthancRestApi::GetContext(call), - archive, - "Archive.zip"); + SubmitJob(call, tmp, context, job, "Archive.zip"); } } @@ -720,51 +112,43 @@ template <bool Extended> static void CreateBatchMedia(RestApiPostCall& call) { - ArchiveIndex archive(ResourceType_Patient); // root + ServerContext& context = OrthancRestApi::GetContext(call); - if (AddResourcesOfInterest(archive, call)) + boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); + std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, true, Extended)); + + if (AddResourcesOfInterest(*job, call)) { - MediaWriterVisitor::Apply(call.GetOutput(), - OrthancRestApi::GetContext(call), - archive, - "Archive.zip", - Extended); + SubmitJob(call, tmp, context, job, "Archive.zip"); } - } - + } + static void CreateArchive(RestApiGetCall& call) { - ServerIndex& index = OrthancRestApi::GetIndex(call); + ServerContext& context = OrthancRestApi::GetContext(call); std::string id = call.GetUriComponent("id", ""); - ResourceIdentifiers resource(index, id); - - ArchiveIndex archive(ResourceType_Patient); // root - archive.Add(OrthancRestApi::GetIndex(call), resource); - ArchiveWriterVisitor::Apply(call.GetOutput(), - OrthancRestApi::GetContext(call), - archive, - id + ".zip"); + boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); + std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, false, false)); + job->AddResource(id); + + SubmitJob(call, tmp, context, job, id + ".zip"); } static void CreateMedia(RestApiGetCall& call) { - ServerIndex& index = OrthancRestApi::GetIndex(call); + ServerContext& context = OrthancRestApi::GetContext(call); std::string id = call.GetUriComponent("id", ""); - ResourceIdentifiers resource(index, id); - - ArchiveIndex archive(ResourceType_Patient); // root - archive.Add(OrthancRestApi::GetIndex(call), resource); - MediaWriterVisitor::Apply(call.GetOutput(), - OrthancRestApi::GetContext(call), - archive, - id + ".zip", - call.HasArgument("extended")); + boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); + std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, true, call.HasArgument("extended"))); + job->AddResource(id); + + SubmitJob(call, tmp, context, job, id + ".zip"); }
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -34,16 +34,15 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" -#include "../OrthancInitialization.h" -#include "../../Core/HttpClient.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Core/Logging.h" -#include "../../Core/DicomParsing/FromDcmtkBridge.h" -#include "../Scheduler/ServerJob.h" -#include "../Scheduler/StoreScuCommand.h" -#include "../Scheduler/StorePeerCommand.h" +#include "../OrthancInitialization.h" #include "../QueryRetrieveHandler.h" +#include "../ServerJobs/DicomModalityStoreJob.h" +#include "../ServerJobs/OrthancPeerStoreJob.h" #include "../ServerToolbox.h" + namespace Orthanc { /*************************************************************************** @@ -55,12 +54,15 @@ ServerContext& context = OrthancRestApi::GetContext(call); const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); try { - if (locker.GetConnection().Echo()) + DicomUserConnection connection(localAet, remote); + connection.Open(); + + if (connection.Echo()) { // Echo has succeeded call.GetOutput().AnswerBuffer("{}", "application/json"); @@ -176,11 +178,16 @@ } const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + + DicomFindAnswers answers(false); - DicomFindAnswers answers(false); - FindPatient(answers, locker.GetConnection(), fields); + { + DicomUserConnection connection(localAet, remote); + connection.Open(); + FindPatient(answers, connection, fields); + } Json::Value result; answers.ToJson(result, true); @@ -206,11 +213,16 @@ } const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); DicomFindAnswers answers(false); - FindStudy(answers, locker.GetConnection(), fields); + + { + DicomUserConnection connection(localAet, remote); + connection.Open(); + FindStudy(answers, connection, fields); + } Json::Value result; answers.ToJson(result, true); @@ -237,11 +249,16 @@ } const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); DicomFindAnswers answers(false); - FindSeries(answers, locker.GetConnection(), fields); + + { + DicomUserConnection connection(localAet, remote); + connection.Open(); + FindSeries(answers, connection, fields); + } Json::Value result; answers.ToJson(result, true); @@ -269,11 +286,16 @@ } const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); DicomFindAnswers answers(false); - FindInstance(answers, locker.GetConnection(), fields); + + { + DicomUserConnection connection(localAet, remote); + connection.Open(); + FindInstance(answers, connection, fields); + } Json::Value result; answers.ToJson(result, true); @@ -306,11 +328,14 @@ } const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + DicomUserConnection connection(localAet, remote); + connection.Open(); + DicomFindAnswers patients(false); - FindPatient(patients, locker.GetConnection(), m); + FindPatient(patients, connection, m); // Loop over the found patients Json::Value result = Json::arrayValue; @@ -328,7 +353,7 @@ CopyTagIfExists(m, patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); DicomFindAnswers studies(false); - FindStudy(studies, locker.GetConnection(), m); + FindStudy(studies, connection, m); patient["Studies"] = Json::arrayValue; @@ -348,7 +373,7 @@ CopyTagIfExists(m, studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); DicomFindAnswers series(false); - FindSeries(series, locker.GetConnection(), m); + FindSeries(series, connection, m); // Loop over the found series study["Series"] = Json::arrayValue; @@ -582,7 +607,7 @@ ***************************************************************************/ static bool GetInstancesToExport(Json::Value& otherArguments, - std::list<std::string>& instances, + SetOfInstancesJob& job, const std::string& remote, RestApiPostCall& call) { @@ -652,25 +677,59 @@ return false; } - if (Configuration::GetGlobalBoolParameter("LogExportedResources", true)) + if (Configuration::GetGlobalBoolParameter("LogExportedResources", false)) { context.GetIndex().LogExportedResource(stripped, remote); } - - std::list<std::string> tmp; - context.GetIndex().GetChildInstances(tmp, stripped); - for (std::list<std::string>::const_iterator - it = tmp.begin(); it != tmp.end(); ++it) - { - instances.push_back(*it); - } + context.AddChildInstances(job, stripped); } return true; } + static void SubmitJob(RestApiPostCall& call, + const Json::Value& request, + SetOfInstancesJob* jobRaw) + { + std::auto_ptr<SetOfInstancesJob> job(jobRaw); + + if (job.get() == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + ServerContext& context = OrthancRestApi::GetContext(call); + + bool permissive = Toolbox::GetJsonBooleanField(request, "Permissive", false); + bool asynchronous = Toolbox::GetJsonBooleanField(request, "Asynchronous", false); + int priority = Toolbox::GetJsonIntegerField(request, "Priority", 0); + + job->SetPermissive(permissive); + + if (asynchronous) + { + // Asynchronous mode: Submit the job, but don't wait for its completion + std::string id; + context.GetJobsEngine().GetRegistry().Submit(id, job.release(), priority); + + Json::Value v; + v["ID"] = id; + call.GetOutput().AnswerJson(v); + } + else if (context.GetJobsEngine().GetRegistry().SubmitAndWait(job.release(), priority)) + { + // Synchronous mode: We have submitted and waited for completion + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + else + { + call.GetOutput().SignalError(HttpStatus_500_InternalServerError); + } + } + + static void DicomStore(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -678,56 +737,29 @@ std::string remote = call.GetUriComponent("id", ""); Json::Value request; - std::list<std::string> instances; - if (!GetInstancesToExport(request, instances, remote, call)) - { - return; - } - - std::string localAet = Toolbox::GetJsonStringField(request, "LocalAet", context.GetDefaultLocalApplicationEntityTitle()); - bool permissive = Toolbox::GetJsonBooleanField(request, "Permissive", false); - bool asynchronous = Toolbox::GetJsonBooleanField(request, "Asynchronous", false); - std::string moveOriginatorAET = Toolbox::GetJsonStringField(request, "MoveOriginatorAet", context.GetDefaultLocalApplicationEntityTitle()); - int moveOriginatorID = Toolbox::GetJsonIntegerField(request, "MoveOriginatorID", 0 /* By default, not a C-MOVE */); + std::auto_ptr<DicomModalityStoreJob> job(new DicomModalityStoreJob(context)); - if (moveOriginatorID < 0 || - moveOriginatorID >= 65536) + if (GetInstancesToExport(request, *job, remote, call)) { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - RemoteModalityParameters p = Configuration::GetModalityUsingSymbolicName(remote); + std::string localAet = Toolbox::GetJsonStringField + (request, "LocalAet", context.GetDefaultLocalApplicationEntityTitle()); + std::string moveOriginatorAET = Toolbox::GetJsonStringField + (request, "MoveOriginatorAet", context.GetDefaultLocalApplicationEntityTitle()); + int moveOriginatorID = Toolbox::GetJsonIntegerField + (request, "MoveOriginatorID", 0 /* By default, not a C-MOVE */); - ServerJob job; - for (std::list<std::string>::const_iterator - it = instances.begin(); it != instances.end(); ++it) - { - std::auto_ptr<StoreScuCommand> command(new StoreScuCommand(context, localAet, p, permissive)); + RemoteModalityParameters p = Configuration::GetModalityUsingSymbolicName(remote); + + job->SetDescription("REST API"); + job->SetLocalAet(localAet); + job->SetRemoteModality(p); if (moveOriginatorID != 0) { - command->SetMoveOriginator(moveOriginatorAET, static_cast<uint16_t>(moveOriginatorID)); + job->SetMoveOriginator(moveOriginatorAET, moveOriginatorID); } - job.AddCommand(command.release()).AddInput(*it); - } - - job.SetDescription("HTTP request: Store-SCU to peer \"" + remote + "\""); - - if (asynchronous) - { - // Asynchronous mode: Submit the job, but don't wait for its completion - context.GetScheduler().Submit(job); - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - else if (context.GetScheduler().SubmitAndWait(job)) - { - // Synchronous mode: We have submitted and waited for completion - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - else - { - call.GetOutput().SignalError(HttpStatus_500_InternalServerError); + SubmitJob(call, request, job.release()); } } @@ -757,18 +789,23 @@ ResourceType level = StringToResourceType(request["Level"].asCString()); - std::string localAet = Toolbox::GetJsonStringField(request, "LocalAet", context.GetDefaultLocalApplicationEntityTitle()); - std::string targetAet = Toolbox::GetJsonStringField(request, "TargetAet", context.GetDefaultLocalApplicationEntityTitle()); + std::string localAet = Toolbox::GetJsonStringField + (request, "LocalAet", context.GetDefaultLocalApplicationEntityTitle()); + std::string targetAet = Toolbox::GetJsonStringField + (request, "TargetAet", context.GetDefaultLocalApplicationEntityTitle()); - const RemoteModalityParameters source = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - + const RemoteModalityParameters source = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + + DicomUserConnection connection(localAet, source); + connection.Open(); + for (Json::Value::ArrayIndex i = 0; i < request[RESOURCES].size(); i++) { DicomMap resource; FromDcmtkBridge::FromJson(resource, request[RESOURCES][i]); - - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, source); - locker.GetConnection().Move(targetAet, level, resource); + + connection.Move(targetAet, level, resource); } // Move has succeeded @@ -844,40 +881,17 @@ std::string remote = call.GetUriComponent("id", ""); Json::Value request; - std::list<std::string> instances; - if (!GetInstancesToExport(request, instances, remote, call)) - { - return; - } + std::auto_ptr<OrthancPeerStoreJob> job(new OrthancPeerStoreJob(context)); - bool asynchronous = Toolbox::GetJsonBooleanField(request, "Asynchronous", false); - - WebServiceParameters peer; - Configuration::GetOrthancPeer(peer, remote); - - ServerJob job; - for (std::list<std::string>::const_iterator - it = instances.begin(); it != instances.end(); ++it) + if (GetInstancesToExport(request, *job, remote, call)) { - job.AddCommand(new StorePeerCommand(context, peer, false)).AddInput(*it); - } - - job.SetDescription("HTTP request: POST to peer \"" + remote + "\""); - - if (asynchronous) - { - // Asynchronous mode: Submit the job, but don't wait for its completion - context.GetScheduler().Submit(job); - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - else if (context.GetScheduler().SubmitAndWait(job)) - { - // Synchronous mode: We have submitted and waited for completion - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - else - { - call.GetOutput().SignalError(HttpStatus_500_InternalServerError); + WebServiceParameters peer; + Configuration::GetOrthancPeer(peer, remote); + + job->SetDescription("REST API"); + job->SetPeer(peer); + + SubmitJob(call, request, job.release()); } } @@ -984,15 +998,17 @@ if (call.ParseJsonRequest(json)) { const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); std::auto_ptr<ParsedDicomFile> query(ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0))); DicomFindAnswers answers(true); { - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); - locker.GetConnection().FindWorklist(answers, *query); + DicomUserConnection connection(localAet, remote); + connection.Open(); + connection.FindWorklist(answers, *query); } Json::Value result;
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -673,12 +673,33 @@ std::list<MetadataType> metadata; OrthancRestApi::GetIndex(call).ListAvailableMetadata(metadata, publicId); - Json::Value result = Json::arrayValue; + + Json::Value result; - for (std::list<MetadataType>::const_iterator - it = metadata.begin(); it != metadata.end(); ++it) + if (call.HasArgument("expand")) { - result.append(EnumerationToString(*it)); + result = Json::objectValue; + + for (std::list<MetadataType>::const_iterator + it = metadata.begin(); it != metadata.end(); ++it) + { + std::string value; + if (OrthancRestApi::GetIndex(call).LookupMetadata(value, publicId, *it)) + { + std::string key = EnumerationToString(*it); + result[key] = value; + } + } + } + else + { + result = Json::arrayValue; + + for (std::list<MetadataType>::const_iterator + it = metadata.begin(); it != metadata.end(); ++it) + { + result.append(EnumerationToString(*it)); + } } call.GetOutput().AnswerJson(result);
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -124,8 +124,8 @@ call.BodyToString(command); { - LuaScripting::Locker locker(context.GetLua()); - locker.GetLua().Execute(result, command); + LuaScripting::Lock lock(context.GetLuaScripting()); + lock.GetLua().Execute(result, command); } call.GetOutput().AnswerBuffer(result, "text/plain"); @@ -146,6 +146,24 @@ } + static void GetDefaultEncoding(RestApiGetCall& call) + { + Encoding encoding = GetDefaultDicomEncoding(); + call.GetOutput().AnswerBuffer(EnumerationToString(encoding), "text/plain"); + } + + + static void SetDefaultEncoding(RestApiPutCall& call) + { + Encoding encoding = StringToEncoding(call.GetBodyData()); + + Configuration::SetDefaultEncoding(encoding); + + call.GetOutput().AnswerBuffer(EnumerationToString(encoding), "text/plain"); + } + + + // Plugins information ------------------------------------------------------ static void ListPlugins(RestApiGetCall& call) @@ -251,23 +269,99 @@ } - static void GetDefaultEncoding(RestApiGetCall& call) + + + // Jobs information ------------------------------------------------------ + + static void ListJobs(RestApiGetCall& call) { - Encoding encoding = GetDefaultDicomEncoding(); - call.GetOutput().AnswerBuffer(EnumerationToString(encoding), "text/plain"); + bool expand = call.HasArgument("expand"); + + Json::Value v = Json::arrayValue; + + std::set<std::string> jobs; + OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().ListJobs(jobs); + + for (std::set<std::string>::const_iterator it = jobs.begin(); + it != jobs.end(); ++it) + { + if (expand) + { + JobInfo info; + if (OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().GetJobInfo(info, *it)) + { + Json::Value tmp; + info.Format(tmp); + v.append(tmp); + } + } + else + { + v.append(*it); + } + } + + call.GetOutput().AnswerJson(v); + } + + static void GetJobInfo(RestApiGetCall& call) + { + std::string id = call.GetUriComponent("id", ""); + + JobInfo info; + if (OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().GetJobInfo(info, id)) + { + Json::Value json; + info.Format(json); + call.GetOutput().AnswerJson(json); + } } - static void SetDefaultEncoding(RestApiPutCall& call) + enum JobAction { - Encoding encoding = StringToEncoding(call.GetBodyData()); + JobAction_Cancel, + JobAction_Pause, + JobAction_Resubmit, + JobAction_Resume + }; + + template <JobAction action> + static void ApplyJobAction(RestApiPostCall& call) + { + std::string id = call.GetUriComponent("id", ""); + + bool ok = false; + + switch (action) + { + case JobAction_Cancel: + ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Cancel(id); + break; - Configuration::SetDefaultEncoding(encoding); + case JobAction_Pause: + ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Pause(id); + break; + + case JobAction_Resubmit: + ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Resubmit(id); + break; - call.GetOutput().AnswerBuffer(EnumerationToString(encoding), "text/plain"); + case JobAction_Resume: + ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Resume(id); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (ok) + { + call.GetOutput().AnswerBuffer("{}", "application/json"); + } } - + void OrthancRestApi::RegisterSystem() { Register("/", ServeRoot); @@ -284,5 +378,12 @@ Register("/plugins", ListPlugins); Register("/plugins/{id}", GetPlugin); Register("/plugins/explorer.js", GetOrthancExplorerPlugins); + + Register("/jobs", ListJobs); + Register("/jobs/{id}", GetJobInfo); + Register("/jobs/{id}/cancel", ApplyJobAction<JobAction_Cancel>); + Register("/jobs/{id}/pause", ApplyJobAction<JobAction_Pause>); + Register("/jobs/{id}/resubmit", ApplyJobAction<JobAction_Resubmit>); + Register("/jobs/{id}/resume", ApplyJobAction<JobAction_Resume>); } }
--- a/OrthancServer/QueryRetrieveHandler.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/QueryRetrieveHandler.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -35,21 +35,24 @@ #include "QueryRetrieveHandler.h" #include "OrthancInitialization.h" + #include "../Core/DicomParsing/FromDcmtkBridge.h" +#include "../Core/Logging.h" namespace Orthanc { - static void FixQuery(DicomMap& query, - ServerContext& context, - const std::string& modality) + static void FixQueryLua(DicomMap& query, + ServerContext& context, + const std::string& modality) { static const char* LUA_CALLBACK = "OutgoingFindRequestFilter"; - LuaScripting::Locker locker(context.GetLua()); - if (locker.GetLua().IsExistingFunction(LUA_CALLBACK)) + LuaScripting::Lock lock(context.GetLuaScripting()); + + if (lock.GetLua().IsExistingFunction(LUA_CALLBACK)) { - LuaFunctionCall call(locker.GetLua(), LUA_CALLBACK); + LuaFunctionCall call(lock.GetLua(), LUA_CALLBACK); call.PushDicom(query); call.PushJson(modality); FromDcmtkBridge::ExecuteToDicom(query, call); @@ -57,8 +60,8 @@ } - static void FixQuery(DicomMap& query, - ModalityManufacturer manufacturer) + static void FixQueryBuiltin(DicomMap& query, + ModalityManufacturer manufacturer) { /** * Introduce patches for specific manufacturers below. @@ -76,6 +79,19 @@ { done_ = false; answers_.Clear(); + connection_.reset(NULL); + } + + + DicomUserConnection& QueryRetrieveHandler::GetConnection() + { + if (connection_.get() == NULL) + { + connection_.reset(new DicomUserConnection(localAet_, modality_)); + connection_->Open(); + } + + return *connection_; } @@ -86,16 +102,12 @@ // Firstly, fix the content of the query for specific manufacturers DicomMap fixed; fixed.Assign(query_); - FixQuery(fixed, modality_.GetManufacturer()); + FixQueryBuiltin(fixed, modality_.GetManufacturer()); // Secondly, possibly fix the query with the user-provider Lua callback - FixQuery(fixed, context_, modality_.GetApplicationEntityTitle()); + FixQueryLua(fixed, context_, modality_.GetApplicationEntityTitle()); - { - // Finally, run the C-FIND SCU against the fixed query - ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); - locker.GetConnection().Find(answers_, level_, fixed); - } + GetConnection().Find(answers_, level_, fixed); done_ = true; } @@ -155,11 +167,7 @@ { DicomMap map; GetAnswer(map, i); - - { - ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); - locker.GetConnection().Move(target, map); - } + GetConnection().Move(target, map); }
--- a/OrthancServer/QueryRetrieveHandler.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/QueryRetrieveHandler.h Fri Jul 13 10:07:45 2018 +0200 @@ -49,8 +49,11 @@ DicomFindAnswers answers_; std::string modalityName_; + std::auto_ptr<DicomUserConnection> connection_; + void Invalidate(); + DicomUserConnection& GetConnection(); public: QueryRetrieveHandler(ServerContext& context);
--- a/OrthancServer/Scheduler/CallSystemCommand.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "CallSystemCommand.h" - -#include "../../Core/Logging.h" -#include "../../Core/Toolbox.h" -#include "../../Core/TemporaryFile.h" - -namespace Orthanc -{ - CallSystemCommand::CallSystemCommand(ServerContext& context, - const std::string& command, - const std::vector<std::string>& arguments) : - context_(context), - command_(command), - arguments_(arguments) - { - } - - bool CallSystemCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Calling system command " << command_ << " on instance " << *it; - - try - { - std::string dicom; - context_.ReadDicom(dicom, *it); - - TemporaryFile tmp; - tmp.Write(dicom); - - std::vector<std::string> args = arguments_; - args.push_back(tmp.GetPath()); - - SystemToolbox::ExecuteSystemCommand(command_, args); - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to call system command " << command_ - << " on instance " << *it << " in a Lua script: " << e.What(); - } - } - - return true; - } -}
--- a/OrthancServer/Scheduler/CallSystemCommand.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class CallSystemCommand : public IServerCommand - { - private: - ServerContext& context_; - std::string command_; - std::vector<std::string> arguments_; - - public: - CallSystemCommand(ServerContext& context, - const std::string& command, - const std::vector<std::string>& arguments); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -}
--- a/OrthancServer/Scheduler/DeleteInstanceCommand.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "DeleteInstanceCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - bool DeleteInstanceCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Deleting instance " << *it; - - try - { - Json::Value tmp; - context_.DeleteResource(tmp, *it, ResourceType_Instance); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to delete instance " << *it << ": " << e.What(); - } - } - - return true; - } -}
--- a/OrthancServer/Scheduler/DeleteInstanceCommand.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class DeleteInstanceCommand : public IServerCommand - { - private: - ServerContext& context_; - - public: - DeleteInstanceCommand(ServerContext& context) : context_(context) - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -}
--- a/OrthancServer/Scheduler/IServerCommand.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <list> -#include <string> -#include <boost/noncopyable.hpp> - -namespace Orthanc -{ - class IServerCommand : public boost::noncopyable - { - public: - typedef std::list<std::string> ListOfStrings; - - virtual ~IServerCommand() - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) = 0; - }; -}
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,124 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "ModifyInstanceCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - ModifyInstanceCommand::ModifyInstanceCommand(ServerContext& context, - RequestOrigin origin, - DicomModification* modification) : - context_(context), - origin_(origin), - modification_(modification) - { - modification_->SetAllowManualIdentifiers(true); - - if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID)) - { - modification_->SetLevel(ResourceType_Patient); - } - else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - modification_->SetLevel(ResourceType_Study); - } - else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - modification_->SetLevel(ResourceType_Series); - } - else - { - modification_->SetLevel(ResourceType_Instance); - } - - if (origin_ != RequestOrigin_Lua) - { - // TODO If issued from HTTP, "remoteIp" and "username" must be provided - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - ModifyInstanceCommand::~ModifyInstanceCommand() - { - if (modification_) - { - delete modification_; - } - } - - - bool ModifyInstanceCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Modifying resource " << *it; - - try - { - std::auto_ptr<ParsedDicomFile> modified; - - { - ServerContext::DicomCacheLocker lock(context_, *it); - modified.reset(lock.GetDicom().Clone(true)); - } - - modification_->Apply(*modified); - - DicomInstanceToStore toStore; - assert(origin_ == RequestOrigin_Lua); - toStore.SetLuaOrigin(); - toStore.SetParsedDicomFile(*modified); - // TODO other metadata - toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, *it); - - std::string modifiedId; - context_.Store(modifiedId, toStore); - - // Only chain with other commands if this command succeeds - outputs.push_back(modifiedId); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to modify instance " << *it << ": " << e.What(); - } - } - - return true; - } -}
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IServerCommand.h" -#include "../ServerContext.h" -#include "../../Core/DicomParsing/DicomModification.h" - -namespace Orthanc -{ - class ModifyInstanceCommand : public IServerCommand - { - private: - ServerContext& context_; - RequestOrigin origin_; - DicomModification* modification_; - - public: - ModifyInstanceCommand(ServerContext& context, - RequestOrigin origin, - DicomModification* modification); // takes the ownership - - virtual ~ModifyInstanceCommand(); - - const DicomModification& GetModification() const - { - return *modification_; - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -}
--- a/OrthancServer/Scheduler/ServerCommandInstance.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "ServerCommandInstance.h" - -#include "../../Core/OrthancException.h" - -namespace Orthanc -{ - bool ServerCommandInstance::Execute(IListener& listener) - { - ListOfStrings outputs; - - bool success = false; - - try - { - if (command_->Apply(outputs, inputs_)) - { - success = true; - } - } - catch (OrthancException&) - { - } - - if (!success) - { - listener.SignalFailure(jobId_); - return true; - } - - for (std::list<ServerCommandInstance*>::iterator - it = next_.begin(); it != next_.end(); ++it) - { - for (ListOfStrings::const_iterator - output = outputs.begin(); output != outputs.end(); ++output) - { - (*it)->AddInput(*output); - } - } - - listener.SignalSuccess(jobId_); - return true; - } - - - ServerCommandInstance::ServerCommandInstance(IServerCommand *command, - const std::string& jobId) : - command_(command), - jobId_(jobId), - connectedToSink_(false) - { - if (command_ == NULL) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - ServerCommandInstance::~ServerCommandInstance() - { - if (command_ != NULL) - { - delete command_; - } - } -}
--- a/OrthancServer/Scheduler/ServerCommandInstance.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../../Core/IDynamicObject.h" -#include "IServerCommand.h" - -namespace Orthanc -{ - class ServerCommandInstance : public IDynamicObject - { - friend class ServerScheduler; - - public: - class IListener - { - public: - virtual ~IListener() - { - } - - virtual void SignalSuccess(const std::string& jobId) = 0; - - virtual void SignalFailure(const std::string& jobId) = 0; - }; - - private: - typedef IServerCommand::ListOfStrings ListOfStrings; - - IServerCommand *command_; - std::string jobId_; - ListOfStrings inputs_; - std::list<ServerCommandInstance*> next_; - bool connectedToSink_; - - bool Execute(IListener& listener); - - public: - ServerCommandInstance(IServerCommand *command, - const std::string& jobId); - - virtual ~ServerCommandInstance(); - - const std::string& GetJobId() const - { - return jobId_; - } - - void AddInput(const std::string& input) - { - inputs_.push_back(input); - } - - void ConnectOutput(ServerCommandInstance& next) - { - next_.push_back(&next); - } - - void SetConnectedToSink(bool connected = true) - { - connectedToSink_ = connected; - } - - bool IsConnectedToSink() const - { - return connectedToSink_; - } - - const std::list<ServerCommandInstance*>& GetNextCommands() const - { - return next_; - } - }; -}
--- a/OrthancServer/Scheduler/ServerJob.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "ServerJob.h" - -#include "../../Core/OrthancException.h" -#include "../../Core/Toolbox.h" - -namespace Orthanc -{ - void ServerJob::CheckOrdering() - { - std::map<ServerCommandInstance*, unsigned int> index; - - unsigned int count = 0; - for (std::list<ServerCommandInstance*>::const_iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - index[*it] = count++; - } - - for (std::list<ServerCommandInstance*>::const_iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - const std::list<ServerCommandInstance*>& nextCommands = (*it)->GetNextCommands(); - - for (std::list<ServerCommandInstance*>::const_iterator - next = nextCommands.begin(); next != nextCommands.end(); ++next) - { - if (index.find(*next) == index.end() || - index[*next] <= index[*it]) - { - // You must reorder your calls to "ServerJob::AddCommand" - throw OrthancException(ErrorCode_BadJobOrdering); - } - } - } - } - - - size_t ServerJob::Submit(SharedMessageQueue& target, - ServerCommandInstance::IListener& listener) - { - if (submitted_) - { - // This job has already been submitted - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - CheckOrdering(); - - size_t size = filters_.size(); - - for (std::list<ServerCommandInstance*>::iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - target.Enqueue(*it); - } - - filters_.clear(); - submitted_ = true; - - return size; - } - - - ServerJob::ServerJob() : - jobId_(Toolbox::GenerateUuid()), - submitted_(false), - description_("no description") - { - } - - - ServerJob::~ServerJob() - { - for (std::list<ServerCommandInstance*>::iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - delete *it; - } - - for (std::list<IDynamicObject*>::iterator - it = payloads_.begin(); it != payloads_.end(); ++it) - { - delete *it; - } - } - - - ServerCommandInstance& ServerJob::AddCommand(IServerCommand* filter) - { - if (submitted_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - filters_.push_back(new ServerCommandInstance(filter, jobId_)); - - return *filters_.back(); - } - - - IDynamicObject& ServerJob::AddPayload(IDynamicObject* payload) - { - if (submitted_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - payloads_.push_back(payload); - - return *filters_.back(); - } - -}
--- a/OrthancServer/Scheduler/ServerJob.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ServerCommandInstance.h" -#include "../../Core/MultiThreading/SharedMessageQueue.h" - -namespace Orthanc -{ - class ServerJob - { - friend class ServerScheduler; - - private: - std::list<ServerCommandInstance*> filters_; - std::list<IDynamicObject*> payloads_; - std::string jobId_; - bool submitted_; - std::string description_; - - void CheckOrdering(); - - size_t Submit(SharedMessageQueue& target, - ServerCommandInstance::IListener& listener); - - public: - ServerJob(); - - ~ServerJob(); - - const std::string& GetId() const - { - return jobId_; - } - - void SetDescription(const std::string& description) - { - description_ = description; - } - - const std::string& GetDescription() const - { - return description_; - } - - ServerCommandInstance& AddCommand(IServerCommand* filter); - - // Take the ownership of a payload to a job. This payload will be - // automatically freed when the job succeeds or fails. - IDynamicObject& AddPayload(IDynamicObject* payload); - }; -}
--- a/OrthancServer/Scheduler/ServerScheduler.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,359 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "ServerScheduler.h" - -#include "../../Core/OrthancException.h" -#include "../../Core/Logging.h" - -namespace Orthanc -{ - namespace - { - // Anonymous namespace to avoid clashes between compilation modules - class Sink : public IServerCommand - { - private: - ListOfStrings& target_; - - public: - explicit Sink(ListOfStrings& target) : target_(target) - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - target_.push_back(*it); - } - - return true; - } - }; - } - - - ServerScheduler::JobInfo& ServerScheduler::GetJobInfo(const std::string& jobId) - { - Jobs::iterator info = jobs_.find(jobId); - - if (info == jobs_.end()) - { - throw OrthancException(ErrorCode_InternalError); - } - - return info->second; - } - - - void ServerScheduler::SignalSuccess(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - JobInfo& info = GetJobInfo(jobId); - info.success_++; - - assert(info.failures_ == 0); - - if (info.success_ >= info.size_) - { - if (info.watched_) - { - watchedJobStatus_[jobId] = JobStatus_Success; - watchedJobFinished_.notify_all(); - } - - LOG(INFO) << "Job successfully finished (" << info.description_ << ")"; - jobs_.erase(jobId); - - availableJob_.Release(); - } - } - - - void ServerScheduler::SignalFailure(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - JobInfo& info = GetJobInfo(jobId); - info.failures_++; - - if (info.success_ + info.failures_ >= info.size_) - { - if (info.watched_) - { - watchedJobStatus_[jobId] = JobStatus_Failure; - watchedJobFinished_.notify_all(); - } - - LOG(ERROR) << "Job has failed (" << info.description_ << ")"; - jobs_.erase(jobId); - - availableJob_.Release(); - } - } - - - void ServerScheduler::Worker(ServerScheduler* that) - { - static const int32_t TIMEOUT = 100; - - LOG(WARNING) << "The server scheduler has started"; - - while (!that->finish_) - { - std::auto_ptr<IDynamicObject> object(that->queue_.Dequeue(TIMEOUT)); - if (object.get() != NULL) - { - ServerCommandInstance& filter = dynamic_cast<ServerCommandInstance&>(*object); - - // Skip the execution of this filter if its parent job has - // previously failed. - bool jobHasFailed; - { - boost::mutex::scoped_lock lock(that->mutex_); - JobInfo& info = that->GetJobInfo(filter.GetJobId()); - jobHasFailed = (info.failures_ > 0 || info.cancel_); - } - - if (jobHasFailed) - { - that->SignalFailure(filter.GetJobId()); - } - else - { - filter.Execute(*that); - } - } - } - } - - - void ServerScheduler::SubmitInternal(ServerJob& job, - bool watched) - { - availableJob_.Acquire(); - - boost::mutex::scoped_lock lock(mutex_); - - JobInfo info; - info.size_ = job.Submit(queue_, *this); - info.cancel_ = false; - info.success_ = 0; - info.failures_ = 0; - info.description_ = job.GetDescription(); - info.watched_ = watched; - - assert(info.size_ > 0); - - if (watched) - { - watchedJobStatus_[job.GetId()] = JobStatus_Running; - } - - jobs_[job.GetId()] = info; - - LOG(INFO) << "New job submitted (" << job.description_ << ")"; - } - - - ServerScheduler::ServerScheduler(unsigned int maxJobs) : availableJob_(maxJobs) - { - if (maxJobs == 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - finish_ = false; - worker_ = boost::thread(Worker, this); - } - - - ServerScheduler::~ServerScheduler() - { - if (!finish_) - { - LOG(ERROR) << "INTERNAL ERROR: ServerScheduler::Finalize() should be invoked manually to avoid mess in the destruction order!"; - Stop(); - } - } - - - void ServerScheduler::Stop() - { - if (!finish_) - { - finish_ = true; - - if (worker_.joinable()) - { - worker_.join(); - } - } - } - - - void ServerScheduler::Submit(ServerJob& job) - { - if (job.filters_.empty()) - { - return; - } - - SubmitInternal(job, false); - } - - - bool ServerScheduler::SubmitAndWait(ListOfStrings& outputs, - ServerJob& job) - { - std::string jobId = job.GetId(); - - outputs.clear(); - - if (job.filters_.empty()) - { - return true; - } - - // Add a sink filter to collect all the results of the filters - // that have no next filter. - ServerCommandInstance& sink = job.AddCommand(new Sink(outputs)); - - for (std::list<ServerCommandInstance*>::iterator - it = job.filters_.begin(); it != job.filters_.end(); ++it) - { - if ((*it) != &sink && - (*it)->IsConnectedToSink()) - { - (*it)->ConnectOutput(sink); - } - } - - // Submit the job - SubmitInternal(job, true); - - // Wait for the job to complete (either success or failure) - JobStatus status; - - { - boost::mutex::scoped_lock lock(mutex_); - - assert(watchedJobStatus_.find(jobId) != watchedJobStatus_.end()); - - while (watchedJobStatus_[jobId] == JobStatus_Running) - { - watchedJobFinished_.wait(lock); - } - - status = watchedJobStatus_[jobId]; - watchedJobStatus_.erase(jobId); - } - - return (status == JobStatus_Success); - } - - - bool ServerScheduler::SubmitAndWait(ServerJob& job) - { - ListOfStrings ignoredSink; - return SubmitAndWait(ignoredSink, job); - } - - - bool ServerScheduler::IsRunning(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - return jobs_.find(jobId) != jobs_.end(); - } - - - void ServerScheduler::Cancel(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - Jobs::iterator job = jobs_.find(jobId); - - if (job != jobs_.end()) - { - job->second.cancel_ = true; - LOG(WARNING) << "Canceling a job (" << job->second.description_ << ")"; - } - } - - - float ServerScheduler::GetProgress(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - Jobs::iterator job = jobs_.find(jobId); - - if (job == jobs_.end() || - job->second.size_ == 0 /* should never happen */) - { - // This job is not running - return 1; - } - - if (job->second.failures_ != 0) - { - return 1; - } - - if (job->second.size_ == 1) - { - return static_cast<float>(job->second.success_); - } - - return (static_cast<float>(job->second.success_) / - static_cast<float>(job->second.size_ - 1)); - } - - - void ServerScheduler::GetListOfJobs(ListOfStrings& jobs) - { - boost::mutex::scoped_lock lock(mutex_); - - jobs.clear(); - - for (Jobs::const_iterator - it = jobs_.begin(); it != jobs_.end(); ++it) - { - jobs.push_back(it->first); - } - } -}
--- a/OrthancServer/Scheduler/ServerScheduler.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ServerJob.h" - -#include "../../Core/MultiThreading/Semaphore.h" - -namespace Orthanc -{ - class ServerScheduler : public ServerCommandInstance::IListener - { - private: - struct JobInfo - { - bool watched_; - bool cancel_; - size_t size_; - size_t success_; - size_t failures_; - std::string description_; - }; - - enum JobStatus - { - JobStatus_Running = 1, - JobStatus_Success = 2, - JobStatus_Failure = 3 - }; - - typedef IServerCommand::ListOfStrings ListOfStrings; - typedef std::map<std::string, JobInfo> Jobs; - - boost::mutex mutex_; - boost::condition_variable watchedJobFinished_; - Jobs jobs_; - SharedMessageQueue queue_; - bool finish_; - boost::thread worker_; - std::map<std::string, JobStatus> watchedJobStatus_; - Semaphore availableJob_; - - JobInfo& GetJobInfo(const std::string& jobId); - - virtual void SignalSuccess(const std::string& jobId); - - virtual void SignalFailure(const std::string& jobId); - - static void Worker(ServerScheduler* that); - - void SubmitInternal(ServerJob& job, - bool watched); - - public: - explicit ServerScheduler(unsigned int maxjobs); - - ~ServerScheduler(); - - void Stop(); - - void Submit(ServerJob& job); - - bool SubmitAndWait(ListOfStrings& outputs, - ServerJob& job); - - bool SubmitAndWait(ServerJob& job); - - bool IsRunning(const std::string& jobId); - - void Cancel(const std::string& jobId); - - // Returns a number between 0 and 1 - float GetProgress(const std::string& jobId); - - bool IsRunning(const ServerJob& job) - { - return IsRunning(job.GetId()); - } - - void Cancel(const ServerJob& job) - { - Cancel(job.GetId()); - } - - float GetProgress(const ServerJob& job) - { - return GetProgress(job.GetId()); - } - - void GetListOfJobs(ListOfStrings& jobs); - }; -}
--- a/OrthancServer/Scheduler/StorePeerCommand.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "StorePeerCommand.h" - -#include "../../Core/Logging.h" -#include "../../Core/HttpClient.h" - -namespace Orthanc -{ - StorePeerCommand::StorePeerCommand(ServerContext& context, - const WebServiceParameters& peer, - bool ignoreExceptions) : - context_(context), - peer_(peer), - ignoreExceptions_(ignoreExceptions) - { - } - - bool StorePeerCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - // Configure the HTTP client - HttpClient client(peer_, "instances"); - client.SetMethod(HttpMethod_Post); - - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Sending resource " << *it << " to peer \"" - << peer_.GetUrl() << "\""; - - try - { - context_.ReadDicom(client.GetBody(), *it); - - std::string answer; - if (!client.Apply(answer)) - { - LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << peer_.GetUrl() << "\""; - throw OrthancException(ErrorCode_NetworkProtocol); - } - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to forward to an Orthanc peer in (instance " - << *it << ", peer " << peer_.GetUrl() << "): " << e.What(); - - if (!ignoreExceptions_) - { - throw; - } - } - } - - return true; - } -}
--- a/OrthancServer/Scheduler/StorePeerCommand.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IServerCommand.h" -#include "../ServerContext.h" -#include "../OrthancInitialization.h" - -namespace Orthanc -{ - class StorePeerCommand : public IServerCommand - { - private: - ServerContext& context_; - WebServiceParameters peer_; - bool ignoreExceptions_; - - public: - StorePeerCommand(ServerContext& context, - const WebServiceParameters& peer, - bool ignoreExceptions); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -}
--- a/OrthancServer/Scheduler/StoreScuCommand.cpp Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "StoreScuCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - StoreScuCommand::StoreScuCommand(ServerContext& context, - const std::string& localAet, - const RemoteModalityParameters& modality, - bool ignoreExceptions) : - context_(context), - modality_(modality), - ignoreExceptions_(ignoreExceptions), - localAet_(localAet), - moveOriginatorID_(0) - { - } - - - void StoreScuCommand::SetMoveOriginator(const std::string& aet, - uint16_t id) - { - moveOriginatorAET_ = aet; - moveOriginatorID_ = id; - } - - - bool StoreScuCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); - - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Sending resource " << *it << " to modality \"" - << modality_.GetApplicationEntityTitle() << "\""; - - try - { - std::string dicom; - context_.ReadDicom(dicom, *it); - - locker.GetConnection().Store(dicom, moveOriginatorAET_, moveOriginatorID_); - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - // Ignore transmission errors (e.g. if the remote modality is - // powered off) - LOG(ERROR) << "Unable to forward to a modality in (instance " - << *it << "): " << e.What(); - - if (!ignoreExceptions_) - { - throw; - } - } - } - - return true; - } -}
--- a/OrthancServer/Scheduler/StoreScuCommand.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class StoreScuCommand : public IServerCommand - { - private: - ServerContext& context_; - RemoteModalityParameters modality_; - bool ignoreExceptions_; - std::string localAet_; - std::string moveOriginatorAET_; - uint16_t moveOriginatorID_; - - public: - StoreScuCommand(ServerContext& context, - const std::string& localAet, - const RemoteModalityParameters& modality, - bool ignoreExceptions); - - void SetMoveOriginator(const std::string& aet, - uint16_t id); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -}
--- a/OrthancServer/Search/LookupIdentifierQuery.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/Search/LookupIdentifierQuery.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -46,9 +46,14 @@ { LookupIdentifierQuery::Disjunction::~Disjunction() { - for (size_t i = 0; i < disjunction_.size(); i++) + for (size_t i = 0; i < singleConstraints_.size(); i++) { - delete disjunction_[i]; + delete singleConstraints_[i]; + } + + for (size_t i = 0; i < rangeConstraints_.size(); i++) + { + delete rangeConstraints_[i]; } } @@ -57,14 +62,22 @@ IdentifierConstraintType type, const std::string& value) { - disjunction_.push_back(new Constraint(tag, type, value)); + singleConstraints_.push_back(new SingleConstraint(tag, type, value)); + } + + + void LookupIdentifierQuery::Disjunction::AddRange(const DicomTag& tag, + const std::string& start, + const std::string& end) + { + rangeConstraints_.push_back(new RangeConstraint(tag, start, end)); } LookupIdentifierQuery::~LookupIdentifierQuery() { - for (Constraints::iterator it = constraints_.begin(); - it != constraints_.end(); ++it) + for (Disjunctions::iterator it = disjunctions_.begin(); + it != disjunctions_.end(); ++it) { delete *it; } @@ -76,15 +89,25 @@ const std::string& value) { assert(IsIdentifier(tag)); - constraints_.push_back(new Disjunction); - constraints_.back()->Add(tag, type, value); + disjunctions_.push_back(new Disjunction); + disjunctions_.back()->Add(tag, type, value); + } + + + void LookupIdentifierQuery::AddRange(DicomTag tag, + const std::string& start, + const std::string& end) + { + assert(IsIdentifier(tag)); + disjunctions_.push_back(new Disjunction); + disjunctions_.back()->AddRange(tag, start, end); } LookupIdentifierQuery::Disjunction& LookupIdentifierQuery::AddDisjunction() { - constraints_.push_back(new Disjunction); - return *constraints_.back(); + disjunctions_.push_back(new Disjunction); + return *disjunctions_.back(); } @@ -101,15 +124,26 @@ void LookupIdentifierQuery::Apply(SetOfResources& result, IDatabaseWrapper& database) { - for (size_t i = 0; i < GetSize(); i++) + for (size_t i = 0; i < disjunctions_.size(); i++) { std::list<int64_t> a; - for (size_t j = 0; j < constraints_[i]->GetSize(); j++) + for (size_t j = 0; j < disjunctions_[i]->GetSingleConstraintsCount(); j++) { - const Constraint& constraint = constraints_[i]->GetConstraint(j); + const SingleConstraint& constraint = disjunctions_[i]->GetSingleConstraint(j); std::list<int64_t> b; - database.LookupIdentifier(b, level_, constraint.GetTag(), constraint.GetType(), constraint.GetValue()); + database.LookupIdentifier(b, level_, constraint.GetTag(), + constraint.GetType(), constraint.GetValue()); + + a.splice(a.end(), b); + } + + for (size_t j = 0; j < disjunctions_[i]->GetRangeConstraintsCount(); j++) + { + const RangeConstraint& constraint = disjunctions_[i]->GetRangeConstraint(j); + std::list<int64_t> b; + database.LookupIdentifierRange(b, level_, constraint.GetTag(), + constraint.GetStart(), constraint.GetEnd()); a.splice(a.end(), b); } @@ -122,17 +156,17 @@ void LookupIdentifierQuery::Print(std::ostream& s) const { s << "Constraint: " << std::endl; - for (Constraints::const_iterator - it = constraints_.begin(); it != constraints_.end(); ++it) + for (Disjunctions::const_iterator + it = disjunctions_.begin(); it != disjunctions_.end(); ++it) { - if (it == constraints_.begin()) + if (it == disjunctions_.begin()) s << " "; else s << "OR "; - for (size_t j = 0; j < (*it)->GetSize(); j++) + for (size_t j = 0; j < (*it)->GetSingleConstraintsCount(); j++) { - const Constraint& c = (*it)->GetConstraint(j); + const SingleConstraint& c = (*it)->GetSingleConstraint(j); s << FromDcmtkBridge::GetTagName(c.GetTag(), ""); switch (c.GetType())
--- a/OrthancServer/Search/LookupIdentifierQuery.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/Search/LookupIdentifierQuery.h Fri Jul 13 10:07:45 2018 +0200 @@ -65,8 +65,11 @@ class LookupIdentifierQuery : public boost::noncopyable { + // This class encodes a conjunction ("AND") of disjunctions. Each + // disjunction represents an "OR" of several constraints. + public: - class Constraint + class SingleConstraint { private: DicomTag tag_; @@ -74,9 +77,9 @@ std::string value_; public: - Constraint(const DicomTag& tag, - IdentifierConstraintType type, - const std::string& value) : + SingleConstraint(const DicomTag& tag, + IdentifierConstraintType type, + const std::string& value) : tag_(tag), type_(type), value_(ServerToolbox::NormalizeIdentifier(value)) @@ -100,10 +103,45 @@ }; + class RangeConstraint + { + private: + DicomTag tag_; + std::string start_; + std::string end_; + + public: + RangeConstraint(const DicomTag& tag, + const std::string& start, + const std::string& end) : + tag_(tag), + start_(ServerToolbox::NormalizeIdentifier(start)), + end_(ServerToolbox::NormalizeIdentifier(end)) + { + } + + const DicomTag& GetTag() const + { + return tag_; + } + + const std::string& GetStart() const + { + return start_; + } + + const std::string& GetEnd() const + { + return end_; + } + }; + + class Disjunction : public boost::noncopyable { private: - std::vector<Constraint*> disjunction_; + std::vector<SingleConstraint*> singleConstraints_; + std::vector<RangeConstraint*> rangeConstraints_; public: ~Disjunction(); @@ -112,23 +150,37 @@ IdentifierConstraintType type, const std::string& value); - size_t GetSize() const + void AddRange(const DicomTag& tag, + const std::string& start, + const std::string& end); + + size_t GetSingleConstraintsCount() const { - return disjunction_.size(); + return singleConstraints_.size(); } - const Constraint& GetConstraint(size_t i) const + const SingleConstraint& GetSingleConstraint(size_t i) const + { + return *singleConstraints_[i]; + } + + size_t GetRangeConstraintsCount() const { - return *disjunction_[i]; + return rangeConstraints_.size(); + } + + const RangeConstraint& GetRangeConstraint(size_t i) const + { + return *rangeConstraints_[i]; } }; private: - typedef std::vector<Disjunction*> Constraints; + typedef std::vector<Disjunction*> Disjunctions; ResourceType level_; - Constraints constraints_; + Disjunctions disjunctions_; public: LookupIdentifierQuery(ResourceType level) : level_(level) @@ -146,6 +198,10 @@ IdentifierConstraintType type, const std::string& value); + void AddRange(DicomTag tag, + const std::string& start, + const std::string& end); + Disjunction& AddDisjunction(); ResourceType GetLevel() const @@ -153,11 +209,6 @@ return level_; } - size_t GetSize() const - { - return constraints_.size(); - } - // The database must be locked void Apply(std::list<std::string>& result, IDatabaseWrapper& database);
--- a/OrthancServer/Search/RangeConstraint.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/Search/RangeConstraint.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -59,14 +59,22 @@ void RangeConstraint::Setup(LookupIdentifierQuery& lookup, const DicomTag& tag) const { - if (!lower_.empty()) + if (!lower_.empty() && + !upper_.empty()) { - lookup.AddConstraint(tag, IdentifierConstraintType_GreaterOrEqual, lower_); + lookup.AddRange(tag, lower_, upper_); } + else + { + if (!lower_.empty()) + { + lookup.AddConstraint(tag, IdentifierConstraintType_GreaterOrEqual, lower_); + } - if (!upper_.empty()) - { - lookup.AddConstraint(tag, IdentifierConstraintType_SmallerOrEqual, upper_); + if (!upper_.empty()) + { + lookup.AddConstraint(tag, IdentifierConstraintType_SmallerOrEqual, upper_); + } } }
--- a/OrthancServer/ServerContext.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/ServerContext.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -34,27 +34,22 @@ #include "PrecompiledHeadersServer.h" #include "ServerContext.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/FileStorage/StorageAccessor.h" #include "../Core/HttpServer/FilesystemHttpSender.h" #include "../Core/HttpServer/HttpStreamTranscoder.h" #include "../Core/Logging.h" -#include "../Core/DicomParsing/FromDcmtkBridge.h" +#include "../Plugins/Engine/OrthancPlugins.h" +#include "OrthancInitialization.h" +#include "OrthancRestApi/OrthancRestApi.h" +#include "Search/LookupResource.h" +#include "ServerJobs/OrthancJobUnserializer.h" #include "ServerToolbox.h" -#include "OrthancInitialization.h" #include <EmbeddedResources.h> #include <dcmtk/dcmdata/dcfilefo.h> -#include "Scheduler/CallSystemCommand.h" -#include "Scheduler/DeleteInstanceCommand.h" -#include "Scheduler/ModifyInstanceCommand.h" -#include "Scheduler/StoreScuCommand.h" -#include "Scheduler/StorePeerCommand.h" -#include "OrthancRestApi/OrthancRestApi.h" -#include "../Plugins/Engine/OrthancPlugins.h" -#include "Search/LookupResource.h" - #define ENABLE_DICOM_CACHE 1 @@ -71,11 +66,12 @@ namespace Orthanc { - void ServerContext::ChangeThread(ServerContext* that) + void ServerContext::ChangeThread(ServerContext* that, + unsigned int sleepDelay) { while (!that->done_) { - std::auto_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(100)); + std::auto_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(sleepDelay)); if (obj.get() != NULL) { @@ -112,29 +108,138 @@ } + void ServerContext::SaveJobsThread(ServerContext* that, + unsigned int sleepDelay) + { + static const boost::posix_time::time_duration PERIODICITY = + boost::posix_time::seconds(10); + + boost::posix_time::ptime next = + boost::posix_time::microsec_clock::universal_time() + PERIODICITY; + + while (!that->done_) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(sleepDelay)); + + if (that->haveJobsChanged_ || + boost::posix_time::microsec_clock::universal_time() >= next) + { + that->haveJobsChanged_ = false; + that->SaveJobsEngine(); + next = boost::posix_time::microsec_clock::universal_time() + PERIODICITY; + } + } + } + + + void ServerContext::SignalJobSubmitted(const std::string& jobId) + { + haveJobsChanged_ = true; + lua_.SignalJobSubmitted(jobId); + } + + + void ServerContext::SignalJobSuccess(const std::string& jobId) + { + haveJobsChanged_ = true; + lua_.SignalJobSuccess(jobId); + } + + + void ServerContext::SignalJobFailure(const std::string& jobId) + { + haveJobsChanged_ = true; + lua_.SignalJobFailure(jobId); + } + + + void ServerContext::SetupJobsEngine(bool unitTesting, + bool loadJobsFromDatabase) + { + jobsEngine_.SetWorkersCount(Configuration::GetGlobalUnsignedIntegerParameter("ConcurrentJobs", 2)); + jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200); + + if (loadJobsFromDatabase) + { + std::string serialized; + if (index_.LookupGlobalProperty(serialized, GlobalProperty_JobsRegistry)) + { + LOG(WARNING) << "Reloading the jobs from the last execution of Orthanc"; + OrthancJobUnserializer unserializer(*this); + + try + { + jobsEngine_.LoadRegistryFromString(unserializer, serialized); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Cannot unserialize the jobs engine: " << e.What(); + throw; + } + } + else + { + LOG(INFO) << "The last execution of Orthanc has archived no job"; + } + } + else + { + LOG(WARNING) << "Not reloading the jobs from the last execution of Orthanc"; + } + + //jobsEngine_.GetRegistry().SetMaxCompleted // TODO + + jobsEngine_.GetRegistry().SetObserver(*this); + jobsEngine_.Start(); + } + + + void ServerContext::SaveJobsEngine() + { + VLOG(1) << "Serializing the content of the jobs engine"; + + try + { + Json::Value value; + jobsEngine_.GetRegistry().Serialize(value); + + Json::FastWriter writer; + std::string serialized = writer.write(value); + + index_.SetGlobalProperty(GlobalProperty_JobsRegistry, serialized); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Cannot serialize the jobs engine: " << e.What(); + } + } + + ServerContext::ServerContext(IDatabaseWrapper& database, - IStorageArea& area) : - index_(*this, database), + IStorageArea& area, + bool unitTesting, + bool loadJobsFromDatabase) : + index_(*this, database, (unitTesting ? 20 : 500)), area_(area), compressionEnabled_(false), storeMD5_(true), provider_(*this), dicomCache_(provider_, DICOM_CACHE_SIZE), - scheduler_(Configuration::GetGlobalUnsignedIntegerParameter("LimitJobs", 10)), lua_(*this), #if ORTHANC_ENABLE_PLUGINS == 1 plugins_(NULL), #endif done_(false), + haveJobsChanged_(false), queryRetrieveArchive_(Configuration::GetGlobalUnsignedIntegerParameter("QueryRetrieveSize", 10)), defaultLocalAet_(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC")) { - uint64_t s = Configuration::GetGlobalUnsignedIntegerParameter("DicomAssociationCloseDelay", 5); // In seconds - scu_.SetMillisecondsBeforeClose(s * 1000); // Milliseconds are expected here - listeners_.push_back(ServerListener(lua_, "Lua")); - changeThread_ = boost::thread(ChangeThread, this); + SetupJobsEngine(unitTesting, loadJobsFromDatabase); + + changeThread_ = boost::thread(ChangeThread, this, (unitTesting ? 20 : 100)); + saveJobsThread_ = boost::thread(SaveJobsThread, this, (unitTesting ? 20 : 100)); } @@ -165,10 +270,16 @@ changeThread_.join(); } - scu_.Finalize(); + if (saveJobsThread_.joinable()) + { + saveJobsThread_.join(); + } + + jobsEngine_.GetRegistry().ResetObserver(); + SaveJobsEngine(); // Do not change the order below! - scheduler_.Stop(); + jobsEngine_.Stop(); index_.Stop(); } } @@ -679,4 +790,19 @@ } } + + void ServerContext::AddChildInstances(SetOfInstancesJob& job, + const std::string& publicId) + { + std::list<std::string> instances; + GetIndex().GetChildInstances(instances, publicId); + + job.Reserve(job.GetInstancesCount() + instances.size()); + + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + job.AddInstance(*it); + } + } }
--- a/OrthancServer/ServerContext.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/ServerContext.h Fri Jul 13 10:07:45 2018 +0200 @@ -33,21 +33,21 @@ #pragma once -#include "../Core/MultiThreading/SharedMessageQueue.h" +#include "DicomInstanceToStore.h" +#include "IServerListener.h" +#include "LuaScripting.h" +#include "OrthancHttpHandler.h" +#include "ServerIndex.h" + #include "../Core/Cache/MemoryCache.h" #include "../Core/Cache/SharedArchive.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" #include "../Core/FileStorage/IStorageArea.h" -#include "../Core/Lua/LuaContext.h" +#include "../Core/JobsEngine/JobsEngine.h" +#include "../Core/JobsEngine/SetOfInstancesJob.h" +#include "../Core/MultiThreading/SharedMessageQueue.h" #include "../Core/RestApi/RestApiOutput.h" #include "../Plugins/Engine/OrthancPlugins.h" -#include "DicomInstanceToStore.h" -#include "../Core/DicomNetworking/ReusableDicomUserConnection.h" -#include "IServerListener.h" -#include "LuaScripting.h" -#include "../Core/DicomParsing/ParsedDicomFile.h" -#include "Scheduler/ServerScheduler.h" -#include "ServerIndex.h" -#include "OrthancHttpHandler.h" #include <boost/filesystem.hpp> #include <boost/thread.hpp> @@ -60,7 +60,7 @@ * filesystem (including compression), as well as the index of the * DICOM store. It implements the required locking mechanisms. **/ - class ServerContext + class ServerContext : private JobsRegistry::IObserver { private: class DicomCacheProvider : public ICachePageProvider @@ -104,11 +104,26 @@ typedef std::list<ServerListener> ServerListeners; - static void ChangeThread(ServerContext* that); + static void ChangeThread(ServerContext* that, + unsigned int sleepDelay); + + static void SaveJobsThread(ServerContext* that, + unsigned int sleepDelay); void ReadDicomAsJsonInternal(std::string& result, const std::string& instancePublicId); + void SetupJobsEngine(bool unitTesting, + bool loadJobsFromDatabase); + + void SaveJobsEngine(); + + virtual void SignalJobSubmitted(const std::string& jobId); + + virtual void SignalJobSuccess(const std::string& jobId); + + virtual void SignalJobFailure(const std::string& jobId); + ServerIndex index_; IStorageArea& area_; @@ -118,8 +133,7 @@ DicomCacheProvider provider_; boost::mutex dicomCacheMutex_; MemoryCache dicomCache_; - ReusableDicomUserConnection scu_; - ServerScheduler scheduler_; + JobsEngine jobsEngine_; LuaScripting lua_; @@ -131,8 +145,10 @@ boost::recursive_mutex listenersMutex_; bool done_; + bool haveJobsChanged_; SharedMessageQueue pendingChanges_; boost::thread changeThread_; + boost::thread saveJobsThread_; SharedArchive queryRetrieveArchive_; std::string defaultLocalAet_; @@ -159,7 +175,9 @@ }; ServerContext(IDatabaseWrapper& database, - IStorageArea& area); + IStorageArea& area, + bool unitTesting, + bool loadJobsFromDatabase); ~ServerContext(); @@ -238,14 +256,9 @@ return storeMD5_; } - ReusableDicomUserConnection& GetReusableDicomUserConnection() + JobsEngine& GetJobsEngine() { - return scu_; - } - - ServerScheduler& GetScheduler() - { - return scheduler_; + return jobsEngine_; } bool DeleteResource(Json::Value& target, @@ -264,7 +277,7 @@ return defaultLocalAet_; } - LuaScripting& GetLua() + LuaScripting& GetLuaScripting() { return lua_; } @@ -297,5 +310,8 @@ #endif bool HasPlugins() const; + + void AddChildInstances(SetOfInstancesJob& job, + const std::string& publicId); }; }
--- a/OrthancServer/ServerEnumerations.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/ServerEnumerations.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -67,6 +67,9 @@ dictMetadataType_.Add(MetadataType_Instance_Origin, "Origin"); dictMetadataType_.Add(MetadataType_Instance_TransferSyntax, "TransferSyntax"); dictMetadataType_.Add(MetadataType_Instance_SopClassUid, "SopClassUid"); + dictMetadataType_.Add(MetadataType_Instance_RemoteIp, "RemoteIP"); + dictMetadataType_.Add(MetadataType_Instance_CalledAet, "CalledAET"); + dictMetadataType_.Add(MetadataType_Instance_HttpUsername, "HttpUsername"); dictContentType_.Add(FileContentType_Dicom, "dicom"); dictContentType_.Add(FileContentType_DicomAsJson, "dicom-as-json");
--- a/OrthancServer/ServerEnumerations.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/ServerEnumerations.h Fri Jul 13 10:07:45 2018 +0200 @@ -76,7 +76,20 @@ GlobalProperty_DatabaseSchemaVersion = 1, // Unused in the Orthanc core as of Orthanc 0.9.5 GlobalProperty_FlushSleep = 2, GlobalProperty_AnonymizationSequence = 3, - GlobalProperty_DatabasePatchLevel = 4 // Reserved for internal use of the database plugins + GlobalProperty_JobsRegistry = 5, + + // Reserved values for internal use by the database plugins + GlobalProperty_DatabasePatchLevel = 4, + GlobalProperty_DatabaseInternal0 = 10, + GlobalProperty_DatabaseInternal1 = 11, + GlobalProperty_DatabaseInternal2 = 12, + GlobalProperty_DatabaseInternal3 = 13, + GlobalProperty_DatabaseInternal4 = 14, + GlobalProperty_DatabaseInternal5 = 15, + GlobalProperty_DatabaseInternal6 = 16, + GlobalProperty_DatabaseInternal7 = 17, + GlobalProperty_DatabaseInternal8 = 18, + GlobalProperty_DatabaseInternal9 = 19 }; enum MetadataType @@ -91,6 +104,9 @@ MetadataType_Instance_Origin = 8, // New in Orthanc 0.9.5 MetadataType_Instance_TransferSyntax = 9, // New in Orthanc 1.2.0 MetadataType_Instance_SopClassUid = 10, // New in Orthanc 1.2.0 + MetadataType_Instance_RemoteIp = 11, // New in Orthanc 1.4.0 + MetadataType_Instance_CalledAet = 12, // New in Orthanc 1.4.0 + MetadataType_Instance_HttpUsername = 13, // New in Orthanc 1.4.0 // Make sure that the value "65535" can be stored into this enumeration MetadataType_StartUser = 1024,
--- a/OrthancServer/ServerIndex.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/ServerIndex.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -342,7 +342,8 @@ } - void ServerIndex::FlushThread(ServerIndex* that) + void ServerIndex::FlushThread(ServerIndex* that, + unsigned int threadSleep) { // By default, wait for 10 seconds before flushing unsigned int sleep = 10; @@ -368,7 +369,7 @@ while (!that->done_) { - boost::this_thread::sleep(boost::posix_time::seconds(1)); + boost::this_thread::sleep(boost::posix_time::milliseconds(threadSleep)); count++; if (count < sleep) { @@ -538,7 +539,8 @@ ServerIndex::ServerIndex(ServerContext& context, - IDatabaseWrapper& db) : + IDatabaseWrapper& db, + unsigned int threadSleep) : done_(false), db_(db), maximumStorageSize_(0), @@ -555,10 +557,11 @@ if (db.HasFlushToDisk()) { - flushThread_ = boost::thread(FlushThread, this); + flushThread_ = boost::thread(FlushThread, this, threadSleep); } - unstableResourcesMonitorThread_ = boost::thread(UnstableResourcesMonitorThread, this); + unstableResourcesMonitorThread_ = boost::thread + (UnstableResourcesMonitorThread, this, threadSleep); } @@ -778,16 +781,37 @@ // Attach the auto-computed metadata for the instance level, // reflecting these additions into the input metadata map SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_ReceptionDate, now); - SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_RemoteAet, instanceToStore.GetRemoteAet()); + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_RemoteAet, + instanceToStore.GetOrigin().GetRemoteAetC()); SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_Origin, - EnumerationToString(instanceToStore.GetRequestOrigin())); - + EnumerationToString(instanceToStore.GetOrigin().GetRequestOrigin())); + { std::string s; + if (instanceToStore.LookupTransferSyntax(s)) { + // New in Orthanc 1.2.0 SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_TransferSyntax, s); } + + if (instanceToStore.GetOrigin().LookupRemoteIp(s)) + { + // New in Orthanc 1.4.0 + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_RemoteIp, s); + } + + if (instanceToStore.GetOrigin().LookupCalledAet(s)) + { + // New in Orthanc 1.4.0 + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_CalledAet, s); + } + + if (instanceToStore.GetOrigin().LookupHttpUsername(s)) + { + // New in Orthanc 1.4.0 + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_HttpUsername, s); + } } const DicomValue* value; @@ -1182,7 +1206,12 @@ { boost::mutex::scoped_lock lock(mutex_); + + // Fix wrt. Orthanc <= 1.3.2: A transaction was missing, as + // "GetLastChange()" involves calls to "GetPublicId()" + Transaction transaction(*this); db_.GetChanges(changes, done, since, maxResults); + transaction.Commit(0); } FormatLog(target, changes, "Changes", done, since); @@ -1195,7 +1224,12 @@ { boost::mutex::scoped_lock lock(mutex_); + + // Fix wrt. Orthanc <= 1.3.2: A transaction was missing, as + // "GetLastChange()" involves calls to "GetPublicId()" + Transaction transaction(*this); db_.GetLastChange(changes); + transaction.Commit(0); } FormatLog(target, changes, "Changes", true, 0); @@ -1212,7 +1246,7 @@ ResourceType type; if (!db_.LookupResource(id, type, publicId)) { - throw OrthancException(ErrorCode_InternalError); + throw OrthancException(ErrorCode_InexistentItem); } std::string patientId; @@ -1877,7 +1911,8 @@ } - void ServerIndex::UnstableResourcesMonitorThread(ServerIndex* that) + void ServerIndex::UnstableResourcesMonitorThread(ServerIndex* that, + unsigned int threadSleep) { int stableAge = Configuration::GetGlobalUnsignedIntegerParameter("StableAge", 60); if (stableAge <= 0) @@ -1889,8 +1924,8 @@ while (!that->done_) { - // Check for stable resources each second - boost::this_thread::sleep(boost::posix_time::seconds(1)); + // Check for stable resources each few seconds + boost::this_thread::sleep(boost::posix_time::milliseconds(threadSleep)); boost::mutex::scoped_lock lock(that->mutex_); @@ -2091,13 +2126,20 @@ } + bool ServerIndex::LookupGlobalProperty(std::string& value, + GlobalProperty property) + { + boost::mutex::scoped_lock lock(mutex_); + return db_.LookupGlobalProperty(value, property); + } + + std::string ServerIndex::GetGlobalProperty(GlobalProperty property, const std::string& defaultValue) { - boost::mutex::scoped_lock lock(mutex_); - std::string value; - if (db_.LookupGlobalProperty(value, property)) + + if (LookupGlobalProperty(value, property)) { return value; }
--- a/OrthancServer/ServerIndex.h Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/ServerIndex.h Fri Jul 13 10:07:45 2018 +0200 @@ -74,9 +74,11 @@ uint64_t maximumStorageSize_; unsigned int maximumPatients_; - static void FlushThread(ServerIndex* that); + static void FlushThread(ServerIndex* that, + unsigned int threadSleep); - static void UnstableResourcesMonitorThread(ServerIndex* that); + static void UnstableResourcesMonitorThread(ServerIndex* that, + unsigned int threadSleep); void MainDicomTagsToJson(Json::Value& result, int64_t resourceId, @@ -124,7 +126,8 @@ public: ServerIndex(ServerContext& context, - IDatabaseWrapper& database); + IDatabaseWrapper& database, + unsigned int threadSleep); ~ServerIndex(); @@ -255,6 +258,9 @@ void SetGlobalProperty(GlobalProperty property, const std::string& value); + bool LookupGlobalProperty(std::string& value, + GlobalProperty property); + std::string GetGlobalProperty(GlobalProperty property, const std::string& defaultValue);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/ArchiveJob.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,904 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "ArchiveJob.h" + +#include "../../Core/Compression/HierarchicalZipWriter.h" +#include "../../Core/DicomParsing/DicomDirWriter.h" +#include "../../Core/Logging.h" +#include "../../Core/OrthancException.h" + +#include <stdio.h> + +#if defined(_MSC_VER) +#define snprintf _snprintf +#endif + +static const uint64_t MEGA_BYTES = 1024 * 1024; +static const uint64_t GIGA_BYTES = 1024 * 1024 * 1024; +static const char* MEDIA_IMAGES_FOLDER = "IMAGES"; + +namespace Orthanc +{ + static bool IsZip64Required(uint64_t uncompressedSize, + unsigned int countInstances) + { + static const uint64_t SAFETY_MARGIN = 64 * MEGA_BYTES; // Should be large enough to hold DICOMDIR + static const unsigned int FILES_MARGIN = 10; + + /** + * Determine whether ZIP64 is required. Original ZIP format can + * store up to 2GB of data (some implementation supporting up to + * 4GB of data), and up to 65535 files. + * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 + **/ + + const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES - SAFETY_MARGIN || + countInstances >= 65535 - FILES_MARGIN); + + LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " + << (uncompressedSize / MEGA_BYTES) << "MB using the " + << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; + + return isZip64; + } + + + class ArchiveJob::ResourceIdentifiers : public boost::noncopyable + { + private: + ResourceType level_; + std::string patient_; + std::string study_; + std::string series_; + std::string instance_; + + static void GoToParent(ServerIndex& index, + std::string& current) + { + std::string tmp; + + if (index.LookupParent(tmp, current)) + { + current = tmp; + } + else + { + throw OrthancException(ErrorCode_UnknownResource); + } + } + + + public: + ResourceIdentifiers(ServerIndex& index, + const std::string& publicId) + { + if (!index.LookupResourceType(level_, publicId)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + std::string current = publicId;; + switch (level_) // Do not add "break" below! + { + case ResourceType_Instance: + instance_ = current; + GoToParent(index, current); + + case ResourceType_Series: + series_ = current; + GoToParent(index, current); + + case ResourceType_Study: + study_ = current; + GoToParent(index, current); + + case ResourceType_Patient: + patient_ = current; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + ResourceType GetLevel() const + { + return level_; + } + + const std::string& GetIdentifier(ResourceType level) const + { + // Some sanity check to ensure enumerations are not altered + assert(ResourceType_Patient < ResourceType_Study); + assert(ResourceType_Study < ResourceType_Series); + assert(ResourceType_Series < ResourceType_Instance); + + if (level > level_) + { + throw OrthancException(ErrorCode_InternalError); + } + + switch (level) + { + case ResourceType_Patient: + return patient_; + + case ResourceType_Study: + return study_; + + case ResourceType_Series: + return series_; + + case ResourceType_Instance: + return instance_; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + }; + + + class ArchiveJob::IArchiveVisitor : public boost::noncopyable + { + public: + virtual ~IArchiveVisitor() + { + } + + virtual void Open(ResourceType level, + const std::string& publicId) = 0; + + virtual void Close() = 0; + + virtual void AddInstance(const std::string& instanceId, + const FileInfo& dicom) = 0; + }; + + + class ArchiveJob::ArchiveIndex : public boost::noncopyable + { + private: + struct Instance + { + std::string id_; + FileInfo dicom_; + + Instance(const std::string& id, + const FileInfo& dicom) : + id_(id), dicom_(dicom) + { + } + }; + + // A "NULL" value for ArchiveIndex indicates a non-expanded node + typedef std::map<std::string, ArchiveIndex*> Resources; + + ResourceType level_; + Resources resources_; // Only at patient/study/series level + std::list<Instance> instances_; // Only at instance level + + + void AddResourceToExpand(ServerIndex& index, + const std::string& id) + { + if (level_ == ResourceType_Instance) + { + FileInfo tmp; + if (index.LookupAttachment(tmp, id, FileContentType_Dicom)) + { + instances_.push_back(Instance(id, tmp)); + } + } + else + { + resources_[id] = NULL; + } + } + + + public: + ArchiveIndex(ResourceType level) : + level_(level) + { + } + + ~ArchiveIndex() + { + for (Resources::iterator it = resources_.begin(); + it != resources_.end(); ++it) + { + delete it->second; + } + } + + + void Add(ServerIndex& index, + const ResourceIdentifiers& resource) + { + const std::string& id = resource.GetIdentifier(level_); + Resources::iterator previous = resources_.find(id); + + if (level_ == ResourceType_Instance) + { + AddResourceToExpand(index, id); + } + else if (resource.GetLevel() == level_) + { + // Mark this resource for further expansion + if (previous != resources_.end()) + { + delete previous->second; + } + + resources_[id] = NULL; + } + else if (previous == resources_.end()) + { + // This is the first time we meet this resource + std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); + child->Add(index, resource); + resources_[id] = child.release(); + } + else if (previous->second != NULL) + { + previous->second->Add(index, resource); + } + else + { + // Nothing to do: This item is marked for further expansion + } + } + + + void Expand(ServerIndex& index) + { + if (level_ == ResourceType_Instance) + { + // Expanding an instance node makes no sense + return; + } + + for (Resources::iterator it = resources_.begin(); + it != resources_.end(); ++it) + { + if (it->second == NULL) + { + // This is resource is marked for expansion + std::list<std::string> children; + index.GetChildren(children, it->first); + + std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); + + for (std::list<std::string>::const_iterator + it2 = children.begin(); it2 != children.end(); ++it2) + { + child->AddResourceToExpand(index, *it2); + } + + it->second = child.release(); + } + + assert(it->second != NULL); + it->second->Expand(index); + } + } + + + void Apply(IArchiveVisitor& visitor) const + { + if (level_ == ResourceType_Instance) + { + for (std::list<Instance>::const_iterator + it = instances_.begin(); it != instances_.end(); ++it) + { + visitor.AddInstance(it->id_, it->dicom_); + } + } + else + { + for (Resources::const_iterator it = resources_.begin(); + it != resources_.end(); ++it) + { + assert(it->second != NULL); // There must have been a call to "Expand()" + visitor.Open(level_, it->first); + it->second->Apply(visitor); + visitor.Close(); + } + } + } + }; + + + + class ArchiveJob::ZipCommands : public boost::noncopyable + { + private: + enum Type + { + Type_OpenDirectory, + Type_CloseDirectory, + Type_WriteInstance + }; + + class Command : public boost::noncopyable + { + private: + Type type_; + std::string filename_; + std::string instanceId_; + FileInfo info_; + + public: + explicit Command(Type type) : + type_(type) + { + assert(type_ == Type_CloseDirectory); + } + + Command(Type type, + const std::string& filename) : + type_(type), + filename_(filename) + { + assert(type_ == Type_OpenDirectory); + } + + Command(Type type, + const std::string& filename, + const std::string& instanceId, + const FileInfo& info) : + type_(type), + filename_(filename), + instanceId_(instanceId), + info_(info) + { + assert(type_ == Type_WriteInstance); + } + + void Apply(HierarchicalZipWriter& writer, + ServerContext& context, + DicomDirWriter* dicomDir, + const std::string& dicomDirFolder) const + { + switch (type_) + { + case Type_OpenDirectory: + writer.OpenDirectory(filename_.c_str()); + break; + + case Type_CloseDirectory: + writer.CloseDirectory(); + break; + + case Type_WriteInstance: + { + std::string content; + + try + { + context.ReadAttachment(content, info_); + } + catch (OrthancException& e) + { + LOG(WARNING) << "An instance was removed after the job was issued: " << instanceId_; + return; + } + + //boost::this_thread::sleep(boost::posix_time::milliseconds(300)); + + writer.OpenFile(filename_.c_str()); + writer.Write(content); + + if (dicomDir != NULL) + { + ParsedDicomFile parsed(content); + dicomDir->Add(dicomDirFolder, filename_, parsed); + } + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + }; + + std::deque<Command*> commands_; + uint64_t uncompressedSize_; + unsigned int instancesCount_; + + + void ApplyInternal(HierarchicalZipWriter& writer, + ServerContext& context, + size_t index, + DicomDirWriter* dicomDir, + const std::string& dicomDirFolder) const + { + if (index >= commands_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + commands_[index]->Apply(writer, context, dicomDir, dicomDirFolder); + } + + public: + ZipCommands() : + uncompressedSize_(0), + instancesCount_(0) + { + } + + ~ZipCommands() + { + for (std::deque<Command*>::iterator it = commands_.begin(); + it != commands_.end(); ++it) + { + assert(*it != NULL); + delete *it; + } + } + + size_t GetSize() const + { + return commands_.size(); + } + + unsigned int GetInstancesCount() const + { + return instancesCount_; + } + + uint64_t GetUncompressedSize() const + { + return uncompressedSize_; + } + + void Apply(HierarchicalZipWriter& writer, + ServerContext& context, + size_t index, + DicomDirWriter& dicomDir, + const std::string& dicomDirFolder) const + { + ApplyInternal(writer, context, index, &dicomDir, dicomDirFolder); + } + + void Apply(HierarchicalZipWriter& writer, + ServerContext& context, + size_t index) const + { + ApplyInternal(writer, context, index, NULL, ""); + } + + void AddOpenDirectory(const std::string& filename) + { + commands_.push_back(new Command(Type_OpenDirectory, filename)); + } + + void AddCloseDirectory() + { + commands_.push_back(new Command(Type_CloseDirectory)); + } + + void AddWriteInstance(const std::string& filename, + const std::string& instanceId, + const FileInfo& info) + { + commands_.push_back(new Command(Type_WriteInstance, filename, instanceId, info)); + instancesCount_ ++; + uncompressedSize_ += info.GetUncompressedSize(); + } + + bool IsZip64() const + { + return IsZip64Required(GetUncompressedSize(), GetInstancesCount()); + } + }; + + + + class ArchiveJob::ArchiveIndexVisitor : public IArchiveVisitor + { + private: + ZipCommands& commands_; + ServerContext& context_; + char instanceFormat_[24]; + unsigned int counter_; + + static std::string GetTag(const DicomMap& tags, + const DicomTag& tag) + { + const DicomValue* v = tags.TestAndGetValue(tag); + if (v != NULL && + !v->IsBinary() && + !v->IsNull()) + { + return v->GetContent(); + } + else + { + return ""; + } + } + + public: + ArchiveIndexVisitor(ZipCommands& commands, + ServerContext& context) : + commands_(commands), + context_(context), + counter_(0) + { + if (commands.GetSize() != 0) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); + } + + virtual void Open(ResourceType level, + const std::string& publicId) + { + std::string path; + + DicomMap tags; + if (context_.GetIndex().GetMainDicomTags(tags, publicId, level, level)) + { + switch (level) + { + case ResourceType_Patient: + path = GetTag(tags, DICOM_TAG_PATIENT_ID) + " " + GetTag(tags, DICOM_TAG_PATIENT_NAME); + break; + + case ResourceType_Study: + path = GetTag(tags, DICOM_TAG_ACCESSION_NUMBER) + " " + GetTag(tags, DICOM_TAG_STUDY_DESCRIPTION); + break; + + case ResourceType_Series: + { + std::string modality = GetTag(tags, DICOM_TAG_MODALITY); + path = modality + " " + GetTag(tags, DICOM_TAG_SERIES_DESCRIPTION); + + if (modality.size() == 0) + { + snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); + } + else if (modality.size() == 1) + { + snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%%07d.dcm", + toupper(modality[0])); + } + else if (modality.size() >= 2) + { + snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%c%%06d.dcm", + toupper(modality[0]), toupper(modality[1])); + } + + counter_ = 0; + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + path = Toolbox::StripSpaces(Toolbox::ConvertToAscii(path)); + + if (path.empty()) + { + path = std::string("Unknown ") + EnumerationToString(level); + } + + commands_.AddOpenDirectory(path.c_str()); + } + + virtual void Close() + { + commands_.AddCloseDirectory(); + } + + virtual void AddInstance(const std::string& instanceId, + const FileInfo& dicom) + { + char filename[24]; + snprintf(filename, sizeof(filename) - 1, instanceFormat_, counter_); + counter_ ++; + + commands_.AddWriteInstance(filename, instanceId, dicom); + } + }; + + + class ArchiveJob::MediaIndexVisitor : public IArchiveVisitor + { + private: + ZipCommands& commands_; + ServerContext& context_; + unsigned int counter_; + + public: + MediaIndexVisitor(ZipCommands& commands, + ServerContext& context) : + commands_(commands), + context_(context), + counter_(0) + { + } + + virtual void Open(ResourceType level, + const std::string& publicId) + { + } + + virtual void Close() + { + } + + virtual void AddInstance(const std::string& instanceId, + const FileInfo& dicom) + { + // "DICOM restricts the filenames on DICOM media to 8 + // characters (some systems wrongly use 8.3, but this does not + // conform to the standard)." + std::string filename = "IM" + boost::lexical_cast<std::string>(counter_); + commands_.AddWriteInstance(filename, instanceId, dicom); + + counter_ ++; + } + }; + + + class ArchiveJob::ZipWriterIterator : public boost::noncopyable + { + private: + TemporaryFile& target_; + ServerContext& context_; + ZipCommands commands_; + std::auto_ptr<HierarchicalZipWriter> zip_; + std::auto_ptr<DicomDirWriter> dicomDir_; + bool isMedia_; + + public: + ZipWriterIterator(TemporaryFile& target, + ServerContext& context, + ArchiveIndex& archive, + bool isMedia, + bool enableExtendedSopClass) : + target_(target), + context_(context), + isMedia_(isMedia) + { + if (isMedia) + { + MediaIndexVisitor visitor(commands_, context); + archive.Expand(context.GetIndex()); + + commands_.AddOpenDirectory(MEDIA_IMAGES_FOLDER); + archive.Apply(visitor); + commands_.AddCloseDirectory(); + + dicomDir_.reset(new DicomDirWriter); + dicomDir_->EnableExtendedSopClass(enableExtendedSopClass); + } + else + { + ArchiveIndexVisitor visitor(commands_, context); + archive.Expand(context.GetIndex()); + archive.Apply(visitor); + } + + zip_.reset(new HierarchicalZipWriter(target.GetPath().c_str())); + zip_->SetZip64(commands_.IsZip64()); + } + + size_t GetStepsCount() const + { + return commands_.GetSize() + 1; + } + + void RunStep(size_t index) + { + if (index > commands_.GetSize()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else if (index == commands_.GetSize()) + { + // Last step: Add the DICOMDIR + if (isMedia_) + { + assert(dicomDir_.get() != NULL); + std::string s; + dicomDir_->Encode(s); + + zip_->OpenFile("DICOMDIR"); + zip_->Write(s); + } + } + else + { + if (isMedia_) + { + assert(dicomDir_.get() != NULL); + commands_.Apply(*zip_, context_, index, *dicomDir_, MEDIA_IMAGES_FOLDER); + } + else + { + assert(dicomDir_.get() == NULL); + commands_.Apply(*zip_, context_, index); + } + } + } + + unsigned int GetInstancesCount() const + { + return commands_.GetInstancesCount(); + } + + uint64_t GetUncompressedSize() const + { + return commands_.GetUncompressedSize(); + } + }; + + + ArchiveJob::ArchiveJob(boost::shared_ptr<TemporaryFile>& target, + ServerContext& context, + bool isMedia, + bool enableExtendedSopClass) : + target_(target), + context_(context), + archive_(new ArchiveIndex(ResourceType_Patient)), // root + isMedia_(isMedia), + enableExtendedSopClass_(enableExtendedSopClass), + currentStep_(0), + instancesCount_(0), + uncompressedSize_(0) + { + if (target.get() == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + } + + + void ArchiveJob::AddResource(const std::string& publicId) + { + if (writer_.get() != NULL) // Already started + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + ResourceIdentifiers resource(context_.GetIndex(), publicId); + archive_->Add(context_.GetIndex(), resource); + } + + + void ArchiveJob::SignalResubmit() + { + LOG(ERROR) << "Cannot resubmit the creation of an archive"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + + void ArchiveJob::Start() + { + if (writer_.get() != NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + writer_.reset(new ZipWriterIterator(*target_, context_, *archive_, + isMedia_, enableExtendedSopClass_)); + + instancesCount_ = writer_->GetInstancesCount(); + uncompressedSize_ = writer_->GetUncompressedSize(); + } + + + JobStepResult ArchiveJob::ExecuteStep() + { + assert(writer_.get() != NULL); + + if (target_.unique()) + { + LOG(WARNING) << "A client has disconnected while creating an archive"; + return JobStepResult::Failure(ErrorCode_NetworkProtocol); + } + + if (writer_->GetStepsCount() == 0) + { + writer_.reset(NULL); // Flush all the results + return JobStepResult::Success(); + } + else + { + writer_->RunStep(currentStep_); + + currentStep_ ++; + + if (currentStep_ == writer_->GetStepsCount()) + { + writer_.reset(NULL); // Flush all the results + return JobStepResult::Success(); + } + else + { + return JobStepResult::Continue(); + } + } + } + + + float ArchiveJob::GetProgress() + { + if (writer_.get() == NULL || + writer_->GetStepsCount() == 0) + { + return 1; + } + else + { + return (static_cast<float>(currentStep_) / + static_cast<float>(writer_->GetStepsCount() - 1)); + } + } + + + void ArchiveJob::GetJobType(std::string& target) + { + if (isMedia_) + { + target = "Media"; + } + else + { + target = "Archive"; + } + } + + + void ArchiveJob::GetPublicContent(Json::Value& value) + { + value["Description"] = description_; + value["InstancesCount"] = instancesCount_; + value["UncompressedSizeMB"] = + static_cast<unsigned int>(uncompressedSize_ / MEGA_BYTES); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/ArchiveJob.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,105 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Core/JobsEngine/IJob.h" +#include "../../Core/TemporaryFile.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class ArchiveJob : public IJob + { + private: + class ArchiveIndex; + class ArchiveIndexVisitor; + class IArchiveVisitor; + class MediaIndexVisitor; + class ResourceIdentifiers; + class ZipCommands; + class ZipWriterIterator; + class ZipWriterIterator; + + boost::shared_ptr<TemporaryFile> target_; + ServerContext& context_; + std::auto_ptr<ArchiveIndex> archive_; + bool isMedia_; + bool enableExtendedSopClass_; + std::string description_; + + std::auto_ptr<ZipWriterIterator> writer_; + size_t currentStep_; + unsigned int instancesCount_; + uint64_t uncompressedSize_; + + public: + ArchiveJob(boost::shared_ptr<TemporaryFile>& target, + ServerContext& context, + bool isMedia, + bool enableExtendedSopClass); + + void SetDescription(const std::string& description) + { + description_ = description; + } + + const std::string& GetDescription() const + { + return description_; + } + + void AddResource(const std::string& publicId); + + virtual void SignalResubmit(); + + virtual void Start(); + + virtual JobStepResult ExecuteStep(); + + virtual void ReleaseResources() + { + } + + virtual float GetProgress(); + + virtual void GetJobType(std::string& target); + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& value) + { + return false; // Cannot serialize this kind of job + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,222 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "DicomModalityStoreJob.h" + +#include "../../Core/Logging.h" +#include "../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + void DicomModalityStoreJob::OpenConnection() + { + if (connection_.get() == NULL) + { + connection_.reset(new DicomUserConnection); + connection_->SetLocalApplicationEntityTitle(localAet_); + connection_->SetRemoteModality(remote_); + } + } + + + bool DicomModalityStoreJob::HandleInstance(const std::string& instance) + { + assert(IsStarted()); + OpenConnection(); + + LOG(INFO) << "Sending instance " << instance << " to modality \"" + << remote_.GetApplicationEntityTitle() << "\""; + + std::string dicom; + + try + { + context_.ReadDicom(dicom, instance); + } + catch (OrthancException& e) + { + LOG(WARNING) << "An instance was removed after the job was issued: " << instance; + return false; + } + + if (HasMoveOriginator()) + { + connection_->Store(dicom, moveOriginatorAet_, moveOriginatorId_); + } + else + { + connection_->Store(dicom); + } + + //boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + + return true; + } + + + DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context) : + context_(context), + localAet_("ORTHANC"), + moveOriginatorId_(0) // By default, not a C-MOVE + { + } + + + void DicomModalityStoreJob::SetLocalAet(const std::string& aet) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + localAet_ = aet; + } + } + + + void DicomModalityStoreJob::SetRemoteModality(const RemoteModalityParameters& remote) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + remote_ = remote; + } + } + + + const std::string& DicomModalityStoreJob::GetMoveOriginatorAet() const + { + if (HasMoveOriginator()) + { + return moveOriginatorAet_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + uint16_t DicomModalityStoreJob::GetMoveOriginatorId() const + { + if (HasMoveOriginator()) + { + return moveOriginatorId_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + void DicomModalityStoreJob::SetMoveOriginator(const std::string& aet, + int id) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (id < 0 || + id >= 65536) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + moveOriginatorId_ = static_cast<uint16_t>(id); + moveOriginatorAet_ = aet; + } + } + + void DicomModalityStoreJob::ReleaseResources() // For pausing jobs + { + connection_.reset(NULL); + } + + + void DicomModalityStoreJob::GetPublicContent(Json::Value& value) + { + SetOfInstancesJob::GetPublicContent(value); + + value["LocalAet"] = localAet_; + value["RemoteAet"] = remote_.GetApplicationEntityTitle(); + + if (HasMoveOriginator()) + { + value["MoveOriginatorAET"] = GetMoveOriginatorAet(); + value["MoveOriginatorID"] = GetMoveOriginatorId(); + } + } + + + static const char* LOCAL_AET = "LocalAet"; + static const char* REMOTE = "Remote"; + static const char* MOVE_ORIGINATOR_AET = "MoveOriginatorAet"; + static const char* MOVE_ORIGINATOR_ID = "MoveOriginatorId"; + + + DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context, + const Json::Value& serialized) : + SetOfInstancesJob(serialized), + context_(context) + { + localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET); + remote_ = RemoteModalityParameters(serialized[REMOTE]); + moveOriginatorAet_ = SerializationToolbox::ReadString(serialized, MOVE_ORIGINATOR_AET); + moveOriginatorId_ = static_cast<uint16_t> + (SerializationToolbox::ReadUnsignedInteger(serialized, MOVE_ORIGINATOR_ID)); + } + + + bool DicomModalityStoreJob::Serialize(Json::Value& target) + { + if (!SetOfInstancesJob::Serialize(target)) + { + return false; + } + else + { + target[LOCAL_AET] = localAet_; + remote_.Serialize(target[REMOTE]); + target[MOVE_ORIGINATOR_AET] = moveOriginatorAet_; + target[MOVE_ORIGINATOR_ID] = moveOriginatorId_; + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,101 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Core/JobsEngine/SetOfInstancesJob.h" +#include "../../Core/DicomNetworking/DicomUserConnection.h" + +#include "../ServerContext.h" + +namespace Orthanc +{ + class DicomModalityStoreJob : public SetOfInstancesJob + { + private: + ServerContext& context_; + std::string localAet_; + RemoteModalityParameters remote_; + std::string moveOriginatorAet_; + uint16_t moveOriginatorId_; + std::auto_ptr<DicomUserConnection> connection_; + + void OpenConnection(); + + protected: + virtual bool HandleInstance(const std::string& instance); + + public: + DicomModalityStoreJob(ServerContext& context); + + DicomModalityStoreJob(ServerContext& context, + const Json::Value& serialized); + + const std::string& GetLocalAet() const + { + return localAet_; + } + + void SetLocalAet(const std::string& aet); + + const RemoteModalityParameters& GetRemoteModality() const + { + return remote_; + } + + void SetRemoteModality(const RemoteModalityParameters& remote); + + bool HasMoveOriginator() const + { + return moveOriginatorId_ != 0; + } + + const std::string& GetMoveOriginatorAet() const; + + uint16_t GetMoveOriginatorId() const; + + void SetMoveOriginator(const std::string& aet, + int id); + + virtual void ReleaseResources(); + + virtual void GetJobType(std::string& target) + { + target = "DicomModalityStore"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& target); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/LuaJobManager.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,268 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "LuaJobManager.h" + +#include "../OrthancInitialization.h" +#include "../../Core/Logging.h" + +#include "../../Core/JobsEngine/Operations/LogJobOperation.h" +#include "Operations/DeleteResourceOperation.h" +#include "Operations/ModifyInstanceOperation.h" +#include "Operations/StorePeerOperation.h" +#include "Operations/StoreScuOperation.h" +#include "Operations/SystemCallOperation.h" + +#include "../../Core/JobsEngine/Operations/NullOperationValue.h" +#include "../../Core/JobsEngine/Operations/StringOperationValue.h" +#include "Operations/DicomInstanceOperationValue.h" + +namespace Orthanc +{ + void LuaJobManager::SignalDone(const SequenceOfOperationsJob& job) + { + boost::mutex::scoped_lock lock(mutex_); + + if (&job == currentJob_) + { + currentId_.clear(); + currentJob_ = NULL; + } + } + + + LuaJobManager::LuaJobManager() : + currentJob_(NULL), + maxOperations_(1000), + priority_(0), + trailingTimeout_(5000) + { + dicomTimeout_ = Configuration::GetGlobalUnsignedIntegerParameter("DicomAssociationCloseDelay", 5); + LOG(INFO) << "Lua: DICOM associations will be closed after " + << dicomTimeout_ << " seconds of inactivity"; + } + + + void LuaJobManager::SetMaxOperationsPerJob(size_t count) + { + boost::mutex::scoped_lock lock(mutex_); + maxOperations_ = count; + } + + + void LuaJobManager::SetPriority(int priority) + { + boost::mutex::scoped_lock lock(mutex_); + priority_ = priority; + } + + + void LuaJobManager::SetTrailingOperationTimeout(unsigned int timeout) + { + boost::mutex::scoped_lock lock(mutex_); + trailingTimeout_ = timeout; + } + + + void LuaJobManager::AwakeTrailingSleep() + { + boost::mutex::scoped_lock lock(mutex_); + + LOG(INFO) << "Awaking trailing sleep"; + + if (currentJob_ != NULL) + { + currentJob_->AwakeTrailingSleep(); + } + } + + + LuaJobManager::Lock::Lock(LuaJobManager& that, + JobsEngine& engine) : + that_(that), + lock_(that.mutex_), + engine_(engine) + { + if (that_.currentJob_ == NULL) + { + isNewJob_ = true; + } + else + { + jobLock_.reset(new SequenceOfOperationsJob::Lock(*that_.currentJob_)); + + if (jobLock_->IsDone() || + jobLock_->GetOperationsCount() >= that_.maxOperations_) + { + jobLock_.reset(NULL); + isNewJob_ = true; + } + else + { + isNewJob_ = false; + } + } + + if (isNewJob_) + { + // Need to create a new job, as the previous one is either + // finished, or is getting too long + that_.currentJob_ = new SequenceOfOperationsJob; + that_.currentJob_->SetDescription("Lua"); + + { + jobLock_.reset(new SequenceOfOperationsJob::Lock(*that_.currentJob_)); + jobLock_->SetTrailingOperationTimeout(that_.trailingTimeout_); + jobLock_->SetDicomAssociationTimeout(that_.dicomTimeout_ * 1000); // Milliseconds expected + } + } + + assert(jobLock_.get() != NULL); + } + + + LuaJobManager::Lock::~Lock() + { + bool isEmpty; + + assert(jobLock_.get() != NULL); + isEmpty = (isNewJob_ && + jobLock_->GetOperationsCount() == 0); + + jobLock_.reset(NULL); + + if (isNewJob_) + { + if (isEmpty) + { + // No operation was added, discard the newly created job + isNewJob_ = false; + delete that_.currentJob_; + that_.currentJob_ = NULL; + } + else + { + engine_.GetRegistry().Submit(that_.currentId_, that_.currentJob_, that_.priority_); + } + } + } + + + size_t LuaJobManager::Lock::AddDeleteResourceOperation(ServerContext& context) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation(new DeleteResourceOperation(context)); + } + + + size_t LuaJobManager::Lock::AddLogOperation() + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation(new LogJobOperation); + } + + + size_t LuaJobManager::Lock::AddStoreScuOperation(const std::string& localAet, + const RemoteModalityParameters& modality) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation(new StoreScuOperation(localAet, modality)); + } + + + size_t LuaJobManager::Lock::AddStorePeerOperation(const WebServiceParameters& peer) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation(new StorePeerOperation(peer)); + } + + + size_t LuaJobManager::Lock::AddSystemCallOperation(const std::string& command) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation(new SystemCallOperation(command)); + } + + + size_t LuaJobManager::Lock::AddSystemCallOperation + (const std::string& command, + const std::vector<std::string>& preArguments, + const std::vector<std::string>& postArguments) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation + (new SystemCallOperation(command, preArguments, postArguments)); + } + + + size_t LuaJobManager::Lock::AddModifyInstanceOperation(ServerContext& context, + DicomModification* modification) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation + (new ModifyInstanceOperation(context, RequestOrigin_Lua, modification)); + } + + + void LuaJobManager::Lock::AddNullInput(size_t operation) + { + assert(jobLock_.get() != NULL); + jobLock_->AddInput(operation, NullOperationValue()); + } + + + void LuaJobManager::Lock::AddStringInput(size_t operation, + const std::string& content) + { + assert(jobLock_.get() != NULL); + jobLock_->AddInput(operation, StringOperationValue(content)); + } + + + void LuaJobManager::Lock::AddDicomInstanceInput(size_t operation, + ServerContext& context, + const std::string& instanceId) + { + assert(jobLock_.get() != NULL); + jobLock_->AddInput(operation, DicomInstanceOperationValue(context, instanceId)); + } + + + void LuaJobManager::Lock::Connect(size_t operation1, + size_t operation2) + { + assert(jobLock_.get() != NULL); + jobLock_->Connect(operation1, operation2); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/LuaJobManager.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,115 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Core/DicomParsing/DicomModification.h" +#include "../../Core/JobsEngine/JobsEngine.h" +#include "../../Core/JobsEngine/Operations/SequenceOfOperationsJob.h" +#include "../../Core/WebServiceParameters.h" + +namespace Orthanc +{ + class ServerContext; + + class LuaJobManager : private SequenceOfOperationsJob::IObserver + { + private: + boost::mutex mutex_; + std::string currentId_; + SequenceOfOperationsJob* currentJob_; + size_t maxOperations_; + int priority_; + unsigned int trailingTimeout_; + unsigned int dicomTimeout_; + + virtual void SignalDone(const SequenceOfOperationsJob& job); + + public: + LuaJobManager(); + + void SetMaxOperationsPerJob(size_t count); + + void SetPriority(int priority); + + void SetTrailingOperationTimeout(unsigned int timeout); + + void AwakeTrailingSleep(); + + class Lock : public boost::noncopyable + { + private: + LuaJobManager& that_; + boost::mutex::scoped_lock lock_; + JobsEngine& engine_; + std::auto_ptr<SequenceOfOperationsJob::Lock> jobLock_; + bool isNewJob_; + + public: + Lock(LuaJobManager& that, + JobsEngine& engine); + + ~Lock(); + + size_t AddLogOperation(); + + size_t AddDeleteResourceOperation(ServerContext& context); + + size_t AddStoreScuOperation(const std::string& localAet, + const RemoteModalityParameters& modality); + + size_t AddStorePeerOperation(const WebServiceParameters& peer); + + size_t AddSystemCallOperation(const std::string& command); + + size_t AddSystemCallOperation(const std::string& command, + const std::vector<std::string>& preArguments, + const std::vector<std::string>& postArguments); + + size_t AddModifyInstanceOperation(ServerContext& context, + DicomModification* modification); + + void AddNullInput(size_t operation); + + void AddStringInput(size_t operation, + const std::string& content); + + void AddDicomInstanceInput(size_t operation, + ServerContext& context, + const std::string& instanceId); + + void Connect(size_t operation1, + size_t operation2); + }; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,72 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../PrecompiledHeadersServer.h" +#include "DeleteResourceOperation.h" + +#include "DicomInstanceOperationValue.h" + +#include "../../../Core/Logging.h" +#include "../../../Core/OrthancException.h" + +namespace Orthanc +{ + void DeleteResourceOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + switch (input.GetType()) + { + case JobOperationValue::Type_DicomInstance: + { + const DicomInstanceOperationValue& instance = dynamic_cast<const DicomInstanceOperationValue&>(input); + LOG(INFO) << "Lua: Deleting instance: " << instance.GetId(); + + try + { + Json::Value tmp; + context_.DeleteResource(tmp, instance.GetId(), ResourceType_Instance); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: Unable to delete instance " << instance.GetId() << ": " << e.What(); + } + + break; + } + + default: + break; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,64 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../../Core/JobsEngine/Operations/IJobOperation.h" + +#include "../../ServerContext.h" + +namespace Orthanc +{ + class DeleteResourceOperation : public IJobOperation + { + private: + ServerContext& context_; + + public: + DeleteResourceOperation(ServerContext& context) : + context_(context) + { + } + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager); + + virtual void Serialize(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = "DeleteResource"; + } + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,84 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../../Core/JobsEngine/Operations/JobOperationValue.h" + +#include "../../ServerContext.h" + +namespace Orthanc +{ + class DicomInstanceOperationValue : public JobOperationValue + { + private: + ServerContext& context_; + std::string id_; + + public: + DicomInstanceOperationValue(ServerContext& context, + const std::string& id) : + JobOperationValue(Type_DicomInstance), + context_(context), + id_(id) + { + } + + ServerContext& GetServerContext() const + { + return context_; + } + + const std::string& GetId() const + { + return id_; + } + + void ReadDicom(std::string& dicom) const + { + context_.ReadDicom(dicom, id_); + } + + virtual JobOperationValue* Clone() const + { + return new DicomInstanceOperationValue(context_, id_); + } + + virtual void Serialize(Json::Value& target) const + { + target = Json::objectValue; + target["Type"] = "DicomInstance"; + target["ID"] = id_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,153 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../PrecompiledHeadersServer.h" +#include "ModifyInstanceOperation.h" + +#include "DicomInstanceOperationValue.h" + +#include "../../../Core/Logging.h" +#include "../../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + ModifyInstanceOperation::ModifyInstanceOperation(ServerContext& context, + RequestOrigin origin, + DicomModification* modification) : + context_(context), + origin_(origin), + modification_(modification) + { + if (modification == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + modification_->SetAllowManualIdentifiers(true); + + if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID)) + { + modification_->SetLevel(ResourceType_Patient); + } + else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + modification_->SetLevel(ResourceType_Study); + } + else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + modification_->SetLevel(ResourceType_Series); + } + else + { + modification_->SetLevel(ResourceType_Instance); + } + + if (origin_ != RequestOrigin_Lua) + { + // TODO If issued from HTTP, "remoteIp" and "username" must be provided + throw OrthancException(ErrorCode_NotImplemented); + } + } + + void ModifyInstanceOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + if (input.GetType() != JobOperationValue::Type_DicomInstance) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + const DicomInstanceOperationValue& instance = + dynamic_cast<const DicomInstanceOperationValue&>(input); + + LOG(INFO) << "Lua: Modifying instance " << instance.GetId(); + + std::auto_ptr<ParsedDicomFile> modified; + + { + ServerContext::DicomCacheLocker lock(context_, instance.GetId()); + modified.reset(lock.GetDicom().Clone(true)); + } + + try + { + modification_->Apply(*modified); + + DicomInstanceToStore toStore; + assert(origin_ == RequestOrigin_Lua); + toStore.SetOrigin(DicomInstanceOrigin::FromLua()); + toStore.SetParsedDicomFile(*modified); + + // TODO other metadata + toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, instance.GetId()); + + std::string modifiedId; + context_.Store(modifiedId, toStore); + + // Only chain with other commands if this command succeeds + outputs.Append(new DicomInstanceOperationValue(instance.GetServerContext(), modifiedId)); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: Unable to modify instance " << instance.GetId() + << ": " << e.What(); + } + } + + + void ModifyInstanceOperation::Serialize(Json::Value& target) const + { + target = Json::objectValue; + target["Type"] = "ModifyInstance"; + target["Origin"] = EnumerationToString(origin_); + modification_->Serialize(target["Modification"]); + } + + + ModifyInstanceOperation::ModifyInstanceOperation(ServerContext& context, + const Json::Value& serialized) : + context_(context) + { + if (SerializationToolbox::ReadString(serialized, "Type") != "ModifyInstance" || + !serialized.isMember("Modification")) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + origin_ = StringToRequestOrigin(SerializationToolbox::ReadString(serialized, "Origin")); + + modification_.reset(new DicomModification(serialized["Modification"])); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,75 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../../Core/JobsEngine/Operations/IJobOperation.h" +#include "../../../Core/DicomParsing/DicomModification.h" + +#include "../../ServerContext.h" + +namespace Orthanc +{ + class ModifyInstanceOperation : public IJobOperation + { + private: + ServerContext& context_; + RequestOrigin origin_; + std::auto_ptr<DicomModification> modification_; + + public: + ModifyInstanceOperation(ServerContext& context, + RequestOrigin origin, + DicomModification* modification); // Takes ownership + + ModifyInstanceOperation(ServerContext& context, + const Json::Value& serialized); + + const RequestOrigin& GetRequestOrigin() const + { + return origin_; + } + + const DicomModification& GetModification() const + { + return *modification_; + } + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager); + + virtual void Serialize(Json::Value& target) const; + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,104 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../PrecompiledHeadersServer.h" +#include "StorePeerOperation.h" + +#include "DicomInstanceOperationValue.h" + +#include "../../../Core/Logging.h" +#include "../../../Core/OrthancException.h" +#include "../../../Core/HttpClient.h" +#include "../../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + void StorePeerOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + // Configure the HTTP client + HttpClient client(peer_, "instances"); + client.SetMethod(HttpMethod_Post); + + if (input.GetType() != JobOperationValue::Type_DicomInstance) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + const DicomInstanceOperationValue& instance = + dynamic_cast<const DicomInstanceOperationValue&>(input); + + LOG(INFO) << "Lua: Sending instance " << instance.GetId() << " to Orthanc peer \"" + << peer_.GetUrl() << "\""; + + try + { + instance.ReadDicom(client.GetBody()); + + std::string answer; + if (!client.Apply(answer)) + { + LOG(ERROR) << "Lua: Unable to send instance " << instance.GetId() + << " to Orthanc peer \"" << peer_.GetUrl(); + } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: Unable to send instance " << instance.GetId() + << " to Orthanc peer \"" << peer_.GetUrl() << "\": " << e.What(); + } + + outputs.Append(input.Clone()); + } + + + void StorePeerOperation::Serialize(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = "StorePeer"; + peer_.Serialize(result["Peer"]); + } + + + StorePeerOperation::StorePeerOperation(const Json::Value& serialized) + { + if (SerializationToolbox::ReadString(serialized, "Type") != "StorePeer" || + !serialized.isMember("Peer")) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + peer_ = WebServiceParameters(serialized["Peer"]); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,66 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../../Core/JobsEngine/Operations/IJobOperation.h" +#include "../../../Core/WebServiceParameters.h" + +namespace Orthanc +{ + class StorePeerOperation : public IJobOperation + { + private: + WebServiceParameters peer_; + + public: + StorePeerOperation(const WebServiceParameters& peer) : + peer_(peer) + { + } + + StorePeerOperation(const Json::Value& serialized); + + const WebServiceParameters& GetPeer() const + { + return peer_; + } + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager); + + virtual void Serialize(Json::Value& result) const; + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,105 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../PrecompiledHeadersServer.h" +#include "StoreScuOperation.h" + +#include "DicomInstanceOperationValue.h" + +#include "../../../Core/Logging.h" +#include "../../../Core/OrthancException.h" +#include "../../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + void StoreScuOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + std::auto_ptr<IDicomConnectionManager::IResource> resource + (connectionManager.AcquireConnection(localAet_, modality_)); + + if (resource.get() == NULL) + { + LOG(ERROR) << "Lua: Cannot connect to modality: " << modality_.GetApplicationEntityTitle(); + return; + } + + if (input.GetType() != JobOperationValue::Type_DicomInstance) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + const DicomInstanceOperationValue& instance = + dynamic_cast<const DicomInstanceOperationValue&>(input); + + LOG(INFO) << "Lua: Sending instance " << instance.GetId() << " to modality \"" + << modality_.GetApplicationEntityTitle() << "\""; + + try + { + std::string dicom; + instance.ReadDicom(dicom); + resource->GetConnection().Store(dicom); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: Unable to send instance " << instance.GetId() << " to modality \"" + << modality_.GetApplicationEntityTitle() << "\": " << e.What(); + } + + outputs.Append(input.Clone()); + } + + + void StoreScuOperation::Serialize(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = "StoreScu"; + result["LocalAET"] = localAet_; + modality_.Serialize(result["Modality"]); + } + + + StoreScuOperation::StoreScuOperation(const Json::Value& serialized) + { + if (SerializationToolbox::ReadString(serialized, "Type") != "StoreScu" || + !serialized.isMember("LocalAET")) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + localAet_ = SerializationToolbox::ReadString(serialized, "LocalAET"); + modality_ = RemoteModalityParameters(serialized["Modality"]); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,74 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../../Core/JobsEngine/Operations/IJobOperation.h" +#include "../../../Core/DicomNetworking/RemoteModalityParameters.h" + +namespace Orthanc +{ + class StoreScuOperation : public IJobOperation + { + private: + std::string localAet_; + RemoteModalityParameters modality_; + + public: + StoreScuOperation(const std::string& localAet, + const RemoteModalityParameters& modality) : + localAet_(localAet), + modality_(modality) + { + } + + StoreScuOperation(const Json::Value& serialized); + + const std::string& GetLocalAet() const + { + return localAet_; + } + + const RemoteModalityParameters& GetRemoteModality() const + { + return modality_; + } + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& manager); + + virtual void Serialize(Json::Value& result) const; + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,165 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../PrecompiledHeadersServer.h" +#include "SystemCallOperation.h" + +#include "DicomInstanceOperationValue.h" + +#include "../../../Core/JobsEngine/Operations/StringOperationValue.h" +#include "../../../Core/Logging.h" +#include "../../../Core/OrthancException.h" +#include "../../../Core/SerializationToolbox.h" +#include "../../../Core/TemporaryFile.h" +#include "../../../Core/Toolbox.h" + +namespace Orthanc +{ + const std::string& SystemCallOperation::GetPreArgument(size_t i) const + { + if (i >= preArguments_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return preArguments_[i]; + } + } + + + const std::string& SystemCallOperation::GetPostArgument(size_t i) const + { + if (i >= postArguments_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return postArguments_[i]; + } + } + + + void SystemCallOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + std::vector<std::string> arguments = preArguments_; + + arguments.reserve(arguments.size() + postArguments_.size() + 1); + + std::auto_ptr<TemporaryFile> tmp; + + switch (input.GetType()) + { + case JobOperationValue::Type_DicomInstance: + { + const DicomInstanceOperationValue& instance = + dynamic_cast<const DicomInstanceOperationValue&>(input); + + std::string dicom; + instance.ReadDicom(dicom); + + tmp.reset(new TemporaryFile); + tmp->Write(dicom); + + arguments.push_back(tmp->GetPath()); + break; + } + + case JobOperationValue::Type_String: + { + const StringOperationValue& value = + dynamic_cast<const StringOperationValue&>(input); + + arguments.push_back(value.GetContent()); + break; + } + + case JobOperationValue::Type_Null: + break; + + default: + throw OrthancException(ErrorCode_BadParameterType); + } + + for (size_t i = 0; i < postArguments_.size(); i++) + { + arguments.push_back(postArguments_[i]); + } + + std::string info = command_; + for (size_t i = 0; i < arguments.size(); i++) + { + info += " " + arguments[i]; + } + + LOG(INFO) << "Lua: System call: \"" << info << "\""; + + try + { + SystemToolbox::ExecuteSystemCommand(command_, arguments); + + // Only chain with other commands if this operation succeeds + outputs.Append(input.Clone()); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: Failed system call - \"" << info << "\": " << e.What(); + } + } + + + void SystemCallOperation::Serialize(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = "SystemCall"; + result["Command"] = command_; + SerializationToolbox::WriteArrayOfStrings(result, preArguments_, "PreArguments"); + SerializationToolbox::WriteArrayOfStrings(result, postArguments_, "PostArguments"); + } + + + SystemCallOperation::SystemCallOperation(const Json::Value& serialized) + { + if (SerializationToolbox::ReadString(serialized, "Type") != "SystemCall") + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + command_ = SerializationToolbox::ReadString(serialized, "Command"); + SerializationToolbox::ReadArrayOfStrings(preArguments_, serialized, "PreArguments"); + SerializationToolbox::ReadArrayOfStrings(postArguments_, serialized, "PostArguments"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,102 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../../Core/JobsEngine/Operations/IJobOperation.h" + +#include <string> + +namespace Orthanc +{ + class SystemCallOperation : public IJobOperation + { + private: + std::string command_; + std::vector<std::string> preArguments_; + std::vector<std::string> postArguments_; + + public: + SystemCallOperation(const std::string& command) : + command_(command) + { + } + + SystemCallOperation(const Json::Value& serialized); + + SystemCallOperation(const std::string& command, + const std::vector<std::string>& preArguments, + const std::vector<std::string>& postArguments) : + command_(command), + preArguments_(preArguments), + postArguments_(postArguments) + { + } + + void AddPreArgument(const std::string& argument) + { + preArguments_.push_back(argument); + } + + void AddPostArgument(const std::string& argument) + { + postArguments_.push_back(argument); + } + + const std::string& GetCommand() const + { + return command_; + } + + size_t GetPreArgumentsCount() const + { + return preArguments_.size(); + } + + size_t GetPostArgumentsCount() const + { + return postArguments_.size(); + } + + const std::string& GetPreArgument(size_t i) const; + + const std::string& GetPostArgument(size_t i) const; + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager); + + virtual void Serialize(Json::Value& result) const; + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,121 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "OrthancJobUnserializer.h" + +#include "../../Core/Logging.h" +#include "../../Core/OrthancException.h" +#include "../../Core/SerializationToolbox.h" + +#include "Operations/DeleteResourceOperation.h" +#include "Operations/DicomInstanceOperationValue.h" +#include "Operations/ModifyInstanceOperation.h" +#include "Operations/StorePeerOperation.h" +#include "Operations/StoreScuOperation.h" +#include "Operations/SystemCallOperation.h" + +#include "DicomModalityStoreJob.h" +#include "OrthancPeerStoreJob.h" +#include "ResourceModificationJob.h" + +namespace Orthanc +{ + IJob* OrthancJobUnserializer::UnserializeJob(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + + if (type == "DicomModalityStore") + { + return new DicomModalityStoreJob(context_, source); + } + else if (type == "OrthancPeerStore") + { + return new OrthancPeerStoreJob(context_, source); + } + else if (type == "ResourceModification") + { + return new ResourceModificationJob(context_, source); + } + else + { + return GenericJobUnserializer::UnserializeJob(source); + } + } + + + IJobOperation* OrthancJobUnserializer::UnserializeOperation(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + + if (type == "DeleteResource") + { + return new DeleteResourceOperation(context_); + } + else if (type == "ModifyInstance") + { + return new ModifyInstanceOperation(context_, source); + } + else if (type == "StorePeer") + { + return new StorePeerOperation(source); + } + else if (type == "StoreScu") + { + return new StoreScuOperation(source); + } + else if (type == "SystemCall") + { + return new SystemCallOperation(source); + } + else + { + return GenericJobUnserializer::UnserializeOperation(source); + } + } + + + JobOperationValue* OrthancJobUnserializer::UnserializeValue(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + + if (type == "DicomInstance") + { + return new DicomInstanceOperationValue(context_, SerializationToolbox::ReadString(source, "ID")); + } + else + { + return GenericJobUnserializer::UnserializeValue(source); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../ServerContext.h" +#include "../../Core/JobsEngine/GenericJobUnserializer.h" + +namespace Orthanc +{ + class OrthancJobUnserializer : public GenericJobUnserializer + { + private: + ServerContext& context_; + + public: + OrthancJobUnserializer(ServerContext& context) : + context_(context) + { + } + + virtual IJob* UnserializeJob(const Json::Value& value); + + virtual IJobOperation* UnserializeOperation(const Json::Value& value); + + virtual JobOperationValue* UnserializeValue(const Json::Value& value); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,129 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "OrthancPeerStoreJob.h" + +#include "../../Core/Logging.h" + + +namespace Orthanc +{ + bool OrthancPeerStoreJob::HandleInstance(const std::string& instance) + { + //boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + + if (client_.get() == NULL) + { + client_.reset(new HttpClient(peer_, "instances")); + client_->SetMethod(HttpMethod_Post); + } + + LOG(INFO) << "Sending instance " << instance << " to peer \"" + << peer_.GetUrl() << "\""; + + try + { + context_.ReadDicom(client_->GetBody(), instance); + } + catch (OrthancException& e) + { + LOG(WARNING) << "An instance was removed after the job was issued: " << instance; + return false; + } + + std::string answer; + if (client_->Apply(answer)) + { + return true; + } + else + { + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + + + void OrthancPeerStoreJob::SetPeer(const WebServiceParameters& peer) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + peer_ = peer; + } + } + + + void OrthancPeerStoreJob::ReleaseResources() // For pausing jobs + { + client_.reset(NULL); + } + + + void OrthancPeerStoreJob::GetPublicContent(Json::Value& value) + { + SetOfInstancesJob::GetPublicContent(value); + + Json::Value v; + peer_.ToJson(v, false /* don't include passwords */); + value["Peer"] = v; + } + + + static const char* PEER = "Peer"; + + OrthancPeerStoreJob::OrthancPeerStoreJob(ServerContext& context, + const Json::Value& serialized) : + SetOfInstancesJob(serialized), + context_(context) + { + peer_ = WebServiceParameters(serialized[PEER]); + } + + + bool OrthancPeerStoreJob::Serialize(Json::Value& target) + { + if (!SetOfInstancesJob::Serialize(target)) + { + return false; + } + else + { + peer_.Serialize(target[PEER]); + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/OrthancPeerStoreJob.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,81 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Core/JobsEngine/SetOfInstancesJob.h" +#include "../../Core/HttpClient.h" + +#include "../ServerContext.h" + + +namespace Orthanc +{ + class OrthancPeerStoreJob : public SetOfInstancesJob + { + private: + ServerContext& context_; + WebServiceParameters peer_; + std::auto_ptr<HttpClient> client_; + + protected: + virtual bool HandleInstance(const std::string& instance); + + public: + OrthancPeerStoreJob(ServerContext& context) : + context_(context) + { + } + + OrthancPeerStoreJob(ServerContext& context, + const Json::Value& serialize); + + void SetPeer(const WebServiceParameters& peer); + + const WebServiceParameters& GetPeer() const + { + return peer_; + } + + virtual void ReleaseResources(); // For pausing jobs + + virtual void GetJobType(std::string& target) + { + target = "OrthancPeerStore"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& target); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/ResourceModificationJob.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,321 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "ResourceModificationJob.h" + +#include "../../Core/Logging.h" +#include "../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + ResourceModificationJob::Output::Output(ResourceType level) : + level_(level), + isFirst_(true) + { + if (level_ != ResourceType_Patient && + level_ != ResourceType_Study && + level_ != ResourceType_Series) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + void ResourceModificationJob::Output::Update(DicomInstanceHasher& hasher) + { + boost::mutex::scoped_lock lock(mutex_); + + if (isFirst_) + { + switch (level_) + { + case ResourceType_Series: + id_ = hasher.HashSeries(); + break; + + case ResourceType_Study: + id_ = hasher.HashStudy(); + break; + + case ResourceType_Patient: + id_ = hasher.HashPatient(); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + patientId_ = hasher.HashPatient(); + isFirst_ = false; + } + } + + + bool ResourceModificationJob::Output::Format(Json::Value& target) + { + boost::mutex::scoped_lock lock(mutex_); + + if (isFirst_) + { + return false; + } + else + { + target = Json::objectValue; + target["Type"] = EnumerationToString(level_); + target["ID"] = id_; + target["Path"] = GetBasePath(level_, id_); + target["PatientID"] = patientId_; + return true; + } + } + + + bool ResourceModificationJob::Output::GetIdentifier(std::string& id) + { + boost::mutex::scoped_lock lock(mutex_); + + if (isFirst_) + { + return false; + } + else + { + id = id_; + return true; + } + } + + + bool ResourceModificationJob::HandleInstance(const std::string& instance) + { + if (modification_.get() == NULL) + { + LOG(ERROR) << "No modification was provided for this job"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + + LOG(INFO) << "Modifying instance in a job: " << instance; + + std::auto_ptr<ServerContext::DicomCacheLocker> locker; + + try + { + locker.reset(new ServerContext::DicomCacheLocker(context_, instance)); + } + catch (OrthancException&) + { + LOG(WARNING) << "An instance was removed after the job was issued: " << instance; + return false; + } + + + ParsedDicomFile& original = locker->GetDicom(); + DicomInstanceHasher originalHasher = original.GetHasher(); + + + /** + * Compute the resulting DICOM instance. + **/ + + std::auto_ptr<ParsedDicomFile> modified(original.Clone(true)); + modification_->Apply(*modified); + + DicomInstanceToStore toStore; + toStore.SetOrigin(origin_); + toStore.SetParsedDicomFile(*modified); + + + /** + * Prepare the metadata information to associate with the + * resulting DICOM instance (AnonymizedFrom/ModifiedFrom). + **/ + + DicomInstanceHasher modifiedHasher = modified->GetHasher(); + + MetadataType metadataType = (isAnonymization_ ? + MetadataType_AnonymizedFrom : + MetadataType_ModifiedFrom); + + if (originalHasher.HashSeries() != modifiedHasher.HashSeries()) + { + toStore.AddMetadata(ResourceType_Series, metadataType, originalHasher.HashSeries()); + } + + if (originalHasher.HashStudy() != modifiedHasher.HashStudy()) + { + toStore.AddMetadata(ResourceType_Study, metadataType, originalHasher.HashStudy()); + } + + if (originalHasher.HashPatient() != modifiedHasher.HashPatient()) + { + toStore.AddMetadata(ResourceType_Patient, metadataType, originalHasher.HashPatient()); + } + + assert(instance == originalHasher.HashInstance()); + toStore.AddMetadata(ResourceType_Instance, metadataType, instance); + + + /** + * Store the resulting DICOM instance into the Orthanc store. + **/ + + std::string modifiedInstance; + if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success) + { + LOG(ERROR) << "Error while storing a modified instance " << instance; + throw OrthancException(ErrorCode_CannotStoreInstance); + } + + // Sanity checks in debug mode + assert(modifiedInstance == modifiedHasher.HashInstance()); + + + if (output_.get() != NULL) + { + output_->Update(modifiedHasher); + } + + return true; + } + + + + void ResourceModificationJob::SetModification(DicomModification* modification, + bool isAnonymization) + { + if (modification == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + modification_.reset(modification); + isAnonymization_ = isAnonymization; + } + } + + + void ResourceModificationJob::SetOutput(boost::shared_ptr<Output>& output) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + output_ = output; + } + } + + + void ResourceModificationJob::SetOrigin(const DicomInstanceOrigin& origin) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + origin_ = origin; + } + } + + + void ResourceModificationJob::SetOrigin(const RestApiCall& call) + { + SetOrigin(DicomInstanceOrigin::FromRest(call)); + } + + + const DicomModification& ResourceModificationJob::GetModification() const + { + if (modification_.get() == NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return *modification_; + } + } + + + void ResourceModificationJob::GetPublicContent(Json::Value& value) + { + SetOfInstancesJob::GetPublicContent(value); + + value["IsAnonymization"] = isAnonymization_; + } + + + static const char* MODIFICATION = "Modification"; + static const char* ORIGIN = "Origin"; + static const char* IS_ANONYMIZATION = "IsAnonymization"; + + + ResourceModificationJob::ResourceModificationJob(ServerContext& context, + const Json::Value& serialized) : + SetOfInstancesJob(serialized), + context_(context) + { + isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION); + origin_ = DicomInstanceOrigin(serialized[ORIGIN]); + modification_.reset(new DicomModification(serialized[MODIFICATION])); + } + + bool ResourceModificationJob::Serialize(Json::Value& value) + { + if (!SetOfInstancesJob::Serialize(value)) + { + return false; + } + else + { + value[IS_ANONYMIZATION] = isAnonymization_; + origin_.Serialize(value[ORIGIN]); + + Json::Value tmp; + modification_->Serialize(tmp); + value[MODIFICATION] = tmp; + + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/ResourceModificationJob.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,122 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Core/JobsEngine/SetOfInstancesJob.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class ResourceModificationJob : public SetOfInstancesJob + { + public: + class Output : public boost::noncopyable + { + private: + boost::mutex mutex_; + ResourceType level_; + bool isFirst_; + std::string id_; + std::string patientId_; + + public: + Output(ResourceType level); + + ResourceType GetLevel() const + { + return level_; + } + + void Update(DicomInstanceHasher& hasher); + + bool Format(Json::Value& target); + + bool GetIdentifier(std::string& id); + }; + + private: + ServerContext& context_; + std::auto_ptr<DicomModification> modification_; + boost::shared_ptr<Output> output_; + bool isAnonymization_; + DicomInstanceOrigin origin_; + + protected: + virtual bool HandleInstance(const std::string& instance); + + public: + ResourceModificationJob(ServerContext& context) : + context_(context), + isAnonymization_(false) + { + } + + ResourceModificationJob(ServerContext& context, + const Json::Value& serialized); + + void SetModification(DicomModification* modification, // Takes ownership + bool isAnonymization); + + void SetOutput(boost::shared_ptr<Output>& output); + + void SetOrigin(const DicomInstanceOrigin& origin); + + void SetOrigin(const RestApiCall& call); + + const DicomModification& GetModification() const; + + bool IsAnonymization() const + { + return isAnonymization_; + } + + const DicomInstanceOrigin& GetOrigin() const + { + return origin_; + } + + virtual void ReleaseResources() + { + } + + virtual void GetJobType(std::string& target) + { + target = "ResourceModification"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& value); + }; +}
--- a/OrthancServer/main.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/OrthancServer/main.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -42,7 +42,6 @@ #include "../Core/Lua/LuaFunctionCall.h" #include "../Core/DicomFormat/DicomArray.h" #include "../Core/DicomNetworking/DicomServer.h" -#include "../Core/DicomNetworking/ReusableDicomUserConnection.h" #include "OrthancInitialization.h" #include "ServerContext.h" #include "OrthancFindRequestHandler.h" @@ -76,7 +75,8 @@ if (dicomFile.size() > 0) { DicomInstanceToStore toStore; - toStore.SetDicomProtocolOrigin(remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str()); + toStore.SetOrigin(DicomInstanceOrigin::FromDicomProtocol + (remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str())); toStore.SetBuffer(dicomFile); toStore.SetSummary(dicomSummary); toStore.SetJson(dicomJson); @@ -168,9 +168,9 @@ class OrthancApplicationEntityFilter : public IApplicationEntityFilter { private: - ServerContext& context_; - bool alwaysAllowEcho_; - bool alwaysAllowStore_; + ServerContext& context_; + bool alwaysAllowEcho_; + bool alwaysAllowStore_; public: OrthancApplicationEntityFilter(ServerContext& context) : @@ -264,13 +264,13 @@ } { - std::string lua = "Is" + configuration; + std::string name = "Is" + configuration; - LuaScripting::Locker locker(context_.GetLua()); + LuaScripting::Lock lock(context_.GetLuaScripting()); - if (locker.GetLua().IsExistingFunction(lua.c_str())) + if (lock.GetLua().IsExistingFunction(name.c_str())) { - LuaFunctionCall call(locker.GetLua(), lua.c_str()); + LuaFunctionCall call(lock.GetLua(), name.c_str()); call.PushString(remoteAet); call.PushString(remoteIp); call.PushString(calledAet); @@ -291,11 +291,11 @@ { std::string lua = "Is" + std::string(configuration); - LuaScripting::Locker locker(context_.GetLua()); + LuaScripting::Lock lock(context_.GetLuaScripting()); - if (locker.GetLua().IsExistingFunction(lua.c_str())) + if (lock.GetLua().IsExistingFunction(lua.c_str())) { - LuaFunctionCall call(locker.GetLua(), lua.c_str()); + LuaFunctionCall call(lock.GetLua(), lua.c_str()); call.PushString(remoteAet); call.PushString(remoteIp); call.PushString(calledAet); @@ -311,7 +311,7 @@ class MyIncomingHttpRequestFilter : public IIncomingHttpRequestFilter { private: - ServerContext& context_; + ServerContext& context_; OrthancPlugins* plugins_; public: @@ -327,7 +327,7 @@ const char* ip, const char* username, const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::GetArguments& getArguments) const + const IHttpHandler::GetArguments& getArguments) { if (plugins_ != NULL && !plugins_->IsAllowed(method, uri, ip, username, httpHeaders, getArguments)) @@ -337,12 +337,12 @@ static const char* HTTP_FILTER = "IncomingHttpRequestFilter"; - LuaScripting::Locker locker(context_.GetLua()); + LuaScripting::Lock lock(context_.GetLuaScripting()); // Test if the instance must be filtered out - if (locker.GetLua().IsExistingFunction(HTTP_FILTER)) + if (lock.GetLua().IsExistingFunction(HTTP_FILTER)) { - LuaFunctionCall call(locker.GetLua(), HTTP_FILTER); + LuaFunctionCall call(lock.GetLua(), HTTP_FILTER); switch (method) { @@ -488,6 +488,8 @@ << " --upgrade\t\tallow Orthanc to upgrade the version of the" << std::endl << "\t\t\tdatabase (beware that the database will become" << std::endl << "\t\t\tincompatible with former versions of Orthanc)" << std::endl + << " --no-jobs\t\tDon't restart the jobs that were stored during" << std::endl + << "\t\t\tthe last execution of Orthanc" << std::endl << " --version\t\toutput version information and exit" << std::endl << std::endl << "Exit status:" << std::endl @@ -574,6 +576,7 @@ PrintErrorCode(ErrorCode_NotAcceptable, "Cannot send a response which is acceptable according to the Accept HTTP header"); PrintErrorCode(ErrorCode_NullPointer, "Cannot handle a NULL pointer"); PrintErrorCode(ErrorCode_DatabaseUnavailable, "The database is currently not available (probably a transient situation)"); + PrintErrorCode(ErrorCode_CanceledJob, "This job was canceled"); PrintErrorCode(ErrorCode_SQLiteNotOpened, "SQLite: The database is not opened"); PrintErrorCode(ErrorCode_SQLiteAlreadyOpened, "SQLite: Connection is already open"); PrintErrorCode(ErrorCode_SQLiteCannotOpen, "SQLite: Unable to open the database"); @@ -640,25 +643,6 @@ -static void LoadLuaScripts(ServerContext& context) -{ - std::list<std::string> luaScripts; - Configuration::GetGlobalListOfStringsParameter(luaScripts, "LuaScripts"); - for (std::list<std::string>::const_iterator - it = luaScripts.begin(); it != luaScripts.end(); ++it) - { - std::string path = Configuration::InterpretStringParameterAsPath(*it); - LOG(WARNING) << "Installing the Lua scripts from: " << path; - std::string script; - SystemToolbox::ReadFile(script, path); - - LuaScripting::Locker locker(context.GetLua()); - locker.GetLua().Execute(script); - } -} - - - #if ORTHANC_ENABLE_PLUGINS == 1 static void LoadPlugins(OrthancPlugins& plugins) { @@ -689,7 +673,8 @@ } #endif - context.GetLua().Execute("Initialize"); + context.GetLuaScripting().Start(); + context.GetLuaScripting().Execute("Initialize"); bool restart; @@ -723,7 +708,8 @@ } } - context.GetLua().Execute("Finalize"); + context.GetLuaScripting().Execute("Finalize"); + context.GetLuaScripting().Stop(); #if ORTHANC_ENABLE_PLUGINS == 1 if (context.HasPlugins()) @@ -972,7 +958,8 @@ static bool ConfigureServerContext(IDatabaseWrapper& database, IStorageArea& storageArea, - OrthancPlugins *plugins) + OrthancPlugins *plugins, + bool loadJobsFromDatabase) { // These configuration options must be set before creating the // ServerContext, otherwise the possible Lua scripts will not be @@ -985,7 +972,7 @@ DicomUserConnection::SetDefaultTimeout(Configuration::GetGlobalUnsignedIntegerParameter("DicomScuTimeout", 10)); - ServerContext context(database, storageArea); + ServerContext context(database, storageArea, false /* not running unit tests */, loadJobsFromDatabase); context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false)); context.SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true)); @@ -1008,7 +995,11 @@ context.GetIndex().SetMaximumStorageSize(0); } - LoadLuaScripts(context); + LOG(INFO) << "Initializing Lua for the event handler"; + context.GetLuaScripting().LoadGlobalConfiguration(); + + context.GetJobsEngine().GetRegistry().SetMaxCompletedJobs + (Configuration::GetGlobalUnsignedIntegerParameter("JobsHistorySize", 10)); #if ORTHANC_ENABLE_PLUGINS == 1 if (plugins) @@ -1052,7 +1043,8 @@ static bool ConfigureDatabase(IDatabaseWrapper& database, IStorageArea& storageArea, OrthancPlugins *plugins, - bool upgradeDatabase) + bool upgradeDatabase, + bool loadJobsFromDatabase) { database.Open(); @@ -1071,7 +1063,8 @@ throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } - bool success = ConfigureServerContext(database, storageArea, plugins); + bool success = ConfigureServerContext + (database, storageArea, plugins, loadJobsFromDatabase); database.Close(); @@ -1081,7 +1074,8 @@ static bool ConfigurePlugins(int argc, char* argv[], - bool upgradeDatabase) + bool upgradeDatabase, + bool loadJobsFromDatabase) { std::auto_ptr<IDatabaseWrapper> databasePtr; std::auto_ptr<IStorageArea> storage; @@ -1116,14 +1110,16 @@ assert(database != NULL); assert(storage.get() != NULL); - return ConfigureDatabase(*database, *storage, &plugins, upgradeDatabase); + return ConfigureDatabase(*database, *storage, &plugins, + upgradeDatabase, loadJobsFromDatabase); #elif ORTHANC_ENABLE_PLUGINS == 0 // The plugins are disabled databasePtr.reset(Configuration::CreateDatabaseWrapper()); storage.reset(Configuration::CreateStorageArea()); - return ConfigureDatabase(*databasePtr, *storage, NULL, upgradeDatabase); + return ConfigureDatabase(*databasePtr, *storage, NULL, + upgradeDatabase, loadJobsFromDatabase); #else # error The macro ORTHANC_ENABLE_PLUGINS must be set to 0 or 1 @@ -1133,9 +1129,10 @@ static bool StartOrthanc(int argc, char* argv[], - bool upgradeDatabase) + bool upgradeDatabase, + bool loadJobsFromDatabase) { - return ConfigurePlugins(argc, argv, upgradeDatabase); + return ConfigurePlugins(argc, argv, upgradeDatabase, loadJobsFromDatabase); } @@ -1152,6 +1149,7 @@ Logging::Initialize(); bool upgradeDatabase = false; + bool loadJobsFromDatabase = true; const char* configurationFile = NULL; @@ -1242,6 +1240,10 @@ { upgradeDatabase = true; } + else if (argument == "--no-jobs") + { + loadJobsFromDatabase = false; + } else if (boost::starts_with(argument, "--config=")) { // TODO WHAT IS THE ENCODING? @@ -1305,7 +1307,7 @@ { OrthancInitialize(configurationFile); - bool restart = StartOrthanc(argc, argv, upgradeDatabase); + bool restart = StartOrthanc(argc, argv, upgradeDatabase, loadJobsFromDatabase); if (restart) { OrthancFinalize();
--- a/Plugins/Engine/OrthancPluginDatabase.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Plugins/Engine/OrthancPluginDatabase.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -634,6 +634,34 @@ } + void OrthancPluginDatabase::LookupIdentifierRange(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end) + { + if (extensions_.lookupIdentifierRange == NULL) + { + // Default implementation, for plugins using Orthanc SDK <= 1.3.2 + + LookupIdentifier(result, level, tag, IdentifierConstraintType_GreaterOrEqual, start); + + std::list<int64_t> b; + LookupIdentifier(result, level, tag, IdentifierConstraintType_SmallerOrEqual, end); + + result.splice(result.end(), b); + } + else + { + ResetAnswers(); + CheckSuccess(extensions_.lookupIdentifierRange(GetContext(), payload_, Plugins::Convert(level), + tag.GetGroup(), tag.GetElement(), + start.c_str(), end.c_str())); + ForwardAnswers(result); + } + } + + bool OrthancPluginDatabase::LookupMetadata(std::string& target, int64_t id, MetadataType type)
--- a/Plugins/Engine/OrthancPluginDatabase.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Plugins/Engine/OrthancPluginDatabase.h Fri Jul 13 10:07:45 2018 +0200 @@ -213,6 +213,12 @@ IdentifierConstraintType type, const std::string& value); + virtual void LookupIdentifierRange(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end); + virtual bool LookupMetadata(std::string& target, int64_t id, MetadataType type);
--- a/Plugins/Engine/OrthancPlugins.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -1544,7 +1544,7 @@ switch (service) { case _OrthancPluginService_GetInstanceRemoteAet: - *p.resultString = instance.GetRemoteAet(); + *p.resultString = instance.GetOrigin().GetRemoteAetC(); return; case _OrthancPluginService_GetInstanceSize: @@ -1585,7 +1585,7 @@ } case _OrthancPluginService_GetInstanceOrigin: // New in Orthanc 0.9.5 - *p.resultOrigin = Plugins::Convert(instance.GetRequestOrigin()); + *p.resultOrigin = Plugins::Convert(instance.GetOrigin().GetRequestOrigin()); return; default: @@ -3115,7 +3115,7 @@ const char* ip, const char* username, const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::GetArguments& getArguments) const + const IHttpHandler::GetArguments& getArguments) { OrthancPluginHttpMethod cMethod = Plugins::Convert(method);
--- a/Plugins/Engine/OrthancPlugins.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Fri Jul 13 10:07:45 2018 +0200 @@ -284,7 +284,7 @@ const char* ip, const char* username, const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::GetArguments& getArguments) const; + const IHttpHandler::GetArguments& getArguments); bool HasFindHandler();
--- a/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Fri Jul 13 10:07:45 2018 +0200 @@ -682,6 +682,18 @@ OrthancPluginResourceType resourceType, const OrthancPluginDicomTag* tag, OrthancPluginIdentifierConstraint constraint); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifierRange) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + uint16_t group, + uint16_t element, + const char* start, + const char* end); } OrthancPluginDatabaseExtensions; /*<! @endcond */ @@ -695,13 +707,8 @@ } _OrthancPluginRegisterDatabaseBackend; /** - * Register a custom database back-end. + * Register a custom database back-end (for legacy plugins). * - * Instead of manually filling the OrthancPluginDatabaseBackend - * structure, you should instead implement a concrete C++ class - * deriving from ::OrthancPlugins::IDatabaseBackend, and register it - * using ::OrthancPlugins::DatabaseBackendAdapter::Register(). - * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param backend The callbacks of the custom database engine. * @param payload Pointer containing private information for the database engine. @@ -754,11 +761,6 @@ /** * Register a custom database back-end. * - * Instead of manually filling the OrthancPluginDatabaseBackendV2 - * structure, you should instead implement a concrete C++ class - * deriving from ::OrthancPlugins::IDatabaseBackend, and register it - * using ::OrthancPlugins::DatabaseBackendAdapter::Register(). - * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param backend The callbacks of the custom database engine. * @param payload Pointer containing private information for the database engine.
--- a/Plugins/Include/orthanc/OrthancCPlugin.h Mon Jul 09 11:37:17 2018 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Fri Jul 13 10:07:45 2018 +0200 @@ -235,6 +235,7 @@ OrthancPluginErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, OrthancPluginErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */, OrthancPluginErrorCode_DatabaseUnavailable = 36 /*!< The database is currently not available (probably a transient situation) */, + OrthancPluginErrorCode_CanceledJob = 37 /*!< This job was canceled */, OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, @@ -1273,20 +1274,26 @@ /** - * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. + * @brief Check that the version of the hosting Orthanc is above a given version. * - * This function checks whether the version of this C header is - * compatible with the current version of Orthanc. The result of - * this function should always be checked in the - * OrthancPluginInitialize() entry point of the plugin. + * This function checks whether the version of the Orthanc server + * running this plugin, is above the given version. Contrarily to + * OrthancPluginCheckVersion(), it is up to the developer of the + * plugin to make sure that all the Orthanc SDK services called by + * the plugin are actually implemented in the given version of + * Orthanc. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @return 1 if and only if the versions are compatible. If the * result is 0, the initialization of the plugin should fail. + * @see OrthancPluginCheckVersion * @ingroup Callbacks **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( - OrthancPluginContext* context) + ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersionAdvanced( + OrthancPluginContext* context, + int expectedMajor, + int expectedMinor, + int expectedRevision) { int major, minor, revision; @@ -1331,31 +1338,31 @@ /* Check the major number of the version */ - if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + if (major > expectedMajor) { return 1; } - if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + if (major < expectedMajor) { return 0; } /* Check the minor number of the version */ - if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + if (minor > expectedMinor) { return 1; } - if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + if (minor < expectedMinor) { return 0; } /* Check the revision number of the version */ - if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER) + if (revision >= expectedRevision) { return 1; } @@ -1367,6 +1374,33 @@ /** + * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. + * + * This function checks whether the version of the Orthanc server + * running this plugin, is above the version of the current Orthanc + * SDK header. This guarantees that the plugin is compatible with + * the hosting Orthanc (i.e. it will not call unavailable services). + * The result of this function should always be checked in the + * OrthancPluginInitialize() entry point of the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @see OrthancPluginCheckVersionAdvanced + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( + OrthancPluginContext* context) + { + return OrthancPluginCheckVersionAdvanced( + context, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + } + + + /** * @brief Free a memory buffer. * * Free a memory buffer that was allocated by the core system of Orthanc. @@ -2929,7 +2963,6 @@ * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @return The version. * @ingroup Callbacks - * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase() **/ ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( OrthancPluginContext* context) @@ -4301,9 +4334,7 @@ * This function requests the Orthanc core to reconstruct the main * DICOM tags of all the resources of the given type. This function * can only be used as a part of the upgrade of a custom database - * back-end - * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A - * database transaction will be automatically setup. + * back-end. A database transaction will be automatically setup. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param storageArea The storage area.
--- a/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h Mon Jul 09 11:37:17 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1896 +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-2018 Osimis S.A., 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. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - -#pragma once - -#include "OrthancCDatabasePlugin.h" - -#include <stdexcept> -#include <list> -#include <string> - -namespace OrthancPlugins -{ -//! @cond Doxygen_Suppress - // This class mimics "boost::noncopyable" - class NonCopyable - { - private: - NonCopyable(const NonCopyable&); - - NonCopyable& operator= (const NonCopyable&); - - protected: - NonCopyable() - { - } - - ~NonCopyable() - { - } - }; -//! @endcond - - - /** - * @ingroup Callbacks - **/ - class DatabaseException - { - private: - OrthancPluginErrorCode code_; - - public: - DatabaseException() : code_(OrthancPluginErrorCode_DatabasePlugin) - { - } - - DatabaseException(OrthancPluginErrorCode code) : code_(code) - { - } - - OrthancPluginErrorCode GetErrorCode() const - { - return code_; - } - }; - - - /** - * @ingroup Callbacks - **/ - class DatabaseBackendOutput : public NonCopyable - { - friend class DatabaseBackendAdapter; - - private: - enum AllowedAnswers - { - AllowedAnswers_All, - AllowedAnswers_None, - AllowedAnswers_Attachment, - AllowedAnswers_Change, - AllowedAnswers_DicomTag, - AllowedAnswers_ExportedResource - }; - - OrthancPluginContext* context_; - OrthancPluginDatabaseContext* database_; - AllowedAnswers allowedAnswers_; - - void SetAllowedAnswers(AllowedAnswers allowed) - { - allowedAnswers_ = allowed; - } - - public: - DatabaseBackendOutput(OrthancPluginContext* context, - OrthancPluginDatabaseContext* database) : - context_(context), - database_(database), - allowedAnswers_(AllowedAnswers_All /* for unit tests */) - { - } - - OrthancPluginContext* GetContext() - { - return context_; - } - - void LogError(const std::string& message) - { - OrthancPluginLogError(context_, message.c_str()); - } - - void LogWarning(const std::string& message) - { - OrthancPluginLogWarning(context_, message.c_str()); - } - - void LogInfo(const std::string& message) - { - OrthancPluginLogInfo(context_, message.c_str()); - } - - void SignalDeletedAttachment(const std::string& uuid, - int32_t contentType, - uint64_t uncompressedSize, - const std::string& uncompressedHash, - int32_t compressionType, - uint64_t compressedSize, - const std::string& compressedHash) - { - OrthancPluginAttachment attachment; - attachment.uuid = uuid.c_str(); - attachment.contentType = contentType; - attachment.uncompressedSize = uncompressedSize; - attachment.uncompressedHash = uncompressedHash.c_str(); - attachment.compressionType = compressionType; - attachment.compressedSize = compressedSize; - attachment.compressedHash = compressedHash.c_str(); - - OrthancPluginDatabaseSignalDeletedAttachment(context_, database_, &attachment); - } - - void SignalDeletedResource(const std::string& publicId, - OrthancPluginResourceType resourceType) - { - OrthancPluginDatabaseSignalDeletedResource(context_, database_, publicId.c_str(), resourceType); - } - - void SignalRemainingAncestor(const std::string& ancestorId, - OrthancPluginResourceType ancestorType) - { - OrthancPluginDatabaseSignalRemainingAncestor(context_, database_, ancestorId.c_str(), ancestorType); - } - - void AnswerAttachment(const std::string& uuid, - int32_t contentType, - uint64_t uncompressedSize, - const std::string& uncompressedHash, - int32_t compressionType, - uint64_t compressedSize, - const std::string& compressedHash) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_Attachment) - { - throw std::runtime_error("Cannot answer with an attachment in the current state"); - } - - OrthancPluginAttachment attachment; - attachment.uuid = uuid.c_str(); - attachment.contentType = contentType; - attachment.uncompressedSize = uncompressedSize; - attachment.uncompressedHash = uncompressedHash.c_str(); - attachment.compressionType = compressionType; - attachment.compressedSize = compressedSize; - attachment.compressedHash = compressedHash.c_str(); - - OrthancPluginDatabaseAnswerAttachment(context_, database_, &attachment); - } - - void AnswerChange(int64_t seq, - int32_t changeType, - OrthancPluginResourceType resourceType, - const std::string& publicId, - const std::string& date) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_Change) - { - throw std::runtime_error("Cannot answer with a change in the current state"); - } - - OrthancPluginChange change; - change.seq = seq; - change.changeType = changeType; - change.resourceType = resourceType; - change.publicId = publicId.c_str(); - change.date = date.c_str(); - - OrthancPluginDatabaseAnswerChange(context_, database_, &change); - } - - void AnswerDicomTag(uint16_t group, - uint16_t element, - const std::string& value) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_DicomTag) - { - throw std::runtime_error("Cannot answer with a DICOM tag in the current state"); - } - - OrthancPluginDicomTag tag; - tag.group = group; - tag.element = element; - tag.value = value.c_str(); - - OrthancPluginDatabaseAnswerDicomTag(context_, database_, &tag); - } - - void AnswerExportedResource(int64_t seq, - OrthancPluginResourceType resourceType, - const std::string& publicId, - const std::string& modality, - const std::string& date, - const std::string& patientId, - const std::string& studyInstanceUid, - const std::string& seriesInstanceUid, - const std::string& sopInstanceUid) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_ExportedResource) - { - throw std::runtime_error("Cannot answer with an exported resource in the current state"); - } - - OrthancPluginExportedResource exported; - exported.seq = seq; - exported.resourceType = resourceType; - exported.publicId = publicId.c_str(); - exported.modality = modality.c_str(); - exported.date = date.c_str(); - exported.patientId = patientId.c_str(); - exported.studyInstanceUid = studyInstanceUid.c_str(); - exported.seriesInstanceUid = seriesInstanceUid.c_str(); - exported.sopInstanceUid = sopInstanceUid.c_str(); - - OrthancPluginDatabaseAnswerExportedResource(context_, database_, &exported); - } - }; - - - /** - * @ingroup Callbacks - **/ - class IDatabaseBackend : public NonCopyable - { - friend class DatabaseBackendAdapter; - - private: - DatabaseBackendOutput* output_; - - void Finalize() - { - if (output_ != NULL) - { - delete output_; - output_ = NULL; - } - } - - protected: - DatabaseBackendOutput& GetOutput() - { - return *output_; - } - - public: - IDatabaseBackend() : output_(NULL) - { - } - - virtual ~IDatabaseBackend() - { - Finalize(); - } - - // This takes the ownership - void RegisterOutput(DatabaseBackendOutput* output) - { - Finalize(); - output_ = output; - } - - virtual void Open() = 0; - - virtual void Close() = 0; - - virtual void AddAttachment(int64_t id, - const OrthancPluginAttachment& attachment) = 0; - - virtual void AttachChild(int64_t parent, - int64_t child) = 0; - - virtual void ClearChanges() = 0; - - virtual void ClearExportedResources() = 0; - - virtual int64_t CreateResource(const char* publicId, - OrthancPluginResourceType type) = 0; - - virtual void DeleteAttachment(int64_t id, - int32_t attachment) = 0; - - virtual void DeleteMetadata(int64_t id, - int32_t metadataType) = 0; - - virtual void DeleteResource(int64_t id) = 0; - - virtual void GetAllInternalIds(std::list<int64_t>& target, - OrthancPluginResourceType resourceType) = 0; - - virtual void GetAllPublicIds(std::list<std::string>& target, - OrthancPluginResourceType resourceType) = 0; - - virtual void GetAllPublicIds(std::list<std::string>& target, - OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) = 0; - - /* Use GetOutput().AnswerChange() */ - virtual void GetChanges(bool& done /*out*/, - int64_t since, - uint32_t maxResults) = 0; - - virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/, - int64_t id) = 0; - - virtual void GetChildrenPublicId(std::list<std::string>& target /*out*/, - int64_t id) = 0; - - /* Use GetOutput().AnswerExportedResource() */ - virtual void GetExportedResources(bool& done /*out*/, - int64_t since, - uint32_t maxResults) = 0; - - /* Use GetOutput().AnswerChange() */ - virtual void GetLastChange() = 0; - - /* Use GetOutput().AnswerExportedResource() */ - virtual void GetLastExportedResource() = 0; - - /* Use GetOutput().AnswerDicomTag() */ - virtual void GetMainDicomTags(int64_t id) = 0; - - virtual std::string GetPublicId(int64_t resourceId) = 0; - - virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType) = 0; - - virtual OrthancPluginResourceType GetResourceType(int64_t resourceId) = 0; - - virtual uint64_t GetTotalCompressedSize() = 0; - - virtual uint64_t GetTotalUncompressedSize() = 0; - - virtual bool IsExistingResource(int64_t internalId) = 0; - - virtual bool IsProtectedPatient(int64_t internalId) = 0; - - virtual void ListAvailableMetadata(std::list<int32_t>& target /*out*/, - int64_t id) = 0; - - virtual void ListAvailableAttachments(std::list<int32_t>& target /*out*/, - int64_t id) = 0; - - virtual void LogChange(const OrthancPluginChange& change) = 0; - - virtual void LogExportedResource(const OrthancPluginExportedResource& resource) = 0; - - /* Use GetOutput().AnswerAttachment() */ - virtual bool LookupAttachment(int64_t id, - int32_t contentType) = 0; - - virtual bool LookupGlobalProperty(std::string& target /*out*/, - int32_t property) = 0; - - virtual void LookupIdentifier(std::list<int64_t>& target /*out*/, - OrthancPluginResourceType resourceType, - uint16_t group, - uint16_t element, - OrthancPluginIdentifierConstraint constraint, - const char* value) = 0; - - virtual bool LookupMetadata(std::string& target /*out*/, - int64_t id, - int32_t metadataType) = 0; - - virtual bool LookupParent(int64_t& parentId /*out*/, - int64_t resourceId) = 0; - - virtual bool LookupResource(int64_t& id /*out*/, - OrthancPluginResourceType& type /*out*/, - const char* publicId) = 0; - - virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/) = 0; - - virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/, - int64_t patientIdToAvoid) = 0; - - virtual void SetGlobalProperty(int32_t property, - const char* value) = 0; - - virtual void SetMainDicomTag(int64_t id, - uint16_t group, - uint16_t element, - const char* value) = 0; - - virtual void SetIdentifierTag(int64_t id, - uint16_t group, - uint16_t element, - const char* value) = 0; - - virtual void SetMetadata(int64_t id, - int32_t metadataType, - const char* value) = 0; - - virtual void SetProtectedPatient(int64_t internalId, - bool isProtected) = 0; - - virtual void StartTransaction() = 0; - - virtual void RollbackTransaction() = 0; - - virtual void CommitTransaction() = 0; - - virtual uint32_t GetDatabaseVersion() = 0; - - /** - * Upgrade the database to the specified version of the database - * schema. The upgrade script is allowed to make calls to - * OrthancPluginReconstructMainDicomTags(). - **/ - virtual void UpgradeDatabase(uint32_t targetVersion, - OrthancPluginStorageArea* storageArea) = 0; - - virtual void ClearMainDicomTags(int64_t internalId) = 0; - }; - - - - /** - * @brief Bridge between C and C++ database engines. - * - * Class creating the bridge between the C low-level primitives for - * custom database engines, and the high-level IDatabaseBackend C++ - * interface. - * - * @ingroup Callbacks - **/ - class DatabaseBackendAdapter - { - private: - // This class cannot be instantiated - DatabaseBackendAdapter() - { - } - - static void LogError(IDatabaseBackend* backend, - const std::runtime_error& e) - { - backend->GetOutput().LogError("Exception in database back-end: " + std::string(e.what())); - } - - - static OrthancPluginErrorCode AddAttachment(void* payload, - int64_t id, - const OrthancPluginAttachment* attachment) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->AddAttachment(id, *attachment); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode AttachChild(void* payload, - int64_t parent, - int64_t child) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->AttachChild(parent, child); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode ClearChanges(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->ClearChanges(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode ClearExportedResources(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->ClearExportedResources(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode CreateResource(int64_t* id, - void* payload, - const char* publicId, - OrthancPluginResourceType resourceType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *id = backend->CreateResource(publicId, resourceType); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode DeleteAttachment(void* payload, - int64_t id, - int32_t contentType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->DeleteAttachment(id, contentType); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode DeleteMetadata(void* payload, - int64_t id, - int32_t metadataType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->DeleteMetadata(id, metadataType); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode DeleteResource(void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->DeleteResource(id); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetAllInternalIds(OrthancPluginDatabaseContext* context, - void* payload, - OrthancPluginResourceType resourceType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int64_t> target; - backend->GetAllInternalIds(target, resourceType); - - for (std::list<int64_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, *it); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetAllPublicIds(OrthancPluginDatabaseContext* context, - void* payload, - OrthancPluginResourceType resourceType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<std::string> ids; - backend->GetAllPublicIds(ids, resourceType); - - for (std::list<std::string>::const_iterator - it = ids.begin(); it != ids.end(); ++it) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - it->c_str()); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetAllPublicIdsWithLimit(OrthancPluginDatabaseContext* context, - void* payload, - OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<std::string> ids; - backend->GetAllPublicIds(ids, resourceType, since, limit); - - for (std::list<std::string>::const_iterator - it = ids.begin(); it != ids.end(); ++it) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - it->c_str()); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetChanges(OrthancPluginDatabaseContext* context, - void* payload, - int64_t since, - uint32_t maxResult) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change); - - try - { - bool done; - backend->GetChanges(done, since, maxResult); - - if (done) - { - OrthancPluginDatabaseAnswerChangesDone(backend->GetOutput().context_, - backend->GetOutput().database_); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetChildrenInternalId(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int64_t> target; - backend->GetChildrenInternalId(target, id); - - for (std::list<int64_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, *it); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetChildrenPublicId(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<std::string> ids; - backend->GetChildrenPublicId(ids, id); - - for (std::list<std::string>::const_iterator - it = ids.begin(); it != ids.end(); ++it) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - it->c_str()); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetExportedResources(OrthancPluginDatabaseContext* context, - void* payload, - int64_t since, - uint32_t maxResult) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource); - - try - { - bool done; - backend->GetExportedResources(done, since, maxResult); - - if (done) - { - OrthancPluginDatabaseAnswerExportedResourcesDone(backend->GetOutput().context_, - backend->GetOutput().database_); - } - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetLastChange(OrthancPluginDatabaseContext* context, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change); - - try - { - backend->GetLastChange(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetLastExportedResource(OrthancPluginDatabaseContext* context, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource); - - try - { - backend->GetLastExportedResource(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetMainDicomTags(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_DicomTag); - - try - { - backend->GetMainDicomTags(id); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetPublicId(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::string s = backend->GetPublicId(id); - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - s.c_str()); - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetResourceCount(uint64_t* target, - void* payload, - OrthancPluginResourceType resourceType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *target = backend->GetResourceCount(resourceType); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetResourceType(OrthancPluginResourceType* resourceType, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *resourceType = backend->GetResourceType(id); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetTotalCompressedSize(uint64_t* target, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *target = backend->GetTotalCompressedSize(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetTotalUncompressedSize(uint64_t* target, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *target = backend->GetTotalUncompressedSize(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode IsExistingResource(int32_t* existing, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *existing = backend->IsExistingResource(id); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode IsProtectedPatient(int32_t* isProtected, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *isProtected = backend->IsProtectedPatient(id); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode ListAvailableMetadata(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int32_t> target; - backend->ListAvailableMetadata(target, id); - - for (std::list<int32_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_, - backend->GetOutput().database_, - *it); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode ListAvailableAttachments(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int32_t> target; - backend->ListAvailableAttachments(target, id); - - for (std::list<int32_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_, - backend->GetOutput().database_, - *it); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LogChange(void* payload, - const OrthancPluginChange* change) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->LogChange(*change); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LogExportedResource(void* payload, - const OrthancPluginExportedResource* exported) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->LogExportedResource(*exported); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupAttachment(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id, - int32_t contentType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Attachment); - - try - { - backend->LookupAttachment(id, contentType); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupGlobalProperty(OrthancPluginDatabaseContext* context, - void* payload, - int32_t property) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::string s; - if (backend->LookupGlobalProperty(s, property)) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - s.c_str()); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupIdentifier3(OrthancPluginDatabaseContext* context, - void* payload, - OrthancPluginResourceType resourceType, - const OrthancPluginDicomTag* tag, - OrthancPluginIdentifierConstraint constraint) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int64_t> target; - backend->LookupIdentifier(target, resourceType, tag->group, tag->element, constraint, tag->value); - - for (std::list<int64_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, *it); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupMetadata(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id, - int32_t metadata) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::string s; - if (backend->LookupMetadata(s, id, metadata)) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, s.c_str()); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupParent(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t parent; - if (backend->LookupParent(parent, id)) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, parent); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupResource(OrthancPluginDatabaseContext* context, - void* payload, - const char* publicId) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t id; - OrthancPluginResourceType type; - if (backend->LookupResource(id, type, publicId)) - { - OrthancPluginDatabaseAnswerResource(backend->GetOutput().context_, - backend->GetOutput().database_, - id, type); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SelectPatientToRecycle(OrthancPluginDatabaseContext* context, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t id; - if (backend->SelectPatientToRecycle(id)) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, id); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SelectPatientToRecycle2(OrthancPluginDatabaseContext* context, - void* payload, - int64_t patientIdToAvoid) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t id; - if (backend->SelectPatientToRecycle(id, patientIdToAvoid)) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, id); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SetGlobalProperty(void* payload, - int32_t property, - const char* value) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetGlobalProperty(property, value); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SetMainDicomTag(void* payload, - int64_t id, - const OrthancPluginDicomTag* tag) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetMainDicomTag(id, tag->group, tag->element, tag->value); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SetIdentifierTag(void* payload, - int64_t id, - const OrthancPluginDicomTag* tag) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetIdentifierTag(id, tag->group, tag->element, tag->value); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SetMetadata(void* payload, - int64_t id, - int32_t metadata, - const char* value) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetMetadata(id, metadata, value); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SetProtectedPatient(void* payload, - int64_t id, - int32_t isProtected) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetProtectedPatient(id, (isProtected != 0)); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode StartTransaction(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->StartTransaction(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode RollbackTransaction(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->RollbackTransaction(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode CommitTransaction(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->CommitTransaction(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode Open(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->Open(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode Close(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->Close(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetDatabaseVersion(uint32_t* version, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - - try - { - *version = backend->GetDatabaseVersion(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode UpgradeDatabase(void* payload, - uint32_t targetVersion, - OrthancPluginStorageArea* storageArea) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - - try - { - backend->UpgradeDatabase(targetVersion, storageArea); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode ClearMainDicomTags(void* payload, - int64_t internalId) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - - try - { - backend->ClearMainDicomTags(internalId); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - public: - /** - * Register a custom database back-end written in C++. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param backend Your custom database engine. - **/ - - static void Register(OrthancPluginContext* context, - IDatabaseBackend& backend) - { - OrthancPluginDatabaseBackend params; - memset(¶ms, 0, sizeof(params)); - - OrthancPluginDatabaseExtensions extensions; - memset(&extensions, 0, sizeof(extensions)); - - params.addAttachment = AddAttachment; - params.attachChild = AttachChild; - params.clearChanges = ClearChanges; - params.clearExportedResources = ClearExportedResources; - params.createResource = CreateResource; - params.deleteAttachment = DeleteAttachment; - params.deleteMetadata = DeleteMetadata; - params.deleteResource = DeleteResource; - params.getAllPublicIds = GetAllPublicIds; - params.getChanges = GetChanges; - params.getChildrenInternalId = GetChildrenInternalId; - params.getChildrenPublicId = GetChildrenPublicId; - params.getExportedResources = GetExportedResources; - params.getLastChange = GetLastChange; - params.getLastExportedResource = GetLastExportedResource; - params.getMainDicomTags = GetMainDicomTags; - params.getPublicId = GetPublicId; - params.getResourceCount = GetResourceCount; - params.getResourceType = GetResourceType; - params.getTotalCompressedSize = GetTotalCompressedSize; - params.getTotalUncompressedSize = GetTotalUncompressedSize; - params.isExistingResource = IsExistingResource; - params.isProtectedPatient = IsProtectedPatient; - params.listAvailableMetadata = ListAvailableMetadata; - params.listAvailableAttachments = ListAvailableAttachments; - params.logChange = LogChange; - params.logExportedResource = LogExportedResource; - params.lookupAttachment = LookupAttachment; - params.lookupGlobalProperty = LookupGlobalProperty; - params.lookupIdentifier = NULL; // Unused starting with Orthanc 0.9.5 (db v6) - params.lookupIdentifier2 = NULL; // Unused starting with Orthanc 0.9.5 (db v6) - params.lookupMetadata = LookupMetadata; - params.lookupParent = LookupParent; - params.lookupResource = LookupResource; - params.selectPatientToRecycle = SelectPatientToRecycle; - params.selectPatientToRecycle2 = SelectPatientToRecycle2; - params.setGlobalProperty = SetGlobalProperty; - params.setMainDicomTag = SetMainDicomTag; - params.setIdentifierTag = SetIdentifierTag; - params.setMetadata = SetMetadata; - params.setProtectedPatient = SetProtectedPatient; - params.startTransaction = StartTransaction; - params.rollbackTransaction = RollbackTransaction; - params.commitTransaction = CommitTransaction; - params.open = Open; - params.close = Close; - - extensions.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit; - extensions.getDatabaseVersion = GetDatabaseVersion; - extensions.upgradeDatabase = UpgradeDatabase; - extensions.clearMainDicomTags = ClearMainDicomTags; - extensions.getAllInternalIds = GetAllInternalIds; // New in Orthanc 0.9.5 (db v6) - extensions.lookupIdentifier3 = LookupIdentifier3; // New in Orthanc 0.9.5 (db v6) - - OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackendV2(context, ¶ms, &extensions, &backend); - if (!context) - { - throw std::runtime_error("Unable to register the database backend"); - } - - backend.RegisterOutput(new DatabaseBackendOutput(context, database)); - } - }; -}
--- a/Resources/CMake/OpenSslConfiguration.cmake Mon Jul 09 11:37:17 2018 +0200 +++ b/Resources/CMake/OpenSslConfiguration.cmake Fri Jul 13 10:07:45 2018 +0200 @@ -95,8 +95,30 @@ ${OPENSSL_SOURCES_DIR}/ssl/ssl3.h ${OPENSSL_SOURCES_DIR}/ssl/tls1.h ) - file(COPY ${header} DESTINATION ${OPENSSL_SOURCES_DIR}/include/openssl) + file(COPY ${header} DESTINATION ${OPENSSL_SOURCES_DIR}/include/openssl) endforeach() + + file(RENAME + ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2.h + ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2_source.h) + + # The following patch of "e_os2.h" prevents from building OpenSSL + # as a DLL under Windows. Otherwise, symbols have inconsistent + # linkage if ${OPENSSL_SOURCES} is used to create a DLL (notably + # if building an Orthanc plugin such as MySQL). + file(WRITE ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2.h " +#include \"e_os2_source.h\" +#if defined(_WIN32) +# undef OPENSSL_EXPORT +# undef OPENSSL_IMPORT +# undef OPENSSL_EXTERN +# undef OPENSSL_GLOBAL +# define OPENSSL_EXPORT +# define OPENSSL_IMPORT +# define OPENSSL_EXTERN extern +# define OPENSSL_GLOBAL +#endif +") endif() add_definitions( @@ -118,7 +140,7 @@ -DOPENSSL_NO_KRB5 -DOPENSSL_NO_MD2 -DOPENSSL_NO_MDC2 - -DOPENSSL_NO_MD4 + #-DOPENSSL_NO_MD4 # MD4 is necessary for MariaDB/MySQL client -DOPENSSL_NO_RC2 -DOPENSSL_NO_RC4 -DOPENSSL_NO_RC5 @@ -159,6 +181,7 @@ ${OPENSSL_SOURCES_DIR}/crypto/evp ${OPENSSL_SOURCES_DIR}/crypto/hmac ${OPENSSL_SOURCES_DIR}/crypto/lhash + ${OPENSSL_SOURCES_DIR}/crypto/md4 ${OPENSSL_SOURCES_DIR}/crypto/md5 ${OPENSSL_SOURCES_DIR}/crypto/modes ${OPENSSL_SOURCES_DIR}/crypto/objects @@ -180,6 +203,12 @@ ${OPENSSL_SOURCES_DIR}/ssl ) + if (ENABLE_OPENSSL_ENGINES) + list(APPEND OPENSSL_SOURCES_SUBDIRS + ${OPENSSL_SOURCES_DIR}/engines + ) + endif() + if (ENABLE_PKCS11) list(APPEND OPENSSL_SOURCES_SUBDIRS # EC, ECDH and ECDSA are necessary for PKCS11 @@ -220,6 +249,9 @@ ${OPENSSL_SOURCES_DIR}/crypto/evp/e_dsa.c ${OPENSSL_SOURCES_DIR}/crypto/evp/m_ripemd.c ${OPENSSL_SOURCES_DIR}/crypto/lhash/lh_test.c + ${OPENSSL_SOURCES_DIR}/crypto/md4/md4.c + ${OPENSSL_SOURCES_DIR}/crypto/md4/md4s.cpp + ${OPENSSL_SOURCES_DIR}/crypto/md4/md4test.c ${OPENSSL_SOURCES_DIR}/crypto/md5/md5s.cpp ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/bio_ber.c ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pk7_enc.c @@ -280,6 +312,9 @@ ${OPENSSL_SOURCES_DIR}/ssl/heartbeat_test.c ${OPENSSL_SOURCES_DIR}/ssl/fatalerrtest.c ${OPENSSL_SOURCES_DIR}/ssl/dtlstest.c + ${OPENSSL_SOURCES_DIR}/ssl/bad_dtls_test.c + ${OPENSSL_SOURCES_DIR}/ssl/clienthellotest.c + ${OPENSSL_SOURCES_DIR}/ssl/sslv2conftest.c ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256_table.c ) @@ -289,6 +324,10 @@ ${OPENSSL_SOURCES} PROPERTIES COMPILE_DEFINITIONS "OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN") + + if (ENABLE_OPENSSL_ENGINES) + link_libraries(crypt32) + endif() endif() source_group(ThirdParty\\OpenSSL REGULAR_EXPRESSION ${OPENSSL_SOURCES_DIR}/.*)
--- a/Resources/CMake/OrthancFrameworkConfiguration.cmake Mon Jul 09 11:37:17 2018 +0200 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Fri Jul 13 10:07:45 2018 +0200 @@ -43,6 +43,7 @@ if (NOT ENABLE_CRYPTO_OPTIONS) unset(ENABLE_SSL CACHE) unset(ENABLE_PKCS11 CACHE) + unset(ENABLE_OPENSSL_ENGINES CACHE) unset(USE_SYSTEM_OPENSSL CACHE) unset(USE_SYSTEM_LIBP11 CACHE) add_definitions( @@ -83,6 +84,7 @@ if (NOT ENABLE_LUA) unset(USE_SYSTEM_LUA CACHE) + unset(ENABLE_LUA_MODULES CACHE) add_definitions(-DORTHANC_ENABLE_LUA=0) endif() @@ -130,6 +132,7 @@ ${ORTHANC_ROOT}/Core/DicomFormat/DicomTag.cpp ${ORTHANC_ROOT}/Core/DicomFormat/DicomValue.cpp ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/FileStorage/MemoryStorageArea.cpp ${ORTHANC_ROOT}/Core/Images/Font.cpp ${ORTHANC_ROOT}/Core/Images/FontRegistry.cpp ${ORTHANC_ROOT}/Core/Images/IImageWriter.cpp @@ -139,7 +142,16 @@ ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp ${ORTHANC_ROOT}/Core/Images/PamReader.cpp ${ORTHANC_ROOT}/Core/Images/PamWriter.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/GenericJobUnserializer.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/JobInfo.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/JobStatus.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/JobStepResult.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/Operations/JobOperationValues.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/Operations/LogJobOperation.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/SetOfInstancesJob.cpp ${ORTHANC_ROOT}/Core/Logging.cpp + ${ORTHANC_ROOT}/Core/SerializationToolbox.cpp ${ORTHANC_ROOT}/Core/Toolbox.cpp ${ORTHANC_ROOT}/Core/WebServiceParameters.cpp ) @@ -179,6 +191,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/OpenSslConfiguration.cmake) add_definitions(-DORTHANC_ENABLE_SSL=1) else() + unset(ENABLE_OPENSSL_ENGINES CACHE) unset(USE_SYSTEM_OPENSSL CACHE) add_definitions(-DORTHANC_ENABLE_SSL=0) endif() @@ -425,13 +438,12 @@ ${ORTHANC_ROOT}/Core/DicomNetworking/DicomFindAnswers.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/DicomServer.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/DicomUserConnection.cpp - ${ORTHANC_ROOT}/Core/DicomNetworking/RemoteModalityParameters.cpp - ${ORTHANC_ROOT}/Core/DicomNetworking/ReusableDicomUserConnection.cpp - ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/CommandDispatcher.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/FindScp.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/MoveScp.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/StoreScp.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/RemoteModalityParameters.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp ) else() add_definitions(-DORTHANC_ENABLE_DCMTK_NETWORKING=0) @@ -487,24 +499,12 @@ -DORTHANC_SANDBOXED=0 ) - if (ORTHANC_FRAMEWORK_PLUGIN) - add_definitions( - -DORTHANC_ENABLE_LOGGING_PLUGIN=1 - ) - else() - add_definitions( - -DORTHANC_ENABLE_LOGGING_PLUGIN=0 - ) - endif() - list(APPEND ORTHANC_CORE_SOURCES_INTERNAL ${ORTHANC_ROOT}/Core/Cache/SharedArchive.cpp ${ORTHANC_ROOT}/Core/FileStorage/FilesystemStorage.cpp - ${ORTHANC_ROOT}/Core/MultiThreading/BagOfTasksProcessor.cpp - ${ORTHANC_ROOT}/Core/MultiThreading/Mutex.cpp - ${ORTHANC_ROOT}/Core/MultiThreading/ReaderWriterLock.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/JobsEngine.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/JobsRegistry.cpp ${ORTHANC_ROOT}/Core/MultiThreading/RunnableWorkersPool.cpp - ${ORTHANC_ROOT}/Core/MultiThreading/Semaphore.cpp ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp ${ORTHANC_ROOT}/Core/SharedLibrary.cpp ${ORTHANC_ROOT}/Core/SystemToolbox.cpp @@ -516,9 +516,11 @@ if (HAS_EMBEDDED_RESOURCES) add_definitions(-DORTHANC_HAS_EMBEDDED_RESOURCES=1) - list(APPEND ORTHANC_CORE_SOURCES_INTERNAL - ${ORTHANC_ROOT}/Core/HttpServer/EmbeddedResourceHttpHandler.cpp - ) + if (ENABLE_WEB_SERVER) + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/HttpServer/EmbeddedResourceHttpHandler.cpp + ) + endif() else() add_definitions(-DORTHANC_HAS_EMBEDDED_RESOURCES=0) endif()
--- a/Resources/CMake/OrthancFrameworkParameters.cmake Mon Jul 09 11:37:17 2018 +0200 +++ b/Resources/CMake/OrthancFrameworkParameters.cmake Fri Jul 13 10:07:45 2018 +0200 @@ -98,6 +98,7 @@ set(ENABLE_WEB_SERVER OFF CACHE INTERNAL "Enable embedded Web server") set(ENABLE_DCMTK OFF CACHE INTERNAL "Enable DCMTK") set(ENABLE_DCMTK_NETWORKING OFF CACHE INTERNAL "Enable DICOM networking in DCMTK") +set(ENABLE_OPENSSL_ENGINES OFF CACHE INTERNAL "Enable support of engines in OpenSSL") set(HAS_EMBEDDED_RESOURCES OFF CACHE INTERNAL "Whether resources are auto-generated using EmbedResources.py")
--- a/Resources/Configuration.json Mon Jul 09 11:37:17 2018 +0200 +++ b/Resources/Configuration.json Fri Jul 13 10:07:45 2018 +0200 @@ -43,6 +43,11 @@ "Plugins" : [ ], + // Maximum number of processing jobs that are simultanously running + // at any given time. A value of "0" indicates to use all the + // available CPU logical cores. To emulate Orthanc <= 1.3.2, set + // this value to "1". + "ConcurrentJobs" : 2, /** @@ -295,11 +300,13 @@ // some job finishes. "LimitJobs" : 10, - // If this option is set to "false", Orthanc will not log the - // resources that are exported to other DICOM modalities of Orthanc - // peers in the URI "/exports". This is useful to prevent the index - // to grow indefinitely in auto-routing tasks. - "LogExportedResources" : true, + // If this option is set to "true" (default behavior until Orthanc + // 1.3.2), Orthanc will log the resources that are exported to other + // DICOM modalities or Orthanc peers, inside the URI + // "/exports". Setting this option to "false" is useful to prevent + // the index to grow indefinitely in auto-routing tasks (this is the + // default behavior since Orthanc 1.4.0). + "LogExportedResources" : false, // Enable or disable HTTP Keep-Alive (deprecated). Set this option // to "true" only in the case of high HTTP loads. @@ -310,10 +317,11 @@ // this option might prevent the upgrade to newer versions of Orthanc. "StoreDicom" : true, - // DICOM associations are kept open as long as new DICOM commands - // are issued. This option sets the number of seconds of inactivity - // to wait before automatically closing a DICOM association. If set - // to 0, the connection is closed immediately. + // DICOM associations initiated by Lua scripts are kept open as long + // as new DICOM commands are issued. This option sets the number of + // seconds of inactivity to wait before automatically closing a + // DICOM association used by Lua. If set to 0, the connection is + // closed immediately. "DicomAssociationCloseDelay" : 5, // Maximum number of query/retrieve DICOM requests that are @@ -371,5 +379,16 @@ // "00e1,10c2" : [ "UI", "PET-CT Multi Modality Name", 1, 1, "ELSCINT1" ] // "7053,1003" : [ "ST", "Original Image Filename", 1, 1, "Philips PET Private Group" ] // "2001,5f" : [ "SQ", "StackSequence", 1, 1, "Philips Imaging DD 001" ] - } + }, + + // Whether to run DICOM C-Move operations synchronously. If set to + // "false" (the default), each incoming C-Move request results in + // creating a new background job. Until Orthanc 1.3.2, the default + // behavior was to use synchronous C-Move. + "SynchronousCMove" : false, + + // Maximum number of completed jobs that are kept in memory. A + // processing job is considered as complete once it is tagged as + // "Success" or "Failure". + "JobsHistorySize" : 10 }
--- a/Resources/DownloadOrthancFramework.cmake Mon Jul 09 11:37:17 2018 +0200 +++ b/Resources/DownloadOrthancFramework.cmake Fri Jul 13 10:07:45 2018 +0200 @@ -63,27 +63,29 @@ set(ORTHANC_FRAMEWORK_MD5 "") - if (ORTHANC_FRAMEWORK_VERSION STREQUAL "mainline") - set(ORTHANC_FRAMEWORK_BRANCH "default") + if (NOT DEFINED ORTHANC_FRAMEWORK_BRANCH) + if (ORTHANC_FRAMEWORK_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_BRANCH "default") - else() - set(ORTHANC_FRAMEWORK_BRANCH "Orthanc-${ORTHANC_FRAMEWORK_VERSION}") + else() + set(ORTHANC_FRAMEWORK_BRANCH "Orthanc-${ORTHANC_FRAMEWORK_VERSION}") - set(RE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$") - string(REGEX REPLACE ${RE} "\\1" ORTHANC_FRAMEWORK_MAJOR ${ORTHANC_FRAMEWORK_VERSION}) - string(REGEX REPLACE ${RE} "\\2" ORTHANC_FRAMEWORK_MINOR ${ORTHANC_FRAMEWORK_VERSION}) - string(REGEX REPLACE ${RE} "\\3" ORTHANC_FRAMEWORK_REVISION ${ORTHANC_FRAMEWORK_VERSION}) + set(RE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$") + string(REGEX REPLACE ${RE} "\\1" ORTHANC_FRAMEWORK_MAJOR ${ORTHANC_FRAMEWORK_VERSION}) + string(REGEX REPLACE ${RE} "\\2" ORTHANC_FRAMEWORK_MINOR ${ORTHANC_FRAMEWORK_VERSION}) + string(REGEX REPLACE ${RE} "\\3" ORTHANC_FRAMEWORK_REVISION ${ORTHANC_FRAMEWORK_VERSION}) - if (NOT ORTHANC_FRAMEWORK_MAJOR MATCHES "^[0-9]+$" OR - NOT ORTHANC_FRAMEWORK_MINOR MATCHES "^[0-9]+$" OR - NOT ORTHANC_FRAMEWORK_REVISION MATCHES "^[0-9]+$") - message("Bad version of the Orthanc framework: ${ORTHANC_FRAMEWORK_VERSION}") - endif() + if (NOT ORTHANC_FRAMEWORK_MAJOR MATCHES "^[0-9]+$" OR + NOT ORTHANC_FRAMEWORK_MINOR MATCHES "^[0-9]+$" OR + NOT ORTHANC_FRAMEWORK_REVISION MATCHES "^[0-9]+$") + message("Bad version of the Orthanc framework: ${ORTHANC_FRAMEWORK_VERSION}") + endif() - if (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.1") - set(ORTHANC_FRAMEWORK_MD5 "dac95bd6cf86fb19deaf4e612961f378") - elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.2") - set(ORTHANC_FRAMEWORK_MD5 "d0ccdf68e855d8224331f13774992750") + if (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.1") + set(ORTHANC_FRAMEWORK_MD5 "dac95bd6cf86fb19deaf4e612961f378") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.2") + set(ORTHANC_FRAMEWORK_MD5 "d0ccdf68e855d8224331f13774992750") + endif() endif() endif() endif()
--- a/Resources/EmbedResources.py Mon Jul 09 11:37:17 2018 +0200 +++ b/Resources/EmbedResources.py Fri Jul 13 10:07:45 2018 +0200 @@ -40,8 +40,8 @@ UPCASE_CHECK = True USE_SYSTEM_EXCEPTION = False EXCEPTION_CLASS = 'OrthancException' -OUT_OF_RANGE_EXCEPTION = 'OrthancException(ErrorCode_ParameterOutOfRange)' -INEXISTENT_PATH_EXCEPTION = 'OrthancException(ErrorCode_InexistentItem)' +OUT_OF_RANGE_EXCEPTION = '::Orthanc::OrthancException(::Orthanc::ErrorCode_ParameterOutOfRange)' +INEXISTENT_PATH_EXCEPTION = '::Orthanc::OrthancException(::Orthanc::ErrorCode_InexistentItem)' NAMESPACE = 'Orthanc' ARGS = []
--- a/Resources/ErrorCodes.json Mon Jul 09 11:37:17 2018 +0200 +++ b/Resources/ErrorCodes.json Fri Jul 13 10:07:45 2018 +0200 @@ -207,6 +207,11 @@ "Name": "DatabaseUnavailable", "Description": "The database is currently not available (probably a transient situation)" }, + { + "Code": 37, + "Name": "CanceledJob", + "Description": "This job was canceled" + },
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/BagOfTasks.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,84 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../ICommand.h" + +#include <list> +#include <cstddef> + +namespace Orthanc +{ + class BagOfTasks : public boost::noncopyable + { + private: + typedef std::list<ICommand*> Tasks; + + Tasks tasks_; + + public: + ~BagOfTasks() + { + for (Tasks::iterator it = tasks_.begin(); it != tasks_.end(); ++it) + { + delete *it; + } + } + + ICommand* Pop() + { + ICommand* task = tasks_.front(); + tasks_.pop_front(); + return task; + } + + void Push(ICommand* task) // Takes ownership + { + if (task != NULL) + { + tasks_.push_back(task); + } + } + + size_t GetSize() const + { + return tasks_.size(); + } + + bool IsEmpty() const + { + return tasks_.empty(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/BagOfTasksProcessor.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,277 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "BagOfTasksProcessor.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +#include <stdio.h> + +namespace Orthanc +{ + class BagOfTasksProcessor::Task : public IDynamicObject + { + private: + uint64_t bag_; + std::auto_ptr<ICommand> command_; + + public: + Task(uint64_t bag, + ICommand* command) : + bag_(bag), + command_(command) + { + } + + bool Execute() + { + try + { + return command_->Execute(); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Exception while processing a bag of tasks: " << e.What(); + return false; + } + catch (std::runtime_error& e) + { + LOG(ERROR) << "Runtime exception while processing a bag of tasks: " << e.what(); + return false; + } + catch (...) + { + LOG(ERROR) << "Native exception while processing a bag of tasks"; + return false; + } + } + + uint64_t GetBag() + { + return bag_; + } + }; + + + void BagOfTasksProcessor::SignalProgress(Task& task, + Bag& bag) + { + assert(bag.done_ < bag.size_); + + bag.done_ += 1; + + if (bag.done_ == bag.size_) + { + exitStatus_[task.GetBag()] = (bag.status_ == BagStatus_Running); + bagFinished_.notify_all(); + } + } + + void BagOfTasksProcessor::Worker(BagOfTasksProcessor* that) + { + while (that->continue_) + { + std::auto_ptr<IDynamicObject> obj(that->queue_.Dequeue(100)); + if (obj.get() != NULL) + { + Task& task = *dynamic_cast<Task*>(obj.get()); + + { + boost::mutex::scoped_lock lock(that->mutex_); + + Bags::iterator bag = that->bags_.find(task.GetBag()); + assert(bag != that->bags_.end()); + assert(bag->second.done_ < bag->second.size_); + + if (bag->second.status_ != BagStatus_Running) + { + // Do not execute this task, as its parent bag of tasks + // has failed or is tagged as canceled + that->SignalProgress(task, bag->second); + continue; + } + } + + bool success = task.Execute(); + + { + boost::mutex::scoped_lock lock(that->mutex_); + + Bags::iterator bag = that->bags_.find(task.GetBag()); + assert(bag != that->bags_.end()); + + if (!success) + { + bag->second.status_ = BagStatus_Failed; + } + + that->SignalProgress(task, bag->second); + } + } + } + } + + + void BagOfTasksProcessor::Cancel(int64_t bag) + { + boost::mutex::scoped_lock lock(mutex_); + + Bags::iterator it = bags_.find(bag); + if (it != bags_.end()) + { + it->second.status_ = BagStatus_Canceled; + } + } + + + bool BagOfTasksProcessor::Join(int64_t bag) + { + boost::mutex::scoped_lock lock(mutex_); + + while (continue_) + { + ExitStatus::iterator it = exitStatus_.find(bag); + if (it == exitStatus_.end()) // The bag is still running + { + bagFinished_.wait(lock); + } + else + { + bool status = it->second; + exitStatus_.erase(it); + return status; + } + } + + return false; // The processor is stopping + } + + + float BagOfTasksProcessor::GetProgress(int64_t bag) + { + boost::mutex::scoped_lock lock(mutex_); + + Bags::const_iterator it = bags_.find(bag); + if (it == bags_.end()) + { + // The bag of tasks has finished + return 1.0f; + } + else + { + return (static_cast<float>(it->second.done_) / + static_cast<float>(it->second.size_)); + } + } + + + bool BagOfTasksProcessor::Handle::Join() + { + if (hasJoined_) + { + return status_; + } + else + { + status_ = that_.Join(bag_); + hasJoined_ = true; + return status_; + } + } + + + BagOfTasksProcessor::BagOfTasksProcessor(size_t countThreads) : + countBags_(0), + continue_(true) + { + if (countThreads == 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + threads_.resize(countThreads); + + for (size_t i = 0; i < threads_.size(); i++) + { + threads_[i] = new boost::thread(Worker, this); + } + } + + + BagOfTasksProcessor::~BagOfTasksProcessor() + { + continue_ = false; + + bagFinished_.notify_all(); // Wakes up all the pending "Join()" + + for (size_t i = 0; i < threads_.size(); i++) + { + if (threads_[i]) + { + if (threads_[i]->joinable()) + { + threads_[i]->join(); + } + + delete threads_[i]; + threads_[i] = NULL; + } + } + } + + + BagOfTasksProcessor::Handle* BagOfTasksProcessor::Submit(BagOfTasks& tasks) + { + if (tasks.GetSize() == 0) + { + return new Handle(*this, 0, true); + } + + boost::mutex::scoped_lock lock(mutex_); + + uint64_t id = countBags_; + countBags_ += 1; + + Bag bag(tasks.GetSize()); + bags_[id] = bag; + + while (!tasks.IsEmpty()) + { + queue_.Enqueue(new Task(id, tasks.Pop())); + } + + return new Handle(*this, id, false); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/BagOfTasksProcessor.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,150 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "BagOfTasks.h" +#include "SharedMessageQueue.h" + +#include <stdint.h> +#include <map> + +namespace Orthanc +{ + class BagOfTasksProcessor : public boost::noncopyable + { + private: + enum BagStatus + { + BagStatus_Running, + BagStatus_Canceled, + BagStatus_Failed + }; + + + struct Bag + { + size_t size_; + size_t done_; + BagStatus status_; + + Bag() : + size_(0), + done_(0), + status_(BagStatus_Failed) + { + } + + explicit Bag(size_t size) : + size_(size), + done_(0), + status_(BagStatus_Running) + { + } + }; + + class Task; + + + typedef std::map<uint64_t, Bag> Bags; + typedef std::map<uint64_t, bool> ExitStatus; + + SharedMessageQueue queue_; + + boost::mutex mutex_; + uint64_t countBags_; + Bags bags_; + std::vector<boost::thread*> threads_; + ExitStatus exitStatus_; + bool continue_; + + boost::condition_variable bagFinished_; + + static void Worker(BagOfTasksProcessor* that); + + void Cancel(int64_t bag); + + bool Join(int64_t bag); + + float GetProgress(int64_t bag); + + void SignalProgress(Task& task, + Bag& bag); + + public: + class Handle : public boost::noncopyable + { + friend class BagOfTasksProcessor; + + private: + BagOfTasksProcessor& that_; + uint64_t bag_; + bool hasJoined_; + bool status_; + + Handle(BagOfTasksProcessor& that, + uint64_t bag, + bool empty) : + that_(that), + bag_(bag), + hasJoined_(empty) + { + } + + public: + ~Handle() + { + Join(); + } + + void Cancel() + { + that_.Cancel(bag_); + } + + bool Join(); + + float GetProgress() + { + return that_.GetProgress(bag_); + } + }; + + + explicit BagOfTasksProcessor(size_t countThreads); + + ~BagOfTasksProcessor(); + + Handle* Submit(BagOfTasks& tasks); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/ILockable.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class ILockable : public boost::noncopyable + { + friend class Locker; + + protected: + virtual void Lock() = 0; + + virtual void Unlock() = 0; + + public: + virtual ~ILockable() + { + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/Locker.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,56 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ILockable.h" + +namespace Orthanc +{ + class Locker : public boost::noncopyable + { + private: + ILockable& lockable_; + + public: + Locker(ILockable& lockable) : lockable_(lockable) + { + lockable_.Lock(); + } + + virtual ~Locker() + { + lockable_.Unlock(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/Mutex.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,122 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "Mutex.h" + +#include "../OrthancException.h" + +#if defined(_WIN32) +#include <windows.h> +#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) +#include <pthread.h> +#else +#error Support your platform here +#endif + +namespace Orthanc +{ +#if defined (_WIN32) + + struct Mutex::PImpl + { + CRITICAL_SECTION criticalSection_; + }; + + Mutex::Mutex() + { + pimpl_ = new PImpl; + ::InitializeCriticalSection(&pimpl_->criticalSection_); + } + + Mutex::~Mutex() + { + ::DeleteCriticalSection(&pimpl_->criticalSection_); + delete pimpl_; + } + + void Mutex::Lock() + { + ::EnterCriticalSection(&pimpl_->criticalSection_); + } + + void Mutex::Unlock() + { + ::LeaveCriticalSection(&pimpl_->criticalSection_); + } + + +#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) + + struct Mutex::PImpl + { + pthread_mutex_t mutex_; + }; + + Mutex::Mutex() + { + pimpl_ = new PImpl; + + if (pthread_mutex_init(&pimpl_->mutex_, NULL) != 0) + { + delete pimpl_; + throw OrthancException(ErrorCode_InternalError); + } + } + + Mutex::~Mutex() + { + pthread_mutex_destroy(&pimpl_->mutex_); + delete pimpl_; + } + + void Mutex::Lock() + { + if (pthread_mutex_lock(&pimpl_->mutex_) != 0) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + void Mutex::Unlock() + { + if (pthread_mutex_unlock(&pimpl_->mutex_) != 0) + { + throw OrthancException(ErrorCode_InternalError); + } + } + +#else +#error Support your plateform here +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/Mutex.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ILockable.h" + +namespace Orthanc +{ + class Mutex : public ILockable + { + private: + struct PImpl; + + PImpl *pimpl_; + + protected: + virtual void Lock(); + + virtual void Unlock(); + + public: + Mutex(); + + ~Mutex(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/ReaderWriterLock.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,126 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ReaderWriterLock.h" + +#include <boost/thread/shared_mutex.hpp> + +namespace Orthanc +{ + namespace + { + // Anonymous namespace to avoid clashes between compilation + // modules. + + class ReaderLockable : public ILockable + { + private: + boost::shared_mutex& lock_; + + protected: + virtual void Lock() + { + lock_.lock_shared(); + } + + virtual void Unlock() + { + lock_.unlock_shared(); + } + + public: + explicit ReaderLockable(boost::shared_mutex& lock) : lock_(lock) + { + } + }; + + + class WriterLockable : public ILockable + { + private: + boost::shared_mutex& lock_; + + protected: + virtual void Lock() + { + lock_.lock(); + } + + virtual void Unlock() + { + lock_.unlock(); + } + + public: + explicit WriterLockable(boost::shared_mutex& lock) : lock_(lock) + { + } + }; + } + + struct ReaderWriterLock::PImpl + { + boost::shared_mutex lock_; + ReaderLockable reader_; + WriterLockable writer_; + + PImpl() : reader_(lock_), writer_(lock_) + { + } + }; + + + ReaderWriterLock::ReaderWriterLock() + { + pimpl_ = new PImpl; + } + + + ReaderWriterLock::~ReaderWriterLock() + { + delete pimpl_; + } + + + ILockable& ReaderWriterLock::ForReader() + { + return pimpl_->reader_; + } + + + ILockable& ReaderWriterLock::ForWriter() + { + return pimpl_->writer_; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/ReaderWriterLock.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ILockable.h" + +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class ReaderWriterLock : public boost::noncopyable + { + private: + struct PImpl; + + PImpl *pimpl_; + + public: + ReaderWriterLock(); + + virtual ~ReaderWriterLock(); + + ILockable& ForReader(); + + ILockable& ForWriter(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/Semaphore.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,65 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "Semaphore.h" + +#include "../OrthancException.h" + + +namespace Orthanc +{ + Semaphore::Semaphore(unsigned int count) : count_(count) + { + } + + void Semaphore::Release() + { + boost::mutex::scoped_lock lock(mutex_); + + count_++; + condition_.notify_one(); + } + + void Semaphore::Acquire() + { + boost::mutex::scoped_lock lock(mutex_); + + while (count_ == 0) + { + condition_.wait(lock); + } + + count_++; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/Semaphore.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,73 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/noncopyable.hpp> +#include <boost/thread.hpp> + +namespace Orthanc +{ + class Semaphore : public boost::noncopyable + { + private: + unsigned int count_; + boost::mutex mutex_; + boost::condition_variable condition_; + + public: + explicit Semaphore(unsigned int count); + + void Release(); + + void Acquire(); + + class Locker : public boost::noncopyable + { + private: + Semaphore& that_; + + public: + explicit Locker(Semaphore& that) : + that_(that) + { + that_.Acquire(); + } + + ~Locker() + { + that_.Release(); + } + }; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/CallSystemCommand.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,85 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "CallSystemCommand.h" + +#include "../../Core/Logging.h" +#include "../../Core/Toolbox.h" +#include "../../Core/TemporaryFile.h" + +namespace Orthanc +{ + CallSystemCommand::CallSystemCommand(ServerContext& context, + const std::string& command, + const std::vector<std::string>& arguments) : + context_(context), + command_(command), + arguments_(arguments) + { + } + + bool CallSystemCommand::Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + LOG(INFO) << "Calling system command " << command_ << " on instance " << *it; + + try + { + std::string dicom; + context_.ReadDicom(dicom, *it); + + TemporaryFile tmp; + tmp.Write(dicom); + + std::vector<std::string> args = arguments_; + args.push_back(tmp.GetPath()); + + SystemToolbox::ExecuteSystemCommand(command_, args); + + // Only chain with other commands if this command succeeds + outputs.push_back(*it); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Unable to call system command " << command_ + << " on instance " << *it << " in a Lua script: " << e.What(); + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/CallSystemCommand.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,56 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IServerCommand.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class CallSystemCommand : public IServerCommand + { + private: + ServerContext& context_; + std::string command_; + std::vector<std::string> arguments_; + + public: + CallSystemCommand(ServerContext& context, + const std::string& command, + const std::vector<std::string>& arguments); + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,62 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "DeleteInstanceCommand.h" + +#include "../../Core/Logging.h" + +namespace Orthanc +{ + bool DeleteInstanceCommand::Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + LOG(INFO) << "Deleting instance " << *it; + + try + { + Json::Value tmp; + context_.DeleteResource(tmp, *it, ResourceType_Instance); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Unable to delete instance " << *it << ": " << e.What(); + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IServerCommand.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class DeleteInstanceCommand : public IServerCommand + { + private: + ServerContext& context_; + + public: + DeleteInstanceCommand(ServerContext& context) : context_(context) + { + } + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/IServerCommand.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <list> +#include <string> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class IServerCommand : public boost::noncopyable + { + public: + typedef std::list<std::string> ListOfStrings; + + virtual ~IServerCommand() + { + } + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,124 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "ModifyInstanceCommand.h" + +#include "../../Core/Logging.h" + +namespace Orthanc +{ + ModifyInstanceCommand::ModifyInstanceCommand(ServerContext& context, + RequestOrigin origin, + DicomModification* modification) : + context_(context), + origin_(origin), + modification_(modification) + { + modification_->SetAllowManualIdentifiers(true); + + if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID)) + { + modification_->SetLevel(ResourceType_Patient); + } + else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + modification_->SetLevel(ResourceType_Study); + } + else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + modification_->SetLevel(ResourceType_Series); + } + else + { + modification_->SetLevel(ResourceType_Instance); + } + + if (origin_ != RequestOrigin_Lua) + { + // TODO If issued from HTTP, "remoteIp" and "username" must be provided + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + ModifyInstanceCommand::~ModifyInstanceCommand() + { + if (modification_) + { + delete modification_; + } + } + + + bool ModifyInstanceCommand::Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + LOG(INFO) << "Modifying resource " << *it; + + try + { + std::auto_ptr<ParsedDicomFile> modified; + + { + ServerContext::DicomCacheLocker lock(context_, *it); + modified.reset(lock.GetDicom().Clone(true)); + } + + modification_->Apply(*modified); + + DicomInstanceToStore toStore; + assert(origin_ == RequestOrigin_Lua); + toStore.SetLuaOrigin(); + toStore.SetParsedDicomFile(*modified); + // TODO other metadata + toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, *it); + + std::string modifiedId; + context_.Store(modifiedId, toStore); + + // Only chain with other commands if this command succeeds + outputs.push_back(modifiedId); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Unable to modify instance " << *it << ": " << e.What(); + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,64 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IServerCommand.h" +#include "../ServerContext.h" +#include "../../Core/DicomParsing/DicomModification.h" + +namespace Orthanc +{ + class ModifyInstanceCommand : public IServerCommand + { + private: + ServerContext& context_; + RequestOrigin origin_; + DicomModification* modification_; + + public: + ModifyInstanceCommand(ServerContext& context, + RequestOrigin origin, + DicomModification* modification); // takes the ownership + + virtual ~ModifyInstanceCommand(); + + const DicomModification& GetModification() const + { + return *modification_; + } + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,188 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ReusableDicomUserConnection.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +namespace Orthanc +{ + static boost::posix_time::ptime Now() + { + return boost::posix_time::microsec_clock::local_time(); + } + + void ReusableDicomUserConnection::Open(const std::string& localAet, + const RemoteModalityParameters& remote) + { + if (connection_ != NULL && + connection_->GetLocalApplicationEntityTitle() == localAet && + connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() && + connection_->GetRemoteHost() == remote.GetHost() && + connection_->GetRemotePort() == remote.GetPort() && + connection_->GetRemoteManufacturer() == remote.GetManufacturer()) + { + // The current connection can be reused + LOG(INFO) << "Reusing the previous SCU connection"; + return; + } + + Close(); + + connection_ = new DicomUserConnection(); + connection_->SetLocalApplicationEntityTitle(localAet); + connection_->SetRemoteModality(remote); + connection_->Open(); + } + + void ReusableDicomUserConnection::Close() + { + if (connection_ != NULL) + { + delete connection_; + connection_ = NULL; + } + } + + void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that) + { + for (;;) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + if (!that->continue_) + { + //LOG(INFO) << "Finishing the thread watching the global SCU connection"; + return; + } + + { + boost::mutex::scoped_lock lock(that->mutex_); + if (that->connection_ != NULL && + Now() >= that->lastUse_ + that->timeBeforeClose_) + { + LOG(INFO) << "Closing the global SCU connection after timeout"; + that->Close(); + } + } + } + } + + + ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that, + const std::string& localAet, + const RemoteModalityParameters& remote) : + ::Orthanc::Locker(that) + { + that.Open(localAet, remote); + connection_ = that.connection_; + } + + + DicomUserConnection& ReusableDicomUserConnection::Locker::GetConnection() + { + if (connection_ == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + return *connection_; + } + + ReusableDicomUserConnection::ReusableDicomUserConnection() : + connection_(NULL), + timeBeforeClose_(boost::posix_time::seconds(5)) // By default, close connection after 5 seconds + { + lastUse_ = Now(); + continue_ = true; + closeThread_ = boost::thread(CloseThread, this); + } + + ReusableDicomUserConnection::~ReusableDicomUserConnection() + { + if (continue_) + { + LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!"; + Finalize(); + } + } + + void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms) + { + boost::mutex::scoped_lock lock(mutex_); + + if (ms == 0) + { + ms = 1; + } + + timeBeforeClose_ = boost::posix_time::milliseconds(ms); + } + + void ReusableDicomUserConnection::Lock() + { + mutex_.lock(); + } + + void ReusableDicomUserConnection::Unlock() + { + if (connection_ != NULL && + connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp) + { + // "storescp" from DCMTK has problems when reusing a + // connection. Always close. + Close(); + } + + lastUse_ = Now(); + mutex_.unlock(); + } + + + void ReusableDicomUserConnection::Finalize() + { + if (continue_) + { + continue_ = false; + + if (closeThread_.joinable()) + { + closeThread_.join(); + } + + Close(); + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,89 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "DicomUserConnection.h" +#include "../../Core/MultiThreading/Locker.h" + +#include <boost/thread.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +namespace Orthanc +{ + class ReusableDicomUserConnection : public ILockable + { + private: + boost::mutex mutex_; + DicomUserConnection* connection_; + bool continue_; + boost::posix_time::time_duration timeBeforeClose_; + boost::posix_time::ptime lastUse_; + boost::thread closeThread_; + + void Open(const std::string& localAet, + const RemoteModalityParameters& remote); + + void Close(); + + static void CloseThread(ReusableDicomUserConnection* that); + + protected: + virtual void Lock(); + + virtual void Unlock(); + + public: + class Locker : public ::Orthanc::Locker + { + private: + DicomUserConnection* connection_; + + public: + Locker(ReusableDicomUserConnection& that, + const std::string& localAet, + const RemoteModalityParameters& remote); + + DicomUserConnection& GetConnection(); + }; + + ReusableDicomUserConnection(); + + virtual ~ReusableDicomUserConnection(); + + void SetMillisecondsBeforeClose(uint64_t ms); + + void Finalize(); + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,99 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "ServerCommandInstance.h" + +#include "../../Core/OrthancException.h" + +namespace Orthanc +{ + bool ServerCommandInstance::Execute(IListener& listener) + { + ListOfStrings outputs; + + bool success = false; + + try + { + if (command_->Apply(outputs, inputs_)) + { + success = true; + } + } + catch (OrthancException&) + { + } + + if (!success) + { + listener.SignalFailure(jobId_); + return true; + } + + for (std::list<ServerCommandInstance*>::iterator + it = next_.begin(); it != next_.end(); ++it) + { + for (ListOfStrings::const_iterator + output = outputs.begin(); output != outputs.end(); ++output) + { + (*it)->AddInput(*output); + } + } + + listener.SignalSuccess(jobId_); + return true; + } + + + ServerCommandInstance::ServerCommandInstance(IServerCommand *command, + const std::string& jobId) : + command_(command), + jobId_(jobId), + connectedToSink_(false) + { + if (command_ == NULL) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + ServerCommandInstance::~ServerCommandInstance() + { + if (command_ != NULL) + { + delete command_; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerCommandInstance.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,105 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Core/IDynamicObject.h" +#include "IServerCommand.h" + +namespace Orthanc +{ + class ServerCommandInstance : public IDynamicObject + { + friend class ServerScheduler; + + public: + class IListener + { + public: + virtual ~IListener() + { + } + + virtual void SignalSuccess(const std::string& jobId) = 0; + + virtual void SignalFailure(const std::string& jobId) = 0; + }; + + private: + typedef IServerCommand::ListOfStrings ListOfStrings; + + IServerCommand *command_; + std::string jobId_; + ListOfStrings inputs_; + std::list<ServerCommandInstance*> next_; + bool connectedToSink_; + + bool Execute(IListener& listener); + + public: + ServerCommandInstance(IServerCommand *command, + const std::string& jobId); + + virtual ~ServerCommandInstance(); + + const std::string& GetJobId() const + { + return jobId_; + } + + void AddInput(const std::string& input) + { + inputs_.push_back(input); + } + + void ConnectOutput(ServerCommandInstance& next) + { + next_.push_back(&next); + } + + void SetConnectedToSink(bool connected = true) + { + connectedToSink_ = connected; + } + + bool IsConnectedToSink() const + { + return connectedToSink_; + } + + const std::list<ServerCommandInstance*>& GetNextCommands() const + { + return next_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerJob.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,147 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "ServerJob.h" + +#include "../../Core/OrthancException.h" +#include "../../Core/Toolbox.h" + +namespace Orthanc +{ + void ServerJob::CheckOrdering() + { + std::map<ServerCommandInstance*, unsigned int> index; + + unsigned int count = 0; + for (std::list<ServerCommandInstance*>::const_iterator + it = filters_.begin(); it != filters_.end(); ++it) + { + index[*it] = count++; + } + + for (std::list<ServerCommandInstance*>::const_iterator + it = filters_.begin(); it != filters_.end(); ++it) + { + const std::list<ServerCommandInstance*>& nextCommands = (*it)->GetNextCommands(); + + for (std::list<ServerCommandInstance*>::const_iterator + next = nextCommands.begin(); next != nextCommands.end(); ++next) + { + if (index.find(*next) == index.end() || + index[*next] <= index[*it]) + { + // You must reorder your calls to "ServerJob::AddCommand" + throw OrthancException(ErrorCode_BadJobOrdering); + } + } + } + } + + + size_t ServerJob::Submit(SharedMessageQueue& target, + ServerCommandInstance::IListener& listener) + { + if (submitted_) + { + // This job has already been submitted + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + CheckOrdering(); + + size_t size = filters_.size(); + + for (std::list<ServerCommandInstance*>::iterator + it = filters_.begin(); it != filters_.end(); ++it) + { + target.Enqueue(*it); + } + + filters_.clear(); + submitted_ = true; + + return size; + } + + + ServerJob::ServerJob() : + jobId_(Toolbox::GenerateUuid()), + submitted_(false), + description_("no description") + { + } + + + ServerJob::~ServerJob() + { + for (std::list<ServerCommandInstance*>::iterator + it = filters_.begin(); it != filters_.end(); ++it) + { + delete *it; + } + + for (std::list<IDynamicObject*>::iterator + it = payloads_.begin(); it != payloads_.end(); ++it) + { + delete *it; + } + } + + + ServerCommandInstance& ServerJob::AddCommand(IServerCommand* filter) + { + if (submitted_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + filters_.push_back(new ServerCommandInstance(filter, jobId_)); + + return *filters_.back(); + } + + + IDynamicObject& ServerJob::AddPayload(IDynamicObject* payload) + { + if (submitted_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + payloads_.push_back(payload); + + return *filters_.back(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerJob.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ServerCommandInstance.h" +#include "../../Core/MultiThreading/SharedMessageQueue.h" + +namespace Orthanc +{ + class ServerJob + { + friend class ServerScheduler; + + private: + std::list<ServerCommandInstance*> filters_; + std::list<IDynamicObject*> payloads_; + std::string jobId_; + bool submitted_; + std::string description_; + + void CheckOrdering(); + + size_t Submit(SharedMessageQueue& target, + ServerCommandInstance::IListener& listener); + + public: + ServerJob(); + + ~ServerJob(); + + const std::string& GetId() const + { + return jobId_; + } + + void SetDescription(const std::string& description) + { + description_ = description; + } + + const std::string& GetDescription() const + { + return description_; + } + + ServerCommandInstance& AddCommand(IServerCommand* filter); + + // Take the ownership of a payload to a job. This payload will be + // automatically freed when the job succeeds or fails. + IDynamicObject& AddPayload(IDynamicObject* payload); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerScheduler.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,359 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "ServerScheduler.h" + +#include "../../Core/OrthancException.h" +#include "../../Core/Logging.h" + +namespace Orthanc +{ + namespace + { + // Anonymous namespace to avoid clashes between compilation modules + class Sink : public IServerCommand + { + private: + ListOfStrings& target_; + + public: + explicit Sink(ListOfStrings& target) : target_(target) + { + } + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + target_.push_back(*it); + } + + return true; + } + }; + } + + + ServerScheduler::JobInfo& ServerScheduler::GetJobInfo(const std::string& jobId) + { + Jobs::iterator info = jobs_.find(jobId); + + if (info == jobs_.end()) + { + throw OrthancException(ErrorCode_InternalError); + } + + return info->second; + } + + + void ServerScheduler::SignalSuccess(const std::string& jobId) + { + boost::mutex::scoped_lock lock(mutex_); + + JobInfo& info = GetJobInfo(jobId); + info.success_++; + + assert(info.failures_ == 0); + + if (info.success_ >= info.size_) + { + if (info.watched_) + { + watchedJobStatus_[jobId] = JobStatus_Success; + watchedJobFinished_.notify_all(); + } + + LOG(INFO) << "Job successfully finished (" << info.description_ << ")"; + jobs_.erase(jobId); + + availableJob_.Release(); + } + } + + + void ServerScheduler::SignalFailure(const std::string& jobId) + { + boost::mutex::scoped_lock lock(mutex_); + + JobInfo& info = GetJobInfo(jobId); + info.failures_++; + + if (info.success_ + info.failures_ >= info.size_) + { + if (info.watched_) + { + watchedJobStatus_[jobId] = JobStatus_Failure; + watchedJobFinished_.notify_all(); + } + + LOG(ERROR) << "Job has failed (" << info.description_ << ")"; + jobs_.erase(jobId); + + availableJob_.Release(); + } + } + + + void ServerScheduler::Worker(ServerScheduler* that) + { + static const int32_t TIMEOUT = 100; + + LOG(WARNING) << "The server scheduler has started"; + + while (!that->finish_) + { + std::auto_ptr<IDynamicObject> object(that->queue_.Dequeue(TIMEOUT)); + if (object.get() != NULL) + { + ServerCommandInstance& filter = dynamic_cast<ServerCommandInstance&>(*object); + + // Skip the execution of this filter if its parent job has + // previously failed. + bool jobHasFailed; + { + boost::mutex::scoped_lock lock(that->mutex_); + JobInfo& info = that->GetJobInfo(filter.GetJobId()); + jobHasFailed = (info.failures_ > 0 || info.cancel_); + } + + if (jobHasFailed) + { + that->SignalFailure(filter.GetJobId()); + } + else + { + filter.Execute(*that); + } + } + } + } + + + void ServerScheduler::SubmitInternal(ServerJob& job, + bool watched) + { + availableJob_.Acquire(); + + boost::mutex::scoped_lock lock(mutex_); + + JobInfo info; + info.size_ = job.Submit(queue_, *this); + info.cancel_ = false; + info.success_ = 0; + info.failures_ = 0; + info.description_ = job.GetDescription(); + info.watched_ = watched; + + assert(info.size_ > 0); + + if (watched) + { + watchedJobStatus_[job.GetId()] = JobStatus_Running; + } + + jobs_[job.GetId()] = info; + + LOG(INFO) << "New job submitted (" << job.description_ << ")"; + } + + + ServerScheduler::ServerScheduler(unsigned int maxJobs) : availableJob_(maxJobs) + { + if (maxJobs == 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + finish_ = false; + worker_ = boost::thread(Worker, this); + } + + + ServerScheduler::~ServerScheduler() + { + if (!finish_) + { + LOG(ERROR) << "INTERNAL ERROR: ServerScheduler::Finalize() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } + } + + + void ServerScheduler::Stop() + { + if (!finish_) + { + finish_ = true; + + if (worker_.joinable()) + { + worker_.join(); + } + } + } + + + void ServerScheduler::Submit(ServerJob& job) + { + if (job.filters_.empty()) + { + return; + } + + SubmitInternal(job, false); + } + + + bool ServerScheduler::SubmitAndWait(ListOfStrings& outputs, + ServerJob& job) + { + std::string jobId = job.GetId(); + + outputs.clear(); + + if (job.filters_.empty()) + { + return true; + } + + // Add a sink filter to collect all the results of the filters + // that have no next filter. + ServerCommandInstance& sink = job.AddCommand(new Sink(outputs)); + + for (std::list<ServerCommandInstance*>::iterator + it = job.filters_.begin(); it != job.filters_.end(); ++it) + { + if ((*it) != &sink && + (*it)->IsConnectedToSink()) + { + (*it)->ConnectOutput(sink); + } + } + + // Submit the job + SubmitInternal(job, true); + + // Wait for the job to complete (either success or failure) + JobStatus status; + + { + boost::mutex::scoped_lock lock(mutex_); + + assert(watchedJobStatus_.find(jobId) != watchedJobStatus_.end()); + + while (watchedJobStatus_[jobId] == JobStatus_Running) + { + watchedJobFinished_.wait(lock); + } + + status = watchedJobStatus_[jobId]; + watchedJobStatus_.erase(jobId); + } + + return (status == JobStatus_Success); + } + + + bool ServerScheduler::SubmitAndWait(ServerJob& job) + { + ListOfStrings ignoredSink; + return SubmitAndWait(ignoredSink, job); + } + + + bool ServerScheduler::IsRunning(const std::string& jobId) + { + boost::mutex::scoped_lock lock(mutex_); + return jobs_.find(jobId) != jobs_.end(); + } + + + void ServerScheduler::Cancel(const std::string& jobId) + { + boost::mutex::scoped_lock lock(mutex_); + + Jobs::iterator job = jobs_.find(jobId); + + if (job != jobs_.end()) + { + job->second.cancel_ = true; + LOG(WARNING) << "Canceling a job (" << job->second.description_ << ")"; + } + } + + + float ServerScheduler::GetProgress(const std::string& jobId) + { + boost::mutex::scoped_lock lock(mutex_); + + Jobs::iterator job = jobs_.find(jobId); + + if (job == jobs_.end() || + job->second.size_ == 0 /* should never happen */) + { + // This job is not running + return 1; + } + + if (job->second.failures_ != 0) + { + return 1; + } + + if (job->second.size_ == 1) + { + return static_cast<float>(job->second.success_); + } + + return (static_cast<float>(job->second.success_) / + static_cast<float>(job->second.size_ - 1)); + } + + + void ServerScheduler::GetListOfJobs(ListOfStrings& jobs) + { + boost::mutex::scoped_lock lock(mutex_); + + jobs.clear(); + + for (Jobs::const_iterator + it = jobs_.begin(); it != jobs_.end(); ++it) + { + jobs.push_back(it->first); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerScheduler.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,123 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ServerJob.h" + +#include "../../Core/MultiThreading/Semaphore.h" + +namespace Orthanc +{ + class ServerScheduler : public ServerCommandInstance::IListener + { + private: + struct JobInfo + { + bool watched_; + bool cancel_; + size_t size_; + size_t success_; + size_t failures_; + std::string description_; + }; + + enum JobStatus + { + JobStatus_Running = 1, + JobStatus_Success = 2, + JobStatus_Failure = 3 + }; + + typedef IServerCommand::ListOfStrings ListOfStrings; + typedef std::map<std::string, JobInfo> Jobs; + + boost::mutex mutex_; + boost::condition_variable watchedJobFinished_; + Jobs jobs_; + SharedMessageQueue queue_; + bool finish_; + boost::thread worker_; + std::map<std::string, JobStatus> watchedJobStatus_; + Semaphore availableJob_; + + JobInfo& GetJobInfo(const std::string& jobId); + + virtual void SignalSuccess(const std::string& jobId); + + virtual void SignalFailure(const std::string& jobId); + + static void Worker(ServerScheduler* that); + + void SubmitInternal(ServerJob& job, + bool watched); + + public: + explicit ServerScheduler(unsigned int maxjobs); + + ~ServerScheduler(); + + void Stop(); + + void Submit(ServerJob& job); + + bool SubmitAndWait(ListOfStrings& outputs, + ServerJob& job); + + bool SubmitAndWait(ServerJob& job); + + bool IsRunning(const std::string& jobId); + + void Cancel(const std::string& jobId); + + // Returns a number between 0 and 1 + float GetProgress(const std::string& jobId); + + bool IsRunning(const ServerJob& job) + { + return IsRunning(job.GetId()); + } + + void Cancel(const ServerJob& job) + { + Cancel(job.GetId()); + } + + float GetProgress(const ServerJob& job) + { + return GetProgress(job.GetId()); + } + + void GetListOfJobs(ListOfStrings& jobs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/StorePeerCommand.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,92 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "StorePeerCommand.h" + +#include "../../Core/Logging.h" +#include "../../Core/HttpClient.h" + +namespace Orthanc +{ + StorePeerCommand::StorePeerCommand(ServerContext& context, + const WebServiceParameters& peer, + bool ignoreExceptions) : + context_(context), + peer_(peer), + ignoreExceptions_(ignoreExceptions) + { + } + + bool StorePeerCommand::Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + // Configure the HTTP client + HttpClient client(peer_, "instances"); + client.SetMethod(HttpMethod_Post); + + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + LOG(INFO) << "Sending resource " << *it << " to peer \"" + << peer_.GetUrl() << "\""; + + try + { + context_.ReadDicom(client.GetBody(), *it); + + std::string answer; + if (!client.Apply(answer)) + { + LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << peer_.GetUrl() << "\""; + throw OrthancException(ErrorCode_NetworkProtocol); + } + + // Only chain with other commands if this command succeeds + outputs.push_back(*it); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Unable to forward to an Orthanc peer in (instance " + << *it << ", peer " << peer_.GetUrl() << "): " << e.What(); + + if (!ignoreExceptions_) + { + throw; + } + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/StorePeerCommand.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IServerCommand.h" +#include "../ServerContext.h" +#include "../OrthancInitialization.h" + +namespace Orthanc +{ + class StorePeerCommand : public IServerCommand + { + private: + ServerContext& context_; + WebServiceParameters peer_; + bool ignoreExceptions_; + + public: + StorePeerCommand(ServerContext& context, + const WebServiceParameters& peer, + bool ignoreExceptions); + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/StoreScuCommand.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,99 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "StoreScuCommand.h" + +#include "../../Core/Logging.h" + +namespace Orthanc +{ + StoreScuCommand::StoreScuCommand(ServerContext& context, + const std::string& localAet, + const RemoteModalityParameters& modality, + bool ignoreExceptions) : + context_(context), + modality_(modality), + ignoreExceptions_(ignoreExceptions), + localAet_(localAet), + moveOriginatorID_(0) + { + } + + + void StoreScuCommand::SetMoveOriginator(const std::string& aet, + uint16_t id) + { + moveOriginatorAET_ = aet; + moveOriginatorID_ = id; + } + + + bool StoreScuCommand::Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); + + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + LOG(INFO) << "Sending resource " << *it << " to modality \"" + << modality_.GetApplicationEntityTitle() << "\""; + + try + { + std::string dicom; + context_.ReadDicom(dicom, *it); + + locker.GetConnection().Store(dicom, moveOriginatorAET_, moveOriginatorID_); + + // Only chain with other commands if this command succeeds + outputs.push_back(*it); + } + catch (OrthancException& e) + { + // Ignore transmission errors (e.g. if the remote modality is + // powered off) + LOG(ERROR) << "Unable to forward to a modality in (instance " + << *it << "): " << e.What(); + + if (!ignoreExceptions_) + { + throw; + } + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/StoreScuCommand.h Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,63 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IServerCommand.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class StoreScuCommand : public IServerCommand + { + private: + ServerContext& context_; + RemoteModalityParameters modality_; + bool ignoreExceptions_; + std::string localAet_; + std::string moveOriginatorAET_; + uint16_t moveOriginatorID_; + + public: + StoreScuCommand(ServerContext& context, + const std::string& localAet, + const RemoteModalityParameters& modality, + bool ignoreExceptions); + + void SetMoveOriginator(const std::string& aet, + uint16_t id); + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/ImplementationNotes/JobsEngineStates.dot Fri Jul 13 10:07:45 2018 +0200 @@ -0,0 +1,28 @@ +// dot -Tpdf JobsEngineStates.dot -o JobsEngineStates.pdf + +digraph G +{ + rankdir="LR"; + init [shape=point]; + failure, success [shape=doublecircle]; + + // Internal transitions + init -> pending; + pending -> running; + running -> success; + running -> failure; + running -> retry; + retry -> pending [label="timeout"]; + + // External actions + failure -> pending [label="Resubmit()" fontcolor="red"]; + paused -> pending [label="Resume()" fontcolor="red"]; + pending -> paused [label="Pause()" fontcolor="red"]; + retry -> paused [label="Pause()" fontcolor="red"]; + running -> paused [label="Pause()" fontcolor="red"]; + + paused -> failure [label="Cancel()" fontcolor="red"]; + pending -> failure [label="Cancel()" fontcolor="red"]; + retry -> failure [label="Cancel()" fontcolor="red"]; + running -> failure [label="Cancel()" fontcolor="red"]; +}
--- a/Resources/OrthancPlugin.doxygen Mon Jul 09 11:37:17 2018 +0200 +++ b/Resources/OrthancPlugin.doxygen Fri Jul 13 10:07:45 2018 +0200 @@ -755,8 +755,7 @@ # Note: If this tag is empty the current directory is searched. INPUT = @CMAKE_SOURCE_DIR@/Plugins/Include/orthanc/OrthancCPlugin.h \ - @CMAKE_SOURCE_DIR@/Plugins/Include/orthanc/OrthancCDatabasePlugin.h \ - @CMAKE_SOURCE_DIR@/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h + @CMAKE_SOURCE_DIR@/Plugins/Include/orthanc/OrthancCDatabasePlugin.h # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
--- a/UnitTestsSources/MultiThreadingTests.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/UnitTestsSources/MultiThreadingTests.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -34,18 +34,166 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include "../OrthancServer/Scheduler/ServerScheduler.h" +#include "../Core/FileStorage/MemoryStorageArea.h" +#include "../Core/JobsEngine/JobsEngine.h" +#include "../Core/Logging.h" +#include "../Core/MultiThreading/SharedMessageQueue.h" #include "../Core/OrthancException.h" +#include "../Core/SerializationToolbox.h" #include "../Core/SystemToolbox.h" #include "../Core/Toolbox.h" -#include "../Core/MultiThreading/Locker.h" -#include "../Core/MultiThreading/Mutex.h" -#include "../Core/MultiThreading/ReaderWriterLock.h" +#include "../OrthancServer/DatabaseWrapper.h" +#include "../OrthancServer/ServerContext.h" +#include "../OrthancServer/ServerJobs/LuaJobManager.h" +#include "../OrthancServer/ServerJobs/OrthancJobUnserializer.h" + +#include "../Core/JobsEngine/Operations/JobOperationValues.h" +#include "../Core/JobsEngine/Operations/NullOperationValue.h" +#include "../Core/JobsEngine/Operations/StringOperationValue.h" +#include "../OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h" + +#include "../Core/JobsEngine/Operations/LogJobOperation.h" +#include "../OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h" +#include "../OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h" +#include "../OrthancServer/ServerJobs/Operations/StorePeerOperation.h" +#include "../OrthancServer/ServerJobs/Operations/StoreScuOperation.h" +#include "../OrthancServer/ServerJobs/Operations/SystemCallOperation.h" + +#include "../OrthancServer/ServerJobs/ArchiveJob.h" +#include "../OrthancServer/ServerJobs/DicomModalityStoreJob.h" +#include "../OrthancServer/ServerJobs/OrthancPeerStoreJob.h" +#include "../OrthancServer/ServerJobs/ResourceModificationJob.h" + using namespace Orthanc; namespace { + class DummyJob : public IJob + { + private: + bool fails_; + unsigned int count_; + unsigned int steps_; + + public: + DummyJob() : + fails_(false), + count_(0), + steps_(4) + { + } + + explicit DummyJob(bool fails) : + fails_(fails), + count_(0), + steps_(4) + { + } + + virtual void Start() + { + } + + virtual void SignalResubmit() + { + } + + virtual JobStepResult ExecuteStep() + { + if (fails_) + { + return JobStepResult::Failure(ErrorCode_ParameterOutOfRange); + } + else if (count_ == steps_ - 1) + { + return JobStepResult::Success(); + } + else + { + count_++; + return JobStepResult::Continue(); + } + } + + virtual void ReleaseResources() + { + } + + virtual float GetProgress() + { + return static_cast<float>(count_) / static_cast<float>(steps_ - 1); + } + + virtual void GetJobType(std::string& type) + { + type = "DummyJob"; + } + + virtual bool Serialize(Json::Value& value) + { + value = Json::objectValue; + value["Type"] = "DummyJob"; + return true; + } + + virtual void GetPublicContent(Json::Value& value) + { + value["hello"] = "world"; + } + }; + + + class DummyInstancesJob : public SetOfInstancesJob + { + protected: + virtual bool HandleInstance(const std::string& instance) + { + return (instance != "nope"); + } + + public: + DummyInstancesJob() + { + } + + DummyInstancesJob(const Json::Value& value) : + SetOfInstancesJob(value) + { + } + + virtual void ReleaseResources() + { + } + + virtual void GetJobType(std::string& s) + { + s = "DummyInstancesJob"; + } + }; + + + class DummyUnserializer : public GenericJobUnserializer + { + public: + virtual IJob* UnserializeJob(const Json::Value& value) + { + if (SerializationToolbox::ReadString(value, "Type") == "DummyInstancesJob") + { + return new DummyInstancesJob(value); + } + else if (SerializationToolbox::ReadString(value, "Type") == "DummyJob") + { + return new DummyJob; + } + else + { + return GenericJobUnserializer::UnserializeJob(value); + } + } + }; + + class DynamicInteger : public IDynamicObject { private: @@ -106,156 +254,1252 @@ } -TEST(MultiThreading, Mutex) + + +static bool CheckState(JobsRegistry& registry, + const std::string& id, + JobState state) { - Mutex mutex; - Locker locker(mutex); + JobState s; + if (registry.GetState(s, id)) + { + return state == s; + } + else + { + return false; + } } -TEST(MultiThreading, ReaderWriterLock) +static bool CheckErrorCode(JobsRegistry& registry, + const std::string& id, + ErrorCode code) { - ReaderWriterLock lock; - + JobInfo s; + if (registry.GetJobInfo(s, id)) { - Locker locker1(lock.ForReader()); - Locker locker2(lock.ForReader()); + return code == s.GetStatus().GetErrorCode(); } - + else { - Locker locker3(lock.ForWriter()); + return false; } } +TEST(JobsRegistry, Priority) +{ + JobsRegistry registry; -#include "../Core/DicomNetworking/ReusableDicomUserConnection.h" + std::string i1, i2, i3, i4; + registry.Submit(i1, new DummyJob(), 10); + registry.Submit(i2, new DummyJob(), 30); + registry.Submit(i3, new DummyJob(), 20); + registry.Submit(i4, new DummyJob(), 5); + + registry.SetMaxCompletedJobs(2); + + std::set<std::string> id; + registry.ListJobs(id); + + ASSERT_EQ(4u, id.size()); + ASSERT_TRUE(id.find(i1) != id.end()); + ASSERT_TRUE(id.find(i2) != id.end()); + ASSERT_TRUE(id.find(i3) != id.end()); + ASSERT_TRUE(id.find(i4) != id.end()); + + ASSERT_TRUE(CheckState(registry, i2, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(30, job.GetPriority()); + ASSERT_EQ(i2, job.GetId()); + + ASSERT_TRUE(CheckState(registry, i2, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i2, JobState_Failure)); + ASSERT_TRUE(CheckState(registry, i3, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(20, job.GetPriority()); + ASSERT_EQ(i3, job.GetId()); + + job.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, i3, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i3, JobState_Success)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(10, job.GetPriority()); + ASSERT_EQ(i1, job.GetId()); + } + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(5, job.GetPriority()); + ASSERT_EQ(i4, job.GetId()); + } + + { + JobsRegistry::RunningJob job(registry, 1); + ASSERT_FALSE(job.IsValid()); + } + + JobState s; + ASSERT_TRUE(registry.GetState(s, i1)); + ASSERT_FALSE(registry.GetState(s, i2)); // Removed because oldest + ASSERT_FALSE(registry.GetState(s, i3)); // Removed because second oldest + ASSERT_TRUE(registry.GetState(s, i4)); + + registry.SetMaxCompletedJobs(1); // (*) + ASSERT_FALSE(registry.GetState(s, i1)); // Just discarded by (*) + ASSERT_TRUE(registry.GetState(s, i4)); +} + + +TEST(JobsRegistry, Simultaneous) +{ + JobsRegistry registry; + + std::string i1, i2; + registry.Submit(i1, new DummyJob(), 20); + registry.Submit(i2, new DummyJob(), 10); -TEST(ReusableDicomUserConnection, DISABLED_Basic) + ASSERT_TRUE(CheckState(registry, i1, JobState_Pending)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Pending)); + + { + JobsRegistry::RunningJob job1(registry, 0); + JobsRegistry::RunningJob job2(registry, 0); + + ASSERT_TRUE(job1.IsValid()); + ASSERT_TRUE(job2.IsValid()); + + job1.MarkFailure(); + job2.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, i1, JobState_Running)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i1, JobState_Failure)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Success)); +} + + +TEST(JobsRegistry, Resubmit) { - ReusableDicomUserConnection c; - c.SetMillisecondsBeforeClose(200); - printf("START\n"); fflush(stdout); + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkFailure(); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, Retry) +{ + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkRetry(0); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.ScheduleRetries(); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); { - RemoteModalityParameters remote("STORESCP", "localhost", 2000, ModalityManufacturer_Generic); - ReusableDicomUserConnection::Locker lock(c, "ORTHANC", remote); - lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281"); + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, PausePending) +{ + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); +} + + +TEST(JobsRegistry, PauseRunning) +{ + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + registry.Resubmit(id); + job.MarkPause(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, PauseRetry) +{ + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkRetry(0); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); } - printf("**\n"); fflush(stdout); - SystemToolbox::USleep(1000000); - printf("**\n"); fflush(stdout); + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); { - RemoteModalityParameters remote("STORESCP", "localhost", 2000, ModalityManufacturer_Generic); - ReusableDicomUserConnection::Locker lock(c, "ORTHANC", remote); - lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277"); + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, Cancel) +{ + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_FALSE(registry.Cancel("nope")); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); } - SystemToolbox::ServerBarrier(); - printf("DONE\n"); fflush(stdout); + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + registry.Submit(id, new DummyJob(), 10); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + job.MarkCanceled(); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Pause(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + job.MarkRetry(500); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); } -class Tutu : public IServerCommand +TEST(JobsEngine, SubmitAndWait) +{ + JobsEngine engine; + engine.SetThreadSleep(10); + engine.SetWorkersCount(3); + engine.Start(); + + ASSERT_TRUE(engine.GetRegistry().SubmitAndWait(new DummyJob(), rand() % 10)); + ASSERT_FALSE(engine.GetRegistry().SubmitAndWait(new DummyJob(true), rand() % 10)); + + engine.Stop(); +} + + +TEST(JobsEngine, DISABLED_SequenceOfOperationsJob) { -private: - int factor_; + JobsEngine engine; + engine.SetThreadSleep(10); + engine.SetWorkersCount(3); + engine.Start(); + + std::string id; + SequenceOfOperationsJob* job = NULL; + + { + std::auto_ptr<SequenceOfOperationsJob> a(new SequenceOfOperationsJob); + job = a.get(); + engine.GetRegistry().Submit(id, a.release(), 0); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + + { + SequenceOfOperationsJob::Lock lock(*job); + size_t i = lock.AddOperation(new LogJobOperation); + size_t j = lock.AddOperation(new LogJobOperation); + size_t k = lock.AddOperation(new LogJobOperation); + lock.AddInput(i, StringOperationValue("Hello")); + lock.AddInput(i, StringOperationValue("World")); + lock.Connect(i, j); + lock.Connect(j, k); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); -public: - Tutu(int f) : factor_(f) + engine.Stop(); + +} + + +TEST(JobsEngine, DISABLED_Lua) +{ + JobsEngine engine; + engine.SetThreadSleep(10); + engine.SetWorkersCount(2); + engine.Start(); + + LuaJobManager lua; + lua.SetMaxOperationsPerJob(5); + lua.SetTrailingOperationTimeout(200); + + for (size_t i = 0; i < 30; i++) { + boost::this_thread::sleep(boost::posix_time::milliseconds(150)); + + LuaJobManager::Lock lock(lua, engine); + size_t a = lock.AddLogOperation(); + size_t b = lock.AddLogOperation(); + size_t c = lock.AddSystemCallOperation("echo"); + lock.AddStringInput(a, boost::lexical_cast<std::string>(i)); + lock.AddNullInput(a); + lock.Connect(a, b); + lock.Connect(a, c); } - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - int a = boost::lexical_cast<int>(*it); - int b = factor_ * a; + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + + engine.Stop(); +} + - printf("%d * %d = %d\n", a, factor_, b); - - //if (a == 84) { printf("BREAK\n"); return false; } +static bool CheckSameJson(const Json::Value& a, + const Json::Value& b) +{ + std::string s = a.toStyledString(); + std::string t = b.toStyledString(); - outputs.push_back(boost::lexical_cast<std::string>(b)); - } - - SystemToolbox::USleep(30000); - + if (s == t) + { return true; } -}; + else + { + LOG(ERROR) << "Expected serialization: " << s; + LOG(ERROR) << "Actual serialization: " << t; + return false; + } +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + IJob& job) +{ + Json::Value a = 42; + + if (!job.Serialize(a)) + { + return false; + } + else + { + std::auto_ptr<IJob> unserialized(unserializer.UnserializeJob(a)); + + Json::Value b = 43; + if (unserialized->Serialize(b)) + { + return CheckSameJson(a, b); + } + else + { + return false; + } + } +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + IJobOperation& operation) +{ + Json::Value a = 42; + operation.Serialize(a); + + std::auto_ptr<IJobOperation> unserialized(unserializer.UnserializeOperation(a)); + + Json::Value b = 43; + unserialized->Serialize(b); + + return CheckSameJson(a, b); +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + JobOperationValue& value) +{ + Json::Value a = 42; + value.Serialize(a); + + std::auto_ptr<JobOperationValue> unserialized(unserializer.UnserializeValue(a)); + + Json::Value b = 43; + unserialized->Serialize(b); + + return CheckSameJson(a, b); +} -static void Tata(ServerScheduler* s, ServerJob* j, bool* done) +TEST(JobsSerialization, BadFileFormat) { - typedef IServerCommand::ListOfStrings ListOfStrings; + GenericJobUnserializer unserializer; + + Json::Value s; + + s = Json::objectValue; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + s = Json::arrayValue; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + s = "hello"; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); - while (!(*done)) + s = 42; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); +} + + +TEST(JobsSerialization, JobOperationValues) +{ + Json::Value s; + + { + JobOperationValues values; + values.Append(new NullOperationValue); + values.Append(new StringOperationValue("hello")); + values.Append(new StringOperationValue("world")); + + s = 42; + values.Serialize(s); + } + { - ListOfStrings l; - s->GetListOfJobs(l); - for (ListOfStrings::iterator it = l.begin(); it != l.end(); ++it) - { - printf(">> %s: %0.1f\n", it->c_str(), 100.0f * s->GetProgress(*it)); - } - SystemToolbox::USleep(3000); + GenericJobUnserializer unserializer; + std::auto_ptr<JobOperationValues> values(JobOperationValues::Unserialize(unserializer, s)); + ASSERT_EQ(3u, values->GetSize()); + ASSERT_EQ(JobOperationValue::Type_Null, values->GetValue(0).GetType()); + ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(1).GetType()); + ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(2).GetType()); + + ASSERT_EQ("hello", dynamic_cast<const StringOperationValue&>(values->GetValue(1)).GetContent()); + ASSERT_EQ("world", dynamic_cast<const StringOperationValue&>(values->GetValue(2)).GetContent()); + } +} + + +TEST(JobsSerialization, GenericValues) +{ + GenericJobUnserializer unserializer; + Json::Value s; + + { + NullOperationValue null; + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, null)); + null.Serialize(s); + } + + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::auto_ptr<JobOperationValue> value; + value.reset(unserializer.UnserializeValue(s)); + + ASSERT_EQ(JobOperationValue::Type_Null, value->GetType()); + + { + StringOperationValue str("Hello"); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, str)); + str.Serialize(s); + } + + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + value.reset(unserializer.UnserializeValue(s)); + + ASSERT_EQ(JobOperationValue::Type_String, value->GetType()); + ASSERT_EQ("Hello", dynamic_cast<StringOperationValue&>(*value).GetContent()); +} + + +TEST(JobsSerialization, GenericOperations) +{ + DummyUnserializer unserializer; + Json::Value s; + + { + LogJobOperation operation; + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + + { + std::auto_ptr<IJobOperation> operation; + operation.reset(unserializer.UnserializeOperation(s)); + + // Make sure that we have indeed unserialized a log operation + ASSERT_THROW(dynamic_cast<DeleteResourceOperation&>(*operation), std::bad_cast); + dynamic_cast<LogJobOperation&>(*operation); } } -TEST(MultiThreading, ServerScheduler) +TEST(JobsSerialization, GenericJobs) +{ + Json::Value s; + + // This tests SetOfInstancesJob + + { + DummyInstancesJob job; + job.SetDescription("description"); + job.AddInstance("hello"); + job.AddInstance("nope"); + job.AddInstance("world"); + job.SetPermissive(true); + ASSERT_THROW(job.ExecuteStep(), OrthancException); // Not started yet + job.Start(); + job.ExecuteStep(); + job.ExecuteStep(); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, job)); + } + + ASSERT_TRUE(job.Serialize(s)); + } + + { + DummyUnserializer unserializer; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + const DummyInstancesJob& tmp = dynamic_cast<const DummyInstancesJob&>(*job); + ASSERT_FALSE(tmp.IsStarted()); + ASSERT_TRUE(tmp.IsPermissive()); + ASSERT_EQ("description", tmp.GetDescription()); + ASSERT_EQ(3u, tmp.GetInstancesCount()); + ASSERT_EQ(2u, tmp.GetPosition()); + ASSERT_EQ(1u, tmp.GetFailedInstances().size()); + ASSERT_EQ("hello", tmp.GetInstance(0)); + ASSERT_EQ("nope", tmp.GetInstance(1)); + ASSERT_EQ("world", tmp.GetInstance(2)); + ASSERT_TRUE(tmp.IsFailedInstance("nope")); + } + + // SequenceOfOperationsJob + + { + SequenceOfOperationsJob job; + job.SetDescription("hello"); + + { + SequenceOfOperationsJob::Lock lock(job); + size_t a = lock.AddOperation(new LogJobOperation); + size_t b = lock.AddOperation(new LogJobOperation); + lock.Connect(a, b); + lock.AddInput(a, StringOperationValue("hello")); + lock.AddInput(a, StringOperationValue("world")); + lock.SetDicomAssociationTimeout(200); + lock.SetTrailingOperationTimeout(300); + } + + ASSERT_EQ(JobStepCode_Continue, job.ExecuteStep().GetCode()); + + { + GenericJobUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, job)); + } + + ASSERT_TRUE(job.Serialize(s)); + } + + { + GenericJobUnserializer unserializer; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + std::string tmp; + dynamic_cast<SequenceOfOperationsJob&>(*job).GetDescription(tmp); + ASSERT_EQ("hello", tmp); + } +} + + +static bool IsSameTagValue(ParsedDicomFile& dicom1, + ParsedDicomFile& dicom2, + DicomTag tag) { - ServerScheduler scheduler(10); + std::string a, b; + return (dicom1.GetTagValue(a, tag) && + dicom2.GetTagValue(b, tag) && + (a == b)); +} + + + +TEST(JobsSerialization, DicomModification) +{ + Json::Value s; + + ParsedDicomFile source(true); + source.Insert(DICOM_TAG_STUDY_DESCRIPTION, "Test 1", false); + source.Insert(DICOM_TAG_SERIES_DESCRIPTION, "Test 2", false); + source.Insert(DICOM_TAG_PATIENT_NAME, "Test 3", false); + + std::auto_ptr<ParsedDicomFile> modified(source.Clone(true)); - ServerJob job; - ServerCommandInstance& f2 = job.AddCommand(new Tutu(2)); - ServerCommandInstance& f3 = job.AddCommand(new Tutu(3)); - ServerCommandInstance& f4 = job.AddCommand(new Tutu(4)); - ServerCommandInstance& f5 = job.AddCommand(new Tutu(5)); - f2.AddInput(boost::lexical_cast<std::string>(42)); - //f3.AddInput(boost::lexical_cast<std::string>(42)); - //f4.AddInput(boost::lexical_cast<std::string>(42)); - f2.ConnectOutput(f3); - f3.ConnectOutput(f4); - f4.ConnectOutput(f5); + { + DicomModification modification; + modification.SetLevel(ResourceType_Series); + modification.Clear(DICOM_TAG_STUDY_DESCRIPTION); + modification.Remove(DICOM_TAG_SERIES_DESCRIPTION); + modification.Replace(DICOM_TAG_PATIENT_NAME, "Test 4", true); + + modification.Apply(*modified); + + s = 42; + modification.Serialize(s); + } + + { + DicomModification modification(s); + ASSERT_EQ(ResourceType_Series, modification.GetLevel()); + + std::auto_ptr<ParsedDicomFile> second(source.Clone(true)); + modification.Apply(*second); + + std::string s; + ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_STUDY_DESCRIPTION)); + ASSERT_TRUE(s.empty()); + ASSERT_FALSE(second->GetTagValue(s, DICOM_TAG_SERIES_DESCRIPTION)); + ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("Test 4", s); + + ASSERT_TRUE(IsSameTagValue(source, *modified, DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(IsSameTagValue(source, *second, DICOM_TAG_STUDY_INSTANCE_UID)); + + ASSERT_FALSE(IsSameTagValue(source, *second, DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(IsSameTagValue(*modified, *second, DICOM_TAG_SERIES_INSTANCE_UID)); + } +} + + +TEST(JobsSerialization, DicomInstanceOrigin) +{ + Json::Value s; + std::string t; + + { + DicomInstanceOrigin origin; + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Unknown, origin.GetRequestOrigin()); + ASSERT_EQ("", std::string(origin.GetRemoteAetC())); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromDicomProtocol("host", "aet", "called")); - f3.SetConnectedToSink(true); - f5.SetConnectedToSink(true); + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_DicomProtocol, origin.GetRequestOrigin()); + ASSERT_EQ("aet", std::string(origin.GetRemoteAetC())); + ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t); + ASSERT_TRUE(origin.LookupRemoteAet(t)); ASSERT_EQ("aet", t); + ASSERT_TRUE(origin.LookupCalledAet(t)); ASSERT_EQ("called", t); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromHttp("host", "username")); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_RestApi, origin.GetRequestOrigin()); + ASSERT_EQ("", std::string(origin.GetRemoteAetC())); + ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_TRUE(origin.LookupHttpUsername(t)); ASSERT_EQ("username", t); + } - job.SetDescription("tutu"); + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromLua()); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Lua, origin.GetRequestOrigin()); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } - bool done = false; - boost::thread t(Tata, &scheduler, &job, &done); + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromPlugins()); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Plugins, origin.GetRequestOrigin()); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } +} - //scheduler.Submit(job); +namespace +{ + class OrthancJobsSerialization : public testing::Test + { + private: + MemoryStorageArea storage_; + DatabaseWrapper db_; // The SQLite DB is in memory + std::auto_ptr<ServerContext> context_; + TimeoutDicomConnectionManager manager_; + + public: + OrthancJobsSerialization() + { + db_.Open(); + context_.reset(new ServerContext(db_, storage_, true /* running unit tests */, + false /* don't reload jobs */)); + } + + virtual ~OrthancJobsSerialization() + { + context_->Stop(); + context_.reset(NULL); + db_.Close(); + } + + ServerContext& GetContext() + { + return *context_; + } + + bool CreateInstance(std::string& id) + { + // Create a sample DICOM file + ParsedDicomFile dicom(true); + dicom.Replace(DICOM_TAG_PATIENT_NAME, std::string("JODOGNE"), + false, DicomReplaceMode_InsertIfAbsent); - IServerCommand::ListOfStrings l; - scheduler.SubmitAndWait(l, job); + DicomInstanceToStore toStore; + toStore.SetParsedDicomFile(dicom); + + return (context_->Store(id, toStore) == StoreStatus_Success); + } + }; +} + + +TEST_F(OrthancJobsSerialization, Values) +{ + std::string id; + ASSERT_TRUE(CreateInstance(id)); + + Json::Value s; + OrthancJobUnserializer unserializer(GetContext()); + + { + DicomInstanceOperationValue instance(GetContext(), id); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, instance)); + instance.Serialize(s); + } + + std::auto_ptr<JobOperationValue> value; + value.reset(unserializer.UnserializeValue(s)); + ASSERT_EQ(JobOperationValue::Type_DicomInstance, value->GetType()); + ASSERT_EQ(id, dynamic_cast<DicomInstanceOperationValue&>(*value).GetId()); + + { + std::string content; + dynamic_cast<DicomInstanceOperationValue&>(*value).ReadDicom(content); + + ParsedDicomFile dicom(content); + ASSERT_TRUE(dicom.GetTagValue(content, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("JODOGNE", content); + } +} - ASSERT_EQ(2u, l.size()); - ASSERT_EQ(42 * 2 * 3, boost::lexical_cast<int>(l.front())); - ASSERT_EQ(42 * 2 * 3 * 4 * 5, boost::lexical_cast<int>(l.back())); + +TEST_F(OrthancJobsSerialization, Operations) +{ + std::string id; + ASSERT_TRUE(CreateInstance(id)); + + Json::Value s; + OrthancJobUnserializer unserializer(GetContext()); + + // DeleteResourceOperation + + { + DeleteResourceOperation operation(GetContext()); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + std::auto_ptr<IJobOperation> operation; + + { + operation.reset(unserializer.UnserializeOperation(s)); + + ASSERT_THROW(dynamic_cast<LogJobOperation&>(*operation), std::bad_cast); + dynamic_cast<DeleteResourceOperation&>(*operation); + } + + // StorePeerOperation + + { + WebServiceParameters peer; + peer.SetUrl("http://localhost/"); + peer.SetUsername("username"); + peer.SetPassword("password"); + peer.SetPkcs11Enabled(true); + + StorePeerOperation operation(peer); - for (IServerCommand::ListOfStrings::iterator i = l.begin(); i != l.end(); i++) + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const StorePeerOperation& tmp = dynamic_cast<StorePeerOperation&>(*operation); + ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl()); + ASSERT_EQ("username", tmp.GetPeer().GetUsername()); + ASSERT_EQ("password", tmp.GetPeer().GetPassword()); + ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled()); + } + + // StoreScuOperation + { - printf("** %s\n", i->c_str()); + RemoteModalityParameters modality; + modality.SetApplicationEntityTitle("REMOTE"); + modality.SetHost("192.168.1.1"); + modality.SetPortNumber(1000); + modality.SetManufacturer(ModalityManufacturer_StoreScp); + + StoreScuOperation operation("TEST", modality); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const StoreScuOperation& tmp = dynamic_cast<StoreScuOperation&>(*operation); + ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost()); + ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetRemoteModality().GetManufacturer()); + ASSERT_EQ("TEST", tmp.GetLocalAet()); } - //SystemToolbox::ServerBarrier(); - //SystemToolbox::USleep(3000000); + // SystemCallOperation - scheduler.Stop(); + { + SystemCallOperation operation(std::string("echo")); + operation.AddPreArgument("a"); + operation.AddPreArgument("b"); + operation.AddPostArgument("c"); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); - done = true; - if (t.joinable()) + const SystemCallOperation& tmp = dynamic_cast<SystemCallOperation&>(*operation); + ASSERT_EQ("echo", tmp.GetCommand()); + ASSERT_EQ(2u, tmp.GetPreArgumentsCount()); + ASSERT_EQ(1u, tmp.GetPostArgumentsCount()); + ASSERT_EQ("a", tmp.GetPreArgument(0)); + ASSERT_EQ("b", tmp.GetPreArgument(1)); + ASSERT_EQ("c", tmp.GetPostArgument(0)); + } + + // ModifyInstanceOperation + { - t.join(); + std::auto_ptr<DicomModification> modification(new DicomModification); + modification->SetupAnonymization(DicomVersion_2008); + + ModifyInstanceOperation operation(GetContext(), RequestOrigin_Lua, modification.release()); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const ModifyInstanceOperation& tmp = dynamic_cast<ModifyInstanceOperation&>(*operation); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetRequestOrigin()); + ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); } } + + +TEST_F(OrthancJobsSerialization, Jobs) +{ + // ArchiveJob + + Json::Value s; + + { + boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); + ArchiveJob job(tmp, GetContext(), false, false); + ASSERT_FALSE(job.Serialize(s)); // Cannot serialize this + } + + // DicomModalityStoreJob + + OrthancJobUnserializer unserializer(GetContext()); + + { + RemoteModalityParameters modality; + modality.SetApplicationEntityTitle("REMOTE"); + modality.SetHost("192.168.1.1"); + modality.SetPortNumber(1000); + modality.SetManufacturer(ModalityManufacturer_StoreScp); + + DicomModalityStoreJob job(GetContext()); + job.SetLocalAet("LOCAL"); + job.SetRemoteModality(modality); + job.SetMoveOriginator("MOVESCU", 42); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + DicomModalityStoreJob& tmp = dynamic_cast<DicomModalityStoreJob&>(*job); + ASSERT_EQ("LOCAL", tmp.GetLocalAet()); + ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost()); + ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetRemoteModality().GetManufacturer()); + ASSERT_TRUE(tmp.HasMoveOriginator()); + ASSERT_EQ("MOVESCU", tmp.GetMoveOriginatorAet()); + ASSERT_EQ(42, tmp.GetMoveOriginatorId()); + } + + // OrthancPeerStoreJob + + { + WebServiceParameters peer; + peer.SetUrl("http://localhost/"); + peer.SetUsername("username"); + peer.SetPassword("password"); + peer.SetPkcs11Enabled(true); + + OrthancPeerStoreJob job(GetContext()); + job.SetPeer(peer); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + OrthancPeerStoreJob& tmp = dynamic_cast<OrthancPeerStoreJob&>(*job); + ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl()); + ASSERT_EQ("username", tmp.GetPeer().GetUsername()); + ASSERT_EQ("password", tmp.GetPeer().GetPassword()); + ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled()); + } + + // ResourceModificationJob + + { + std::auto_ptr<DicomModification> modification(new DicomModification); + modification->SetupAnonymization(DicomVersion_2008); + + ResourceModificationJob job(GetContext()); + job.SetModification(modification.release(), true); + job.SetOrigin(DicomInstanceOrigin::FromLua()); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job); + ASSERT_TRUE(tmp.IsAnonymization()); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); + ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); + } +} + + +TEST(JobsSerialization, Registry) +{ + Json::Value s; + std::string i1, i2; + + { + JobsRegistry registry; + registry.Submit(i1, new DummyJob(), 10); + registry.Submit(i2, new SequenceOfOperationsJob(), 30); + registry.Serialize(s); + } + + { + DummyUnserializer unserializer; + JobsRegistry registry(unserializer, s); + + Json::Value t; + registry.Serialize(t); + ASSERT_TRUE(CheckSameJson(s, t)); + } +}
--- a/UnitTestsSources/SQLiteChromiumTests.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/UnitTestsSources/SQLiteChromiumTests.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -51,35 +51,38 @@ ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc ********************************************************************/ -class SQLConnectionTest : public testing::Test +namespace { -public: - SQLConnectionTest() - { - } - - virtual ~SQLConnectionTest() - { - } - - virtual void SetUp() + class SQLConnectionTest : public testing::Test { - db_.OpenInMemory(); - } + public: + SQLConnectionTest() + { + } - virtual void TearDown() - { - db_.Close(); - } + virtual ~SQLConnectionTest() + { + } + + virtual void SetUp() + { + db_.OpenInMemory(); + } - Connection& db() - { - return db_; - } + virtual void TearDown() + { + db_.Close(); + } -private: - Connection db_; -}; + Connection& db() + { + return db_; + } + + private: + Connection db_; + }; +} @@ -266,23 +269,26 @@ ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc ********************************************************************/ -class SQLTransactionTest : public SQLConnectionTest +namespace { -public: - virtual void SetUp() + class SQLTransactionTest : public SQLConnectionTest { - SQLConnectionTest::SetUp(); - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - } + public: + virtual void SetUp() + { + SQLConnectionTest::SetUp(); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + } - // Returns the number of rows in table "foo". - int CountFoo() - { - Statement count(db(), "SELECT count(*) FROM foo"); - count.Step(); - return count.ColumnInt(0); - } -}; + // Returns the number of rows in table "foo". + int CountFoo() + { + Statement count(db(), "SELECT count(*) FROM foo"); + count.Step(); + return count.ColumnInt(0); + } + }; +} TEST_F(SQLTransactionTest, Commit) {
--- a/UnitTestsSources/ServerIndexTests.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/UnitTestsSources/ServerIndexTests.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -675,7 +675,8 @@ FilesystemStorage storage(path); DatabaseWrapper db; // The SQLite DB is in memory db.Open(); - ServerContext context(db, storage); + ServerContext context(db, storage, true /* running unit tests */, + false /* don't reload jobs */); ServerIndex& index = context.GetIndex(); ASSERT_EQ(1u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); @@ -773,7 +774,8 @@ FilesystemStorage storage(path); DatabaseWrapper db; // The SQLite DB is in memory db.Open(); - ServerContext context(db, storage); + ServerContext context(db, storage, true /* running unit tests */, + false /* don't reload jobs */); ServerIndex& index = context.GetIndex(); index.SetMaximumStorageSize(10);
--- a/UnitTestsSources/UnitTestsMain.cpp Mon Jul 09 11:37:17 2018 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Fri Jul 13 10:07:45 2018 +0200 @@ -677,6 +677,14 @@ } ASSERT_THROW(StringToValueRepresentation("nope", true), OrthancException); + + ASSERT_EQ(JobState_Pending, StringToJobState(EnumerationToString(JobState_Pending))); + ASSERT_EQ(JobState_Running, StringToJobState(EnumerationToString(JobState_Running))); + ASSERT_EQ(JobState_Success, StringToJobState(EnumerationToString(JobState_Success))); + ASSERT_EQ(JobState_Failure, StringToJobState(EnumerationToString(JobState_Failure))); + ASSERT_EQ(JobState_Paused, StringToJobState(EnumerationToString(JobState_Paused))); + ASSERT_EQ(JobState_Retry, StringToJobState(EnumerationToString(JobState_Retry))); + ASSERT_THROW(StringToJobState("nope"), OrthancException); }