# HG changeset patch # User Sebastien Jodogne # Date 1531117102 -7200 # Node ID 83c991aeb61102ba70db00492e9e349bf32e604f # Parent 33de4a82c46641555511599d3d89bfa5f9336847# Parent 38a3054b22ffb80b7c2fe2e237f60f057a17cb1c integration mainline->jobs diff -r 38a3054b22ff -r 83c991aeb611 CMakeLists.txt --- a/CMakeLists.txt Fri Jul 06 16:51:08 2018 +0200 +++ b/CMakeLists.txt Mon Jul 09 08:18:22 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) diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomFormat/DicomTag.cpp --- a/Core/DicomFormat/DicomTag.cpp Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/DicomFormat/DicomTag.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -39,9 +39,32 @@ #include #include #include +#include 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) diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomFormat/DicomTag.h --- a/Core/DicomFormat/DicomTag.h Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/DicomFormat/DicomTag.h Mon Jul 09 08:18:22 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& target, diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomNetworking/DicomUserConnection.cpp --- a/Core/DicomNetworking/DicomUserConnection.cpp Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/DicomNetworking/DicomUserConnection.cpp Mon Jul 09 08:18:22 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() { @@ -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.GetPort() && + manufacturer_ == remote.GetManufacturer()); + } } diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomNetworking/DicomUserConnection.h --- a/Core/DicomNetworking/DicomUserConnection.h Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/DicomNetworking/DicomUserConnection.h Mon Jul 09 08:18:22 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; }; } diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomNetworking/IDicomConnectionManager.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IDicomConnectionManager.h Mon Jul 09 08:18:22 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 . + **/ + + +#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 diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomNetworking/RemoteModalityParameters.cpp --- a/Core/DicomNetworking/RemoteModalityParameters.cpp Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/DicomNetworking/RemoteModalityParameters.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -36,6 +36,7 @@ #include "../Logging.h" #include "../OrthancException.h" +#include "../SerializationToolbox.h" #include #include @@ -125,4 +126,26 @@ value.append(GetPort()); 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 + (SerializationToolbox::ReadUnsignedInteger(serialized, "Port")); + manufacturer_ = StringToModalityManufacturer + (SerializationToolbox::ReadString(serialized, "Manufacturer")); + } } diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomNetworking/RemoteModalityParameters.h --- a/Core/DicomNetworking/RemoteModalityParameters.h Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/DicomNetworking/RemoteModalityParameters.h Mon Jul 09 08:18:22 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, @@ -105,5 +107,7 @@ void FromJson(const Json::Value& modality); void ToJson(Json::Value& value) const; + + void Serialize(Json::Value& target) const; }; } diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomNetworking/ReusableDicomUserConnection.cpp --- a/Core/DicomNetworking/ReusableDicomUserConnection.cpp Fri Jul 06 16:51:08 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 . - **/ - - -#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(); - } - } -} - diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomNetworking/ReusableDicomUserConnection.h --- a/Core/DicomNetworking/ReusableDicomUserConnection.h Fri Jul 06 16:51:08 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 . - **/ - - -#pragma once - -#include "DicomUserConnection.h" -#include "../../Core/MultiThreading/Locker.h" - -#include -#include - -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(); - }; -} - diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomNetworking/TimeoutDicomConnectionManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#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); + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomNetworking/TimeoutDicomConnectionManager.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.h Mon Jul 09 08:18:22 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 . + **/ + + +#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 + +namespace Orthanc +{ + class TimeoutDicomConnectionManager : public IDicomConnectionManager + { + private: + class Resource; + + std::auto_ptr 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 diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomParsing/DicomModification.cpp --- a/Core/DicomParsing/DicomModification.cpp Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/DicomParsing/DicomModification.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -36,6 +36,7 @@ #include "../Logging.h" #include "../OrthancException.h" +#include "../SerializationToolbox.h" #include "FromDcmtkBridge.h" #include "ITagVisitor.h" @@ -1237,4 +1238,166 @@ 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* 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_; + + 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); + + 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); + } } diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomParsing/DicomModification.h --- a/Core/DicomParsing/DicomModification.h Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/DicomParsing/DicomModification.h Mon Jul 09 08:18:22 2018 +0200 @@ -107,9 +107,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 +178,7 @@ { identifierGenerator_ = &generator; } + + void Serialize(Json::Value& value) const; }; } diff -r 38a3054b22ff -r 83c991aeb611 Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Mon Jul 09 08:18:22 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 diff -r 38a3054b22ff -r 83c991aeb611 Core/Enumerations.cpp --- a/Core/Enumerations.cpp Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/Enumerations.cpp Mon Jul 09 08:18:22 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) diff -r 38a3054b22ff -r 83c991aeb611 Core/Enumerations.h --- a/Core/Enumerations.h Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/Enumerations.h Mon Jul 09 08:18:22 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); diff -r 38a3054b22ff -r 83c991aeb611 Core/FileStorage/MemoryStorageArea.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileStorage/MemoryStorageArea.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#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(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); + } + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/FileStorage/MemoryStorageArea.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileStorage/MemoryStorageArea.h Mon Jul 09 08:18:22 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 . + **/ + + +#pragma once + +#include "IStorageArea.h" + +#include +#include + +namespace Orthanc +{ + class MemoryStorageArea : public IStorageArea + { + private: + typedef std::map 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); + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/HttpClient.cpp --- a/Core/HttpClient.cpp Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/HttpClient.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -46,16 +46,6 @@ #include -#if ORTHANC_ENABLE_SSL == 1 -// For OpenSSL initialization and finalization -# include -# include -# include -# include -# include -#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 - } } diff -r 38a3054b22ff -r 83c991aeb611 Core/HttpClient.h --- a/Core/HttpClient.h Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/HttpClient.h Mon Jul 09 08:18:22 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); diff -r 38a3054b22ff -r 83c991aeb611 Core/HttpServer/IIncomingHttpRequestFilter.h --- a/Core/HttpServer/IIncomingHttpRequestFilter.h Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/HttpServer/IIncomingHttpRequestFilter.h Mon Jul 09 08:18:22 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; }; } diff -r 38a3054b22ff -r 83c991aeb611 Core/HttpServer/MongooseServer.cpp --- a/Core/HttpServer/MongooseServer.cpp Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/HttpServer/MongooseServer.cpp Mon Jul 09 08:18:22 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, diff -r 38a3054b22ff -r 83c991aeb611 Core/HttpServer/MongooseServer.h --- a/Core/HttpServer/MongooseServer.h Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/HttpServer/MongooseServer.h Mon Jul 09 08:18:22 2018 +0200 @@ -162,7 +162,7 @@ void SetHttpCompressionEnabled(bool enabled); - const IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const + IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const { return filter_; } diff -r 38a3054b22ff -r 83c991aeb611 Core/Images/ImageProcessing.cpp --- a/Core/Images/ImageProcessing.cpp Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/Images/ImageProcessing.cpp Mon Jul 09 08:18:22 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++) diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/GenericJobUnserializer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/GenericJobUnserializer.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#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); + } + } +} + diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/GenericJobUnserializer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/GenericJobUnserializer.h Mon Jul 09 08:18:22 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 . + **/ + + +#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); + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/IJob.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/IJob.h Mon Jul 09 08:18:22 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 . + **/ + + +#pragma once + +#include "JobStepResult.h" + +#include +#include + +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; + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/IJobUnserializer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/IJobUnserializer.h Mon Jul 09 08:18:22 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 . + **/ + + +#pragma once + +#include "IJob.h" +#include "Operations/JobOperationValue.h" +#include "Operations/IJobOperation.h" + +#include + +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; + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/JobInfo.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobInfo.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#include "../PrecompiledHeaders.h" +#include "JobInfo.h" + +#include "../OrthancException.h" + +// This "include" is mandatory for Release builds using Linux Standard Base +#include + +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(runtime_.total_milliseconds()); + + if (status_.GetProgress() > 0.01f && + ms > 0.01f) + { + float ratio = static_cast(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(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(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()); + } + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/JobInfo.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobInfo.h Mon Jul 09 08:18:22 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 . + **/ + + +#pragma once + +#include "JobStatus.h" + +#include + +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; + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/JobStatus.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStatus.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#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_; + } + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/JobStatus.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStatus.h Mon Jul 09 08:18:22 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 . + **/ + + +#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_; + } + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/JobStepResult.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStepResult.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#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); + } + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/JobStepResult.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStepResult.h Mon Jul 09 08:18:22 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 . + **/ + + +#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; + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/JobsEngine.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsEngine.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#include "../PrecompiledHeaders.h" +#include "JobsEngine.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +#include + +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"; + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/JobsEngine.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsEngine.h Mon Jul 09 08:18:22 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 . + **/ + + +#pragma once + +#include "JobsRegistry.h" + +#include + +namespace Orthanc +{ + class JobsEngine + { + private: + enum State + { + State_Setup, + State_Running, + State_Stopping, + State_Done + }; + + boost::mutex stateMutex_; + State state_; + std::auto_ptr registry_; + boost::thread retryHandler_; + unsigned int threadSleep_; + std::vector 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(); + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/JobsRegistry.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsRegistry.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#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 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(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& 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 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(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 job(new JobHandler(unserializer, s[JOBS][*it], *it)); + + std::string id; + SubmitInternal(id, job.release(), true); + } + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/JobsRegistry.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsRegistry.h Mon Jul 09 08:18:22 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 . + **/ + + +#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 +#include +#include +#include +#include + +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 JobsIndex; + typedef std::list CompletedJobs; + typedef std::set RetryJobs; + typedef std::priority_queue, // 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& 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); + }; + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/Operations/IJobOperation.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/IJobOperation.h Mon Jul 09 08:18:22 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 . + **/ + + +#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; + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/Operations/JobOperationValue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValue.h Mon Jul 09 08:18:22 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 . + **/ + + +#pragma once + +#include +#include + +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; + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/Operations/JobOperationValues.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValues.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#include "../../PrecompiledHeaders.h" +#include "JobOperationValues.h" + +#include "../IJobUnserializer.h" +#include "../../OrthancException.h" + +#include +#include + +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 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(); + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/Operations/JobOperationValues.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValues.h Mon Jul 09 08:18:22 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 . + **/ + + +#pragma once + +#include "JobOperationValue.h" + +#include + +namespace Orthanc +{ + class IJobUnserializer; + + class JobOperationValues : public boost::noncopyable + { + private: + std::vector 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); + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/Operations/LogJobOperation.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/LogJobOperation.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#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(input).GetContent(); + break; + + case JobOperationValue::Type_Null: + LOG(INFO) << "Job value: (null)"; + break; + + default: + LOG(INFO) << "Job value: (unsupport)"; + break; + } + + outputs.Append(input.Clone()); + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/Operations/LogJobOperation.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/LogJobOperation.h Mon Jul 09 08:18:22 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 . + **/ + + +#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"; + } + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/Operations/NullOperationValue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/NullOperationValue.h Mon Jul 09 08:18:22 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 . + **/ + + +#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"; + } + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#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 operation_; + std::auto_ptr originalInputs_; + std::auto_ptr workInputs_; + std::list 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::iterator first = nextOperations_.begin(); + outputs.Move(*(*first)->workInputs_); + + std::list::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(currentInput_); + operation_->Serialize(target[OPERATION]); + originalInputs_->Serialize(target[ORIGINAL_INPUTS]); + workInputs_->Serialize(target[WORK_INPUTS]); + + Json::Value tmp = Json::arrayValue; + for (std::list::const_iterator it = nextOperations_.begin(); + it != nextOperations_.end(); ++it) + { + tmp.append(static_cast((*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::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(current_) / + static_cast(operations_.size() + 1)); + } + + + void SequenceOfOperationsJob::GetPublicContent(Json::Value& value) + { + boost::mutex::scoped_lock lock(mutex_); + + value["CountOperations"] = static_cast(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(trailingTimeout_.total_milliseconds()); + value[DICOM_TIMEOUT] = connectionManager_.GetTimeout(); + value[CURRENT] = static_cast(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); + } + } + } + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/Operations/SequenceOfOperationsJob.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.h Mon Jul 09 08:18:22 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 . + **/ + + +#pragma once + +#include "../IJob.h" +#include "IJobOperation.h" + +#include "../../DicomNetworking/TimeoutDicomConnectionManager.h" + +#include +#include + +#include + +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 operations_; + size_t current_; + boost::condition_variable operationAdded_; + boost::posix_time::time_duration trailingTimeout_; + std::list 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(); + } + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/Operations/StringOperationValue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/StringOperationValue.h Mon Jul 09 08:18:22 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 . + **/ + + +#pragma once + +#include "JobOperationValue.h" + +#include + +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_; + } + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/SetOfInstancesJob.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/SetOfInstancesJob.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#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(position_) / + static_cast(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(instances_.size()); + value["FailedInstancesCount"] = static_cast(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(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); + } + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/JobsEngine/SetOfInstancesJob.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/SetOfInstancesJob.h Mon Jul 09 08:18:22 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 . + **/ + + +#pragma once + +#include "IJob.h" + +#include + +namespace Orthanc +{ + class SetOfInstancesJob : public IJob + { + private: + bool started_; + std::vector instances_; + bool permissive_; + size_t position_; + std::set 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& 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); + }; +} diff -r 38a3054b22ff -r 83c991aeb611 Core/MultiThreading/BagOfTasks.h --- a/Core/MultiThreading/BagOfTasks.h Fri Jul 06 16:51:08 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 . - **/ - - -#pragma once - -#include "../ICommand.h" - -#include -#include - -namespace Orthanc -{ - class BagOfTasks : public boost::noncopyable - { - private: - typedef std::list 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(); - } - }; -} diff -r 38a3054b22ff -r 83c991aeb611 Core/MultiThreading/BagOfTasksProcessor.cpp --- a/Core/MultiThreading/BagOfTasksProcessor.cpp Fri Jul 06 16:51:08 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 . - **/ - - -#include "../PrecompiledHeaders.h" -#include "BagOfTasksProcessor.h" - -#include "../Logging.h" -#include "../OrthancException.h" - -#include - -namespace Orthanc -{ - class BagOfTasksProcessor::Task : public IDynamicObject - { - private: - uint64_t bag_; - std::auto_ptr 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 obj(that->queue_.Dequeue(100)); - if (obj.get() != NULL) - { - Task& task = *dynamic_cast(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(it->second.done_) / - static_cast(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); - } -} diff -r 38a3054b22ff -r 83c991aeb611 Core/MultiThreading/BagOfTasksProcessor.h --- a/Core/MultiThreading/BagOfTasksProcessor.h Fri Jul 06 16:51:08 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 . - **/ - - -#pragma once - -#include "BagOfTasks.h" -#include "SharedMessageQueue.h" - -#include -#include - -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 Bags; - typedef std::map ExitStatus; - - SharedMessageQueue queue_; - - boost::mutex mutex_; - uint64_t countBags_; - Bags bags_; - std::vector 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); - }; -} diff -r 38a3054b22ff -r 83c991aeb611 Core/MultiThreading/ILockable.h --- a/Core/MultiThreading/ILockable.h Fri Jul 06 16:51:08 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 . - **/ - - -#pragma once - -#include - -namespace Orthanc -{ - class ILockable : public boost::noncopyable - { - friend class Locker; - - protected: - virtual void Lock() = 0; - - virtual void Unlock() = 0; - - public: - virtual ~ILockable() - { - } - }; -} diff -r 38a3054b22ff -r 83c991aeb611 Core/MultiThreading/Locker.h --- a/Core/MultiThreading/Locker.h Fri Jul 06 16:51:08 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 . - **/ - - -#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(); - } - }; -} diff -r 38a3054b22ff -r 83c991aeb611 Core/MultiThreading/Mutex.cpp --- a/Core/MultiThreading/Mutex.cpp Fri Jul 06 16:51:08 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 . - **/ - - -#include "../PrecompiledHeaders.h" -#include "Mutex.h" - -#include "../OrthancException.h" - -#if defined(_WIN32) -#include -#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) -#include -#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 -} diff -r 38a3054b22ff -r 83c991aeb611 Core/MultiThreading/Mutex.h --- a/Core/MultiThreading/Mutex.h Fri Jul 06 16:51:08 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 . - **/ - - -#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(); - }; -} diff -r 38a3054b22ff -r 83c991aeb611 Core/MultiThreading/ReaderWriterLock.cpp --- a/Core/MultiThreading/ReaderWriterLock.cpp Fri Jul 06 16:51:08 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 . - **/ - - -#include "../PrecompiledHeaders.h" -#include "ReaderWriterLock.h" - -#include - -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_; - } -} diff -r 38a3054b22ff -r 83c991aeb611 Core/MultiThreading/ReaderWriterLock.h --- a/Core/MultiThreading/ReaderWriterLock.h Fri Jul 06 16:51:08 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 . - **/ - - -#pragma once - -#include "ILockable.h" - -#include - -namespace Orthanc -{ - class ReaderWriterLock : public boost::noncopyable - { - private: - struct PImpl; - - PImpl *pimpl_; - - public: - ReaderWriterLock(); - - virtual ~ReaderWriterLock(); - - ILockable& ForReader(); - - ILockable& ForWriter(); - }; -} diff -r 38a3054b22ff -r 83c991aeb611 Core/MultiThreading/Semaphore.cpp --- a/Core/MultiThreading/Semaphore.cpp Fri Jul 06 16:51:08 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 . - **/ - - -#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_++; - } -} diff -r 38a3054b22ff -r 83c991aeb611 Core/MultiThreading/Semaphore.h --- a/Core/MultiThreading/Semaphore.h Fri Jul 06 16:51:08 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 . - **/ - - -#pragma once - -#include -#include - -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(); - } - }; - }; -} diff -r 38a3054b22ff -r 83c991aeb611 Core/SerializationToolbox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SerializationToolbox.cpp Mon Jul 09 08:18:22 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 . + **/ + + +#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(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& 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& target, + const Json::Value& value, + const std::string& field) + { + std::vector 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& target, + const Json::Value& value, + const std::string& field) + { + std::vector tmp; + ReadArrayOfStrings(tmp, value, field); + + target.clear(); + for (size_t i = 0; i < tmp.size(); i++) + { + target.insert(tmp[i]); + } + } + + + void ReadSetOfTags(std::set& 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& 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& 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::const_iterator it = values.begin(); + it != values.end(); ++it) + { + value.append(*it); + } + } + + + void WriteSetOfTags(Json::Value& target, + const std::set& 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::const_iterator it = tags.begin(); + it != tags.end(); ++it) + { + value.append(it->Format()); + } + } + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/SerializationToolbox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SerializationToolbox.h Mon Jul 09 08:18:22 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 . + **/ + + +#pragma once + +#include "DicomFormat/DicomTag.h" + +#include +#include + +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& target, + const Json::Value& value, + const std::string& field); + + void ReadListOfStrings(std::list& target, + const Json::Value& value, + const std::string& field); + + void ReadSetOfStrings(std::set& target, + const Json::Value& value, + const std::string& field); + + void ReadSetOfTags(std::set& target, + const Json::Value& value, + const std::string& field); + + void WriteArrayOfStrings(Json::Value& target, + const std::vector& values, + const std::string& field); + + void WriteSetOfStrings(Json::Value& target, + const std::set& values, + const std::string& field); + + void WriteSetOfTags(Json::Value& target, + const std::set& tags, + const std::string& field); + } +} diff -r 38a3054b22ff -r 83c991aeb611 Core/Toolbox.cpp --- a/Core/Toolbox.cpp Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/Toolbox.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -62,6 +62,15 @@ # include #endif +#if ORTHANC_ENABLE_SSL == 1 +// For OpenSSL initialization and finalization +# include +# include +# include +# include +# include +#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 diff -r 38a3054b22ff -r 83c991aeb611 Core/Toolbox.h --- a/Core/Toolbox.h Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/Toolbox.h Mon Jul 09 08:18:22 2018 +0200 @@ -233,6 +233,10 @@ std::string ToUpperCaseWithAccents(const std::string& source); #endif + void InitializeOpenSsl(); + + void FinalizeOpenSsl(); + std::string GenerateUuid(); } } diff -r 38a3054b22ff -r 83c991aeb611 Core/WebServiceParameters.cpp --- a/Core/WebServiceParameters.cpp Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/WebServiceParameters.cpp Mon Jul 09 08:18:22 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"); + } } diff -r 38a3054b22ff -r 83c991aeb611 Core/WebServiceParameters.h --- a/Core/WebServiceParameters.h Fri Jul 06 16:51:08 2018 +0200 +++ b/Core/WebServiceParameters.h Mon Jul 09 08:18:22 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; }; } diff -r 38a3054b22ff -r 83c991aeb611 NEWS --- a/NEWS Fri Jul 06 16:51:08 2018 +0200 +++ b/NEWS Mon Jul 09 08:18:22 2018 +0200 @@ -1,6 +1,15 @@ 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 + Orthanc Explorer ---------------- @@ -9,6 +18,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" @@ -26,10 +36,18 @@ "image/pam" MIME type to retrieve images in PAM format (https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format) +Plugins +------- + +* New primitive in database SDK: "lookupIdentifierRange" to speed up range searches + Maintenance ----------- * Fix generation of DICOMDIR if PatientID is empty +* Fix issue 25 (Deadlock with Lua scripts): The event queue is now implemented for Lua +* Configuration option "LogExportedResources" is now "false" by default +* Header "OrthancCppDatabasePlugin.h" is now part of the "orthanc-databases" project * Upgraded dependencies for static and Windows builds: - boost 1.67.0 diff -r 38a3054b22ff -r 83c991aeb611 OrthancExplorer/explorer.html --- a/OrthancExplorer/explorer.html Fri Jul 06 16:51:08 2018 +0200 +++ b/OrthancExplorer/explorer.html Mon Jul 09 08:18:22 2018 +0200 @@ -38,12 +38,13 @@
@@ -56,12 +57,13 @@
@@ -109,6 +111,7 @@
@@ -169,6 +172,7 @@
@@ -223,6 +227,7 @@
@@ -279,6 +284,7 @@
@@ -468,6 +474,48 @@
+ +
+
+

Jobs

+ +
+
+
    +
+
+
+ +
+
+

Job

+ +
+ Jobs +
+
+
+
    +
+ +
+
+
+ + + + +
+
+
+
+