# HG changeset patch # User Sebastien Jodogne # Date 1432220310 -7200 # Node ID 111e23bb49049730fff779e87a6c1c85f7f705f2 # Parent f894be6e7cc180a0172f7b205623b443f628e844# Parent feaf2840917c105383e1f1b0f4884cb0037fc7aa integration mainline->query-retrieve diff -r f894be6e7cc1 -r 111e23bb4904 .travis.yml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.travis.yml Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,68 @@ +language: cpp + +env: + - TRAVIS_MINGW=OFF + - TRAVIS_MINGW=ON + +compiler: + - gcc + - clang + +os: + - osx + - linux + +osx_image: xcode61 + +matrix: + exclude: + # This excludes OSX builds from the build matrix for gcc + - os: osx + compiler: gcc + + # Do not compile for OS X or clang when MinGW is enabled + - os: osx + env: TRAVIS_MINGW=ON + - compiler: clang + env: TRAVIS_MINGW=ON + +before_install: + - if [ $TRAVIS_OS_NAME == linux ]; then sudo apt-get update -qq && sudo apt-get install + -qq build-essential unzip cmake mercurial uuid-dev libcurl4-openssl-dev liblua5.1-0-dev + libgtest-dev libpng-dev libsqlite3-dev libssl-dev zlib1g-dev libdcmtk2-dev libwrap0-dev + libcharls-dev; fi + - if [ $TRAVIS_OS_NAME == linux -a $TRAVIS_MINGW == ON ]; then sudo apt-get install mingw32; fi + + +before_script: + - mkdir Build + - cd Build + - if [ $TRAVIS_OS_NAME == linux -a $TRAVIS_MINGW == OFF ]; then cmake + -DCMAKE_BUILD_TYPE=Debug "-DDCMTK_LIBRARIES=CharLS;dcmjpls;wrap;oflog" + -DALLOW_DOWNLOADS=ON -DUSE_SYSTEM_BOOST=OFF -DUSE_SYSTEM_MONGOOSE=OFF -DUSE_SYSTEM_JSONCPP=OFF + -DUSE_SYSTEM_GOOGLE_LOG=OFF -DUSE_SYSTEM_PUGIXML=OFF -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON + ..; fi + - if [ $TRAVIS_OS_NAME == linux -a $TRAVIS_MINGW == ON ]; then cmake + -DCMAKE_BUILD_TYPE=Debug -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DALLOW_DOWNLOADS=ON + -DCMAKE_TOOLCHAIN_FILE=Resources/MinGWToolchain.cmake + ..; fi + - if [ $TRAVIS_OS_NAME == osx ]; then cmake + -DCMAKE_BUILD_TYPE=Debug -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DALLOW_DOWNLOADS=ON + ..; fi + +script: make && if [ $TRAVIS_MINGW == OFF ]; then ./UnitTests; fi + +#script: cp ../README Orthanc +#deploy: +# provider: releases +# api_key: +# secure: WU+niKLAKMoJHST5EK23BayK4qXSrXELKlJYc8wRjMO4ay1KSgvzlY2UGKeW1EPClBfZZ0Uh5VKF8l34exsfirFuwCX2qceozduZproUszZ4Z88X8wt8Ctu8tBuuKLZYFc9iNH4zw+QZyRuPyXK9iWpS0L9O20pqy5upTsagM3o= +# file_glob: true +# file: +# - 'Build/Orthanc' +# - 'Build/UnitTests' +# - 'BuildMinGW32/Orthanc.exe' +# - 'BuildMinGW32/UnitTests.exe' +# skip_cleanup: true +# on: +# all_branches: true diff -r f894be6e7cc1 -r 111e23bb4904 AUTHORS --- a/AUTHORS Wed Jun 25 15:34:40 2014 +0200 +++ b/AUTHORS Thu May 21 16:58:30 2015 +0200 @@ -6,9 +6,11 @@ ------------------ * Sebastien Jodogne - Department of Medical Physics, CHU of Liege, Belgium + Department of Medical Physics + University Hospital of Liege + Belgium - Overall design and main developper. + Overall design and lead developer. Client library diff -r f894be6e7cc1 -r 111e23bb4904 CMakeLists.txt --- a/CMakeLists.txt Wed Jun 25 15:34:40 2014 +0200 +++ b/CMakeLists.txt Thu May 21 16:58:30 2015 +0200 @@ -34,22 +34,33 @@ SET(USE_SYSTEM_CURL ON CACHE BOOL "Use the system version of LibCurl") SET(USE_SYSTEM_OPENSSL ON CACHE BOOL "Use the system version of OpenSSL") SET(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib") +SET(USE_SYSTEM_PUGIXML ON CACHE BOOL "Use the system version of Pugixml)") + +# Experimental options +SET(USE_PUGIXML ON CACHE BOOL "Use the Pugixml parser (turn off only for debug)") # Distribution-specific settings SET(USE_GTEST_DEBIAN_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") +SET(SYSTEM_MONGOOSE_USE_CALLBACKS ON CACHE BOOL "The system version of Mongoose uses callbacks (version >= 3.7)") +SET(USE_BOOST_ICONV ON CACHE BOOL "Use iconv instead of wconv (Windows only)") + mark_as_advanced(USE_GTEST_DEBIAN_SOURCE_PACKAGE) +mark_as_advanced(SYSTEM_MONGOOSE_USE_CALLBACKS) +mark_as_advanced(USE_BOOST_ICONV) + +# Path to the root folder of the Orthanc distribution +set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}) # Some basic inclusions include(CheckIncludeFiles) include(CheckIncludeFileCXX) include(CheckLibraryExists) +include(FindPythonInterp) include(${CMAKE_SOURCE_DIR}/Resources/CMake/AutoGeneratedCode.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/DownloadPackage.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/Compiler.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/VisualStudioPrecompiledHeaders.cmake) -set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}) - @@ -72,7 +83,7 @@ Core/DicomFormat/DicomIntegerPixelAccessor.cpp Core/DicomFormat/DicomInstanceHasher.cpp Core/Enumerations.cpp - Core/FileStorage/FileStorage.cpp + Core/FileStorage/FilesystemStorage.cpp Core/FileStorage/StorageAccessor.cpp Core/FileStorage/CompressedFileStorageAccessor.cpp Core/FileStorage/FileStorageAccessor.cpp @@ -84,6 +95,9 @@ Core/HttpServer/MongooseServer.cpp Core/HttpServer/HttpFileSender.cpp Core/HttpServer/FilesystemHttpSender.cpp + Core/RestApi/RestApiCall.cpp + Core/RestApi/RestApiGetCall.cpp + Core/RestApi/RestApiHierarchy.cpp Core/RestApi/RestApiPath.cpp Core/RestApi/RestApiOutput.cpp Core/RestApi/RestApi.cpp @@ -91,6 +105,7 @@ Core/MultiThreading/BagOfRunnablesBySteps.cpp Core/MultiThreading/Mutex.cpp Core/MultiThreading/ReaderWriterLock.cpp + Core/MultiThreading/Semaphore.cpp Core/MultiThreading/SharedMessageQueue.cpp Core/MultiThreading/ThreadedCommandProcessor.cpp Core/ImageFormats/ImageAccessor.cpp @@ -114,6 +129,11 @@ OrthancCppClient/Series.cpp OrthancCppClient/Instance.cpp OrthancCppClient/Patient.cpp + + Plugins/Engine/SharedLibrary.cpp + Plugins/Engine/PluginsManager.cpp + Plugins/Engine/OrthancPlugins.cpp + Plugins/Engine/OrthancPluginDatabase.cpp ) @@ -126,6 +146,7 @@ OrthancServer/DicomModification.cpp OrthancServer/FromDcmtkBridge.cpp OrthancServer/ParsedDicomFile.cpp + OrthancServer/DicomDirWriter.cpp OrthancServer/Internals/CommandDispatcher.cpp OrthancServer/Internals/FindScp.cpp OrthancServer/Internals/MoveScp.cpp @@ -148,26 +169,51 @@ OrthancServer/ServerToolbox.cpp OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancMoveRequestHandler.cpp + OrthancServer/ExportedResource.cpp + OrthancServer/ResourceFinder.cpp + OrthancServer/DicomFindQuery.cpp + + # From "lua-scripting" branch + OrthancServer/DicomInstanceToStore.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/Scheduler/CallSystemCommand.cpp ) set(ORTHANC_UNIT_TESTS_SOURCES - UnitTestsSources/DicomMap.cpp - UnitTestsSources/FileStorage.cpp - UnitTestsSources/FromDcmtk.cpp - UnitTestsSources/MemoryCache.cpp - UnitTestsSources/Png.cpp - UnitTestsSources/RestApi.cpp - UnitTestsSources/SQLite.cpp - UnitTestsSources/SQLiteChromium.cpp + UnitTestsSources/DicomMapTests.cpp + UnitTestsSources/FileStorageTests.cpp + UnitTestsSources/FromDcmtkTests.cpp + UnitTestsSources/MemoryCacheTests.cpp + UnitTestsSources/PngTests.cpp + UnitTestsSources/RestApiTests.cpp + UnitTestsSources/SQLiteTests.cpp + UnitTestsSources/SQLiteChromiumTests.cpp UnitTestsSources/ServerIndexTests.cpp - UnitTestsSources/Versions.cpp - UnitTestsSources/Zip.cpp - UnitTestsSources/Lua.cpp - UnitTestsSources/MultiThreading.cpp + UnitTestsSources/VersionsTests.cpp + UnitTestsSources/ZipTests.cpp + UnitTestsSources/LuaTests.cpp + UnitTestsSources/MultiThreadingTests.cpp UnitTestsSources/UnitTestsMain.cpp UnitTestsSources/ImageProcessingTests.cpp - UnitTestsSources/JpegLossless.cpp + UnitTestsSources/JpegLosslessTests.cpp + UnitTestsSources/PluginsTests.cpp + ) + + +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 ) @@ -198,6 +244,7 @@ include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/LuaConfiguration.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibCurlConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/PugixmlConfiguration.cmake) if (${ENABLE_SSL}) @@ -227,19 +274,11 @@ ## Autogeneration of files ##################################################################### -# Prepare the embedded files -set(EMBEDDED_FILES - PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql - UPGRADE_DATABASE_3_TO_4 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade3To4.sql - CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json - LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua - ) - if (${STANDALONE_BUILD}) # We embed all the resources in the binaries for standalone builds add_definitions(-DORTHANC_STANDALONE=1) EmbedResources( - ${EMBEDDED_FILES} + ${ORTHANC_EMBEDDED_FILES} ORTHANC_EXPLORER ${CMAKE_CURRENT_SOURCE_DIR}/OrthancExplorer ${DCMTK_DICTIONARIES} ) @@ -249,7 +288,7 @@ -DORTHANC_PATH=\"${CMAKE_SOURCE_DIR}\" ) EmbedResources( - ${EMBEDDED_FILES} + ${ORTHANC_EMBEDDED_FILES} ) endif() @@ -309,7 +348,7 @@ OrthancServer/main.cpp ) -target_link_libraries(Orthanc ServerLibrary CoreLibrary ${STATIC_LUA} ${STATIC_GOOGLE_LOG}) +target_link_libraries(Orthanc ServerLibrary CoreLibrary ${STATIC_LUA} ${STATIC_GOOGLE_LOG} ${DCMTK_LIBRARIES}) if (${OPENSSL_SOURCES_LENGTH} GREATER 0) target_link_libraries(Orthanc OpenSSL) @@ -338,7 +377,7 @@ ${GTEST_SOURCES} ${ORTHANC_UNIT_TESTS_SOURCES} ) -target_link_libraries(UnitTests ServerLibrary CoreLibrary ${STATIC_LUA} ${STATIC_GOOGLE_LOG}) +target_link_libraries(UnitTests ServerLibrary CoreLibrary ${STATIC_LUA} ${STATIC_GOOGLE_LOG} ${DCMTK_LIBRARIES}) if (${OPENSSL_SOURCES_LENGTH} GREATER 0) target_link_libraries(UnitTests OpenSSL) @@ -395,7 +434,8 @@ ) if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") set_target_properties(OrthancClient PROPERTIES LINK_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined -Wl,--as-needed -Wl,--version-script=${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/Laaw/VersionScript.map" ) @@ -442,7 +482,11 @@ ) install( - FILES ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h + FILES + ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h + ${ORTHANC_ROOT}/Plugins/Include/OrthancCPlugin.h + ${ORTHANC_ROOT}/Plugins/Include/OrthancCDatabasePlugin.h + ${ORTHANC_ROOT}/Plugins/Include/OrthancCppDatabasePlugin.h DESTINATION include/orthanc ) endif() @@ -461,10 +505,26 @@ ${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 internal documentation with Doxygen" VERBATIM + COMMENT "Generating plugin documentation with Doxygen" VERBATIM + ) + + install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/OrthancPluginDocumentation/doc/ + DESTINATION share/doc/orthanc/OrthancPlugin ) if (BUILD_CLIENT_LIBRARY) diff -r f894be6e7cc1 -r 111e23bb4904 Core/Cache/ICachePageProvider.h --- a/Core/Cache/ICachePageProvider.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Cache/ICachePageProvider.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/Cache/LeastRecentlyUsedIndex.h --- a/Core/Cache/LeastRecentlyUsedIndex.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Cache/LeastRecentlyUsedIndex.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/Cache/MemoryCache.cpp --- a/Core/Cache/MemoryCache.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Cache/MemoryCache.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/Cache/MemoryCache.h --- a/Core/Cache/MemoryCache.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Cache/MemoryCache.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/ChunkedBuffer.cpp --- a/Core/ChunkedBuffer.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ChunkedBuffer.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/ChunkedBuffer.h --- a/Core/ChunkedBuffer.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ChunkedBuffer.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/Compression/BufferCompressor.cpp --- a/Core/Compression/BufferCompressor.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Compression/BufferCompressor.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/Compression/BufferCompressor.h --- a/Core/Compression/BufferCompressor.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Compression/BufferCompressor.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/Compression/HierarchicalZipWriter.cpp --- a/Core/Compression/HierarchicalZipWriter.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Compression/HierarchicalZipWriter.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -53,7 +53,7 @@ if (c == '^') c = ' '; - if (c < 128 && + if (c <= 127 && c >= 0) { if (isspace(c)) diff -r f894be6e7cc1 -r 111e23bb4904 Core/Compression/HierarchicalZipWriter.h --- a/Core/Compression/HierarchicalZipWriter.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Compression/HierarchicalZipWriter.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -63,8 +63,6 @@ Stack stack_; - std::string GetCurrentDirectoryPath() const; - std::string EnsureUniqueFilename(const char* filename); public: @@ -83,6 +81,8 @@ void CloseDirectory(); + std::string GetCurrentDirectoryPath() const; + static std::string KeepAlphanumeric(const std::string& source); }; @@ -114,12 +114,27 @@ return writer_.GetCompressionLevel(); } + void SetAppendToExisting(bool append) + { + writer_.SetAppendToExisting(append); + } + + bool IsAppendToExisting() const + { + return writer_.IsAppendToExisting(); + } + void OpenFile(const char* name); void OpenDirectory(const char* name); void CloseDirectory(); + std::string GetCurrentDirectoryPath() const + { + return indexer_.GetCurrentDirectoryPath(); + } + void Write(const char* data, size_t length) { writer_.Write(data, length); diff -r f894be6e7cc1 -r 111e23bb4904 Core/Compression/ZipWriter.cpp --- a/Core/Compression/ZipWriter.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Compression/ZipWriter.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -38,10 +38,11 @@ #include "ZipWriter.h" -#include "../../Resources/ThirdParty/minizip/zip.h" +#include +#include #include -#include +#include "../../Resources/ThirdParty/minizip/zip.h" #include "../OrthancException.h" @@ -77,15 +78,19 @@ struct ZipWriter::PImpl { zipFile file_; + + PImpl() : file_(NULL) + { + } }; - ZipWriter::ZipWriter() : pimpl_(new PImpl) + ZipWriter::ZipWriter() : + pimpl_(new PImpl), + isZip64_(false), + hasFileInZip_(false), + append_(false), + compressionLevel_(6) { - compressionLevel_ = 6; - hasFileInZip_ = false; - isZip64_ = false; - - pimpl_->file_ = NULL; } ZipWriter::~ZipWriter() @@ -122,13 +127,20 @@ hasFileInZip_ = false; + int mode = APPEND_STATUS_CREATE; + if (append_ && + boost::filesystem::exists(path_)) + { + mode = APPEND_STATUS_ADDINZIP; + } + if (isZip64_) { - pimpl_->file_ = zipOpen64(path_.c_str(), APPEND_STATUS_CREATE); + pimpl_->file_ = zipOpen64(path_.c_str(), mode); } else { - pimpl_->file_ = zipOpen(path_.c_str(), APPEND_STATUS_CREATE); + pimpl_->file_ = zipOpen(path_.c_str(), mode); } if (!pimpl_->file_) @@ -230,4 +242,13 @@ length -= bytes; } } + + + void ZipWriter::SetAppendToExisting(bool append) + { + Close(); + append_ = append; + } + + } diff -r f894be6e7cc1 -r 111e23bb4904 Core/Compression/ZipWriter.h --- a/Core/Compression/ZipWriter.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Compression/ZipWriter.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -50,6 +50,7 @@ bool isZip64_; bool hasFileInZip_; + bool append_; uint8_t compressionLevel_; std::string path_; @@ -71,6 +72,13 @@ { return compressionLevel_; } + + void SetAppendToExisting(bool append); + + bool IsAppendToExisting() const + { + return append_; + } void Open(); diff -r f894be6e7cc1 -r 111e23bb4904 Core/Compression/ZlibCompressor.cpp --- a/Core/Compression/ZlibCompressor.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Compression/ZlibCompressor.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/Compression/ZlibCompressor.h --- a/Core/Compression/ZlibCompressor.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Compression/ZlibCompressor.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomArray.cpp --- a/Core/DicomFormat/DicomArray.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomArray.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomArray.h --- a/Core/DicomFormat/DicomArray.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomArray.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomElement.h --- a/Core/DicomFormat/DicomElement.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomElement.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomImageInformation.cpp --- a/Core/DicomFormat/DicomImageInformation.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomImageInformation.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -39,6 +39,7 @@ #include "DicomImageInformation.h" #include "../OrthancException.h" +#include "../Toolbox.h" #include #include #include @@ -53,6 +54,66 @@ try { + std::string p = values.GetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION).AsString(); + Toolbox::ToUpperCase(p); + + if (p == "RGB") + { + photometric_ = PhotometricInterpretation_RGB; + } + else if (p == "MONOCHROME1") + { + photometric_ = PhotometricInterpretation_Monochrome1; + } + else if (p == "MONOCHROME2") + { + photometric_ = PhotometricInterpretation_Monochrome2; + } + else if (p == "PALETTE COLOR") + { + photometric_ = PhotometricInterpretation_Palette; + } + else if (p == "HSV") + { + photometric_ = PhotometricInterpretation_HSV; + } + else if (p == "ARGB") + { + photometric_ = PhotometricInterpretation_ARGB; + } + else if (p == "CMYK") + { + photometric_ = PhotometricInterpretation_CMYK; + } + else if (p == "YBR_FULL") + { + photometric_ = PhotometricInterpretation_YBRFull; + } + else if (p == "YBR_FULL_422") + { + photometric_ = PhotometricInterpretation_YBRFull422; + } + else if (p == "YBR_PARTIAL_420") + { + photometric_ = PhotometricInterpretation_YBRPartial420; + } + else if (p == "YBR_PARTIAL_422") + { + photometric_ = PhotometricInterpretation_YBRPartial422; + } + else if (p == "YBR_ICT") + { + photometric_ = PhotometricInterpretation_YBR_ICT; + } + else if (p == "YBR_RCT") + { + photometric_ = PhotometricInterpretation_YBR_RCT; + } + else + { + photometric_ = PhotometricInterpretation_Unknown; + } + width_ = boost::lexical_cast(values.GetValue(DICOM_TAG_COLUMNS).AsString()); height_ = boost::lexical_cast(values.GetValue(DICOM_TAG_ROWS).AsString()); bitsAllocated_ = boost::lexical_cast(values.GetValue(DICOM_TAG_BITS_ALLOCATED).AsString()); @@ -159,30 +220,37 @@ bool DicomImageInformation::ExtractPixelFormat(PixelFormat& format) const { - if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned()) + if (photometric_ == PhotometricInterpretation_Monochrome1 || + photometric_ == PhotometricInterpretation_Monochrome2) { - format = PixelFormat_Grayscale8; - return true; + if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned()) + { + format = PixelFormat_Grayscale8; + return true; + } + + if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && !IsSigned()) + { + format = PixelFormat_Grayscale16; + return true; + } + + if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && IsSigned()) + { + format = PixelFormat_SignedGrayscale16; + return true; + } } - if (GetBitsStored() == 8 && GetChannelCount() == 3 && !IsSigned()) + if (GetBitsStored() == 8 && + GetChannelCount() == 3 && + !IsSigned() && + photometric_ == PhotometricInterpretation_RGB) { format = PixelFormat_RGB24; return true; } - if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && !IsSigned()) - { - format = PixelFormat_Grayscale16; - return true; - } - - if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && IsSigned()) - { - format = PixelFormat_SignedGrayscale16; - return true; - } - return false; } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomImageInformation.h --- a/Core/DicomFormat/DicomImageInformation.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomImageInformation.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -54,6 +54,8 @@ unsigned int bitsStored_; unsigned int highBit_; + PhotometricInterpretation photometric_; + public: DicomImageInformation(const DicomMap& values); @@ -112,6 +114,11 @@ return highBit_ + 1 - bitsStored_; } + PhotometricInterpretation GetPhotometricInterpretation() const + { + return photometric_; + } + bool ExtractPixelFormat(PixelFormat& format) const; }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomInstanceHasher.cpp --- a/Core/DicomFormat/DicomInstanceHasher.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomInstanceHasher.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -48,8 +48,7 @@ seriesUid_ = seriesUid; instanceUid_ = instanceUid; - if (patientId_.size() == 0 || - studyUid_.size() == 0 || + if (studyUid_.size() == 0 || seriesUid_.size() == 0 || instanceUid_.size() == 0) { @@ -59,7 +58,9 @@ DicomInstanceHasher::DicomInstanceHasher(const DicomMap& instance) { - Setup(instance.GetValue(DICOM_TAG_PATIENT_ID).AsString(), + const DicomValue* patientId = instance.TestAndGetValue(DICOM_TAG_PATIENT_ID); + + Setup(patientId == NULL ? "" : patientId->AsString(), instance.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString(), instance.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString(), instance.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString()); diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomInstanceHasher.h --- a/Core/DicomFormat/DicomInstanceHasher.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomInstanceHasher.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomIntegerPixelAccessor.cpp --- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomIntegerPixelAccessor.h --- a/Core/DicomFormat/DicomIntegerPixelAccessor.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -80,5 +80,10 @@ { return pixelData_; } + + size_t GetSize() const + { + return size_; + } }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomMap.cpp --- a/Core/DicomFormat/DicomMap.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomMap.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -36,6 +36,7 @@ #include #include #include "DicomString.h" +#include "DicomArray.h" #include "../OrthancException.h" @@ -190,6 +191,17 @@ } + void DicomMap::Assign(const DicomMap& other) + { + Clear(); + + for (Map::const_iterator it = other.map_.begin(); it != other.map_.end(); ++it) + { + map_.insert(std::make_pair(it->first, it->second->Clone())); + } + } + + const DicomValue& DicomMap::GetValue(const DicomTag& tag) const { const DicomValue* value = TestAndGetValue(tag); @@ -389,30 +401,21 @@ } - void DicomMap::ExtractMainDicomTagsForLevel(DicomMap& result, - ResourceType level) const + void DicomMap::Print(FILE* fp) const { - switch (level) - { - case ResourceType_Patient: - ExtractPatientInformation(result); - break; + DicomArray a(*this); + a.Print(fp); + } + - case ResourceType_Study: - ExtractStudyInformation(result); - break; + void DicomMap::GetTags(std::set& tags) const + { + tags.clear(); - case ResourceType_Series: - ExtractSeriesInformation(result); - break; - - case ResourceType_Instance: - ExtractInstanceInformation(result); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + for (Map::const_iterator it = map_.begin(); + it != map_.end(); ++it) + { + tags.insert(it->first); } } - } diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomMap.h --- a/Core/DicomFormat/DicomMap.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomMap.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -78,8 +78,15 @@ Clear(); } + size_t GetSize() const + { + return map_.size(); + } + DicomMap* Clone() const; + void Assign(const DicomMap& other); + void Clear(); void SetValue(uint16_t group, @@ -125,11 +132,13 @@ const DicomValue& GetValue(const DicomTag& tag) const; + // DO NOT delete the returned value! const DicomValue* TestAndGetValue(uint16_t group, uint16_t element) const { return TestAndGetValue(DicomTag(group, element)); } + // DO NOT delete the returned value! const DicomValue* TestAndGetValue(const DicomTag& tag) const; void Remove(const DicomTag& tag); @@ -161,7 +170,8 @@ static void GetMainDicomTags(std::set& result); - void ExtractMainDicomTagsForLevel(DicomMap& result, - ResourceType level) const; + void Print(FILE* fp) const; + + void GetTags(std::set& tags) const; }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomNullValue.h --- a/Core/DicomFormat/DicomNullValue.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomNullValue.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomString.h --- a/Core/DicomFormat/DicomString.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomString.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomTag.cpp --- a/Core/DicomFormat/DicomTag.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomTag.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -116,4 +116,142 @@ return ""; } + + + void DicomTag::GetTagsForModule(std::set& target, + DicomModule module) + { + // REFERENCE: 11_03pu.pdf, DICOM PS 3.3 2011 - Information Object Definitions + target.clear(); + + switch (module) + { + case DicomModule_Patient: + // This is Table C.7-1 "Patient Module Attributes" (p. 373) + target.insert(DicomTag(0x0010, 0x0010)); // Patient's name + target.insert(DicomTag(0x0010, 0x0020)); // Patient ID + target.insert(DicomTag(0x0010, 0x0030)); // Patient's birth date + target.insert(DicomTag(0x0010, 0x0040)); // Patient's sex + target.insert(DicomTag(0x0008, 0x1120)); // Referenced patient sequence + target.insert(DicomTag(0x0010, 0x0032)); // Patient's birth time + target.insert(DicomTag(0x0010, 0x1000)); // Other patient IDs + target.insert(DicomTag(0x0010, 0x1002)); // Other patient IDs sequence + target.insert(DicomTag(0x0010, 0x1001)); // Other patient names + target.insert(DicomTag(0x0010, 0x2160)); // Ethnic group + target.insert(DicomTag(0x0010, 0x4000)); // Patient comments + target.insert(DicomTag(0x0010, 0x2201)); // Patient species description + target.insert(DicomTag(0x0010, 0x2202)); // Patient species code sequence + target.insert(DicomTag(0x0010, 0x2292)); // Patient breed description + target.insert(DicomTag(0x0010, 0x2293)); // Patient breed code sequence + target.insert(DicomTag(0x0010, 0x2294)); // Breed registration sequence + target.insert(DicomTag(0x0010, 0x2297)); // Responsible person + target.insert(DicomTag(0x0010, 0x2298)); // Responsible person role + target.insert(DicomTag(0x0010, 0x2299)); // Responsible organization + target.insert(DicomTag(0x0012, 0x0062)); // Patient identity removed + target.insert(DicomTag(0x0012, 0x0063)); // De-identification method + target.insert(DicomTag(0x0012, 0x0064)); // De-identification method code sequence + + // Table 10-18 ISSUER OF PATIENT ID MACRO (p. 112) + target.insert(DicomTag(0x0010, 0x0021)); // Issuer of Patient ID + target.insert(DicomTag(0x0010, 0x0024)); // Issuer of Patient ID qualifiers sequence + break; + + case DicomModule_Study: + // This is Table C.7-3 "General Study Module Attributes" (p. 378) + target.insert(DicomTag(0x0020, 0x000d)); // Study instance UID + target.insert(DicomTag(0x0008, 0x0020)); // Study date + target.insert(DicomTag(0x0008, 0x0030)); // Study time + target.insert(DicomTag(0x0008, 0x0090)); // Referring physician's name + target.insert(DicomTag(0x0008, 0x0096)); // Referring physician identification sequence + target.insert(DicomTag(0x0020, 0x0010)); // Study ID + target.insert(DicomTag(0x0008, 0x0050)); // Accession number + target.insert(DicomTag(0x0008, 0x0051)); // Issuer of accession number sequence + target.insert(DicomTag(0x0008, 0x1030)); // Study description + target.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of record + target.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of record identification sequence + target.insert(DicomTag(0x0008, 0x1060)); // Name of physician(s) reading study + target.insert(DicomTag(0x0008, 0x1062)); // Physician(s) reading study identification sequence + target.insert(DicomTag(0x0032, 0x1034)); // Requesting service code sequence + target.insert(DicomTag(0x0008, 0x1110)); // Referenced study sequence + target.insert(DicomTag(0x0008, 0x1032)); // Procedure code sequence + target.insert(DicomTag(0x0040, 0x1012)); // Reason for performed procedure code sequence + break; + + case DicomModule_Series: + // This is Table C.7-5 "General Series Module Attributes" (p. 385) + target.insert(DicomTag(0x0008, 0x0060)); // Modality + target.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID + target.insert(DicomTag(0x0020, 0x0011)); // Series Number + target.insert(DicomTag(0x0020, 0x0060)); // Laterality + target.insert(DicomTag(0x0008, 0x0021)); // Series Date + target.insert(DicomTag(0x0008, 0x0031)); // Series Time + target.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians’ Name + target.insert(DicomTag(0x0008, 0x1052)); // Performing Physician Identification Sequence + target.insert(DicomTag(0x0018, 0x1030)); // Protocol Name + target.insert(DicomTag(0x0008, 0x103e)); // Series Description + target.insert(DicomTag(0x0008, 0x103f)); // Series Description Code Sequence + target.insert(DicomTag(0x0008, 0x1070)); // Operators' Name + target.insert(DicomTag(0x0008, 0x1072)); // Operator Identification Sequence + target.insert(DicomTag(0x0008, 0x1111)); // Referenced Performed Procedure Step Sequence + target.insert(DicomTag(0x0008, 0x1250)); // Related Series Sequence + target.insert(DicomTag(0x0018, 0x0015)); // Body Part Examined + target.insert(DicomTag(0x0018, 0x5100)); // Patient Position + target.insert(DicomTag(0x0028, 0x0108)); // Smallest Pixel Value in Series + target.insert(DicomTag(0x0029, 0x0109)); // Largest Pixel Value in Series + target.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + target.insert(DicomTag(0x0010, 0x2210)); // Anatomical Orientation Type + + // Table 10-16 PERFORMED PROCEDURE STEP SUMMARY MACRO ATTRIBUTES + target.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID + target.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date + target.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time + target.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description + target.insert(DicomTag(0x0040, 0x0260)); // Performed Protocol Code Sequence + target.insert(DicomTag(0x0040, 0x0280)); // Comments on the Performed Procedure Step + break; + + case DicomModule_Instance: + // This is Table C.12-1 "SOP Common Module Attributes" (p. 1207) + target.insert(DicomTag(0x0008, 0x0016)); // SOP Class UID + target.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID + target.insert(DicomTag(0x0008, 0x0005)); // Specific Character Set + target.insert(DicomTag(0x0008, 0x0012)); // Instance Creation Date + target.insert(DicomTag(0x0008, 0x0013)); // Instance Creation Time + target.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID + target.insert(DicomTag(0x0008, 0x001a)); // Related General SOP Class UID + target.insert(DicomTag(0x0008, 0x001b)); // Original Specialized SOP Class UID + target.insert(DicomTag(0x0008, 0x0110)); // Coding Scheme Identification Sequence + target.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC + target.insert(DicomTag(0x0018, 0xa001)); // Contributing Equipment Sequence + target.insert(DicomTag(0x0020, 0x0013)); // Instance Number + target.insert(DicomTag(0x0100, 0x0410)); // SOP Instance Status + target.insert(DicomTag(0x0100, 0x0420)); // SOP Authorization DateTime + target.insert(DicomTag(0x0100, 0x0424)); // SOP Authorization Comment + target.insert(DicomTag(0x0100, 0x0426)); // Authorization Equipment Certification Number + target.insert(DicomTag(0x0400, 0x0500)); // Encrypted Attributes Sequence + target.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence + target.insert(DicomTag(0x0040, 0xa390)); // HL7 Structured Document Reference Sequence + target.insert(DicomTag(0x0028, 0x0303)); // Longitudinal Temporal Information Modified + + // Table C.12-6 "DIGITAL SIGNATURES MACRO ATTRIBUTES" (p. 1216) + target.insert(DicomTag(0x4ffe, 0x0001)); // MAC Parameters sequence + target.insert(DicomTag(0xfffa, 0xfffa)); // Digital signatures sequence + break; + + // TODO IMAGE MODULE? + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + bool DicomTag::IsIdentifier() const + { + return (*this == DICOM_TAG_PATIENT_ID || + *this == DICOM_TAG_STUDY_INSTANCE_UID || + *this == DICOM_TAG_ACCESSION_NUMBER || + *this == DICOM_TAG_SERIES_INSTANCE_UID || + *this == DICOM_TAG_SOP_INSTANCE_UID); + } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomTag.h --- a/Core/DicomFormat/DicomTag.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomTag.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,8 +33,10 @@ #pragma once #include +#include #include +#include "../Enumerations.h" namespace Orthanc { @@ -81,6 +83,11 @@ std::string Format() const; friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag); + + static void GetTagsForModule(std::set& target, + DicomModule module); + + bool IsIdentifier() const; }; // Aliases for the most useful tags @@ -104,10 +111,11 @@ static const DicomTag DICOM_TAG_PATIENT_NAME(0x0010, 0x0010); - // The following is used for "modify" operations + // The following is used for "modify/anonymize" operations static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016); static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID(0x0002, 0x0002); static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID(0x0002, 0x0003); + static const DicomTag DICOM_TAG_DEIDENTIFICATION_METHOD(0x0012, 0x0063); // DICOM tags used for fMRI (thanks to Will Ryder) static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105); diff -r f894be6e7cc1 -r 111e23bb4904 Core/DicomFormat/DicomValue.h --- a/Core/DicomFormat/DicomValue.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/DicomFormat/DicomValue.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/EnumerationDictionary.h --- a/Core/EnumerationDictionary.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/EnumerationDictionary.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,7 @@ #include "OrthancException.h" +#include "Toolbox.h" #include #include #include @@ -53,26 +54,23 @@ StringToEnumeration stringToEnumeration_; public: + void Clear() + { + enumerationToString_.clear(); + stringToEnumeration_.clear(); + } + void Add(Enumeration value, const std::string& str) { // Check if these values are free if (enumerationToString_.find(value) != enumerationToString_.end() || - stringToEnumeration_.find(str) != stringToEnumeration_.end()) + stringToEnumeration_.find(str) != stringToEnumeration_.end() || + Toolbox::IsInteger(str) /* Prevent the registration of a number */) { throw OrthancException(ErrorCode_BadRequest); } - // Prevent the registration of a number - try - { - boost::lexical_cast(str); - throw OrthancException(ErrorCode_BadRequest); - } - catch (boost::bad_lexical_cast) - { - // OK, the string is not a number - } - + // OK, the string is free and is not a number enumerationToString_[value] = str; stringToEnumeration_[str] = value; stringToEnumeration_[boost::lexical_cast(static_cast(value))] = value; diff -r f894be6e7cc1 -r 111e23bb4904 Core/Enumerations.cpp --- a/Core/Enumerations.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Enumerations.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -261,6 +261,197 @@ } + const char* EnumerationToString(Encoding encoding) + { + switch (encoding) + { + case Encoding_Ascii: + return "Ascii"; + + case Encoding_Utf8: + return "Utf8"; + + case Encoding_Latin1: + return "Latin1"; + + case Encoding_Latin2: + return "Latin2"; + + case Encoding_Latin3: + return "Latin3"; + + case Encoding_Latin4: + return "Latin4"; + + case Encoding_Latin5: + return "Latin5"; + + case Encoding_Cyrillic: + return "Cyrillic"; + + case Encoding_Windows1251: + return "Windows1251"; + + case Encoding_Arabic: + return "Arabic"; + + case Encoding_Greek: + return "Greek"; + + case Encoding_Hebrew: + return "Hebrew"; + + case Encoding_Thai: + return "Thai"; + + case Encoding_Japanese: + return "Japanese"; + + case Encoding_Chinese: + return "Chinese"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(PhotometricInterpretation photometric) + { + switch (photometric) + { + case PhotometricInterpretation_RGB: + return "RGB"; + + case PhotometricInterpretation_Monochrome1: + return "Monochrome1"; + + case PhotometricInterpretation_Monochrome2: + return "Monochrome2"; + + case PhotometricInterpretation_ARGB: + return "ARGB"; + + case PhotometricInterpretation_CMYK: + return "CMYK"; + + case PhotometricInterpretation_HSV: + return "HSV"; + + case PhotometricInterpretation_Palette: + return "Palette color"; + + case PhotometricInterpretation_YBRFull: + return "YBR full"; + + case PhotometricInterpretation_YBRFull422: + return "YBR full 422"; + + case PhotometricInterpretation_YBRPartial420: + return "YBR partial 420"; + + case PhotometricInterpretation_YBRPartial422: + return "YBR partial 422"; + + case PhotometricInterpretation_YBR_ICT: + return "YBR ICT"; + + case PhotometricInterpretation_YBR_RCT: + return "YBR RCT"; + + case PhotometricInterpretation_Unknown: + return "Unknown"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + Encoding StringToEncoding(const char* encoding) + { + std::string s(encoding); + Toolbox::ToUpperCase(s); + + if (s == "UTF8") + { + return Encoding_Utf8; + } + + if (s == "ASCII") + { + return Encoding_Ascii; + } + + if (s == "LATIN1") + { + return Encoding_Latin1; + } + + if (s == "LATIN2") + { + return Encoding_Latin2; + } + + if (s == "LATIN3") + { + return Encoding_Latin3; + } + + if (s == "LATIN4") + { + return Encoding_Latin4; + } + + if (s == "LATIN5") + { + return Encoding_Latin5; + } + + if (s == "CYRILLIC") + { + return Encoding_Cyrillic; + } + + if (s == "WINDOWS1251") + { + return Encoding_Windows1251; + } + + if (s == "ARABIC") + { + return Encoding_Arabic; + } + + if (s == "GREEK") + { + return Encoding_Greek; + } + + if (s == "HEBREW") + { + return Encoding_Hebrew; + } + + if (s == "THAI") + { + return Encoding_Thai; + } + + if (s == "JAPANESE") + { + return Encoding_Japanese; + } + + if (s == "CHINESE") + { + return Encoding_Chinese; + } + + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + ResourceType StringToResourceType(const char* type) { std::string s(type); @@ -323,4 +514,115 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } + + + bool GetDicomEncoding(Encoding& encoding, + const char* specificCharacterSet) + { + std::string s = specificCharacterSet; + Toolbox::ToUpperCase(s); + + // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ + // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java + if (s == "ISO_IR 6" || + s == "ISO_IR 192" || + s == "ISO 2022 IR 6") + { + encoding = Encoding_Utf8; + } + else if (s == "ISO_IR 100" || + s == "ISO 2022 IR 100") + { + encoding = Encoding_Latin1; + } + else if (s == "ISO_IR 101" || + s == "ISO 2022 IR 101") + { + encoding = Encoding_Latin2; + } + else if (s == "ISO_IR 109" || + s == "ISO 2022 IR 109") + { + encoding = Encoding_Latin3; + } + else if (s == "ISO_IR 110" || + s == "ISO 2022 IR 110") + { + encoding = Encoding_Latin4; + } + else if (s == "ISO_IR 148" || + s == "ISO 2022 IR 148") + { + encoding = Encoding_Latin5; + } + else if (s == "ISO_IR 144" || + s == "ISO 2022 IR 144") + { + encoding = Encoding_Cyrillic; + } + else if (s == "ISO_IR 127" || + s == "ISO 2022 IR 127") + { + encoding = Encoding_Arabic; + } + else if (s == "ISO_IR 126" || + s == "ISO 2022 IR 126") + { + encoding = Encoding_Greek; + } + else if (s == "ISO_IR 138" || + s == "ISO 2022 IR 138") + { + encoding = Encoding_Hebrew; + } + else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166") + { + encoding = Encoding_Thai; + } + else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13") + { + encoding = Encoding_Japanese; + } + else if (s == "GB18030") + { + encoding = Encoding_Chinese; + } + /* + else if (s == "ISO 2022 IR 149") + { + TODO + } + else if (s == "ISO 2022 IR 159") + { + TODO + } + else if (s == "ISO 2022 IR 87") + { + TODO + } + */ + else + { + return false; + } + + // The encoding was properly detected + return true; + } + + + const char* GetMimeType(FileContentType type) + { + switch (type) + { + case FileContentType_Dicom: + return "application/dicom"; + + case FileContentType_DicomAsJson: + return "application/json"; + + default: + return "application/octet-stream"; + } + } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/Enumerations.h --- a/Core/Enumerations.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Enumerations.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -57,6 +57,8 @@ ErrorCode_InexistentItem, ErrorCode_BadRequest, ErrorCode_NetworkProtocol, + ErrorCode_SystemCommand, + ErrorCode_Database, // Specific error codes ErrorCode_UriSyntax, @@ -71,7 +73,9 @@ ErrorCode_InexistentTag, ErrorCode_ReadOnly, ErrorCode_IncompatibleImageFormat, - ErrorCode_IncompatibleImageSize + ErrorCode_IncompatibleImageSize, + ErrorCode_SharedLibrary, + ErrorCode_Plugin }; /** @@ -228,10 +232,56 @@ }; + // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ enum Encoding { + Encoding_Ascii, Encoding_Utf8, - Encoding_Latin1 + Encoding_Latin1, + Encoding_Latin2, + Encoding_Latin3, + Encoding_Latin4, + Encoding_Latin5, // Turkish + Encoding_Cyrillic, + Encoding_Windows1251, // Windows-1251 (commonly used for Cyrillic) + Encoding_Arabic, + Encoding_Greek, + Encoding_Hebrew, + Encoding_Thai, // TIS 620-2533 + Encoding_Japanese, // JIS X 0201 (Shift JIS): Katakana + Encoding_Chinese // GB18030 - Chinese simplified + //Encoding_JapaneseKanji, // Multibyte - JIS X 0208: Kanji + //Encoding_JapaneseSupplementaryKanji, // Multibyte - JIS X 0212: Supplementary Kanji set + //Encoding_Korean, // Multibyte - KS X 1001: Hangul and Hanja + }; + + + // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.2/ + enum PhotometricInterpretation + { + PhotometricInterpretation_ARGB, // Retired + PhotometricInterpretation_CMYK, // Retired + PhotometricInterpretation_HSV, // Retired + PhotometricInterpretation_Monochrome1, + PhotometricInterpretation_Monochrome2, + PhotometricInterpretation_Palette, + PhotometricInterpretation_RGB, + PhotometricInterpretation_YBRFull, + PhotometricInterpretation_YBRFull422, + PhotometricInterpretation_YBRPartial420, + PhotometricInterpretation_YBRPartial422, + PhotometricInterpretation_YBR_ICT, + PhotometricInterpretation_YBR_RCT, + PhotometricInterpretation_Unknown + }; + + enum DicomModule + { + DicomModule_Patient, + DicomModule_Study, + DicomModule_Series, + DicomModule_Instance, + DicomModule_Image }; @@ -249,6 +299,9 @@ enum FileContentType { + // If you add a value below, insert it in "PluginStorageArea" in + // the file "Plugins/Engine/OrthancPlugins.cpp" + FileContentType_Unknown = 0, FileContentType_Dicom = 1, FileContentType_DicomAsJson = 2, @@ -274,9 +327,20 @@ const char* EnumerationToString(ImageFormat format); + const char* EnumerationToString(Encoding encoding); + + const char* EnumerationToString(PhotometricInterpretation photometric); + + Encoding StringToEncoding(const char* encoding); + ResourceType StringToResourceType(const char* type); ImageFormat StringToImageFormat(const char* format); unsigned int GetBytesPerPixel(PixelFormat format); + + bool GetDicomEncoding(Encoding& encoding, + const char* specificCharacterSet); + + const char* GetMimeType(FileContentType type); } diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/CompressedFileStorageAccessor.cpp --- a/Core/FileStorage/CompressedFileStorageAccessor.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -36,6 +36,10 @@ #include "../OrthancException.h" #include "FileStorageAccessor.h" #include "../HttpServer/BufferHttpSender.h" +#include "../Uuid.h" + +#include +#include namespace Orthanc { @@ -43,6 +47,8 @@ size_t size, FileContentType type) { + std::string uuid = Toolbox::GenerateUuid(); + std::string md5; if (storeMD5_) @@ -54,7 +60,7 @@ { case CompressionType_None: { - std::string uuid = storage_.Create(data, size); + GetStorageArea().Create(uuid.c_str(), data, size, type); return FileInfo(uuid, type, size, md5); } @@ -70,7 +76,15 @@ Toolbox::ComputeMD5(compressedMD5, compressed); } - std::string uuid = storage_.Create(compressed); + if (compressed.size() > 0) + { + GetStorageArea().Create(uuid.c_str(), &compressed[0], compressed.size(), type); + } + else + { + GetStorageArea().Create(uuid.c_str(), NULL, 0, type); + } + return FileInfo(uuid, type, size, md5, CompressionType_Zlib, compressed.size(), compressedMD5); } @@ -80,25 +94,47 @@ } } - CompressedFileStorageAccessor::CompressedFileStorageAccessor(FileStorage& storage) : - storage_(storage) + + CompressedFileStorageAccessor::CompressedFileStorageAccessor() : + storage_(NULL), + compressionType_(CompressionType_None) { - compressionType_ = CompressionType_None; + } + + + CompressedFileStorageAccessor::CompressedFileStorageAccessor(IStorageArea& storage) : + storage_(&storage), + compressionType_(CompressionType_None) + { } + + IStorageArea& CompressedFileStorageAccessor::GetStorageArea() + { + if (storage_ == NULL) + { + LOG(ERROR) << "No storage area is currently available"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + return *storage_; + } + + void CompressedFileStorageAccessor::Read(std::string& content, - const std::string& uuid) + const std::string& uuid, + FileContentType type) { switch (compressionType_) { case CompressionType_None: - storage_.ReadFile(content, uuid); + GetStorageArea().Read(content, uuid, type); break; case CompressionType_Zlib: { std::string compressed; - storage_.ReadFile(compressed, uuid); + GetStorageArea().Read(compressed, uuid, type); zlib_.Uncompress(content, compressed); break; } @@ -108,20 +144,21 @@ } } - HttpFileSender* CompressedFileStorageAccessor::ConstructHttpFileSender(const std::string& uuid) + HttpFileSender* CompressedFileStorageAccessor::ConstructHttpFileSender(const std::string& uuid, + FileContentType type) { switch (compressionType_) { case CompressionType_None: { - FileStorageAccessor uncompressedAccessor(storage_); - return uncompressedAccessor.ConstructHttpFileSender(uuid); + FileStorageAccessor uncompressedAccessor(GetStorageArea()); + return uncompressedAccessor.ConstructHttpFileSender(uuid, type); } case CompressionType_Zlib: { std::string compressed; - storage_.ReadFile(compressed, uuid); + GetStorageArea().Read(compressed, uuid, type); std::auto_ptr sender(new BufferHttpSender); zlib_.Uncompress(sender->GetBuffer(), compressed); @@ -133,4 +170,11 @@ throw OrthancException(ErrorCode_NotImplemented); } } + + + void CompressedFileStorageAccessor::Remove(const std::string& uuid, + FileContentType type) + { + GetStorageArea().Remove(uuid, type); + } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/CompressedFileStorageAccessor.h --- a/Core/FileStorage/CompressedFileStorageAccessor.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/FileStorage/CompressedFileStorageAccessor.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -32,8 +32,8 @@ #pragma once +#include "IStorageArea.h" #include "StorageAccessor.h" -#include "FileStorage.h" #include "../Compression/ZlibCompressor.h" namespace Orthanc @@ -41,7 +41,7 @@ class CompressedFileStorageAccessor : public StorageAccessor { private: - FileStorage& storage_; + IStorageArea* storage_; ZlibCompressor zlib_; CompressionType compressionType_; @@ -51,7 +51,21 @@ FileContentType type); public: - CompressedFileStorageAccessor(FileStorage& storage); + CompressedFileStorageAccessor(); + + CompressedFileStorageAccessor(IStorageArea& storage); + + void SetStorageArea(IStorageArea& storage) + { + storage_ = &storage; + } + + bool HasStorageArea() const + { + return storage_ != NULL; + } + + IStorageArea& GetStorageArea(); void SetCompressionForNextOperations(CompressionType compression) { @@ -64,8 +78,13 @@ } virtual void Read(std::string& content, - const std::string& uuid); + const std::string& uuid, + FileContentType type); - virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid); + virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid, + FileContentType type); + + virtual void Remove(const std::string& uuid, + FileContentType type); }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/FileInfo.h --- a/Core/FileStorage/FileInfo.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/FileStorage/FileInfo.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/FileStorage.cpp --- a/Core/FileStorage/FileStorage.cpp Wed Jun 25 15:34:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,309 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * 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 "FileStorage.h" - -// http://stackoverflow.com/questions/1576272/storing-large-number-of-files-in-file-system -// http://stackoverflow.com/questions/446358/storing-a-large-number-of-images - -#include "../OrthancException.h" -#include "../Toolbox.h" -#include "../Uuid.h" - -#include -#include - -static std::string ToString(const boost::filesystem::path& p) -{ -#if BOOST_HAS_FILESYSTEM_V3 == 1 - return p.filename().string(); -#else - return p.filename(); -#endif -} - - -namespace Orthanc -{ - boost::filesystem::path FileStorage::GetPath(const std::string& uuid) const - { - namespace fs = boost::filesystem; - - if (!Toolbox::IsUuid(uuid)) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - fs::path path = root_; - - path /= std::string(&uuid[0], &uuid[2]); - path /= std::string(&uuid[2], &uuid[4]); - path /= uuid; - -#if BOOST_HAS_FILESYSTEM_V3 == 1 - path.make_preferred(); -#endif - - return path; - } - - FileStorage::FileStorage(std::string root) - { - //root_ = boost::filesystem::absolute(root).string(); - root_ = root; - - Toolbox::CreateDirectory(root); - } - - std::string FileStorage::CreateFileWithoutCompression(const void* content, size_t size) - { - std::string uuid; - boost::filesystem::path path; - - for (;;) - { - uuid = Toolbox::GenerateUuid(); - path = GetPath(uuid); - - if (!boost::filesystem::exists(path)) - { - // OK, this is indeed a new file - break; - } - - // Extremely improbable case: This Uuid has already been created - // in the past. Try again. - } - - if (boost::filesystem::exists(path.parent_path())) - { - if (!boost::filesystem::is_directory(path.parent_path())) - { - throw OrthancException("The subdirectory to be created is already occupied by a regular file"); - } - } - else - { - if (!boost::filesystem::create_directories(path.parent_path())) - { - throw OrthancException("Unable to create a subdirectory in the file storage"); - } - } - - boost::filesystem::ofstream f; - f.open(path, std::ofstream::out | std::ios::binary); - if (!f.good()) - { - throw OrthancException("Unable to create a new file in the file storage"); - } - - if (size != 0) - { - f.write(static_cast(content), size); - if (!f.good()) - { - f.close(); - throw OrthancException("Unable to write to the new file in the file storage"); - } - } - - f.close(); - - return uuid; - } - - - std::string FileStorage::Create(const void* content, size_t size) - { - if (!HasBufferCompressor() || size == 0) - { - return CreateFileWithoutCompression(content, size); - } - else - { - std::string compressed; - compressor_->Compress(compressed, content, size); - assert(compressed.size() > 0); - return CreateFileWithoutCompression(&compressed[0], compressed.size()); - } - } - - - std::string FileStorage::Create(const std::vector& content) - { - if (content.size() == 0) - return Create(NULL, 0); - else - return Create(&content[0], content.size()); - } - - std::string FileStorage::Create(const std::string& content) - { - if (content.size() == 0) - return Create(NULL, 0); - else - return Create(&content[0], content.size()); - } - - void FileStorage::ReadFile(std::string& content, - const std::string& uuid) const - { - content.clear(); - - if (HasBufferCompressor()) - { - std::string compressed; - Toolbox::ReadFile(compressed, ToString(GetPath(uuid))); - - if (compressed.size() != 0) - { - compressor_->Uncompress(content, compressed); - } - } - else - { - Toolbox::ReadFile(content, GetPath(uuid).string()); - } - } - - - uintmax_t FileStorage::GetCompressedSize(const std::string& uuid) const - { - boost::filesystem::path path = GetPath(uuid); - return boost::filesystem::file_size(path); - } - - - - void FileStorage::ListAllFiles(std::set& result) const - { - namespace fs = boost::filesystem; - - result.clear(); - - if (fs::exists(root_) && fs::is_directory(root_)) - { - for (fs::recursive_directory_iterator current(root_), end; current != end ; ++current) - { - if (fs::is_regular_file(current->status())) - { - try - { - fs::path d = current->path(); - std::string uuid = ToString(d); - if (Toolbox::IsUuid(uuid)) - { - fs::path p0 = d.parent_path().parent_path().parent_path(); - std::string p1 = ToString(d.parent_path().parent_path()); - std::string p2 = ToString(d.parent_path()); - if (p1.length() == 2 && - p2.length() == 2 && - p1 == uuid.substr(0, 2) && - p2 == uuid.substr(2, 2) && - p0 == root_) - { - result.insert(uuid); - } - } - } - catch (fs::filesystem_error) - { - } - } - } - } - } - - - void FileStorage::Clear() - { - namespace fs = boost::filesystem; - typedef std::set List; - - List result; - ListAllFiles(result); - - for (List::const_iterator it = result.begin(); it != result.end(); ++it) - { - Remove(*it); - } - } - - - void FileStorage::Remove(const std::string& uuid) - { - LOG(INFO) << "Deleting file " << uuid; - namespace fs = boost::filesystem; - - fs::path p = GetPath(uuid); - - try - { - fs::remove(p); - } - catch (...) - { - // Ignore the error - } - - // Remove the two parent directories, ignoring the error code if - // these directories are not empty - - try - { -#if BOOST_HAS_FILESYSTEM_V3 == 1 - boost::system::error_code err; - fs::remove(p.parent_path(), err); - fs::remove(p.parent_path().parent_path(), err); -#else - fs::remove(p.parent_path()); - fs::remove(p.parent_path().parent_path()); -#endif - } - catch (...) - { - // Ignore the error - } - } - - - uintmax_t FileStorage::GetCapacity() const - { - return boost::filesystem::space(root_).capacity; - } - - uintmax_t FileStorage::GetAvailableSpace() const - { - return boost::filesystem::space(root_).available; - } -} diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/FileStorage.h --- a/Core/FileStorage/FileStorage.h Wed Jun 25 15:34:40 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * 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 - -#include "../Compression/BufferCompressor.h" - -namespace Orthanc -{ - class FileStorage : public boost::noncopyable - { - // TODO REMOVE THIS - friend class FilesystemHttpSender; - friend class FileStorageAccessor; - - private: - std::auto_ptr compressor_; - - boost::filesystem::path root_; - - boost::filesystem::path GetPath(const std::string& uuid) const; - - std::string CreateFileWithoutCompression(const void* content, size_t size); - - public: - FileStorage(std::string root); - - void SetBufferCompressor(BufferCompressor* compressor) // Takes the ownership - { - compressor_.reset(compressor); - } - - bool HasBufferCompressor() const - { - return compressor_.get() != NULL; - } - - std::string Create(const void* content, size_t size); - - std::string Create(const std::vector& content); - - std::string Create(const std::string& content); - - void ReadFile(std::string& content, - const std::string& uuid) const; - - void ListAllFiles(std::set& result) const; - - uintmax_t GetCompressedSize(const std::string& uuid) const; - - void Clear(); - - void Remove(const std::string& uuid); - - uintmax_t GetCapacity() const; - - uintmax_t GetAvailableSpace() const; - - std::string GetPath() const - { - return root_.string(); - } - }; -} diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/FileStorageAccessor.cpp --- a/Core/FileStorage/FileStorageAccessor.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/FileStorage/FileStorageAccessor.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,12 @@ #include "../PrecompiledHeaders.h" #include "FileStorageAccessor.h" +#include "../HttpServer/BufferHttpSender.h" +#include "../Uuid.h" + +#include +#include + namespace Orthanc { FileInfo FileStorageAccessor::WriteInternal(const void* data, @@ -46,6 +52,21 @@ Toolbox::ComputeMD5(md5, data, size); } - return FileInfo(storage_.Create(data, size), type, size, md5); + std::string uuid = Toolbox::GenerateUuid(); + storage_.Create(uuid.c_str(), data, size, type); + + return FileInfo(uuid, type, size, md5); } + + + HttpFileSender* FileStorageAccessor::ConstructHttpFileSender(const std::string& uuid, + FileContentType type) + { + std::auto_ptr sender(new BufferHttpSender); + + storage_.Read(sender->GetBuffer(), uuid, type); + + return sender.release(); + } + } diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/FileStorageAccessor.h --- a/Core/FileStorage/FileStorageAccessor.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/FileStorage/FileStorageAccessor.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,15 +33,14 @@ #pragma once #include "StorageAccessor.h" -#include "FileStorage.h" -#include "../HttpServer/FilesystemHttpSender.h" +#include "IStorageArea.h" namespace Orthanc { class FileStorageAccessor : public StorageAccessor { private: - FileStorage& storage_; + IStorageArea& storage_; protected: virtual FileInfo WriteInternal(const void* data, @@ -49,19 +48,24 @@ FileContentType type); public: - FileStorageAccessor(FileStorage& storage) : storage_(storage) + FileStorageAccessor(IStorageArea& storage) : storage_(storage) { } virtual void Read(std::string& content, - const std::string& uuid) + const std::string& uuid, + FileContentType type) { - storage_.ReadFile(content, uuid); + storage_.Read(content, uuid, type); } - virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid) + virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid, + FileContentType type); + + virtual void Remove(const std::string& uuid, + FileContentType type) { - return new FilesystemHttpSender(storage_.GetPath(uuid)); + storage_.Remove(uuid, type); } }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/FilesystemStorage.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileStorage/FilesystemStorage.cpp Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,259 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "FilesystemStorage.h" + +// http://stackoverflow.com/questions/1576272/storing-large-number-of-files-in-file-system +// http://stackoverflow.com/questions/446358/storing-a-large-number-of-images + +#include "../OrthancException.h" +#include "../Toolbox.h" +#include "../Uuid.h" + +#include +#include + +static std::string ToString(const boost::filesystem::path& p) +{ +#if BOOST_HAS_FILESYSTEM_V3 == 1 + return p.filename().string(); +#else + return p.filename(); +#endif +} + + +namespace Orthanc +{ + boost::filesystem::path FilesystemStorage::GetPath(const std::string& uuid) const + { + namespace fs = boost::filesystem; + + if (!Toolbox::IsUuid(uuid)) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + fs::path path = root_; + + path /= std::string(&uuid[0], &uuid[2]); + path /= std::string(&uuid[2], &uuid[4]); + path /= uuid; + +#if BOOST_HAS_FILESYSTEM_V3 == 1 + path.make_preferred(); +#endif + + return path; + } + + FilesystemStorage::FilesystemStorage(std::string root) + { + //root_ = boost::filesystem::absolute(root).string(); + root_ = root; + + Toolbox::CreateDirectory(root); + } + + void FilesystemStorage::Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType /*type*/) + { + boost::filesystem::path path; + + path = GetPath(uuid); + + if (boost::filesystem::exists(path)) + { + // Extremely unlikely case: This Uuid has already been created + // in the past. + throw OrthancException(ErrorCode_InternalError); + } + + if (boost::filesystem::exists(path.parent_path())) + { + if (!boost::filesystem::is_directory(path.parent_path())) + { + throw OrthancException("The subdirectory to be created is already occupied by a regular file"); + } + } + else + { + if (!boost::filesystem::create_directories(path.parent_path())) + { + throw OrthancException("Unable to create a subdirectory in the file storage"); + } + } + + boost::filesystem::ofstream f; + f.open(path, std::ofstream::out | std::ios::binary); + if (!f.good()) + { + throw OrthancException("Unable to create a new file in the file storage"); + } + + if (size != 0) + { + f.write(static_cast(content), size); + if (!f.good()) + { + f.close(); + throw OrthancException("Unable to write to the new file in the file storage"); + } + } + + f.close(); + } + + + void FilesystemStorage::Read(std::string& content, + const std::string& uuid, + FileContentType /*type*/) + { + content.clear(); + Toolbox::ReadFile(content, GetPath(uuid).string()); + } + + + uintmax_t FilesystemStorage::GetSize(const std::string& uuid) const + { + boost::filesystem::path path = GetPath(uuid); + return boost::filesystem::file_size(path); + } + + + + void FilesystemStorage::ListAllFiles(std::set& result) const + { + namespace fs = boost::filesystem; + + result.clear(); + + if (fs::exists(root_) && fs::is_directory(root_)) + { + for (fs::recursive_directory_iterator current(root_), end; current != end ; ++current) + { + if (fs::is_regular_file(current->status())) + { + try + { + fs::path d = current->path(); + std::string uuid = ToString(d); + if (Toolbox::IsUuid(uuid)) + { + fs::path p0 = d.parent_path().parent_path().parent_path(); + std::string p1 = ToString(d.parent_path().parent_path()); + std::string p2 = ToString(d.parent_path()); + if (p1.length() == 2 && + p2.length() == 2 && + p1 == uuid.substr(0, 2) && + p2 == uuid.substr(2, 2) && + p0 == root_) + { + result.insert(uuid); + } + } + } + catch (fs::filesystem_error) + { + } + } + } + } + } + + + void FilesystemStorage::Clear() + { + namespace fs = boost::filesystem; + typedef std::set List; + + List result; + ListAllFiles(result); + + for (List::const_iterator it = result.begin(); it != result.end(); ++it) + { + Remove(*it, FileContentType_Unknown /*ignored in this class*/); + } + } + + + void FilesystemStorage::Remove(const std::string& uuid, + FileContentType /*type*/) + { + LOG(INFO) << "Deleting file " << uuid; + namespace fs = boost::filesystem; + + fs::path p = GetPath(uuid); + + try + { + fs::remove(p); + } + catch (...) + { + // Ignore the error + } + + // Remove the two parent directories, ignoring the error code if + // these directories are not empty + + try + { +#if BOOST_HAS_FILESYSTEM_V3 == 1 + boost::system::error_code err; + fs::remove(p.parent_path(), err); + fs::remove(p.parent_path().parent_path(), err); +#else + fs::remove(p.parent_path()); + fs::remove(p.parent_path().parent_path()); +#endif + } + catch (...) + { + // Ignore the error + } + } + + + uintmax_t FilesystemStorage::GetCapacity() const + { + return boost::filesystem::space(root_).capacity; + } + + uintmax_t FilesystemStorage::GetAvailableSpace() const + { + return boost::filesystem::space(root_).available; + } +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/FilesystemStorage.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileStorage/FilesystemStorage.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,78 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 FilesystemStorage : public IStorageArea + { + // TODO REMOVE THIS + friend class FilesystemHttpSender; + friend class FileStorageAccessor; + + private: + boost::filesystem::path root_; + + boost::filesystem::path GetPath(const std::string& uuid) const; + + public: + FilesystemStorage(std::string root); + + 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); + + void ListAllFiles(std::set& result) const; + + uintmax_t GetSize(const std::string& uuid) const; + + void Clear(); + + uintmax_t GetCapacity() const; + + uintmax_t GetAvailableSpace() const; + }; +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/IStorageArea.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileStorage/IStorageArea.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,60 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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" + +#include + +namespace Orthanc +{ + class IStorageArea : public boost::noncopyable + { + public: + virtual ~IStorageArea() + { + } + + virtual void Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType type) = 0; + + virtual void Read(std::string& content, + const std::string& uuid, + FileContentType type) = 0; + + virtual void Remove(const std::string& uuid, + FileContentType type) = 0; + }; +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/StorageAccessor.cpp --- a/Core/FileStorage/StorageAccessor.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/FileStorage/StorageAccessor.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/FileStorage/StorageAccessor.h --- a/Core/FileStorage/StorageAccessor.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/FileStorage/StorageAccessor.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -85,8 +85,13 @@ FileContentType type); virtual void Read(std::string& content, - const std::string& uuid) = 0; + const std::string& uuid, + FileContentType type) = 0; - virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid) = 0; + virtual void Remove(const std::string& uuid, + FileContentType type) = 0; + + virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid, + FileContentType type) = 0; }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpClient.cpp --- a/Core/HttpClient.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpClient.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -109,6 +109,7 @@ method_ = HttpMethod_Get; lastStatus_ = HttpStatus_200_Ok; isVerbose_ = false; + timeout_ = 0; } @@ -161,13 +162,39 @@ answer.clear(); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer)); + + // Reset the parameters from previous calls to Apply() CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, NULL)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 0L)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 0L)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, NULL)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, NULL)); + + // Set timeouts + if (timeout_ <= 0) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, 10)); /* default: 10 seconds */ + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, 10)); /* default: 10 seconds */ + } + else + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, timeout_)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, timeout_)); + } if (credentials_.size() != 0) { CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, credentials_.c_str())); } + if (proxy_.size() != 0) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, proxy_.c_str())); + } + switch (method_) { case HttpMethod_Get: @@ -177,7 +204,31 @@ case HttpMethod_Post: CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->postHeaders_)); + break; + case HttpMethod_Delete: + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE")); + break; + + case HttpMethod_Put: + // http://stackoverflow.com/a/7570281/881731: Don't use + // CURLOPT_PUT if there is a body + + // CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L)); + + curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "PUT"); /* !!! */ + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->postHeaders_)); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + + if (method_ == HttpMethod_Post || + method_ == HttpMethod_Put) + { if (postData_.size() > 0) { CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, postData_.c_str())); @@ -188,21 +239,8 @@ CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0)); } - - break; - - case HttpMethod_Delete: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE")); - break; + } - case HttpMethod_Put: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L)); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } // Do the actual request CheckCode(curl_easy_perform(pimpl_->curl_)); diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpClient.h --- a/Core/HttpClient.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpClient.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -52,6 +52,8 @@ HttpStatus lastStatus_; std::string postData_; bool isVerbose_; + long timeout_; + std::string proxy_; void Setup(); @@ -89,6 +91,21 @@ return method_; } + void SetTimeout(long seconds) + { + timeout_ = seconds; + } + + long GetTimeout() const + { + return timeout_; + } + + void SetPostData(const std::string& data) + { + postData_ = data; + } + std::string& AccessPostData() { return postData_; @@ -118,6 +135,11 @@ void SetCredentials(const char* username, const char* password); + void SetProxy(const std::string& proxy) + { + proxy_ = proxy; + } + static void GlobalInitialize(); static void GlobalFinalize(); diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/BufferHttpSender.h --- a/Core/HttpServer/BufferHttpSender.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/BufferHttpSender.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -49,7 +49,9 @@ virtual bool SendData(HttpOutput& output) { if (buffer_.size()) - output.Send(&buffer_[0], buffer_.size()); + { + output.SendBody(&buffer_[0], buffer_.size()); + } return true; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/EmbeddedResourceHttpHandler.cpp --- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -51,24 +51,24 @@ } - bool EmbeddedResourceHttpHandler::IsServedUri(const UriComponents& uri) - { - return Toolbox::IsChildUri(baseUri_, uri); - } - - - void EmbeddedResourceHttpHandler::Handle( + bool EmbeddedResourceHttpHandler::Handle( HttpOutput& output, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& arguments, + const GetArguments& arguments, const std::string&) { + if (!Toolbox::IsChildUri(baseUri_, uri)) + { + // This URI is not served by this handler + return false; + } + if (method != HttpMethod_Get) { - output.SendMethodNotAllowedError("GET"); - return; + output.SendMethodNotAllowed("GET"); + return true; } std::string resourcePath = Toolbox::FlattenUri(uri, baseUri_.size()); @@ -78,12 +78,16 @@ { const void* buffer = EmbeddedResources::GetDirectoryResourceBuffer(resourceId_, resourcePath.c_str()); size_t size = EmbeddedResources::GetDirectoryResourceSize(resourceId_, resourcePath.c_str()); - output.AnswerBufferWithContentType(buffer, size, contentType); + + output.SetContentType(contentType.c_str()); + output.SendBody(buffer, size); } catch (OrthancException&) { LOG(WARNING) << "Unable to find HTTP resource: " << resourcePath; - output.SendHeader(HttpStatus_404_NotFound); + output.SendStatus(HttpStatus_404_NotFound); } + + return true; } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/EmbeddedResourceHttpHandler.h --- a/Core/HttpServer/EmbeddedResourceHttpHandler.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -50,14 +50,12 @@ const std::string& baseUri, EmbeddedResources::DirectoryResourceId resourceId); - virtual bool IsServedUri(const UriComponents& uri); - - virtual void Handle( + virtual bool Handle( HttpOutput& output, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& arguments, + const GetArguments& arguments, const std::string&); }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/FilesystemHttpHandler.cpp --- a/Core/HttpServer/FilesystemHttpHandler.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/FilesystemHttpHandler.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -55,16 +55,18 @@ { namespace fs = boost::filesystem; - output.SendOkHeader("text/html", false, 0, NULL); - output.SendString(""); - output.SendString(" "); - output.SendString("

Subdirectories

"); - output.SendString("
    "); + output.SetContentType("text/html"); + + std::string s; + s += ""; + s += " "; + s += "

    Subdirectories

    "; + s += "
      "; if (uri.size() > 0) { std::string h = Toolbox::FlattenUri(uri) + "/.."; - output.SendString("
    • ..
    • "); + s += "
    • ..
    • "; } fs::directory_iterator end; @@ -78,12 +80,12 @@ std::string h = Toolbox::FlattenUri(uri) + "/" + f; if (fs::is_directory(it->status())) - output.SendString("
    • " + f + "
    • "); + s += "
    • " + f + "
    • "; } - output.SendString("
    "); - output.SendString("

    Files

    "); - output.SendString("
      "); + s += "
    "; + s += "

    Files

    "; + s += "
      "; for (fs::directory_iterator it(p) ; it != end; ++it) { @@ -95,12 +97,14 @@ std::string h = Toolbox::FlattenUri(uri) + "/" + f; if (fs::is_regular_file(it->status())) - output.SendString("
    • " + f + "
    • "); + s += "
    • " + f + "
    • "; } - output.SendString("
    "); - output.SendString(" "); - output.SendString(""); + s += "
"; + s += " "; + s += ""; + + output.SendBody(s); } @@ -120,24 +124,24 @@ } - bool FilesystemHttpHandler::IsServedUri(const UriComponents& uri) - { - return Toolbox::IsChildUri(pimpl_->baseUri_, uri); - } - - - void FilesystemHttpHandler::Handle( + bool FilesystemHttpHandler::Handle( HttpOutput& output, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& arguments, + const GetArguments& arguments, const std::string&) { + if (!Toolbox::IsChildUri(pimpl_->baseUri_, uri)) + { + // This URI is not served by this handler + return false; + } + if (method != HttpMethod_Get) { - output.SendMethodNotAllowedError("GET"); - return; + output.SendMethodNotAllowed("GET"); + return true; } namespace fs = boost::filesystem; @@ -162,7 +166,9 @@ } else { - output.SendHeader(HttpStatus_404_NotFound); + output.SendStatus(HttpStatus_404_NotFound); } + + return true; } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/FilesystemHttpHandler.h --- a/Core/HttpServer/FilesystemHttpHandler.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/FilesystemHttpHandler.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -52,14 +52,12 @@ FilesystemHttpHandler(const std::string& baseUri, const std::string& root); - virtual bool IsServedUri(const UriComponents& uri); - - virtual void Handle( + virtual bool Handle( HttpOutput& output, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& arguments, + const GetArguments& arguments, const std::string&); bool IsListDirectoryContent() const diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/FilesystemHttpSender.cpp --- a/Core/HttpServer/FilesystemHttpSender.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/FilesystemHttpSender.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -73,7 +73,7 @@ } else { - output.Send(&buffer[0], nbytes); + output.SendBody(&buffer[0], nbytes); } } @@ -94,7 +94,7 @@ Setup(); } - FilesystemHttpSender::FilesystemHttpSender(const FileStorage& storage, + FilesystemHttpSender::FilesystemHttpSender(const FilesystemStorage& storage, const std::string& uuid) { path_ = storage.GetPath(uuid).string(); diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/FilesystemHttpSender.h --- a/Core/HttpServer/FilesystemHttpSender.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/FilesystemHttpSender.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -32,7 +32,7 @@ #pragma once #include "HttpFileSender.h" -#include "../FileStorage/FileStorage.h" +#include "../FileStorage/FilesystemStorage.h" namespace Orthanc { @@ -53,7 +53,7 @@ FilesystemHttpSender(const boost::filesystem::path& path); - FilesystemHttpSender(const FileStorage& storage, + FilesystemHttpSender(const FilesystemStorage& storage, const std::string& uuid); }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/HttpFileSender.cpp --- a/Core/HttpServer/HttpFileSender.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/HttpFileSender.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,13 +33,25 @@ #include "../PrecompiledHeaders.h" #include "HttpFileSender.h" +#include "../OrthancException.h" + #include namespace Orthanc { void HttpFileSender::SendHeader(HttpOutput& output) { - output.SendOkHeader(contentType_.c_str(), true, GetFileSize(), downloadFilename_.c_str()); + if (contentType_.size() > 0) + { + output.SetContentType(contentType_.c_str()); + } + + if (downloadFilename_.size() > 0) + { + output.SetContentFilename(downloadFilename_.c_str()); + } + + output.SetContentLength(GetFileSize()); } void HttpFileSender::Send(HttpOutput& output) @@ -48,7 +60,8 @@ if (!SendData(output)) { - output.SendHeader(HttpStatus_500_InternalServerError); + throw OrthancException(ErrorCode_InternalError); + //output.SendHeader(HttpStatus_500_InternalServerError); } } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/HttpFileSender.h --- a/Core/HttpServer/HttpFileSender.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/HttpFileSender.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/HttpHandler.cpp --- a/Core/HttpServer/HttpHandler.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/HttpHandler.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,10 +34,12 @@ #include "HttpHandler.h" #include +#include + namespace Orthanc { - static void SplitGETNameValue(HttpHandler::Arguments& result, + static void SplitGETNameValue(HttpHandler::GetArguments& result, const char* start, const char* end) { @@ -58,11 +60,12 @@ Toolbox::UrlDecode(name); Toolbox::UrlDecode(value); - result.insert(std::make_pair(name, value)); + result.push_back(std::make_pair(name, value)); } - void HttpHandler::ParseGetQuery(HttpHandler::Arguments& result, const char* query) + void HttpHandler::ParseGetArguments(HttpHandler::GetArguments& result, + const char* query) { const char* pos = query; @@ -84,7 +87,25 @@ } + void HttpHandler::ParseGetQuery(UriComponents& uri, + HttpHandler::GetArguments& getArguments, + const char* query) + { + const char *questionMark = ::strchr(query, '?'); + if (questionMark == NULL) + { + // No question mark in the string + Toolbox::SplitUriComponents(uri, query); + getArguments.clear(); + } + else + { + Toolbox::SplitUriComponents(uri, std::string(query, questionMark)); + HttpHandler::ParseGetArguments(getArguments, questionMark + 1); + } + } + std::string HttpHandler::GetArgument(const Arguments& getArguments, const std::string& name, const std::string& defaultValue) @@ -101,6 +122,22 @@ } + std::string HttpHandler::GetArgument(const GetArguments& getArguments, + const std::string& name, + const std::string& defaultValue) + { + for (size_t i = 0; i < getArguments.size(); i++) + { + if (getArguments[i].first == name) + { + return getArguments[i].second; + } + } + + return defaultValue; + } + + void HttpHandler::ParseCookies(HttpHandler::Arguments& result, const HttpHandler::Arguments& httpHeaders) @@ -139,4 +176,16 @@ } } } + + + void HttpHandler::CompileGetArguments(Arguments& compiled, + const GetArguments& source) + { + compiled.clear(); + + for (size_t i = 0; i < source.size(); i++) + { + compiled[source[i].first] = source[i].second; + } + } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/HttpHandler.h --- a/Core/HttpServer/HttpHandler.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/HttpHandler.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -44,29 +44,39 @@ class HttpHandler { public: - typedef std::map Arguments; + typedef std::map Arguments; + typedef std::vector< std::pair > GetArguments; virtual ~HttpHandler() { } - virtual bool IsServedUri(const UriComponents& uri) = 0; - - virtual void Handle(HttpOutput& output, + virtual bool Handle(HttpOutput& output, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& getArguments, + const GetArguments& getArguments, const std::string& postData) = 0; - static void ParseGetQuery(HttpHandler::Arguments& result, + static void ParseGetArguments(HttpHandler::GetArguments& result, + const char* query); + + static void ParseGetQuery(UriComponents& uri, + HttpHandler::GetArguments& getArguments, const char* query); static std::string GetArgument(const Arguments& getArguments, const std::string& name, const std::string& defaultValue); + static std::string GetArgument(const GetArguments& getArguments, + const std::string& name, + const std::string& defaultValue); + static void ParseCookies(HttpHandler::Arguments& result, const HttpHandler::Arguments& httpHeaders); + + static void CompileGetArguments(Arguments& compiled, + const GetArguments& source); }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/HttpOutput.cpp --- a/Core/HttpServer/HttpOutput.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/HttpOutput.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -36,163 +36,238 @@ #include #include #include +#include #include #include "../OrthancException.h" #include "../Toolbox.h" namespace Orthanc { - void HttpOutput::SendString(const std::string& s) + HttpOutput::StateMachine::StateMachine(IHttpOutputStream& stream, + bool isKeepAlive) : + stream_(stream), + state_(State_WritingHeader), + status_(HttpStatus_200_Ok), + hasContentLength_(false), + contentPosition_(0), + keepAlive_(isKeepAlive) { - if (s.size() > 0) - { - Send(&s[0], s.size()); - } } - void HttpOutput::PrepareOkHeader(Header& header, - const char* contentType, - bool hasContentLength, - uint64_t contentLength, - const char* contentFilename) + HttpOutput::StateMachine::~StateMachine() { - header.clear(); - - if (contentType && contentType[0] != '\0') - { - header.push_back(std::make_pair("Content-Type", std::string(contentType))); - } - - if (hasContentLength) - { - header.push_back(std::make_pair("Content-Length", boost::lexical_cast(contentLength))); - } - - if (contentFilename && contentFilename[0] != '\0') + if (state_ != State_Done) { - std::string attachment = "attachment; filename=\"" + std::string(contentFilename) + "\""; - header.push_back(std::make_pair("Content-Disposition", attachment)); - } - } - - void HttpOutput::SendOkHeader(const char* contentType, - bool hasContentLength, - uint64_t contentLength, - const char* contentFilename) - { - Header header; - PrepareOkHeader(header, contentType, hasContentLength, contentLength, contentFilename); - SendOkHeader(header); - } - - void HttpOutput::SendOkHeader(const Header& header) - { - std::string s = "HTTP/1.1 200 OK\r\n"; - - for (Header::const_iterator - it = header.begin(); it != header.end(); ++it) - { - s += it->first + ": " + it->second + "\r\n"; + //asm volatile ("int3;"); + //LOG(ERROR) << "This HTTP answer does not contain any body"; } - s += "\r\n"; - - Send(&s[0], s.size()); - } - - - void HttpOutput::SendMethodNotAllowedError(const std::string& allowed) - { - std::string s = - "HTTP/1.1 405 " + std::string(EnumerationToString(HttpStatus_405_MethodNotAllowed)) + - "\r\nAllow: " + allowed + - "\r\n\r\n"; - Send(&s[0], s.size()); - } - - - void HttpOutput::SendHeader(HttpStatus status) - { - if (status == HttpStatus_200_Ok || - status == HttpStatus_405_MethodNotAllowed) + if (hasContentLength_ && contentPosition_ != contentLength_) { - throw OrthancException("Please use the dedicated methods to this HTTP status code in HttpOutput"); - } - - SendHeaderInternal(status); - } - - - void HttpOutput::SendHeaderInternal(HttpStatus status) - { - std::string s = "HTTP/1.1 " + - boost::lexical_cast(status) + - " " + std::string(EnumerationToString(status)) + - "\r\n\r\n"; - Send(&s[0], s.size()); - } - - - void HttpOutput::AnswerBufferWithContentType(const std::string& buffer, - const std::string& contentType) - { - SendOkHeader(contentType.c_str(), true, buffer.size(), NULL); - SendString(buffer); - } - - - void HttpOutput::PrepareCookies(Header& header, - const HttpHandler::Arguments& cookies) - { - for (HttpHandler::Arguments::const_iterator it = cookies.begin(); - it != cookies.end(); ++it) - { - header.push_back(std::make_pair("Set-Cookie", it->first + "=" + it->second)); + LOG(ERROR) << "This HTTP answer has not sent the proper number of bytes in its body"; } } - void HttpOutput::AnswerBufferWithContentType(const std::string& buffer, - const std::string& contentType, - const HttpHandler::Arguments& cookies) + void HttpOutput::StateMachine::SetHttpStatus(HttpStatus status) + { + if (state_ != State_WritingHeader) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + status_ = status; + } + + + void HttpOutput::StateMachine::SetContentLength(uint64_t length) { - Header header; - PrepareOkHeader(header, contentType.c_str(), true, buffer.size(), NULL); - PrepareCookies(header, cookies); - SendOkHeader(header); - SendString(buffer); + if (state_ != State_WritingHeader) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + hasContentLength_ = true; + contentLength_ = length; + } + + void HttpOutput::StateMachine::SetContentType(const char* contentType) + { + AddHeader("Content-Type", contentType); + } + + void HttpOutput::StateMachine::SetContentFilename(const char* filename) + { + // TODO Escape double quotes + AddHeader("Content-Disposition", "filename=\"" + std::string(filename) + "\""); + } + + void HttpOutput::StateMachine::SetCookie(const std::string& cookie, + const std::string& value) + { + if (state_ != State_WritingHeader) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + // TODO Escape "=" characters + AddHeader("Set-Cookie", cookie + "=" + value); } - void HttpOutput::AnswerBufferWithContentType(const void* buffer, - size_t size, - const std::string& contentType) + void HttpOutput::StateMachine::AddHeader(const std::string& header, + const std::string& value) + { + if (state_ != State_WritingHeader) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + headers_.push_back(header + ": " + value + "\r\n"); + } + + void HttpOutput::StateMachine::ClearHeaders() + { + if (state_ != State_WritingHeader) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + headers_.clear(); + } + + void HttpOutput::StateMachine::SendBody(const void* buffer, size_t length) { - SendOkHeader(contentType.c_str(), true, size, NULL); - Send(buffer, size); + if (state_ == State_Done) + { + if (length == 0) + { + return; + } + else + { + LOG(ERROR) << "Because of keep-alive connections, the entire body must be sent at once or Content-Length must be given"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + if (state_ == State_WritingHeader) + { + // Send the HTTP header before writing the body + + stream_.OnHttpStatusReceived(status_); + + std::string s = "HTTP/1.1 " + + boost::lexical_cast(status_) + + " " + std::string(EnumerationToString(status_)) + + "\r\n"; + + if (keepAlive_) + { + s += "Connection: keep-alive\r\n"; + } + + for (std::list::const_iterator + it = headers_.begin(); it != headers_.end(); ++it) + { + s += *it; + } + + if (status_ != HttpStatus_200_Ok) + { + hasContentLength_ = false; + } + + uint64_t contentLength = (hasContentLength_ ? contentLength_ : length); + s += "Content-Length: " + boost::lexical_cast(contentLength) + "\r\n\r\n"; + + stream_.Send(true, s.c_str(), s.size()); + state_ = State_WritingBody; + } + + if (hasContentLength_ && + contentPosition_ + length > contentLength_) + { + LOG(ERROR) << "The body size exceeds what was declared with SetContentSize()"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + if (length > 0) + { + stream_.Send(false, buffer, length); + contentPosition_ += length; + } + + if (!hasContentLength_ || + contentPosition_ == contentLength_) + { + state_ = State_Done; + } } - void HttpOutput::AnswerBufferWithContentType(const void* buffer, - size_t size, - const std::string& contentType, - const HttpHandler::Arguments& cookies) + void HttpOutput::SendMethodNotAllowed(const std::string& allowed) { - Header header; - PrepareOkHeader(header, contentType.c_str(), true, size, NULL); - PrepareCookies(header, cookies); - SendOkHeader(header); - Send(buffer, size); + stateMachine_.ClearHeaders(); + stateMachine_.SetHttpStatus(HttpStatus_405_MethodNotAllowed); + stateMachine_.AddHeader("Allow", allowed); + stateMachine_.SendBody(NULL, 0); } + void HttpOutput::SendStatus(HttpStatus status) + { + if (status == HttpStatus_200_Ok || + status == HttpStatus_301_MovedPermanently || + status == HttpStatus_401_Unauthorized || + status == HttpStatus_405_MethodNotAllowed) + { + LOG(ERROR) << "Please use the dedicated methods to this HTTP status code in HttpOutput"; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + stateMachine_.ClearHeaders(); + stateMachine_.SetHttpStatus(status); + stateMachine_.SendBody(NULL, 0); + } + void HttpOutput::Redirect(const std::string& path) { - std::string s = - "HTTP/1.1 301 " + std::string(EnumerationToString(HttpStatus_301_MovedPermanently)) + - "\r\nLocation: " + path + - "\r\n\r\n"; - Send(&s[0], s.size()); + stateMachine_.ClearHeaders(); + stateMachine_.SetHttpStatus(HttpStatus_301_MovedPermanently); + stateMachine_.AddHeader("Location", path); + stateMachine_.SendBody(NULL, 0); + } + + + void HttpOutput::SendUnauthorized(const std::string& realm) + { + stateMachine_.ClearHeaders(); + stateMachine_.SetHttpStatus(HttpStatus_401_Unauthorized); + stateMachine_.AddHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\""); + stateMachine_.SendBody(NULL, 0); + } + + void HttpOutput::SendBody(const void* buffer, size_t length) + { + stateMachine_.SendBody(buffer, length); + } + + void HttpOutput::SendBody(const std::string& str) + { + if (str.size() == 0) + { + stateMachine_.SendBody(NULL, 0); + } + else + { + stateMachine_.SendBody(str.c_str(), str.size()); + } + } + + void HttpOutput::SendBody() + { + stateMachine_.SendBody(NULL, 0); } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/HttpOutput.h --- a/Core/HttpServer/HttpOutput.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/HttpOutput.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -36,64 +36,109 @@ #include #include #include "../Enumerations.h" +#include "IHttpOutputStream.h" #include "HttpHandler.h" namespace Orthanc { - class HttpOutput + class HttpOutput : public boost::noncopyable { private: typedef std::list< std::pair > Header; - void SendHeaderInternal(HttpStatus status); + class StateMachine : public boost::noncopyable + { + private: + enum State + { + State_WritingHeader, + State_WritingBody, + State_Done + }; + + IHttpOutputStream& stream_; + State state_; + + HttpStatus status_; + bool hasContentLength_; + uint64_t contentLength_; + uint64_t contentPosition_; + bool keepAlive_; + std::list headers_; - void PrepareOkHeader(Header& header, - const char* contentType, - bool hasContentLength, - uint64_t contentLength, - const char* contentFilename); + public: + StateMachine(IHttpOutputStream& stream, + bool isKeepAlive); + + ~StateMachine(); + + void SetHttpStatus(HttpStatus status); + + void SetContentLength(uint64_t length); + + void SetContentType(const char* contentType); - void SendOkHeader(const Header& header); + void SetContentFilename(const char* filename); + + void SetCookie(const std::string& cookie, + const std::string& value); - void PrepareCookies(Header& header, - const HttpHandler::Arguments& cookies); + void AddHeader(const std::string& header, + const std::string& value); + + void ClearHeaders(); + + void SendBody(const void* buffer, size_t length); + }; + + StateMachine stateMachine_; public: - virtual ~HttpOutput() + HttpOutput(IHttpOutputStream& stream, + bool isKeepAlive) : + stateMachine_(stream, isKeepAlive) { } - virtual void Send(const void* buffer, size_t length) = 0; + void SendStatus(HttpStatus status); + + void SetContentType(const char* contentType) + { + stateMachine_.SetContentType(contentType); + } + + void SetContentFilename(const char* filename) + { + stateMachine_.SetContentFilename(filename); + } + + void SetContentLength(uint64_t length) + { + stateMachine_.SetContentLength(length); + } - void SendOkHeader(const char* contentType, - bool hasContentLength, - uint64_t contentLength, - const char* contentFilename); + void SetCookie(const std::string& cookie, + const std::string& value) + { + stateMachine_.SetCookie(cookie, value); + } - void SendString(const std::string& s); + void AddHeader(const std::string& key, + const std::string& value) + { + stateMachine_.AddHeader(key, value); + } - void SendMethodNotAllowedError(const std::string& allowed); + void SendBody(const void* buffer, size_t length); + + void SendBody(const std::string& str); - void SendHeader(HttpStatus status); + void SendBody(); + + void SendMethodNotAllowed(const std::string& allowed); void Redirect(const std::string& path); - // Higher-level constructs to send entire buffers ---------------------------- - - void AnswerBufferWithContentType(const std::string& buffer, - const std::string& contentType); - - void AnswerBufferWithContentType(const std::string& buffer, - const std::string& contentType, - const HttpHandler::Arguments& cookies); - - void AnswerBufferWithContentType(const void* buffer, - size_t size, - const std::string& contentType); - - void AnswerBufferWithContentType(const void* buffer, - size_t size, - const std::string& contentType, - const HttpHandler::Arguments& cookies); + void SendUnauthorized(const std::string& realm); }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/IHttpOutputStream.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/IHttpOutputStream.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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" + +#include +#include + +namespace Orthanc +{ + class IHttpOutputStream : public boost::noncopyable + { + public: + virtual ~IHttpOutputStream() + { + } + + virtual void OnHttpStatusReceived(HttpStatus status) = 0; + + virtual void Send(bool isHeader, const void* buffer, size_t length) = 0; + }; +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/MongooseServer.cpp --- a/Core/HttpServer/MongooseServer.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/MongooseServer.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -68,23 +68,28 @@ namespace { // Anonymous namespace to avoid clashes between compilation modules - class MongooseOutput : public HttpOutput + class MongooseOutputStream : public IHttpOutputStream { private: struct mg_connection* connection_; public: - MongooseOutput(struct mg_connection* connection) : connection_(connection) + MongooseOutputStream(struct mg_connection* connection) : connection_(connection) { } - virtual void Send(const void* buffer, size_t length) + virtual void Send(bool isHeader, const void* buffer, size_t length) { if (length > 0) { mg_write(connection_, buffer, length); } } + + virtual void OnHttpStatusReceived(HttpStatus status) + { + // Ignore this + } }; @@ -255,23 +260,6 @@ - HttpHandler* MongooseServer::FindHandler(const UriComponents& forUri) const - { - for (Handlers::const_iterator it = - handlers_.begin(); it != handlers_.end(); ++it) - { - if ((*it)->IsServedUri(forUri)) - { - return *it; - } - } - - return NULL; - } - - - - static PostDataStatus ReadBody(std::string& postData, struct mg_connection *connection, const HttpHandler::Arguments& headers) @@ -421,18 +409,8 @@ } - static void SendUnauthorized(HttpOutput& output) - { - std::string s = "HTTP/1.1 401 Unauthorized\r\n" - "WWW-Authenticate: Basic realm=\"" ORTHANC_REALM "\"" - "\r\n\r\n"; - output.Send(&s[0], s.size()); - } - - - static bool Authorize(const MongooseServer& that, - const HttpHandler::Arguments& headers, - HttpOutput& output) + static bool IsAccessGranted(const MongooseServer& that, + const HttpHandler::Arguments& headers) { bool granted = false; @@ -440,22 +418,15 @@ if (auth != headers.end()) { std::string s = auth->second; - if (s.substr(0, 6) == "Basic ") + if (s.size() > 6 && + s.substr(0, 6) == "Basic ") { std::string b64 = s.substr(6); granted = that.IsValidBasicHttpAuthentication(b64); } } - if (!granted) - { - SendUnauthorized(output); - return false; - } - else - { - return true; - } + return granted; } @@ -469,7 +440,8 @@ } std::string s = auth->second; - if (s.substr(0, 6) != "Basic ") + if (s.size() <= 6 || + s.substr(0, 6) != "Basic ") { return ""; } @@ -494,7 +466,7 @@ static bool ExtractMethod(HttpMethod& method, const struct mg_request_info *request, const HttpHandler::Arguments& headers, - const HttpHandler::Arguments& argumentsGET) + const HttpHandler::GetArguments& argumentsGET) { std::string overriden; @@ -512,10 +484,13 @@ { // 2. Faking with Ruby on Rail's approach // GET /my/resource?_method=delete <=> DELETE /my/resource - methodOverride = argumentsGET.find("_method"); - if (methodOverride != argumentsGET.end()) + for (size_t i = 0; i < argumentsGET.size(); i++) { - overriden = methodOverride->second; + if (argumentsGET[i].first == "_method") + { + overriden = argumentsGET[i].second; + break; + } } } @@ -568,179 +543,229 @@ } + static void InternalCallback(struct mg_connection *connection, + const struct mg_request_info *request) + { + MongooseServer* that = reinterpret_cast(request->user_data); + MongooseOutputStream stream(connection); + HttpOutput output(stream, that->IsKeepAliveEnabled()); + // Check remote calls + if (!that->IsRemoteAccessAllowed() && + request->remote_ip != LOCALHOST) + { + output.SendUnauthorized(ORTHANC_REALM); + return; + } + + + // Extract the HTTP headers + HttpHandler::Arguments headers; + for (int i = 0; i < request->num_headers; i++) + { + std::string name = request->http_headers[i].name; + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + headers.insert(std::make_pair(name, request->http_headers[i].value)); + } + + + // Extract the GET arguments + HttpHandler::GetArguments argumentsGET; + if (!strcmp(request->request_method, "GET")) + { + HttpHandler::ParseGetArguments(argumentsGET, request->query_string); + } + + + // Compute the HTTP method, taking method faking into consideration + HttpMethod method = HttpMethod_Get; + if (!ExtractMethod(method, request, headers, argumentsGET)) + { + output.SendStatus(HttpStatus_400_BadRequest); + return; + } + + + // Authenticate this connection + if (that->IsAuthenticationEnabled() && !IsAccessGranted(*that, headers)) + { + output.SendUnauthorized(ORTHANC_REALM); + return; + } + + + // Apply the filter, if it is installed + const IIncomingHttpRequestFilter *filter = that->GetIncomingHttpRequestFilter(); + if (filter != NULL) + { + std::string username = GetAuthenticatedUsername(headers); + + char remoteIp[24]; + sprintf(remoteIp, "%d.%d.%d.%d", + reinterpret_cast(&request->remote_ip) [3], + reinterpret_cast(&request->remote_ip) [2], + reinterpret_cast(&request->remote_ip) [1], + reinterpret_cast(&request->remote_ip) [0]); + + if (!filter->IsAllowed(method, request->uri, remoteIp, username.c_str())) + { + output.SendUnauthorized(ORTHANC_REALM); + return; + } + } + + + // Extract the body of the request for PUT and POST + std::string body; + if (method == HttpMethod_Post || + method == HttpMethod_Put) + { + PostDataStatus status; + + HttpHandler::Arguments::const_iterator ct = headers.find("content-type"); + if (ct == headers.end()) + { + // No content-type specified. Assume no multi-part content occurs at this point. + status = ReadBody(body, connection, headers); + } + else + { + std::string contentType = ct->second; + if (contentType.size() >= multipartLength && + !memcmp(contentType.c_str(), multipart, multipartLength)) + { + status = ParseMultipartPost(body, connection, headers, contentType, that->GetChunkStore()); + } + else + { + status = ReadBody(body, connection, headers); + } + } + + switch (status) + { + case PostDataStatus_NoLength: + output.SendStatus(HttpStatus_411_LengthRequired); + return; + + case PostDataStatus_Failure: + output.SendStatus(HttpStatus_400_BadRequest); + return; + + case PostDataStatus_Pending: + output.SendBody(); + return; + + default: + break; + } + } + + + // Decompose the URI into its components + UriComponents uri; + try + { + Toolbox::SplitUriComponents(uri, request->uri); + } + catch (OrthancException) + { + output.SendStatus(HttpStatus_400_BadRequest); + return; + } + + + // Loop over the candidate handlers for this URI + LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); + bool found = false; + + for (MongooseServer::Handlers::const_iterator it = + that->GetHandlers().begin(); it != that->GetHandlers().end() && !found; ++it) + { + try + { + found = (*it)->Handle(output, method, uri, headers, argumentsGET, body); + } + catch (OrthancException& e) + { + // Using this candidate handler results in an exception + LOG(ERROR) << "Exception in the HTTP handler: " << e.What(); + + switch (e.GetErrorCode()) + { + case ErrorCode_InexistentFile: + case ErrorCode_InexistentItem: + case ErrorCode_UnknownResource: + output.SendStatus(HttpStatus_404_NotFound); + break; + + case ErrorCode_BadRequest: + case ErrorCode_UriSyntax: + output.SendStatus(HttpStatus_400_BadRequest); + break; + + default: + output.SendStatus(HttpStatus_500_InternalServerError); + } + + return; + } + catch (boost::bad_lexical_cast&) + { + LOG(ERROR) << "Exception in the HTTP handler: Bad lexical cast"; + output.SendStatus(HttpStatus_400_BadRequest); + return; + } + catch (std::runtime_error&) + { + LOG(ERROR) << "Exception in the HTTP handler: Presumably a bad JSON request"; + output.SendStatus(HttpStatus_400_BadRequest); + return; + } + } + + if (!found) + { + output.SendStatus(HttpStatus_404_NotFound); + } + } + + +#if MONGOOSE_USE_CALLBACKS == 0 static void* Callback(enum mg_event event, struct mg_connection *connection, const struct mg_request_info *request) { if (event == MG_NEW_REQUEST) { - MongooseServer* that = reinterpret_cast(request->user_data); - MongooseOutput output(connection); - - // Check remote calls - if (!that->IsRemoteAccessAllowed() && - request->remote_ip != LOCALHOST) - { - SendUnauthorized(output); - return (void*) ""; - } - - - // Extract the HTTP headers - HttpHandler::Arguments headers; - for (int i = 0; i < request->num_headers; i++) - { - std::string name = request->http_headers[i].name; - std::transform(name.begin(), name.end(), name.begin(), ::tolower); - headers.insert(std::make_pair(name, request->http_headers[i].value)); - } - - - // Extract the GET arguments - HttpHandler::Arguments argumentsGET; - if (!strcmp(request->request_method, "GET")) - { - HttpHandler::ParseGetQuery(argumentsGET, request->query_string); - } - - - // Compute the HTTP method, taking method faking into consideration - HttpMethod method; - if (!ExtractMethod(method, request, headers, argumentsGET)) - { - output.SendHeader(HttpStatus_400_BadRequest); - return (void*) ""; - } - - - // Authenticate this connection - if (that->IsAuthenticationEnabled() && - !Authorize(*that, headers, output)) - { - return (void*) ""; - } - - - // Apply the filter, if it is installed - const IIncomingHttpRequestFilter *filter = that->GetIncomingHttpRequestFilter(); - if (filter != NULL) - { - std::string username = GetAuthenticatedUsername(headers); - - char remoteIp[24]; - sprintf(remoteIp, "%d.%d.%d.%d", - reinterpret_cast(&request->remote_ip) [3], - reinterpret_cast(&request->remote_ip) [2], - reinterpret_cast(&request->remote_ip) [1], - reinterpret_cast(&request->remote_ip) [0]); - - if (!filter->IsAllowed(method, request->uri, remoteIp, username.c_str())) - { - SendUnauthorized(output); - return (void*) ""; - } - } - - - // Extract the body of the request for PUT and POST - std::string body; - if (method == HttpMethod_Post || - method == HttpMethod_Put) - { - PostDataStatus status; - - HttpHandler::Arguments::const_iterator ct = headers.find("content-type"); - if (ct == headers.end()) - { - // No content-type specified. Assume no multi-part content occurs at this point. - status = ReadBody(body, connection, headers); - } - else - { - std::string contentType = ct->second; - if (contentType.size() >= multipartLength && - !memcmp(contentType.c_str(), multipart, multipartLength)) - { - status = ParseMultipartPost(body, connection, headers, contentType, that->GetChunkStore()); - } - else - { - status = ReadBody(body, connection, headers); - } - } - - switch (status) - { - case PostDataStatus_NoLength: - output.SendHeader(HttpStatus_411_LengthRequired); - return (void*) ""; - - case PostDataStatus_Failure: - output.SendHeader(HttpStatus_400_BadRequest); - return (void*) ""; - - case PostDataStatus_Pending: - output.AnswerBufferWithContentType(NULL, 0, ""); - return (void*) ""; - - default: - break; - } - } - - - // Call the proper handler for this URI - UriComponents uri; - try - { - Toolbox::SplitUriComponents(uri, request->uri); - } - catch (OrthancException) - { - output.SendHeader(HttpStatus_400_BadRequest); - return (void*) ""; - } - - - HttpHandler* handler = that->FindHandler(uri); - if (handler) - { - try - { - LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); - handler->Handle(output, method, uri, headers, argumentsGET, body); - } - catch (OrthancException& e) - { - LOG(ERROR) << "MongooseServer Exception [" << e.What() << "]"; - output.SendHeader(HttpStatus_500_InternalServerError); - } - catch (boost::bad_lexical_cast&) - { - LOG(ERROR) << "MongooseServer Exception: Bad lexical cast"; - output.SendHeader(HttpStatus_400_BadRequest); - } - catch (std::runtime_error&) - { - LOG(ERROR) << "MongooseServer Exception: Presumably a bad JSON request"; - output.SendHeader(HttpStatus_400_BadRequest); - } - } - else - { - output.SendHeader(HttpStatus_404_NotFound); - } + InternalCallback(connection, request); // Mark as processed return (void*) ""; - } - else + } + else { return NULL; } } +#elif MONGOOSE_USE_CALLBACKS == 1 + static int Callback(struct mg_connection *connection) + { + struct mg_request_info *request = mg_get_request_info(connection); + + InternalCallback(connection, request); + + return 1; // Do not let Mongoose handle the request by itself + } + +#else +#error Please set MONGOOSE_USE_CALLBACKS +#endif + + + + bool MongooseServer::IsRunning() const { @@ -756,6 +781,7 @@ ssl_ = false; port_ = 8000; filter_ = NULL; + keepAlive_ = false; #if ORTHANC_SSL_ENABLED == 1 // Check for the Heartbleed exploit @@ -794,13 +820,32 @@ } const char *options[] = { + // Set the TCP port for the HTTP server "listening_ports", port.c_str(), + + // Optimization reported by Chris Hafey + // https://groups.google.com/d/msg/orthanc-users/CKueKX0pJ9E/_UCbl8T-VjIJ + "enable_keep_alive", (keepAlive_ ? "yes" : "no"), + + // Set the SSL certificate, if any. This must be the last option. ssl_ ? "ssl_certificate" : NULL, certificate_.c_str(), NULL }; +#if MONGOOSE_USE_CALLBACKS == 0 pimpl_->context_ = mg_start(&Callback, this, options); + +#elif MONGOOSE_USE_CALLBACKS == 1 + struct mg_callbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.begin_request = Callback; + pimpl_->context_ = mg_start(&callbacks, this, options); + +#else +#error Please set MONGOOSE_USE_CALLBACKS +#endif + if (!pimpl_->context_) { throw OrthancException("Unable to launch the Mongoose server"); @@ -818,23 +863,17 @@ } - void MongooseServer::RegisterHandler(HttpHandler* handler) + void MongooseServer::RegisterHandler(HttpHandler& handler) { Stop(); - handlers_.push_back(handler); + handlers_.push_back(&handler); } void MongooseServer::ClearHandlers() { Stop(); - - for (Handlers::iterator it = - handlers_.begin(); it != handlers_.end(); ++it) - { - delete *it; - } } @@ -874,6 +913,15 @@ #endif } + + void MongooseServer::SetKeepAliveEnabled(bool enabled) + { + Stop(); + keepAlive_ = enabled; + LOG(WARNING) << "HTTP keep alive is " << (enabled ? "enabled" : "disabled"); + } + + void MongooseServer::SetAuthenticationEnabled(bool enabled) { Stop(); diff -r f894be6e7cc1 -r 111e23bb4904 Core/HttpServer/MongooseServer.h --- a/Core/HttpServer/MongooseServer.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/HttpServer/MongooseServer.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -59,12 +59,14 @@ class MongooseServer { + public: + typedef std::list Handlers; + private: // http://stackoverflow.com/questions/311166/stdauto-ptr-or-boostshared-ptr-for-pimpl-idiom struct PImpl; boost::shared_ptr pimpl_; - typedef std::list Handlers; Handlers handlers_; typedef std::set RegisteredUsers; @@ -76,6 +78,7 @@ std::string certificate_; uint16_t port_; IIncomingHttpRequestFilter* filter_; + bool keepAlive_; bool IsRunning() const; @@ -100,7 +103,7 @@ void RegisterUser(const char* username, const char* password); - void RegisterHandler(HttpHandler* handler); // This takes the ownership + void RegisterHandler(HttpHandler& handler); bool IsAuthenticationEnabled() const { @@ -116,6 +119,13 @@ void SetSslEnabled(bool enabled); + bool IsKeepAliveEnabled() const + { + return keepAlive_; + } + + void SetKeepAliveEnabled(bool enabled); + const std::string& GetSslCertificate() const { return certificate_; @@ -139,11 +149,13 @@ void ClearHandlers(); - // Can return NULL if no handler is associated to this URI - HttpHandler* FindHandler(const UriComponents& forUri) const; - ChunkStore& GetChunkStore(); bool IsValidBasicHttpAuthentication(const std::string& basic) const; + + const Handlers& GetHandlers() const + { + return handlers_; + } }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/ICommand.h --- a/Core/ICommand.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ICommand.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/IDynamicObject.h --- a/Core/IDynamicObject.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/IDynamicObject.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/ImageFormats/ImageAccessor.cpp --- a/Core/ImageFormats/ImageAccessor.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ImageFormats/ImageAccessor.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -168,7 +168,7 @@ pitch_ = pitch; buffer_ = const_cast(buffer); - assert(GetBytesPerPixel(format_) * width_ <= pitch_); + assert(GetBytesPerPixel() * width_ <= pitch_); } @@ -185,7 +185,7 @@ pitch_ = pitch; buffer_ = buffer; - assert(GetBytesPerPixel(format_) * width_ <= pitch_); + assert(GetBytesPerPixel() * width_ <= pitch_); } diff -r f894be6e7cc1 -r 111e23bb4904 Core/ImageFormats/ImageAccessor.h --- a/Core/ImageFormats/ImageAccessor.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ImageFormats/ImageAccessor.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -62,6 +62,11 @@ return format_; } + unsigned int GetBytesPerPixel() const + { + return ::Orthanc::GetBytesPerPixel(format_); + } + unsigned int GetWidth() const { return width_; diff -r f894be6e7cc1 -r 111e23bb4904 Core/ImageFormats/ImageBuffer.cpp --- a/Core/ImageFormats/ImageBuffer.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ImageFormats/ImageBuffer.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/ImageFormats/ImageBuffer.h --- a/Core/ImageFormats/ImageBuffer.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ImageFormats/ImageBuffer.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/ImageFormats/ImageProcessing.cpp --- a/Core/ImageFormats/ImageProcessing.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ImageFormats/ImageProcessing.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -75,6 +75,44 @@ } + template + static void ConvertColorToGrayscale(ImageAccessor& target, + const ImageAccessor& source) + { + assert(source.GetFormat() == PixelFormat_RGB24); + + const TargetType minValue = std::numeric_limits::min(); + const TargetType maxValue = std::numeric_limits::max(); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + TargetType* t = reinterpret_cast(target.GetRow(y)); + const uint8_t* s = reinterpret_cast(source.GetConstRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s += 3) + { + // Y = 0.2126 R + 0.7152 G + 0.0722 B + int32_t v = (2126 * static_cast(s[0]) + + 7152 * static_cast(s[1]) + + 0722 * static_cast(s[2])) / 1000; + + if (static_cast(v) < static_cast(minValue)) + { + *t = minValue; + } + else if (static_cast(v) > static_cast(maxValue)) + { + *t = maxValue; + } + else + { + *t = static_cast(v); + } + } + } + } + + template static void SetInternal(ImageAccessor& image, int64_t constant) @@ -171,7 +209,7 @@ void MultiplyConstantInternal(ImageAccessor& image, float factor) { - if (abs(factor - 1.0f) <= std::numeric_limits::epsilon()) + if (std::abs(factor - 1.0f) <= std::numeric_limits::epsilon()) { return; } @@ -319,6 +357,27 @@ return; } + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_RGB24) + { + ConvertColorToGrayscale(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale16 && + source.GetFormat() == PixelFormat_RGB24) + { + ConvertColorToGrayscale(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_SignedGrayscale16 && + source.GetFormat() == PixelFormat_RGB24) + { + ConvertColorToGrayscale(target, source); + return; + } + throw OrthancException(ErrorCode_NotImplemented); } diff -r f894be6e7cc1 -r 111e23bb4904 Core/ImageFormats/ImageProcessing.h --- a/Core/ImageFormats/ImageProcessing.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ImageFormats/ImageProcessing.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/ImageFormats/PngReader.cpp --- a/Core/ImageFormats/PngReader.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ImageFormats/PngReader.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/ImageFormats/PngReader.h --- a/Core/ImageFormats/PngReader.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ImageFormats/PngReader.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/ImageFormats/PngWriter.cpp --- a/Core/ImageFormats/PngWriter.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ImageFormats/PngWriter.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/ImageFormats/PngWriter.h --- a/Core/ImageFormats/PngWriter.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/ImageFormats/PngWriter.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/Lua/LuaContext.cpp --- a/Core/Lua/LuaContext.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Lua/LuaContext.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,7 @@ #include "LuaContext.h" #include +#include extern "C" { @@ -43,7 +44,7 @@ namespace Orthanc { - int LuaContext::PrintToLog(lua_State *state) + LuaContext& LuaContext::GetLuaContext(lua_State *state) { // Get the pointer to the "LuaContext" underlying object lua_getglobal(state, "_LuaContext"); @@ -52,6 +53,13 @@ assert(that != NULL); lua_pop(state, 1); + return *that; + } + + int LuaContext::PrintToLog(lua_State *state) + { + LuaContext& that = GetLuaContext(state); + // http://medek.wordpress.com/2009/02/03/wrapping-lua-errors-and-print-function/ int nArgs = lua_gettop(state); lua_getglobal(state, "tostring"); @@ -78,14 +86,243 @@ lua_pop(state, 1); } - LOG(INFO) << "Lua says: " << result; - that->log_.append(result); - that->log_.append("\n"); + LOG(WARNING) << "Lua says: " << result; + that.log_.append(result); + that.log_.append("\n"); + + return 0; + } + + + int LuaContext::SetHttpCredentials(lua_State *state) + { + LuaContext& that = GetLuaContext(state); + + // Check the types of the arguments + int nArgs = lua_gettop(state); + if (nArgs != 2 || + !lua_isstring(state, 1) || // Username + !lua_isstring(state, 2)) // Password + { + LOG(ERROR) << "Lua: Bad parameters to SetHttpCredentials()"; + } + else + { + // Configure the HTTP client + const char* username = lua_tostring(state, 1); + const char* password = lua_tostring(state, 2); + that.httpClient_.SetCredentials(username, password); + } return 0; } + bool LuaContext::AnswerHttpQuery(lua_State* state) + { + std::string str; + + try + { + httpClient_.Apply(str); + } + catch (OrthancException& e) + { + return false; + } + + // Return the result of the HTTP request + lua_pushstring(state, str.c_str()); + + return true; + } + + + int LuaContext::CallHttpGet(lua_State *state) + { + LuaContext& that = GetLuaContext(state); + + // Check the types of the arguments + int nArgs = lua_gettop(state); + if (nArgs != 1 || !lua_isstring(state, 1)) // URL + { + LOG(ERROR) << "Lua: Bad parameters to HttpGet()"; + lua_pushstring(state, "ERROR"); + return 1; + } + + // Configure the HTTP client class + const char* url = lua_tostring(state, 1); + that.httpClient_.SetMethod(HttpMethod_Get); + that.httpClient_.SetUrl(url); + + // Do the HTTP GET request + if (!that.AnswerHttpQuery(state)) + { + LOG(ERROR) << "Lua: Error in HttpGet() for URL " << url; + lua_pushstring(state, "ERROR"); + } + + return 1; + } + + + int LuaContext::CallHttpPostOrPut(lua_State *state, + HttpMethod method) + { + LuaContext& that = GetLuaContext(state); + + // Check the types of the arguments + int nArgs = lua_gettop(state); + if ((nArgs != 1 && nArgs != 2) || + !lua_isstring(state, 1) || // URL + (nArgs >= 2 && !lua_isstring(state, 2))) // Body data + { + LOG(ERROR) << "Lua: Bad parameters to HttpPost() or HttpPut()"; + lua_pushstring(state, "ERROR"); + return 1; + } + + // Configure the HTTP client class + const char* url = lua_tostring(state, 1); + that.httpClient_.SetMethod(method); + that.httpClient_.SetUrl(url); + + if (nArgs >= 2) + { + that.httpClient_.SetPostData(lua_tostring(state, 2)); + } + else + { + that.httpClient_.AccessPostData().clear(); + } + + // Do the HTTP POST/PUT request + if (!that.AnswerHttpQuery(state)) + { + LOG(ERROR) << "Lua: Error in HttpPost() or HttpPut() for URL " << url; + lua_pushstring(state, "ERROR"); + } + + return 1; + } + + + int LuaContext::CallHttpPost(lua_State *state) + { + return CallHttpPostOrPut(state, HttpMethod_Post); + } + + + int LuaContext::CallHttpPut(lua_State *state) + { + return CallHttpPostOrPut(state, HttpMethod_Put); + } + + + int LuaContext::CallHttpDelete(lua_State *state) + { + LuaContext& that = GetLuaContext(state); + + // Check the types of the arguments + int nArgs = lua_gettop(state); + if (nArgs != 1 || !lua_isstring(state, 1)) // URL + { + LOG(ERROR) << "Lua: Bad parameters to HttpDelete()"; + lua_pushstring(state, "ERROR"); + return 1; + } + + // Configure the HTTP client class + const char* url = lua_tostring(state, 1); + that.httpClient_.SetMethod(HttpMethod_Delete); + that.httpClient_.SetUrl(url); + + // Do the HTTP DELETE request + std::string s; + if (!that.httpClient_.Apply(s)) + { + LOG(ERROR) << "Lua: Error in HttpDelete() for URL " << url; + lua_pushstring(state, "ERROR"); + } + else + { + lua_pushstring(state, "SUCCESS"); + } + + return 1; + } + + + void LuaContext::PushJson(const Json::Value& value) + { + if (value.isString()) + { + lua_pushstring(lua_, value.asCString()); + } + else if (value.isDouble()) + { + lua_pushnumber(lua_, value.asDouble()); + } + else if (value.isInt()) + { + lua_pushinteger(lua_, value.asInt()); + } + else if (value.isUInt()) + { + lua_pushinteger(lua_, value.asUInt()); + } + else if (value.isBool()) + { + lua_pushboolean(lua_, value.asBool()); + } + else if (value.isNull()) + { + lua_pushnil(lua_); + } + else if (value.isArray()) + { + lua_newtable(lua_); + + // http://lua-users.org/wiki/SimpleLuaApiExample + for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) + { + // Push the table index (note the "+1" because of Lua conventions) + lua_pushnumber(lua_, i + 1); + + // Push the value of the cell + PushJson(value[i]); + + // Stores the pair in the table + lua_rawset(lua_, -3); + } + } + else if (value.isObject()) + { + lua_newtable(lua_); + + Json::Value::Members members = value.getMemberNames(); + + for (Json::Value::Members::const_iterator + it = members.begin(); it != members.end(); ++it) + { + // Push the index of the cell + lua_pushstring(lua_, it->c_str()); + + // Push the value of the cell + PushJson(value[*it]); + + // Stores the pair in the table + lua_rawset(lua_, -3); + } + } + else + { + throw LuaException("Unsupported JSON conversion"); + } + } + + LuaContext::LuaContext() { lua_ = luaL_newstate(); @@ -96,6 +333,11 @@ luaL_openlibs(lua_); lua_register(lua_, "print", PrintToLog); + lua_register(lua_, "HttpGet", CallHttpGet); + lua_register(lua_, "HttpPost", CallHttpPost); + lua_register(lua_, "HttpPut", CallHttpPut); + lua_register(lua_, "HttpDelete", CallHttpDelete); + lua_register(lua_, "SetHttpCredentials", SetHttpCredentials); lua_pushlightuserdata(lua_, this); lua_setglobal(lua_, "_LuaContext"); @@ -108,11 +350,9 @@ } - void LuaContext::Execute(std::string* output, - const std::string& command) + void LuaContext::ExecuteInternal(std::string* output, + const std::string& command) { - boost::mutex::scoped_lock lock(mutex_); - log_.clear(); int error = (luaL_loadbuffer(lua_, command.c_str(), command.size(), "line") || lua_pcall(lua_, 0, 0, 0)); @@ -123,6 +363,7 @@ std::string description(lua_tostring(lua_, -1)); lua_pop(lua_, 1); /* pop error message from the stack */ + LOG(ERROR) << "Error while executing Lua script: " << description; throw LuaException(description); } @@ -137,15 +378,29 @@ { std::string command; EmbeddedResources::GetFileResource(command, resource); - Execute(command); + ExecuteInternal(NULL, command); } bool LuaContext::IsExistingFunction(const char* name) { - boost::mutex::scoped_lock lock(mutex_); lua_settop(lua_, 0); lua_getglobal(lua_, name); return lua_type(lua_, -1) == LUA_TFUNCTION; } + + + void LuaContext::Execute(Json::Value& output, + const std::string& command) + { + std::string s; + ExecuteInternal(&s, command); + + Json::Reader reader; + if (!reader.parse(s, output)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + } diff -r f894be6e7cc1 -r 111e23bb4904 Core/Lua/LuaContext.h --- a/Core/Lua/LuaContext.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Lua/LuaContext.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,8 +33,7 @@ #pragma once #include "LuaException.h" - -#include +#include "../HttpClient.h" extern "C" { @@ -42,7 +41,7 @@ } #include - +#include namespace Orthanc { @@ -52,14 +51,29 @@ friend class LuaFunctionCall; lua_State *lua_; - boost::mutex mutex_; std::string log_; + HttpClient httpClient_; + + static LuaContext& GetLuaContext(lua_State *state); + + static int PrintToLog(lua_State *state); + + static int SetHttpCredentials(lua_State *state); - static int PrintToLog(lua_State *L); + static int CallHttpPostOrPut(lua_State *state, + HttpMethod method); + static int CallHttpGet(lua_State *state); + static int CallHttpPost(lua_State *state); + static int CallHttpPut(lua_State *state); + static int CallHttpDelete(lua_State *state); - void Execute(std::string* output, - const std::string& command); + bool AnswerHttpQuery(lua_State* state); + void ExecuteInternal(std::string* output, + const std::string& command); + + void PushJson(const Json::Value& value); + public: LuaContext(); @@ -67,17 +81,31 @@ void Execute(const std::string& command) { - Execute(NULL, command); + ExecuteInternal(NULL, command); } void Execute(std::string& output, const std::string& command) { - Execute(&output, command); + ExecuteInternal(&output, command); } + void Execute(Json::Value& output, + const std::string& command); + void Execute(EmbeddedResources::FileResourceId resource); bool IsExistingFunction(const char* name); + + void SetHttpCredentials(const char* username, + const char* password) + { + httpClient_.SetCredentials(username, password); + } + + void SetHttpProxy(const std::string& proxy) + { + httpClient_.SetProxy(proxy); + } }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/Lua/LuaException.h --- a/Core/Lua/LuaException.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Lua/LuaException.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/Lua/LuaFunctionCall.cpp --- a/Core/Lua/LuaFunctionCall.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Lua/LuaFunctionCall.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,10 @@ #include "../PrecompiledHeaders.h" #include "LuaFunctionCall.h" +#include +#include +#include +#include namespace Orthanc { @@ -47,7 +51,6 @@ LuaFunctionCall::LuaFunctionCall(LuaContext& context, const char* functionName) : context_(context), - lock_(context.mutex_), isExecuted_(false) { // Clear the stack to fulfill the invariant @@ -79,77 +82,13 @@ lua_pushnumber(context_.lua_, value); } - void LuaFunctionCall::PushJSON(const Json::Value& value) + void LuaFunctionCall::PushJson(const Json::Value& value) { CheckAlreadyExecuted(); - - if (value.isString()) - { - lua_pushstring(context_.lua_, value.asCString()); - } - else if (value.isDouble()) - { - lua_pushnumber(context_.lua_, value.asDouble()); - } - else if (value.isInt()) - { - lua_pushinteger(context_.lua_, value.asInt()); - } - else if (value.isUInt()) - { - lua_pushinteger(context_.lua_, value.asUInt()); - } - else if (value.isBool()) - { - lua_pushboolean(context_.lua_, value.asBool()); - } - else if (value.isNull()) - { - lua_pushnil(context_.lua_); - } - else if (value.isArray()) - { - lua_newtable(context_.lua_); - - // http://lua-users.org/wiki/SimpleLuaApiExample - for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) - { - // Push the table index (note the "+1" because of Lua conventions) - lua_pushnumber(context_.lua_, i + 1); - - // Push the value of the cell - PushJSON(value[i]); - - // Stores the pair in the table - lua_rawset(context_.lua_, -3); - } - } - else if (value.isObject()) - { - lua_newtable(context_.lua_); - - Json::Value::Members members = value.getMemberNames(); - - for (Json::Value::Members::const_iterator - it = members.begin(); it != members.end(); ++it) - { - // Push the index of the cell - lua_pushstring(context_.lua_, it->c_str()); - - // Push the value of the cell - PushJSON(value[*it]); - - // Stores the pair in the table - lua_rawset(context_.lua_, -3); - } - } - else - { - throw LuaException("Unsupported JSON conversion"); - } + context_.PushJson(value); } - void LuaFunctionCall::Execute(int numOutputs) + void LuaFunctionCall::ExecuteInternal(int numOutputs) { CheckAlreadyExecuted(); @@ -176,13 +115,8 @@ bool LuaFunctionCall::ExecutePredicate() { - Execute(1); - - if (lua_gettop(context_.lua_) == 0) - { - throw LuaException("No output was provided by the function"); - } - + ExecuteInternal(1); + if (!lua_isboolean(context_.lua_, 1)) { throw LuaException("The function is not a predicate (only true/false outputs allowed)"); @@ -190,4 +124,99 @@ return lua_toboolean(context_.lua_, 1) != 0; } + + + static void PopJson(Json::Value& result, + lua_State* lua, + int top) + { + if (lua_istable(lua, top)) + { + Json::Value tmp = Json::objectValue; + bool isArray = true; + size_t size = 0; + + // http://stackoverflow.com/a/6142700/881731 + + // Push another reference to the table on top of the stack (so we know + // where it is, and this function can work for negative, positive and + // pseudo indices + lua_pushvalue(lua, top); + // stack now contains: -1 => table + lua_pushnil(lua); + // stack now contains: -1 => nil; -2 => table + while (lua_next(lua, -2)) + { + // stack now contains: -1 => value; -2 => key; -3 => table + // copy the key so that lua_tostring does not modify the original + lua_pushvalue(lua, -2); + // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table + std::string key(lua_tostring(lua, -1)); + Json::Value v; + PopJson(v, lua, -2); + + tmp[key] = v; + + size += 1; + try + { + if (boost::lexical_cast(key) != size) + { + isArray = false; + } + } + catch (boost::bad_lexical_cast&) + { + isArray = false; + } + + // pop value + copy of key, leaving original key + lua_pop(lua, 2); + // stack now contains: -1 => key; -2 => table + } + // stack now contains: -1 => table (when lua_next returns 0 it pops the key + // but does not push anything.) + // Pop table + lua_pop(lua, 1); + + // Stack is now the same as it was on entry to this function + + if (isArray) + { + result = Json::arrayValue; + for (size_t i = 0; i < size; i++) + { + result.append(tmp[boost::lexical_cast(i + 1)]); + } + } + else + { + result = tmp; + } + } + else if (lua_isnumber(lua, top)) + { + result = static_cast(lua_tonumber(lua, top)); + } + else if (lua_isstring(lua, top)) + { + result = std::string(lua_tostring(lua, top)); + } + else if (lua_isboolean(lua, top)) + { + result = static_cast(lua_toboolean(lua, top)); + } + else + { + LOG(WARNING) << "Unsupported Lua type when returning Json"; + result = Json::nullValue; + } + } + + + void LuaFunctionCall::ExecuteToJson(Json::Value& result) + { + ExecuteInternal(1); + PopJson(result, context_.lua_, lua_gettop(context_.lua_)); + } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/Lua/LuaFunctionCall.h --- a/Core/Lua/LuaFunctionCall.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Lua/LuaFunctionCall.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -36,18 +36,18 @@ #include - namespace Orthanc { class LuaFunctionCall : public boost::noncopyable { private: LuaContext& context_; - boost::mutex::scoped_lock lock_; bool isExecuted_; void CheckAlreadyExecuted(); + void ExecuteInternal(int numOutputs); + public: LuaFunctionCall(LuaContext& context, const char* functionName); @@ -60,10 +60,15 @@ void PushDouble(double value); - void PushJSON(const Json::Value& value); + void PushJson(const Json::Value& value); - void Execute(int numOutputs = 0); + void Execute() + { + ExecuteInternal(0); + } bool ExecutePredicate(); + + void ExecuteToJson(Json::Value& result); }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/ArrayFilledByThreads.cpp --- a/Core/MultiThreading/ArrayFilledByThreads.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/ArrayFilledByThreads.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/BagOfRunnablesBySteps.cpp --- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/BagOfRunnablesBySteps.h --- a/Core/MultiThreading/BagOfRunnablesBySteps.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/BagOfRunnablesBySteps.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/ILockable.h --- a/Core/MultiThreading/ILockable.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/ILockable.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/IRunnableBySteps.h --- a/Core/MultiThreading/IRunnableBySteps.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/IRunnableBySteps.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/Locker.h --- a/Core/MultiThreading/Locker.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/Locker.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/Mutex.cpp --- a/Core/MultiThreading/Mutex.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/Mutex.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -37,7 +37,7 @@ #if defined(_WIN32) #include -#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) +#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) #include #else #error Support your platform here @@ -75,7 +75,7 @@ } -#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) +#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) struct Mutex::PImpl { diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/Mutex.h --- a/Core/MultiThreading/Mutex.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/Mutex.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/ReaderWriterLock.cpp --- a/Core/MultiThreading/ReaderWriterLock.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/ReaderWriterLock.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/ReaderWriterLock.h --- a/Core/MultiThreading/ReaderWriterLock.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/ReaderWriterLock.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/Semaphore.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/Semaphore.cpp Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,67 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "Semaphore.h" + +#include "../OrthancException.h" + + +namespace Orthanc +{ + Semaphore::Semaphore(unsigned int count) : count_(count) + { + if (count == 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + 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 f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/Semaphore.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/Semaphore.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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(); + }; +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/SharedMessageQueue.cpp --- a/Core/MultiThreading/SharedMessageQueue.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/SharedMessageQueue.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,11 +33,40 @@ #include "../PrecompiledHeaders.h" #include "SharedMessageQueue.h" + + +/** + * FIFO (queue): + * + * back front + * +--+--+--+--+--+--+--+--+--+--+--+ + * Enqueue -> | | | | | | | | | | | | + * | | | | | | | | | | | | -> Dequeue + * +--+--+--+--+--+--+--+--+--+--+--+ + * ^ + * | + * Make room here + * + * + * LIFO (stack): + * + * back front + * +--+--+--+--+--+--+--+--+--+--+--+ + * | | | | | | | | | | | | <- Enqueue + * | | | | | | | | | | | | -> Dequeue + * +--+--+--+--+--+--+--+--+--+--+--+ + * ^ + * | + * Make room here + **/ + + namespace Orthanc { - SharedMessageQueue::SharedMessageQueue(unsigned int maxSize) + SharedMessageQueue::SharedMessageQueue(unsigned int maxSize) : + isFifo_(true), + maxSize_(maxSize) { - maxSize_ = maxSize; } @@ -56,12 +85,31 @@ if (maxSize_ != 0 && queue_.size() > maxSize_) { - // Too many elements in the queue: First remove the oldest - delete queue_.front(); - queue_.pop_front(); + if (isFifo_) + { + // Too many elements in the queue: Make room + delete queue_.front(); + queue_.pop_front(); + } + else + { + // Too many elements in the stack: Make room + delete queue_.back(); + queue_.pop_back(); + } } - queue_.push_back(message); + if (isFifo_) + { + // Queue policy (FIFO) + queue_.push_back(message); + } + else + { + // Stack policy (LIFO) + queue_.push_front(message); + } + elementAvailable_.notify_one(); } @@ -124,4 +172,17 @@ return true; } + + + void SharedMessageQueue::SetFifoPolicy() + { + boost::mutex::scoped_lock lock(mutex_); + isFifo_ = true; + } + + void SharedMessageQueue::SetLifoPolicy() + { + boost::mutex::scoped_lock lock(mutex_); + isFifo_ = false; + } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/SharedMessageQueue.h --- a/Core/MultiThreading/SharedMessageQueue.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/SharedMessageQueue.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -40,11 +40,12 @@ namespace Orthanc { - class SharedMessageQueue + class SharedMessageQueue : public boost::noncopyable { private: typedef std::list Queue; + bool isFifo_; unsigned int maxSize_; Queue queue_; boost::mutex mutex_; @@ -52,8 +53,8 @@ boost::condition_variable emptied_; public: - SharedMessageQueue(unsigned int maxSize = 0); - + explicit SharedMessageQueue(unsigned int maxSize = 0); + ~SharedMessageQueue(); // This transfers the ownership of the message @@ -63,5 +64,19 @@ IDynamicObject* Dequeue(int32_t millisecondsTimeout); bool WaitEmpty(int32_t millisecondsTimeout); + + bool IsFifoPolicy() const + { + return isFifo_; + } + + bool IsLifoPolicy() const + { + return !isFifo_; + } + + void SetFifoPolicy(); + + void SetLifoPolicy(); }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/ThreadedCommandProcessor.cpp --- a/Core/MultiThreading/ThreadedCommandProcessor.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/ThreadedCommandProcessor.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -175,7 +175,7 @@ { boost::mutex::scoped_lock lock(mutex_); - while (!remainingCommands_ == 0) + while (remainingCommands_ != 0) { processedCommand_.wait(lock); } diff -r f894be6e7cc1 -r 111e23bb4904 Core/MultiThreading/ThreadedCommandProcessor.h --- a/Core/MultiThreading/ThreadedCommandProcessor.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/MultiThreading/ThreadedCommandProcessor.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/OrthancException.cpp --- a/Core/OrthancException.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/OrthancException.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -121,6 +121,18 @@ case ErrorCode_IncompatibleImageFormat: return "Incompatible format of the images"; + case ErrorCode_SharedLibrary: + return "Error while using a shared library (plugin)"; + + case ErrorCode_SystemCommand: + return "Error while calling a system command"; + + case ErrorCode_Plugin: + return "Error encountered inside a plugin"; + + case ErrorCode_Database: + return "Error with the database engine"; + case ErrorCode_Custom: default: return "???"; diff -r f894be6e7cc1 -r 111e23bb4904 Core/OrthancException.h --- a/Core/OrthancException.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/OrthancException.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/PrecompiledHeaders.cpp --- a/Core/PrecompiledHeaders.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/PrecompiledHeaders.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/PrecompiledHeaders.h --- a/Core/PrecompiledHeaders.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/PrecompiledHeaders.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -32,12 +32,12 @@ #pragma once -#if ORTHANC_USE_PRECOMPILED_HEADERS == 1 - -#ifndef NOMINMAX +#if defined(_WIN32) && !defined(NOMINMAX) #define NOMINMAX #endif +#if ORTHANC_USE_PRECOMPILED_HEADERS == 1 + #include #include #include @@ -49,6 +49,10 @@ #include #include +#if ORTHANC_PUGIXML_ENABLED == 1 +#include +#endif + #include "Enumerations.h" #include "OrthancException.h" #include "Toolbox.h" diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApi.cpp --- a/Core/RestApi/RestApi.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/RestApi/RestApi.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -36,86 +36,87 @@ #include // To define "_exit()" under Windows #include +#include + namespace Orthanc { - bool RestApi::Call::ParseJsonRequestInternal(Json::Value& result, - const char* request) + namespace { - result.clear(); - Json::Reader reader; - return reader.parse(request, result); - } + // Anonymous namespace to avoid clashes between compilation modules + class HttpHandlerVisitor : public RestApiHierarchy::IVisitor + { + private: + RestApi& api_; + RestApiOutput& output_; + HttpMethod method_; + const HttpHandler::Arguments& headers_; + const HttpHandler::Arguments& getArguments_; + const std::string& postData_; + public: + HttpHandlerVisitor(RestApi& api, + RestApiOutput& output, + HttpMethod method, + const HttpHandler::Arguments& headers, + const HttpHandler::Arguments& getArguments, + const std::string& postData) : + api_(api), + output_(output), + method_(method), + headers_(headers), + getArguments_(getArguments), + postData_(postData) + { + } - bool RestApi::GetCall::ParseJsonRequest(Json::Value& result) const - { - result.clear(); + virtual bool Visit(const RestApiHierarchy::Resource& resource, + const UriComponents& uri, + const HttpHandler::Arguments& components, + const UriComponents& trailing) + { + if (resource.HasHandler(method_)) + { + switch (method_) + { + case HttpMethod_Get: + { + RestApiGetCall call(output_, api_, headers_, components, trailing, uri, getArguments_); + resource.Handle(call); + return true; + } + + case HttpMethod_Post: + { + RestApiPostCall call(output_, api_, headers_, components, trailing, uri, postData_); + resource.Handle(call); + return true; + } - for (HttpHandler::Arguments::const_iterator - it = getArguments_.begin(); it != getArguments_.end(); ++it) - { - result[it->first] = it->second; - } + case HttpMethod_Delete: + { + RestApiDeleteCall call(output_, api_, headers_, components, trailing, uri); + resource.Handle(call); + return true; + } - return true; + case HttpMethod_Put: + { + RestApiPutCall call(output_, api_, headers_, components, trailing, uri, postData_); + resource.Handle(call); + return true; + } + + default: + return false; + } + } + + return false; + } + }; } - bool RestApi::IsGetAccepted(const UriComponents& uri) - { - for (GetHandlers::const_iterator it = getHandlers_.begin(); - it != getHandlers_.end(); ++it) - { - if (it->first->Match(uri)) - { - return true; - } - } - - return false; - } - - bool RestApi::IsPutAccepted(const UriComponents& uri) - { - for (PutHandlers::const_iterator it = putHandlers_.begin(); - it != putHandlers_.end(); ++it) - { - if (it->first->Match(uri)) - { - return true; - } - } - - return false; - } - - bool RestApi::IsPostAccepted(const UriComponents& uri) - { - for (PostHandlers::const_iterator it = postHandlers_.begin(); - it != postHandlers_.end(); ++it) - { - if (it->first->Match(uri)) - { - return true; - } - } - - return false; - } - - bool RestApi::IsDeleteAccepted(const UriComponents& uri) - { - for (DeleteHandlers::const_iterator it = deleteHandlers_.begin(); - it != deleteHandlers_.end(); ++it) - { - if (it->first->Match(uri)) - { - return true; - } - } - - return false; - } static void AddMethod(std::string& target, const std::string& method) @@ -126,158 +127,128 @@ target = method; } - std::string RestApi::GetAcceptedMethods(const UriComponents& uri) + static std::string MethodsToString(const std::set& methods) { std::string s; - if (IsGetAccepted(uri)) + if (methods.find(HttpMethod_Get) != methods.end()) + { AddMethod(s, "GET"); + } - if (IsPutAccepted(uri)) - AddMethod(s, "PUT"); + if (methods.find(HttpMethod_Post) != methods.end()) + { + AddMethod(s, "POST"); + } - if (IsPostAccepted(uri)) - AddMethod(s, "POST"); + if (methods.find(HttpMethod_Put) != methods.end()) + { + AddMethod(s, "PUT"); + } - if (IsDeleteAccepted(uri)) + if (methods.find(HttpMethod_Delete) != methods.end()) + { AddMethod(s, "DELETE"); + } return s; } - RestApi::~RestApi() - { - for (GetHandlers::iterator it = getHandlers_.begin(); - it != getHandlers_.end(); ++it) - { - delete it->first; - } - for (PutHandlers::iterator it = putHandlers_.begin(); - it != putHandlers_.end(); ++it) - { - delete it->first; - } - for (PostHandlers::iterator it = postHandlers_.begin(); - it != postHandlers_.end(); ++it) - { - delete it->first; - } - - for (DeleteHandlers::iterator it = deleteHandlers_.begin(); - it != deleteHandlers_.end(); ++it) - { - delete it->first; - } - } - - bool RestApi::IsServedUri(const UriComponents& uri) - { - return (IsGetAccepted(uri) || - IsPutAccepted(uri) || - IsPostAccepted(uri) || - IsDeleteAccepted(uri)); - } - - void RestApi::Handle(HttpOutput& output, + bool RestApi::Handle(HttpOutput& output, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& getArguments, + const GetArguments& getArguments, const std::string& postData) { - bool ok = false; - RestApiOutput restOutput(output); - RestApiPath::Components components; - UriComponents trailing; + RestApiOutput wrappedOutput(output); - if (method == HttpMethod_Get) +#if ORTHANC_PUGIXML_ENABLED == 1 + // Look if the user wishes XML answers instead of JSON + // http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html#z3 + Arguments::const_iterator it = headers.find("accept"); + if (it != headers.end()) { - for (GetHandlers::const_iterator it = getHandlers_.begin(); - it != getHandlers_.end(); ++it) + std::vector accepted; + Toolbox::TokenizeString(accepted, it->second, ';'); + for (size_t i = 0; i < accepted.size(); i++) { - if (it->first->Match(components, trailing, uri)) + if (accepted[i] == "application/xml") { - //LOG(INFO) << "REST GET call on: " << Toolbox::FlattenUri(uri); - ok = true; - GetCall call(restOutput, *this, headers, components, trailing, uri, getArguments); - it->second(call); + wrappedOutput.SetConvertJsonToXml(true); + } + + if (accepted[i] == "application/json") + { + wrappedOutput.SetConvertJsonToXml(false); } } } - else if (method == HttpMethod_Put) - { - for (PutHandlers::const_iterator it = putHandlers_.begin(); - it != putHandlers_.end(); ++it) - { - if (it->first->Match(components, trailing, uri)) - { - //LOG(INFO) << "REST PUT call on: " << Toolbox::FlattenUri(uri); - ok = true; - PutCall call(restOutput, *this, headers, components, trailing, uri, postData); - it->second(call); - } - } - } - else if (method == HttpMethod_Post) +#endif + + Arguments compiled; + HttpHandler::CompileGetArguments(compiled, getArguments); + + HttpHandlerVisitor visitor(*this, wrappedOutput, method, headers, compiled, postData); + + if (root_.LookupResource(uri, visitor)) { - for (PostHandlers::const_iterator it = postHandlers_.begin(); - it != postHandlers_.end(); ++it) - { - if (it->first->Match(components, trailing, uri)) - { - //LOG(INFO) << "REST POST call on: " << Toolbox::FlattenUri(uri); - ok = true; - PostCall call(restOutput, *this, headers, components, trailing, uri, postData); - it->second(call); - } - } - } - else if (method == HttpMethod_Delete) - { - for (DeleteHandlers::const_iterator it = deleteHandlers_.begin(); - it != deleteHandlers_.end(); ++it) - { - if (it->first->Match(components, trailing, uri)) - { - //LOG(INFO) << "REST DELETE call on: " << Toolbox::FlattenUri(uri); - ok = true; - DeleteCall call(restOutput, *this, headers, components, trailing, uri); - it->second(call); - } - } + wrappedOutput.Finalize(); + return true; } - if (!ok) + std::set methods; + root_.GetAcceptedMethods(methods, uri); + + if (methods.empty()) + { + return false; // This URI is not served by this REST API + } + else { LOG(INFO) << "REST method " << EnumerationToString(method) << " not allowed on: " << Toolbox::FlattenUri(uri); - output.SendMethodNotAllowedError(GetAcceptedMethods(uri)); + + output.SendMethodNotAllowed(MethodsToString(methods)); + + return true; } } void RestApi::Register(const std::string& path, - GetHandler handler) + RestApiGetCall::Handler handler) { - getHandlers_.push_back(std::make_pair(new RestApiPath(path), handler)); + root_.Register(path, handler); } void RestApi::Register(const std::string& path, - PutHandler handler) + RestApiPutCall::Handler handler) { - putHandlers_.push_back(std::make_pair(new RestApiPath(path), handler)); + root_.Register(path, handler); } void RestApi::Register(const std::string& path, - PostHandler handler) + RestApiPostCall::Handler handler) { - postHandlers_.push_back(std::make_pair(new RestApiPath(path), handler)); + root_.Register(path, handler); } void RestApi::Register(const std::string& path, - DeleteHandler handler) + RestApiDeleteCall::Handler handler) + { + root_.Register(path, handler); + } + + void RestApi::AutoListChildren(RestApiGetCall& call) { - deleteHandlers_.push_back(std::make_pair(new RestApiPath(path), handler)); + RestApi& context = call.GetContext(); + + Json::Value directory; + if (context.root_.GetDirectory(directory, call.GetFullUri())) + { + call.GetOutput().AnswerJson(directory); + } } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApi.h --- a/Core/RestApi/RestApi.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/RestApi/RestApi.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -32,9 +32,7 @@ #pragma once -#include "../HttpServer/HttpHandler.h" -#include "RestApiPath.h" -#include "RestApiOutput.h" +#include "RestApiHierarchy.h" #include @@ -42,254 +40,29 @@ { class RestApi : public HttpHandler { - public: - class Call - { - friend class RestApi; - - private: - RestApiOutput& output_; - RestApi& context_; - const HttpHandler::Arguments& httpHeaders_; - const RestApiPath::Components& uriComponents_; - const UriComponents& trailing_; - const UriComponents& fullUri_; - - Call(RestApiOutput& output, - RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const RestApiPath::Components& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri) : - output_(output), - context_(context), - httpHeaders_(httpHeaders), - uriComponents_(uriComponents), - trailing_(trailing), - fullUri_(fullUri) - { - } - - protected: - static bool ParseJsonRequestInternal(Json::Value& result, - const char* request); - - public: - RestApiOutput& GetOutput() - { - return output_; - } - - RestApi& GetContext() - { - return context_; - } - - const UriComponents& GetFullUri() const - { - return fullUri_; - } - - const UriComponents& GetTrailingUri() const - { - return trailing_; - } - - std::string GetUriComponent(const std::string& name, - const std::string& defaultValue) const - { - return HttpHandler::GetArgument(uriComponents_, name, defaultValue); - } - - std::string GetHttpHeader(const std::string& name, - const std::string& defaultValue) const - { - return HttpHandler::GetArgument(httpHeaders_, name, defaultValue); - } - - const HttpHandler::Arguments& GetHttpHeaders() const - { - return httpHeaders_; - } - - void ParseCookies(HttpHandler::Arguments& result) const - { - HttpHandler::ParseCookies(result, httpHeaders_); - } - - virtual bool ParseJsonRequest(Json::Value& result) const = 0; - }; - - - class GetCall : public Call - { - friend class RestApi; - - private: - const HttpHandler::Arguments& getArguments_; - - public: - GetCall(RestApiOutput& output, - RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const RestApiPath::Components& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri, - const HttpHandler::Arguments& getArguments) : - Call(output, context, httpHeaders, uriComponents, trailing, fullUri), - getArguments_(getArguments) - { - } - - std::string GetArgument(const std::string& name, - const std::string& defaultValue) const - { - return HttpHandler::GetArgument(getArguments_, name, defaultValue); - } - - bool HasArgument(const std::string& name) const - { - return getArguments_.find(name) != getArguments_.end(); - } - - virtual bool ParseJsonRequest(Json::Value& result) const; - }; - - - class PutCall : public Call - { - friend class RestApi; - - private: - const std::string& data_; - - public: - PutCall(RestApiOutput& output, - RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const RestApiPath::Components& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri, - const std::string& data) : - Call(output, context, httpHeaders, uriComponents, trailing, fullUri), - data_(data) - { - } - - const std::string& GetPutBody() const - { - return data_; - } - - virtual bool ParseJsonRequest(Json::Value& result) const - { - return ParseJsonRequestInternal(result, GetPutBody().c_str()); - } - }; - - class PostCall : public Call - { - friend class RestApi; - - private: - const std::string& data_; - - public: - PostCall(RestApiOutput& output, - RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const RestApiPath::Components& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri, - const std::string& data) : - Call(output, context, httpHeaders, uriComponents, trailing, fullUri), - data_(data) - { - } - - const std::string& GetPostBody() const - { - return data_; - } - - virtual bool ParseJsonRequest(Json::Value& result) const - { - return ParseJsonRequestInternal(result, GetPostBody().c_str()); - } - }; - - class DeleteCall : public Call - { - public: - DeleteCall(RestApiOutput& output, - RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const RestApiPath::Components& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri) : - Call(output, context, httpHeaders, uriComponents, trailing, fullUri) - { - } - - virtual bool ParseJsonRequest(Json::Value& result) const - { - result.clear(); - return true; - } - }; - - typedef void (*GetHandler) (GetCall& call); - - typedef void (*DeleteHandler) (DeleteCall& call); - - typedef void (*PutHandler) (PutCall& call); - - typedef void (*PostHandler) (PostCall& call); - private: - typedef std::list< std::pair > GetHandlers; - typedef std::list< std::pair > PutHandlers; - typedef std::list< std::pair > PostHandlers; - typedef std::list< std::pair > DeleteHandlers; - - GetHandlers getHandlers_; - PutHandlers putHandlers_; - PostHandlers postHandlers_; - DeleteHandlers deleteHandlers_; - - bool IsGetAccepted(const UriComponents& uri); - bool IsPutAccepted(const UriComponents& uri); - bool IsPostAccepted(const UriComponents& uri); - bool IsDeleteAccepted(const UriComponents& uri); - - std::string GetAcceptedMethods(const UriComponents& uri); + RestApiHierarchy root_; public: - RestApi() - { - } + static void AutoListChildren(RestApiGetCall& call); - ~RestApi(); - - virtual bool IsServedUri(const UriComponents& uri); - - virtual void Handle(HttpOutput& output, + virtual bool Handle(HttpOutput& output, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& getArguments, + const GetArguments& getArguments, const std::string& postData); void Register(const std::string& path, - GetHandler handler); + RestApiGetCall::Handler handler); void Register(const std::string& path, - PutHandler handler); + RestApiPutCall::Handler handler); void Register(const std::string& path, - PostHandler handler); + RestApiPostCall::Handler handler); void Register(const std::string& path, - DeleteHandler handler); + RestApiDeleteCall::Handler handler); }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiCall.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiCall.cpp Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,44 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "RestApiCall.h" + +namespace Orthanc +{ + bool RestApiCall::ParseJsonRequestInternal(Json::Value& result, + const char* request) + { + result.clear(); + Json::Reader reader; + return reader.parse(request, result); + } +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiCall.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiCall.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,119 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "../HttpServer/HttpHandler.h" +#include "RestApiPath.h" +#include "RestApiOutput.h" + +#include + +namespace Orthanc +{ + class RestApi; + + class RestApiCall : public boost::noncopyable + { + private: + RestApiOutput& output_; + RestApi& context_; + const HttpHandler::Arguments& httpHeaders_; + const HttpHandler::Arguments& uriComponents_; + const UriComponents& trailing_; + const UriComponents& fullUri_; + + protected: + static bool ParseJsonRequestInternal(Json::Value& result, + const char* request); + + public: + RestApiCall(RestApiOutput& output, + RestApi& context, + const HttpHandler::Arguments& httpHeaders, + const HttpHandler::Arguments& uriComponents, + const UriComponents& trailing, + const UriComponents& fullUri) : + output_(output), + context_(context), + httpHeaders_(httpHeaders), + uriComponents_(uriComponents), + trailing_(trailing), + fullUri_(fullUri) + { + } + + RestApiOutput& GetOutput() + { + return output_; + } + + RestApi& GetContext() + { + return context_; + } + + const UriComponents& GetFullUri() const + { + return fullUri_; + } + + const UriComponents& GetTrailingUri() const + { + return trailing_; + } + + std::string GetUriComponent(const std::string& name, + const std::string& defaultValue) const + { + return HttpHandler::GetArgument(uriComponents_, name, defaultValue); + } + + std::string GetHttpHeader(const std::string& name, + const std::string& defaultValue) const + { + return HttpHandler::GetArgument(httpHeaders_, name, defaultValue); + } + + const HttpHandler::Arguments& GetHttpHeaders() const + { + return httpHeaders_; + } + + void ParseCookies(HttpHandler::Arguments& result) const + { + HttpHandler::ParseCookies(result, httpHeaders_); + } + + virtual bool ParseJsonRequest(Json::Value& result) const = 0; + }; +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiDeleteCall.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiDeleteCall.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,60 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "RestApiCall.h" + +namespace Orthanc +{ + class RestApiDeleteCall : public RestApiCall + { + public: + typedef void (*Handler) (RestApiDeleteCall& call); + + RestApiDeleteCall(RestApiOutput& output, + RestApi& context, + const HttpHandler::Arguments& httpHeaders, + const HttpHandler::Arguments& uriComponents, + const UriComponents& trailing, + const UriComponents& fullUri) : + RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri) + { + } + + virtual bool ParseJsonRequest(Json::Value& result) const + { + result.clear(); + return true; + } + }; +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiGetCall.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiGetCall.cpp Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,49 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "RestApiGetCall.h" + +namespace Orthanc +{ + bool RestApiGetCall::ParseJsonRequest(Json::Value& result) const + { + result.clear(); + + for (HttpHandler::Arguments::const_iterator + it = getArguments_.begin(); it != getArguments_.end(); ++it) + { + result[it->first] = it->second; + } + + return true; + } +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiGetCall.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiGetCall.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,72 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "RestApiCall.h" + +namespace Orthanc +{ + class RestApiGetCall : public RestApiCall + { + private: + const HttpHandler::Arguments& getArguments_; + + public: + typedef void (*Handler) (RestApiGetCall& call); + + RestApiGetCall(RestApiOutput& output, + RestApi& context, + const HttpHandler::Arguments& httpHeaders, + const HttpHandler::Arguments& uriComponents, + const UriComponents& trailing, + const UriComponents& fullUri, + const HttpHandler::Arguments& getArguments) : + RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri), + getArguments_(getArguments) + { + } + + std::string GetArgument(const std::string& name, + const std::string& defaultValue) const + { + return HttpHandler::GetArgument(getArguments_, name, defaultValue); + } + + bool HasArgument(const std::string& name) const + { + return getArguments_.find(name) != getArguments_.end(); + } + + virtual bool ParseJsonRequest(Json::Value& result) const; + }; +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiHierarchy.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiHierarchy.cpp Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,473 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "RestApiHierarchy.h" + +#include "../OrthancException.h" + +#include +#include + +namespace Orthanc +{ + RestApiHierarchy::Resource::Resource() : + getHandler_(NULL), + postHandler_(NULL), + putHandler_(NULL), + deleteHandler_(NULL) + { + } + + + bool RestApiHierarchy::Resource::HasHandler(HttpMethod method) const + { + switch (method) + { + case HttpMethod_Get: + return getHandler_ != NULL; + + case HttpMethod_Post: + return postHandler_ != NULL; + + case HttpMethod_Put: + return putHandler_ != NULL; + + case HttpMethod_Delete: + return deleteHandler_ != NULL; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + bool RestApiHierarchy::Resource::IsEmpty() const + { + return (getHandler_ == NULL && + postHandler_ == NULL && + putHandler_ == NULL && + deleteHandler_ == NULL); + } + + + RestApiHierarchy& RestApiHierarchy::AddChild(Children& children, + const std::string& name) + { + Children::iterator it = children.find(name); + + if (it == children.end()) + { + // Create new child + RestApiHierarchy *child = new RestApiHierarchy; + children[name] = child; + return *child; + } + else + { + return *it->second; + } + } + + + + bool RestApiHierarchy::Resource::Handle(RestApiGetCall& call) const + { + if (getHandler_ != NULL) + { + getHandler_(call); + return true; + } + else + { + return false; + } + } + + + bool RestApiHierarchy::Resource::Handle(RestApiPutCall& call) const + { + if (putHandler_ != NULL) + { + putHandler_(call); + return true; + } + else + { + return false; + } + } + + + bool RestApiHierarchy::Resource::Handle(RestApiPostCall& call) const + { + if (postHandler_ != NULL) + { + postHandler_(call); + return true; + } + else + { + return false; + } + } + + + bool RestApiHierarchy::Resource::Handle(RestApiDeleteCall& call) const + { + if (deleteHandler_ != NULL) + { + deleteHandler_(call); + return true; + } + else + { + return false; + } + } + + + + void RestApiHierarchy::DeleteChildren(Children& children) + { + for (Children::iterator it = children.begin(); + it != children.end(); ++it) + { + delete it->second; + } + } + + + template + void RestApiHierarchy::RegisterInternal(const RestApiPath& path, + Handler handler, + size_t level) + { + if (path.GetLevelCount() == level) + { + if (path.IsUniversalTrailing()) + { + universalHandlers_.Register(handler); + } + else + { + handlers_.Register(handler); + } + } + else + { + RestApiHierarchy* child; + if (path.IsWildcardLevel(level)) + { + child = &AddChild(wildcardChildren_, path.GetWildcardName(level)); + } + else + { + child = &AddChild(children_, path.GetLevelName(level)); + } + + child->RegisterInternal(path, handler, level + 1); + } + } + + + bool RestApiHierarchy::LookupResource(HttpHandler::Arguments& components, + const UriComponents& uri, + IVisitor& visitor, + size_t level) + { + if (uri.size() != 0 && + level > uri.size()) + { + return false; + } + + UriComponents trailing; + + // Look for an exact match on the resource of interest + if (uri.size() == 0 || + level == uri.size()) + { + if (!handlers_.IsEmpty() && + visitor.Visit(handlers_, uri, components, trailing)) + { + return true; + } + } + + + if (level < uri.size()) // A recursive call is possible + { + // Try and go down in the hierarchy, using an exact match for the child + Children::const_iterator child = children_.find(uri[level]); + if (child != children_.end()) + { + if (child->second->LookupResource(components, uri, visitor, level + 1)) + { + return true; + } + } + + // Try and go down in the hierarchy, using wildcard rules for children + for (child = wildcardChildren_.begin(); + child != wildcardChildren_.end(); ++child) + { + HttpHandler::Arguments subComponents = components; + subComponents[child->first] = uri[level]; + + if (child->second->LookupResource(subComponents, uri, visitor, level + 1)) + { + return true; + } + } + } + + + // As a last resort, call the universal handlers, if any + if (!universalHandlers_.IsEmpty()) + { + trailing.resize(uri.size() - level); + size_t pos = 0; + for (size_t i = level; i < uri.size(); i++, pos++) + { + trailing[pos] = uri[i]; + } + + assert(pos == trailing.size()); + + if (visitor.Visit(universalHandlers_, uri, components, trailing)) + { + return true; + } + } + + return false; + } + + + bool RestApiHierarchy::CanGenerateDirectory() const + { + return (universalHandlers_.IsEmpty() && + wildcardChildren_.empty()); + } + + + bool RestApiHierarchy::GetDirectory(Json::Value& result, + const UriComponents& uri, + size_t level) + { + if (uri.size() == level) + { + if (CanGenerateDirectory()) + { + result = Json::arrayValue; + + for (Children::const_iterator it = children_.begin(); + it != children_.end(); ++it) + { + result.append(it->first); + } + + return true; + } + else + { + return false; + } + } + + Children::const_iterator child = children_.find(uri[level]); + if (child != children_.end()) + { + if (child->second->GetDirectory(result, uri, level + 1)) + { + return true; + } + } + + for (child = wildcardChildren_.begin(); + child != wildcardChildren_.end(); ++child) + { + if (child->second->GetDirectory(result, uri, level + 1)) + { + return true; + } + } + + return false; + } + + + RestApiHierarchy::~RestApiHierarchy() + { + DeleteChildren(children_); + DeleteChildren(wildcardChildren_); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApiGetCall::Handler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApiPutCall::Handler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApiPostCall::Handler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApiDeleteCall::Handler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::CreateSiteMap(Json::Value& target) const + { + target = Json::objectValue; + + /*std::string s = " "; + if (handlers_.HasHandler(HttpMethod_Get)) + { + s += "GET "; + } + + if (handlers_.HasHandler(HttpMethod_Post)) + { + s += "POST "; + } + + if (handlers_.HasHandler(HttpMethod_Put)) + { + s += "PUT "; + } + + if (handlers_.HasHandler(HttpMethod_Delete)) + { + s += "DELETE "; + } + + target = s;*/ + + for (Children::const_iterator it = children_.begin(); + it != children_.end(); ++it) + { + it->second->CreateSiteMap(target[it->first]); + } + + for (Children::const_iterator it = wildcardChildren_.begin(); + it != wildcardChildren_.end(); ++it) + { + it->second->CreateSiteMap(target["<" + it->first + ">"]); + } + } + + + bool RestApiHierarchy::LookupResource(const UriComponents& uri, + IVisitor& visitor) + { + HttpHandler::Arguments components; + return LookupResource(components, uri, visitor, 0); + } + + + + namespace + { + // Anonymous namespace to avoid clashes between compilation modules + + class AcceptedMethodsVisitor : public RestApiHierarchy::IVisitor + { + private: + std::set& methods_; + + public: + AcceptedMethodsVisitor(std::set& methods) : methods_(methods) + { + } + + virtual bool Visit(const RestApiHierarchy::Resource& resource, + const UriComponents& uri, + const HttpHandler::Arguments& components, + const UriComponents& trailing) + { + if (trailing.size() == 0) // Ignore universal handlers + { + if (resource.HasHandler(HttpMethod_Get)) + { + methods_.insert(HttpMethod_Get); + } + + if (resource.HasHandler(HttpMethod_Post)) + { + methods_.insert(HttpMethod_Post); + } + + if (resource.HasHandler(HttpMethod_Put)) + { + methods_.insert(HttpMethod_Put); + } + + if (resource.HasHandler(HttpMethod_Delete)) + { + methods_.insert(HttpMethod_Delete); + } + } + + return false; // Continue to check all the possible ways to access this URI + } + }; + } + + void RestApiHierarchy::GetAcceptedMethods(std::set& methods, + const UriComponents& uri) + { + HttpHandler::Arguments components; + AcceptedMethodsVisitor visitor(methods); + LookupResource(components, uri, visitor, 0); + + Json::Value d; + if (GetDirectory(d, uri)) + { + methods.insert(HttpMethod_Get); + } + } +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiHierarchy.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiHierarchy.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,164 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "RestApiGetCall.h" +#include "RestApiPostCall.h" +#include "RestApiPutCall.h" +#include "RestApiDeleteCall.h" + +#include + +namespace Orthanc +{ + class RestApiHierarchy : public boost::noncopyable + { + public: + class Resource : public boost::noncopyable + { + private: + RestApiGetCall::Handler getHandler_; + RestApiPostCall::Handler postHandler_; + RestApiPutCall::Handler putHandler_; + RestApiDeleteCall::Handler deleteHandler_; + + public: + Resource(); + + bool HasHandler(HttpMethod method) const; + + void Register(RestApiGetCall::Handler handler) + { + getHandler_ = handler; + } + + void Register(RestApiPutCall::Handler handler) + { + putHandler_ = handler; + } + + void Register(RestApiPostCall::Handler handler) + { + postHandler_ = handler; + } + + void Register(RestApiDeleteCall::Handler handler) + { + deleteHandler_ = handler; + } + + bool IsEmpty() const; + + bool Handle(RestApiGetCall& call) const; + + bool Handle(RestApiPutCall& call) const; + + bool Handle(RestApiPostCall& call) const; + + bool Handle(RestApiDeleteCall& call) const; + }; + + + class IVisitor : public boost::noncopyable + { + public: + virtual ~IVisitor() + { + } + + virtual bool Visit(const Resource& resource, + const UriComponents& uri, + const HttpHandler::Arguments& components, + const UriComponents& trailing) = 0; + }; + + + private: + typedef std::map Children; + + Resource handlers_; + Children children_; + Children wildcardChildren_; + Resource universalHandlers_; + + static RestApiHierarchy& AddChild(Children& children, + const std::string& name); + + static void DeleteChildren(Children& children); + + template + void RegisterInternal(const RestApiPath& path, + Handler handler, + size_t level); + + bool CanGenerateDirectory() const; + + bool LookupResource(HttpHandler::Arguments& components, + const UriComponents& uri, + IVisitor& visitor, + size_t level); + + bool GetDirectory(Json::Value& result, + const UriComponents& uri, + size_t level); + + public: + ~RestApiHierarchy(); + + void Register(const std::string& uri, + RestApiGetCall::Handler handler); + + void Register(const std::string& uri, + RestApiPutCall::Handler handler); + + void Register(const std::string& uri, + RestApiPostCall::Handler handler); + + void Register(const std::string& uri, + RestApiDeleteCall::Handler handler); + + void CreateSiteMap(Json::Value& target) const; + + bool GetDirectory(Json::Value& result, + const UriComponents& uri) + { + return GetDirectory(result, uri, 0); + } + + bool LookupResource(const UriComponents& uri, + IVisitor& visitor); + + void GetAcceptedMethods(std::set& methods, + const UriComponents& uri); + }; +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiOutput.cpp --- a/Core/RestApi/RestApiOutput.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/RestApi/RestApiOutput.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,22 +34,28 @@ #include "RestApiOutput.h" #include +#include #include "../OrthancException.h" namespace Orthanc { RestApiOutput::RestApiOutput(HttpOutput& output) : - output_(output) + output_(output), + convertJsonToXml_(false) { alreadySent_ = false; } RestApiOutput::~RestApiOutput() { + } + + void RestApiOutput::Finalize() + { if (!alreadySent_) { - output_.SendHeader(HttpStatus_400_BadRequest); + output_.SendStatus(HttpStatus_404_NotFound); } } @@ -71,9 +77,26 @@ void RestApiOutput::AnswerJson(const Json::Value& value) { CheckStatus(); - Json::StyledWriter writer; - std::string s = writer.write(value); - output_.AnswerBufferWithContentType(s, "application/json", cookies_); + + if (convertJsonToXml_) + { +#if ORTHANC_PUGIXML_ENABLED == 1 + std::string s; + Toolbox::JsonToXml(s, value); + output_.SetContentType("application/xml"); + output_.SendBody(s); +#else + LOG(ERROR) << "Orthanc was compiled without XML support"; + throw OrthancException(ErrorCode_InternalError); +#endif + } + else + { + Json::StyledWriter writer; + output_.SetContentType("application/json"); + output_.SendBody(writer.write(value)); + } + alreadySent_ = true; } @@ -81,7 +104,8 @@ const std::string& contentType) { CheckStatus(); - output_.AnswerBufferWithContentType(buffer, contentType, cookies_); + output_.SetContentType(contentType.c_str()); + output_.SendBody(buffer); alreadySent_ = true; } @@ -90,7 +114,8 @@ const std::string& contentType) { CheckStatus(); - output_.AnswerBufferWithContentType(buffer, length, contentType, cookies_); + output_.SetContentType(contentType.c_str()); + output_.SendBody(buffer, length); alreadySent_ = true; } @@ -103,14 +128,16 @@ void RestApiOutput::SignalError(HttpStatus status) { - if (status != HttpStatus_403_Forbidden && + if (status != HttpStatus_400_BadRequest && + status != HttpStatus_403_Forbidden && + status != HttpStatus_500_InternalServerError && status != HttpStatus_415_UnsupportedMediaType) { throw OrthancException("This HTTP status is not allowed in a REST API"); } CheckStatus(); - output_.SendHeader(status); + output_.SendStatus(status); alreadySent_ = true; } @@ -135,7 +162,7 @@ v += ";max-age=" + boost::lexical_cast(maxAge); } - cookies_[name] = v; + output_.SetCookie(name, v); } void RestApiOutput::ResetCookie(const std::string& name) diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiOutput.h --- a/Core/RestApi/RestApiOutput.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/RestApi/RestApiOutput.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -44,7 +44,7 @@ private: HttpOutput& output_; bool alreadySent_; - HttpHandler::Arguments cookies_; + bool convertJsonToXml_; void CheckStatus(); @@ -63,6 +63,16 @@ alreadySent_ = true; } + void SetConvertJsonToXml(bool convert) + { + convertJsonToXml_ = convert; + } + + bool IsConvertJsonToXml() const + { + return convertJsonToXml_; + } + void AnswerFile(HttpFileSender& sender); void AnswerJson(const Json::Value& value); @@ -83,5 +93,7 @@ unsigned int maxAge = 0); void ResetCookie(const std::string& name); + + void Finalize(); }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiPath.cpp --- a/Core/RestApi/RestApiPath.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/RestApi/RestApiPath.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,8 @@ #include "../PrecompiledHeaders.h" #include "RestApiPath.h" +#include "../OrthancException.h" + #include namespace Orthanc @@ -76,7 +78,7 @@ } } - bool RestApiPath::Match(Components& components, + bool RestApiPath::Match(HttpHandler::Arguments& components, UriComponents& trailing, const std::string& uriRaw) const { @@ -85,10 +87,12 @@ return Match(components, trailing, uri); } - bool RestApiPath::Match(Components& components, + bool RestApiPath::Match(HttpHandler::Arguments& components, UriComponents& trailing, const UriComponents& uri) const { + assert(uri_.size() == components_.size()); + if (uri.size() < uri_.size()) { return false; @@ -131,8 +135,46 @@ bool RestApiPath::Match(const UriComponents& uri) const { - Components components; + HttpHandler::Arguments components; UriComponents trailing; return Match(components, trailing, uri); } + + + bool RestApiPath::IsWildcardLevel(size_t level) const + { + assert(uri_.size() == components_.size()); + + if (level >= uri_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + return uri_[level].length() == 0; + } + + const std::string& RestApiPath::GetWildcardName(size_t level) const + { + assert(uri_.size() == components_.size()); + + if (!IsWildcardLevel(level)) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + return components_[level]; + } + + const std::string& RestApiPath::GetLevelName(size_t level) const + { + assert(uri_.size() == components_.size()); + + if (IsWildcardLevel(level)) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + return uri_[level]; + } } + diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiPath.h --- a/Core/RestApi/RestApiPath.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/RestApi/RestApiPath.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,8 @@ #pragma once #include "../Toolbox.h" +#include "../HttpServer/HttpHandler.h" + #include namespace Orthanc @@ -45,19 +47,34 @@ std::vector components_; public: - typedef std::map Components; - RestApiPath(const std::string& uri); // This version is slower - bool Match(Components& components, + bool Match(HttpHandler::Arguments& components, UriComponents& trailing, const std::string& uriRaw) const; - bool Match(Components& components, + bool Match(HttpHandler::Arguments& components, UriComponents& trailing, const UriComponents& uri) const; bool Match(const UriComponents& uri) const; + + size_t GetLevelCount() const + { + return uri_.size(); + } + + bool IsWildcardLevel(size_t level) const; + + bool IsUniversalTrailing() const + { + return hasTrailing_; + } + + const std::string& GetWildcardName(size_t level) const; + + const std::string& GetLevelName(size_t level) const; + }; } diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiPostCall.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiPostCall.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,69 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "RestApiCall.h" + +namespace Orthanc +{ + class RestApiPostCall : public RestApiCall + { + private: + const std::string& data_; + + public: + typedef void (*Handler) (RestApiPostCall& call); + + RestApiPostCall(RestApiOutput& output, + RestApi& context, + const HttpHandler::Arguments& httpHeaders, + const HttpHandler::Arguments& uriComponents, + const UriComponents& trailing, + const UriComponents& fullUri, + const std::string& data) : + RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri), + data_(data) + { + } + + const std::string& GetPostBody() const + { + return data_; + } + + virtual bool ParseJsonRequest(Json::Value& result) const + { + return ParseJsonRequestInternal(result, GetPostBody().c_str()); + } + }; +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/RestApi/RestApiPutCall.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiPutCall.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,69 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "RestApiCall.h" + +namespace Orthanc +{ + class RestApiPutCall : public RestApiCall + { + private: + const std::string& data_; + + public: + typedef void (*Handler) (RestApiPutCall& call); + + RestApiPutCall(RestApiOutput& output, + RestApi& context, + const HttpHandler::Arguments& httpHeaders, + const HttpHandler::Arguments& uriComponents, + const UriComponents& trailing, + const UriComponents& fullUri, + const std::string& data) : + RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri), + data_(data) + { + } + + const std::string& GetPutBody() const + { + return data_; + } + + virtual bool ParseJsonRequest(Json::Value& result) const + { + return ParseJsonRequestInternal(result, GetPutBody().c_str()); + } + }; +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/Connection.cpp --- a/Core/SQLite/Connection.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/Connection.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. * @@ -34,15 +35,21 @@ **/ +#if ORTHANC_SQLITE_STANDALONE != 1 #include "../PrecompiledHeaders.h" +#endif + #include "Connection.h" +#include "OrthancSQLiteException.h" #include #include #include #include +#if ORTHANC_SQLITE_STANDALONE != 1 #include +#endif namespace Orthanc @@ -67,7 +74,7 @@ { if (!db_) { - throw OrthancException("SQLite: The database is not opened"); + throw OrthancSQLiteException("SQLite: The database is not opened"); } } @@ -75,7 +82,7 @@ { if (db_) { - throw OrthancException("SQLite: Connection is already open"); + throw OrthancSQLiteException("SQLite: Connection is already open"); } int err = sqlite3_open(path.c_str(), &db_); @@ -83,7 +90,7 @@ { Close(); db_ = NULL; - throw OrthancException("SQLite: Unable to open the database"); + throw OrthancSQLiteException("SQLite: Unable to open the database"); } // Execute PRAGMAs at this point @@ -129,7 +136,7 @@ { if (i->second->GetReferenceCount() >= 1) { - throw OrthancException("SQLite: This cached statement is already being referred to"); + throw OrthancSQLiteException("SQLite: This cached statement is already being referred to"); } return *i->second; @@ -145,13 +152,16 @@ bool Connection::Execute(const char* sql) { +#if ORTHANC_SQLITE_STANDALONE != 1 VLOG(1) << "SQLite::Connection::Execute " << sql; +#endif + CheckIsOpen(); int error = sqlite3_exec(db_, sql, NULL, NULL, NULL); if (error == SQLITE_ERROR) { - throw OrthancException("SQLite Execute error: " + std::string(sqlite3_errmsg(db_))); + throw OrthancSQLiteException("SQLite Execute error: " + std::string(sqlite3_errmsg(db_))); } else { @@ -272,7 +282,7 @@ { if (!transactionNesting_) { - throw OrthancException("Rolling back a nonexistent transaction"); + throw OrthancSQLiteException("Rolling back a nonexistent transaction"); } transactionNesting_--; @@ -291,7 +301,7 @@ { if (!transactionNesting_) { - throw OrthancException("Committing a nonexistent transaction"); + throw OrthancSQLiteException("Committing a nonexistent transaction"); } transactionNesting_--; @@ -359,7 +369,7 @@ if (err != SQLITE_OK) { delete func; - throw OrthancException("SQLite: Unable to register a function"); + throw OrthancSQLiteException("SQLite: Unable to register a function"); } return func; @@ -368,12 +378,15 @@ void Connection::FlushToDisk() { +#if ORTHANC_SQLITE_STANDALONE != 1 VLOG(1) << "SQLite::Connection::FlushToDisk"; +#endif + int err = sqlite3_wal_checkpoint(db_, NULL); if (err != SQLITE_OK) { - throw OrthancException("SQLite: Unable to flush the database"); + throw OrthancSQLiteException("SQLite: Unable to flush the database"); } } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/Connection.h --- a/Core/SQLite/Connection.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/Connection.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. * @@ -40,7 +41,6 @@ #include "IScalarFunction.h" #include -#include #include struct sqlite3; @@ -52,7 +52,7 @@ { namespace SQLite { - class Connection : boost::noncopyable + class Connection : NonCopyable { friend class Statement; friend class Transaction; diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/FunctionContext.cpp --- a/Core/SQLite/FunctionContext.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/FunctionContext.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -31,8 +32,12 @@ **/ +#if ORTHANC_SQLITE_STANDALONE != 1 #include "../PrecompiledHeaders.h" +#endif + #include "FunctionContext.h" +#include "OrthancSQLiteException.h" #include @@ -57,7 +62,7 @@ { if (index >= argc_) { - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw OrthancSQLiteException("Parameter out of range"); } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/FunctionContext.h --- a/Core/SQLite/FunctionContext.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/FunctionContext.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -33,8 +34,6 @@ #pragma once -#include - #include "Statement.h" struct sqlite3_context; @@ -44,7 +43,7 @@ { namespace SQLite { - class FunctionContext : public boost::noncopyable + class FunctionContext : public NonCopyable { friend class Connection; diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/IScalarFunction.h --- a/Core/SQLite/IScalarFunction.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/IScalarFunction.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -33,13 +34,14 @@ #pragma once +#include "NonCopyable.h" #include "FunctionContext.h" namespace Orthanc { namespace SQLite { - class IScalarFunction : public boost::noncopyable + class IScalarFunction : public NonCopyable { public: virtual ~IScalarFunction() diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/ITransaction.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SQLite/ITransaction.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,66 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +#include "NonCopyable.h" + +namespace Orthanc +{ + namespace SQLite + { + class ITransaction : public NonCopyable + { + public: + virtual ~ITransaction() + { + } + + // Begins the transaction. This uses the default sqlite "deferred" transaction + // type, which means that the DB lock is lazily acquired the next time the + // database is accessed, not in the begin transaction command. + virtual void Begin() = 0; + + // Rolls back the transaction. This will happen automatically if you do + // nothing when the transaction goes out of scope. + virtual void Rollback() = 0; + + // Commits the transaction, returning true on success. + virtual void Commit() = 0; + }; + } +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/NonCopyable.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SQLite/NonCopyable.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,60 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +namespace Orthanc +{ + namespace SQLite + { + // This class mimics "boost::noncopyable" + class NonCopyable + { + private: + NonCopyable(const NonCopyable&); + + NonCopyable& operator= (const NonCopyable&); + + protected: + NonCopyable() + { + } + + ~NonCopyable() + { + } + }; + } +} diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/OrthancSQLiteException.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SQLite/OrthancSQLiteException.h Thu May 21 16:58:30 2015 +0200 @@ -0,0 +1,67 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium + * + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc., the name of the CHU of Liege, + * nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + + +#if ORTHANC_SQLITE_STANDALONE == 1 +#include + +namespace Orthanc +{ + namespace SQLite + { + class OrthancSQLiteException : public ::std::runtime_error + { + public: + OrthancSQLiteException(const std::string& what) : + ::std::runtime_error(what) + { + } + + OrthancSQLiteException(const char* what) : + ::std::runtime_error(what) + { + } + }; + } +} + +#else +# include "../OrthancException.h" +# define OrthancSQLiteException ::Orthanc::OrthancException +#endif diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/README.txt --- a/Core/SQLite/README.txt Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/README.txt Thu May 21 16:58:30 2015 +0200 @@ -19,6 +19,17 @@ coding conventions. +Reuse in another software +========================= + +To use the Orthanc SQLite wrapper in another project than Orthanc, you +just have to define the "ORTHANC_SQLITE_STANDALONE" macro. + +All the C++ exceptions generated by the wrapper will be objects of the +class "::Orthanc::SQLite::OrthancSQLiteException", that derives from +the standard exception class "::std::runtime_error". + + Licensing ========= @@ -26,4 +37,4 @@ order to respect the original license of the code. It is pretty straightforward to extract the code from this folder and -to include it in another project. +to include it in another project. diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/Statement.cpp --- a/Core/SQLite/Statement.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/Statement.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. * @@ -34,16 +35,25 @@ **/ +#if ORTHANC_SQLITE_STANDALONE != 1 #include "../PrecompiledHeaders.h" +#endif + #include "Statement.h" - #include "Connection.h" -#include "../Toolbox.h" -#include #include #include +#include +#include + +#if ORTHANC_SQLITE_STANDALONE != 1 #include +#endif + +#if defined(_MSC_VER) +#define snprintf _snprintf +#endif namespace Orthanc { @@ -54,7 +64,9 @@ bool succeeded = (err == SQLITE_OK || err == SQLITE_ROW || err == SQLITE_DONE); if (!succeeded) { - throw OrthancException("SQLite error code " + boost::lexical_cast(err)); + char buffer[128]; + snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err); + throw OrthancSQLiteException(buffer); } return err; @@ -65,11 +77,13 @@ if (err == SQLITE_RANGE) { // Binding to a non-existent variable is evidence of a serious error. - throw OrthancException("Bind value out of range"); + throw OrthancSQLiteException("Bind value out of range"); } else if (err != SQLITE_OK) { - throw OrthancException("SQLite error code " + boost::lexical_cast(err)); + char buffer[128]; + snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err); + throw OrthancSQLiteException(buffer); } } @@ -108,13 +122,19 @@ bool Statement::Run() { +#if ORTHANC_SQLITE_STANDALONE != 1 VLOG(1) << "SQLite::Statement::Run " << sqlite3_sql(GetStatement()); +#endif + return CheckError(sqlite3_step(GetStatement())) == SQLITE_DONE; } bool Statement::Step() { +#if ORTHANC_SQLITE_STANDALONE != 1 VLOG(1) << "SQLite::Statement::Step " << sqlite3_sql(GetStatement()); +#endif + return CheckError(sqlite3_step(GetStatement())) == SQLITE_ROW; } @@ -206,7 +226,7 @@ ColumnType Statement::GetDeclaredColumnType(int col) const { std::string column_type(sqlite3_column_decltype(GetStatement(), col)); - Toolbox::ToLowerCase(column_type); + std::transform(column_type.begin(), column_type.end(), column_type.begin(), tolower); if (column_type == "integer") return COLUMN_TYPE_INTEGER; diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/Statement.h --- a/Core/SQLite/Statement.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/Statement.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. * @@ -36,13 +37,13 @@ #pragma once -#include "../OrthancException.h" +#include "NonCopyable.h" +#include "OrthancSQLiteException.h" #include "StatementId.h" #include "StatementReference.h" #include #include -#include #if ORTHANC_BUILD_UNIT_TESTS == 1 #include @@ -68,7 +69,7 @@ COLUMN_TYPE_NULL = 5 }; - class Statement : public boost::noncopyable + class Statement : public NonCopyable { friend class Connection; diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/StatementId.cpp --- a/Core/SQLite/StatementId.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/StatementId.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. * @@ -34,7 +35,10 @@ **/ +#if ORTHANC_SQLITE_STANDALONE != 1 #include "../PrecompiledHeaders.h" +#endif + #include "StatementId.h" #include diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/StatementId.h --- a/Core/SQLite/StatementId.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/StatementId.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. * diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/StatementReference.cpp --- a/Core/SQLite/StatementReference.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/StatementReference.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. * @@ -34,13 +35,18 @@ **/ +#if ORTHANC_SQLITE_STANDALONE != 1 #include "../PrecompiledHeaders.h" +#endif + #include "StatementReference.h" +#include "OrthancSQLiteException.h" -#include "../OrthancException.h" +#if ORTHANC_SQLITE_STANDALONE != 1 +#include +#endif #include -#include #include "sqlite3.h" namespace Orthanc @@ -65,7 +71,7 @@ { if (database == NULL || sql == NULL) { - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw OrthancSQLiteException("Parameter out of range"); } root_ = NULL; @@ -74,7 +80,7 @@ int error = sqlite3_prepare_v2(database, sql, -1, &statement_, NULL); if (error != SQLITE_OK) { - throw OrthancException("SQLite: " + std::string(sqlite3_errmsg(database))); + throw OrthancSQLiteException("SQLite: " + std::string(sqlite3_errmsg(database))); } assert(IsRoot()); @@ -109,7 +115,9 @@ // an exception because: // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html +#if ORTHANC_SQLITE_STANDALONE != 1 LOG(ERROR) << "Bad value of the reference counter"; +#endif } else if (statement_ != NULL) { @@ -124,7 +132,9 @@ // an exception because: // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html +#if ORTHANC_SQLITE_STANDALONE != 1 LOG(ERROR) << "Bad value of the reference counter"; +#endif } else { diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/StatementReference.h --- a/Core/SQLite/StatementReference.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/StatementReference.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. * @@ -36,7 +37,8 @@ #pragma once -#include +#include "NonCopyable.h" + #include #include #include @@ -48,7 +50,7 @@ { namespace SQLite { - class StatementReference : boost::noncopyable + class StatementReference : NonCopyable { private: StatementReference* root_; // Only used for non-root nodes diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/Transaction.cpp --- a/Core/SQLite/Transaction.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/Transaction.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. * @@ -34,8 +35,12 @@ **/ +#if ORTHANC_SQLITE_STANDALONE != 1 #include "../PrecompiledHeaders.h" +#endif + #include "Transaction.h" +#include "OrthancSQLiteException.h" namespace Orthanc { @@ -59,13 +64,13 @@ { if (isOpen_) { - throw OrthancException("SQLite: Beginning a transaction twice!"); + throw OrthancSQLiteException("SQLite: Beginning a transaction twice!"); } isOpen_ = connection_.BeginTransaction(); if (!isOpen_) { - throw OrthancException("SQLite: Unable to create a transaction"); + throw OrthancSQLiteException("SQLite: Unable to create a transaction"); } } @@ -73,8 +78,8 @@ { if (!isOpen_) { - throw OrthancException("SQLite: Attempting to roll back a nonexistent transaction. " - "Did you remember to call Begin()?"); + throw OrthancSQLiteException("SQLite: Attempting to roll back a nonexistent transaction. " + "Did you remember to call Begin()?"); } isOpen_ = false; @@ -86,15 +91,15 @@ { if (!isOpen_) { - throw OrthancException("SQLite: Attempting to roll back a nonexistent transaction. " - "Did you remember to call Begin()?"); + throw OrthancSQLiteException("SQLite: Attempting to roll back a nonexistent transaction. " + "Did you remember to call Begin()?"); } isOpen_ = false; if (!connection_.CommitTransaction()) { - throw OrthancException("SQLite: Failure when committing the transaction"); + throw OrthancSQLiteException("SQLite: Failure when committing the transaction"); } } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/SQLite/Transaction.h --- a/Core/SQLite/Transaction.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/SQLite/Transaction.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * + * Copyright (C) 2012-2015 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. * @@ -37,12 +38,13 @@ #pragma once #include "Connection.h" +#include "ITransaction.h" namespace Orthanc { namespace SQLite { - class Transaction : public boost::noncopyable + class Transaction : public ITransaction { private: Connection& connection_; @@ -53,22 +55,17 @@ public: explicit Transaction(Connection& connection); - ~Transaction(); + + virtual ~Transaction(); // Returns true when there is a transaction that has been successfully begun. bool IsOpen() const { return isOpen_; } - // Begins the transaction. This uses the default sqlite "deferred" transaction - // type, which means that the DB lock is lazily acquired the next time the - // database is accessed, not in the begin transaction command. - void Begin(); + virtual void Begin(); - // Rolls back the transaction. This will happen automatically if you do - // nothing when the transaction goes out of scope. - void Rollback(); + virtual void Rollback(); - // Commits the transaction, returning true on success. - void Commit(); + virtual void Commit(); }; } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/Toolbox.cpp --- a/Core/Toolbox.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Toolbox.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -44,9 +44,14 @@ #include #include #include +#include #if defined(_WIN32) #include +#include // For "_spawnvp()" +#else +#include // For "execvp()" +#include // For "waitpid()" #endif #if defined(__APPLE__) && defined(__MACH__) @@ -54,7 +59,7 @@ #include /* PATH_MAX */ #endif -#if defined(__linux) || defined(__FreeBSD_kernel__) +#if defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) #include /* PATH_MAX */ #include #include @@ -70,8 +75,8 @@ #include "../Resources/ThirdParty/base64/base64.h" -#ifdef _MSC_VER -// Patch for the missing "_strtoll" symbol when compiling with Visual Studio +#if defined(_MSC_VER) && (_MSC_VER < 1800) +// Patch for the missing "_strtoll" symbol when compiling with Visual Studio < 2013 extern "C" { int64_t _strtoi64(const char *nptr, char **endptr, int base); @@ -83,6 +88,12 @@ #endif +#if ORTHANC_PUGIXML_ENABLED == 1 +#include "ChunkedBuffer.h" +#include +#endif + + namespace Orthanc { static bool finish; @@ -105,7 +116,7 @@ { #if defined(_WIN32) ::Sleep(static_cast(microSeconds / static_cast(1000))); -#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) +#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) usleep(microSeconds); #else #error Support your platform here @@ -113,7 +124,7 @@ } - void Toolbox::ServerBarrier() + static void ServerBarrierInternal(const bool* stopFlag) { #if defined(_WIN32) SetConsoleCtrlHandler(ConsoleControlHandler, true); @@ -123,10 +134,11 @@ signal(SIGTERM, SignalHandler); #endif + // Active loop that awakens every 100ms finish = false; - while (!finish) + while (!(*stopFlag || finish)) { - USleep(100000); + Toolbox::USleep(100 * 1000); } #if defined(_WIN32) @@ -139,6 +151,17 @@ } + void Toolbox::ServerBarrier(const bool& stopFlag) + { + ServerBarrierInternal(&stopFlag); + } + + void Toolbox::ServerBarrier() + { + const bool stopFlag = false; + ServerBarrierInternal(&stopFlag); + } + void Toolbox::ToUpperCase(std::string& s) { @@ -284,6 +307,28 @@ } + void Toolbox::TruncateUri(UriComponents& target, + const UriComponents& source, + size_t fromLevel) + { + target.clear(); + + if (source.size() > fromLevel) + { + target.resize(source.size() - fromLevel); + + size_t j = 0; + for (size_t i = fromLevel; i < source.size(); i++, j++) + { + target[j] = source[i]; + } + + assert(j == target.size()); + } + } + + + bool Toolbox::IsChildUri(const UriComponents& baseUri, const UriComponents& testedUri) { @@ -449,7 +494,7 @@ #if defined(_WIN32) - std::string Toolbox::GetPathToExecutable() + static std::string GetPathToExecutableInternal() { // Yes, this is ugly, but there is no simple way to get the // required buffer size, so we use a big constant @@ -458,8 +503,8 @@ return std::string(&buffer[0]); } -#elif defined(__linux) || defined(__FreeBSD_kernel__) - std::string Toolbox::GetPathToExecutable() +#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) + static std::string GetPathToExecutableInternal() { std::vector buffer(PATH_MAX + 1); ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1); @@ -472,7 +517,7 @@ } #elif defined(__APPLE__) && defined(__MACH__) - std::string Toolbox::GetPathToExecutable() + static std::string GetPathToExecutableInternal() { char pathbuf[PATH_MAX + 1]; unsigned int bufsize = static_cast(sizeof(pathbuf)); @@ -487,10 +532,17 @@ #endif + std::string Toolbox::GetPathToExecutable() + { + boost::filesystem::path p(GetPathToExecutableInternal()); + return boost::filesystem::absolute(p).string(); + } + + std::string Toolbox::GetDirectoryOfExecutable() { - boost::filesystem::path p(GetPathToExecutable()); - return p.parent_path().string(); + boost::filesystem::path p(GetPathToExecutableInternal()); + return boost::filesystem::absolute(p.parent_path()).string(); } @@ -499,18 +551,71 @@ { const char* encoding; + + // http://bradleyross.users.sourceforge.net/docs/dicom/doc/src-html/org/dcm4che2/data/SpecificCharacterSet.html switch (sourceEncoding) { case Encoding_Utf8: // Already in UTF-8: No conversion is required return source; + case Encoding_Ascii: + return ConvertToAscii(source); + case Encoding_Latin1: encoding = "ISO-8859-1"; break; + case Encoding_Latin2: + encoding = "ISO-8859-2"; + break; + + case Encoding_Latin3: + encoding = "ISO-8859-3"; + break; + + case Encoding_Latin4: + encoding = "ISO-8859-4"; + break; + + case Encoding_Latin5: + encoding = "ISO-8859-9"; + break; + + case Encoding_Cyrillic: + encoding = "ISO-8859-5"; + break; + + case Encoding_Windows1251: + encoding = "WINDOWS-1251"; + break; + + case Encoding_Arabic: + encoding = "ISO-8859-6"; + break; + + case Encoding_Greek: + encoding = "ISO-8859-7"; + break; + + case Encoding_Hebrew: + encoding = "ISO-8859-8"; + break; + + case Encoding_Japanese: + encoding = "SHIFT-JIS"; + break; + + case Encoding_Chinese: + encoding = "GB18030"; + break; + + case Encoding_Thai: + encoding = "TIS620.2533-0"; + break; + default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw OrthancException(ErrorCode_NotImplemented); } try @@ -532,7 +637,7 @@ result.reserve(source.size() + 1); for (size_t i = 0; i < source.size(); i++) { - if (source[i] < 128 && source[i] >= 0 && !iscntrl(source[i])) + if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i])) { result.push_back(source[i]); } @@ -792,5 +897,220 @@ } } } + + + bool Toolbox::IsExistingFile(const std::string& path) + { + return boost::filesystem::exists(path); + } + + +#if ORTHANC_PUGIXML_ENABLED == 1 + class ChunkedBufferWriter : public pugi::xml_writer + { + private: + ChunkedBuffer buffer_; + + public: + virtual void write(const void *data, size_t size) + { + if (size > 0) + { + buffer_.AddChunk(reinterpret_cast(data), size); + } + } + + void Flatten(std::string& s) + { + buffer_.Flatten(s); + } + }; + + + static void JsonToXmlInternal(pugi::xml_node& target, + const Json::Value& source, + const std::string& arrayElement) + { + // http://jsoncpp.sourceforge.net/value_8h_source.html#l00030 + + switch (source.type()) + { + case Json::nullValue: + { + target.append_child(pugi::node_pcdata).set_value("null"); + break; + } + + case Json::intValue: + { + std::string s = boost::lexical_cast(source.asInt()); + target.append_child(pugi::node_pcdata).set_value(s.c_str()); + break; + } + + case Json::uintValue: + { + std::string s = boost::lexical_cast(source.asUInt()); + target.append_child(pugi::node_pcdata).set_value(s.c_str()); + break; + } + + case Json::realValue: + { + std::string s = boost::lexical_cast(source.asFloat()); + target.append_child(pugi::node_pcdata).set_value(s.c_str()); + break; + } + + case Json::stringValue: + { + target.append_child(pugi::node_pcdata).set_value(source.asString().c_str()); + break; + } + + case Json::booleanValue: + { + target.append_child(pugi::node_pcdata).set_value(source.asBool() ? "true" : "false"); + break; + } + + case Json::arrayValue: + { + for (Json::Value::ArrayIndex i = 0; i < source.size(); i++) + { + pugi::xml_node node = target.append_child(); + node.set_name(arrayElement.c_str()); + JsonToXmlInternal(node, source[i], arrayElement); + } + break; + } + + case Json::objectValue: + { + Json::Value::Members members = source.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + pugi::xml_node node = target.append_child(); + node.set_name(members[i].c_str()); + JsonToXmlInternal(node, source[members[i]], arrayElement); + } + + break; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void Toolbox::JsonToXml(std::string& target, + const Json::Value& source, + const std::string& rootElement, + const std::string& arrayElement) + { + pugi::xml_document doc; + + pugi::xml_node n = doc.append_child(rootElement.c_str()); + JsonToXmlInternal(n, source, arrayElement); + + pugi::xml_node decl = doc.prepend_child(pugi::node_declaration); + decl.append_attribute("version").set_value("1.0"); + decl.append_attribute("encoding").set_value("utf-8"); + + ChunkedBufferWriter writer; + doc.save(writer, " ", pugi::format_default, pugi::encoding_utf8); + writer.Flatten(target); + } + +#endif + + + void Toolbox::ExecuteSystemCommand(const std::string& command, + const std::vector& arguments) + { + // Convert the arguments as a C array + std::vector args(arguments.size() + 2); + + args.front() = const_cast(command.c_str()); + + for (size_t i = 0; i < arguments.size(); i++) + { + args[i + 1] = const_cast(arguments[i].c_str()); + } + + args.back() = NULL; + + int status; + +#if defined(_WIN32) + // http://msdn.microsoft.com/en-us/library/275khfab.aspx + status = static_cast(_spawnvp(_P_OVERLAY, command.c_str(), &args[0])); + +#else + int pid = fork(); + + if (pid == -1) + { + // Error in fork() + LOG(ERROR) << "Cannot fork a child process"; + throw OrthancException(ErrorCode_SystemCommand); + } + else if (pid == 0) + { + // Execute the system command in the child process + execvp(command.c_str(), &args[0]); + + // We should never get here + _exit(1); + } + else + { + // Wait for the system command to exit + waitpid(pid, &status, 0); + } +#endif + + if (status != 0) + { + LOG(ERROR) << "System command failed with status code " << status; + throw OrthancException(ErrorCode_SystemCommand); + } + } + + + bool Toolbox::IsInteger(const std::string& str) + { + std::string s = StripSpaces(str); + + if (s.size() == 0) + { + return false; + } + + size_t pos = 0; + if (s[0] == '-') + { + if (s.size() == 1) + { + return false; + } + + pos = 1; + } + + while (pos < s.size()) + { + if (!isdigit(s[pos])) + { + return false; + } + + pos++; + } + + return true; + } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/Toolbox.h --- a/Core/Toolbox.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Toolbox.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -38,6 +38,10 @@ #include #include +#if ORTHANC_PUGIXML_ENABLED == 1 +#include +#endif + namespace Orthanc { typedef std::vector UriComponents; @@ -48,6 +52,8 @@ namespace Toolbox { + void ServerBarrier(const bool& stopFlag); + void ServerBarrier(); void ToUpperCase(std::string& s); // Inplace version @@ -73,6 +79,10 @@ void SplitUriComponents(UriComponents& components, const std::string& uri); + void TruncateUri(UriComponents& target, + const UriComponents& source, + size_t fromLevel); + bool IsChildUri(const UriComponents& baseUri, const UriComponents& testedUri); @@ -130,5 +140,19 @@ const std::string& source); void CreateDirectory(const std::string& path); + + bool IsExistingFile(const std::string& path); + +#if ORTHANC_PUGIXML_ENABLED == 1 + void JsonToXml(std::string& target, + const Json::Value& source, + const std::string& rootElement = "root", + const std::string& arrayElement = "item"); +#endif + + void ExecuteSystemCommand(const std::string& command, + const std::vector& arguments); + + bool IsInteger(const std::string& str); } } diff -r f894be6e7cc1 -r 111e23bb4904 Core/Uuid.cpp --- a/Core/Uuid.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Uuid.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 Core/Uuid.h --- a/Core/Uuid.h Wed Jun 25 15:34:40 2014 +0200 +++ b/Core/Uuid.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -43,6 +43,8 @@ * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid **/ +#include "Toolbox.h" + namespace Orthanc { namespace Toolbox @@ -69,6 +71,16 @@ { return path_; } + + void Write(const std::string& content) + { + Toolbox::WriteFile(content, path_); + } + + void Read(std::string& content) const + { + Toolbox::ReadFile(content, path_); + } }; } } diff -r f894be6e7cc1 -r 111e23bb4904 DarwinCompilation.txt --- a/DarwinCompilation.txt Wed Jun 25 15:34:40 2014 +0200 +++ b/DarwinCompilation.txt Thu May 21 16:58:30 2015 +0200 @@ -28,7 +28,7 @@ ---------------------------- # cd ~/OrthancBuild -# cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON .. +# cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DALLOW_DOWNLOADS=ON ~/Orthanc NB: Adapt the value of "CMAKE_OSX_DEPLOYMENT_TARGET" with respect to your version of XCode. diff -r f894be6e7cc1 -r 111e23bb4904 INSTALL --- a/INSTALL Wed Jun 25 15:34:40 2014 +0200 +++ b/INSTALL Thu May 21 16:58:30 2015 +0200 @@ -54,16 +54,18 @@ -Native Windows build with Microsoft Visual Studio 2005 ------------------------------------------------------- +Native Windows build with Microsoft Visual Studio +------------------------------------------------- # cd [...]\OrthancBuild -# cmake -DSTANDALONE_BUILD=ON -G "Visual Studio 8 2005" [...]\Orthanc +# cmake -DSTANDALONE_BUILD=ON -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON -G "Visual Studio 8 2005" [...]\Orthanc Then open the "[...]/OrthancBuild/Orthanc.sln" with Visual Studio. NOTES: -* More recent versions of Visual Studio should also work. +* More recent versions of Visual Studio than 2005 should also + work. Type "cmake" without arguments to have the list of generators + that are available on your computer. * You will have to install the Platform SDK (version 6 or above) for Visual Studio 2005: http://en.wikipedia.org/wiki/Microsoft_Windows_SDK. diff -r f894be6e7cc1 -r 111e23bb4904 LinuxCompilation.txt --- a/LinuxCompilation.txt Wed Jun 25 15:34:40 2014 +0200 +++ b/LinuxCompilation.txt Thu May 21 16:58:30 2015 +0200 @@ -9,15 +9,17 @@ statically linking against all the third-party dependencies. In this case, the system-wide libraries will not be used. The build tool (CMake) will download the sources of all the required packages and -automatically compile them. This process should work on all the Linux -distributions. +automatically compile them. -We make the assumption that Orthanc source code is placed in the -folder "~/Orthanc" and that the binaries will be compiled to -"~/OrthancBuild". +This process should work on any Linux distribution, provided that a +C/C++ compiler ("build-essential" in Debian-based systems), the Python +interpreter, CMake, the "unzip" system tool, and the development +package for libuuid ("uuid-dev" in Debian) are installed. -To build binaries with debug information: +We now make the assumption that Orthanc source code is placed in the +folder "~/Orthanc" and that the binaries will be compiled to +"~/OrthancBuild". To build binaries with debug information: # cd ~/OrthancBuild # cmake -DSTATIC_BUILD=ON -DCMAKE_BUILD_TYPE=Debug ~/Orthanc @@ -33,10 +35,17 @@ # make doc -Note: When the "STATIC_BUILD" option is set to "ON", the build tool +Note 1- When the "STATIC_BUILD" option is set to "ON", the build tool will not ask you the permission to download packages from the Internet. +Note 2- If the development package of libuuid was not installed when +first invoking cmake, you will have to manually remove the build +directory ("rm -rf ~/OrthancBuild") after installing this package, +then run cmake again. + +Note 3- To build the documentation, you will have to install doxyen. + Use system-wide libraries under Linux ===================================== @@ -51,6 +60,7 @@ # cmake -DCMAKE_BUILD_TYPE=Debug ~/Orthanc # make +Note that to build the documentation, you will have to install doxyen. However, on some Linux distributions, it is still required to download and static link against some third-party dependencies, e.g. when the @@ -77,6 +87,7 @@ -DUSE_SYSTEM_DCMTK=OFF \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ + -DUSE_SYSTEM_PUGIXML=OFF \ -DENABLE_JPEG=OFF \ -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -95,6 +106,7 @@ -DUSE_SYSTEM_GOOGLE_LOG=OFF \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_SYSTEM_PUGIXML=OFF \ -DENABLE_JPEG=OFF \ -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -107,35 +119,34 @@ uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ libgoogle-glog-dev libgtest-dev libpng-dev \ libsqlite3-dev libssl-dev zlib1g-dev libdcmtk2-dev \ - libboost-all-dev libwrap0-dev libjsoncpp-dev + libboost-all-dev libwrap0-dev libjsoncpp-dev libpugixml-dev # cmake -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ - -DENABLE_JPEG=OFF \ - -DENABLE_JPEG_LOSSLESS=OFF \ + -DDCMTK_LIBRARIES=dcmjpls \ ~/Orthanc Note: Have also a look at the official package: http://anonscm.debian.org/viewvc/debian-med/trunk/packages/orthanc/trunk/debian/ -SUPPORTED - Ubuntu 12.04 LTS ----------------------------- +SUPPORTED - Ubuntu 12.04.5 LTS +------------------------------ # sudo apt-get install build-essential unzip cmake mercurial \ uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ libgtest-dev libpng-dev libsqlite3-dev libssl-dev \ - zlib1g-dev libdcmtk2-dev libboost-all-dev libwrap0-dev + zlib1g-dev libdcmtk2-dev libboost1.48-all-dev libwrap0-dev \ + libcharls-dev -# cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ +# cmake "-DDCMTK_LIBRARIES=boost_locale;CharLS;dcmjpls;wrap;oflog" \ -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ -DUSE_SYSTEM_GOOGLE_LOG=OFF \ + -DUSE_SYSTEM_PUGIXML=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ - -DENABLE_JPEG=OFF \ - -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -155,6 +166,7 @@ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_SYSTEM_PUGIXML=OFF \ ~/Orthanc @@ -165,6 +177,7 @@ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_SYSTEM_PUGIXML=OFF \ -DENABLE_JPEG=OFF \ -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -183,29 +196,52 @@ -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_SYSTEM_PUGIXML=OFF \ -DENABLE_JPEG=OFF \ -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc -SUPPORTED - Fedora 18/19/20 ---------------------------- +SUPPORTED - Fedora 19/20 +------------------------ # sudo yum install make automake gcc gcc-c++ python cmake \ boost-devel curl-devel dcmtk-devel glog-devel \ gtest-devel libpng-devel libsqlite3x-devel libuuid-devel \ - mongoose-devel openssl-devel jsoncpp-devel lua-devel + mongoose-devel openssl-devel jsoncpp-devel lua-devel pugixml-devel -# cmake -DENABLE_JPEG=OFF \ - -DENABLE_JPEG_LOSSLESS=OFF \ - ~/Orthanc +# cmake "-DDCMTK_LIBRARIES=CharLS" \ + -DSYSTEM_MONGOOSE_USE_CALLBACKS=OFF \ + ~/Orthanc Note: Have also a look at the official package: http://pkgs.fedoraproject.org/cgit/orthanc.git/tree/?h=f18 +SUPPORTED - Ubuntu 14.04 LTS +---------------------------- + +# cmake -DALLOW_DOWNLOADS=ON \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DDCMTK_LIBRARIES=dcmjpls \ + ~/Orthanc + + + +SUPPORTED - FreeBSD 10.1 +------------------------ + +# pkg install jsoncpp pugixml lua51 curl googletest dcmtk cmake \ + e2fsprogs-libuuid glog boost-libs sqlite3 python libiconv + +# cmake -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DDCMTK_LIBRARIES="dcmdsig;charls;dcmjpls" \ + ~/Orthanc + Other Linux distributions? diff -r f894be6e7cc1 -r 111e23bb4904 NEWS --- a/NEWS Wed Jun 25 15:34:40 2014 +0200 +++ b/NEWS Thu May 21 16:58:30 2015 +0200 @@ -1,9 +1,193 @@ Pending changes in the mainline =============================== -* Official support of OS X (Darwin) +Major +----- + +* "?expand" flag for URIs "/patients", "/studies" and "/series" +* "/tools/find" URI to search for DICOM resources from REST +* Support of FreeBSD + +Minor +----- + +* Speed-up in Orthanc Explorer for large amount of images +* Speed-up of the C-Find SCP server of Orthanc +* Allow replacing PatientID/StudyInstanceUID/SeriesInstanceUID from Lua scripts + +Fixes +----- + +* Prevent freeze on C-FIND if no DICOM tag is to be returned +* Fix slow C-Store SCP on recent versions of Linux, if + USE_SYSTEM_DCMTK is set to OFF (http://forum.dcmtk.org/viewtopic.php?f=1&t=4009) +* Fix issue 30 (QR response missing "Query/Retrieve Level" (008,0052)) +* Fix issue 32 (Cyrillic symbols): Introduction of the "Windows1251" encoding +* Plugins now receive duplicated GET arguments in their REST callbacks + + +Version 0.8.6 (2015/02/12) +========================== + +Major +----- + +* URIs to get all the parents of a given resource in a single REST call +* Instances without PatientID are now allowed +* Support of HTTP proxy to access Orthanc peers + +Minor +----- + +* Support of Tudor DICOM in Query/Retrieve +* More flexible "/modify" and "/anonymize" for single instance +* Access to called AET and remote AET from Lua scripts ("OnStoredInstance") +* Option "DicomAssociationCloseDelay" to set delay before closing DICOM association +* ZIP archives now display the accession number of the studies + +Plugins +------- + +* Introspection of plugins (cf. the "/plugins" URI) +* Plugins can access the command-line arguments used to launch Orthanc +* Plugins can extend Orthanc Explorer with custom JavaScript +* Plugins can get/set global properties to save their configuration +* Plugins can do REST calls to other plugins (cf. "xxxAfterPlugins()") +* Scan of folders for plugins + +Fixes +----- + +* Code refactorings +* Fix issue 25 (AET with underscore not allowed) +* Fix replacement and insertion of private DICOM tags +* Fix anonymization generating non-portable DICOM files + + +Version 0.8.5 (2014/11/04) +========================== + +General +------- + +* Major speed-up thanks to a new database schema +* Plugins can monitor changes through callbacks +* Download ZIP + DICOMDIR from Orthanc Explorer +* Sample plugin framework to serve static resources (./Plugins/Samples/WebSkeleton/) + +Fixes +----- + +* Fix issue 19 (YBR_FULL are decoded incorrectly) +* Fix issue 21 (Microsoft Visual Studio precompiled headers) +* Fix issue 22 (Error decoding multi-frame instances) +* Fix issue 24 (Build fails on OSX when directory has .DS_Store files) +* Fix crash when bad HTTP credentials are provided + + +Version 0.8.4 (2014/09/19) +========================== + +* "/instances-tags" to get the tags of all the child instances of a + patient/study/series with a single REST call (bulk tags retrieval) +* Configuration/Lua to select the accepted C-Store SCP transfer syntaxes +* Fix reporting of errors in Orthanc Explorer when sending images to peers/modalities +* Installation of plugin SDK in CMake + + +Version 0.8.3 (2014/09/11) +========================== + +Major +----- + +* Creation of ZIP archives for media storage, with DICOMDIR +* URIs to get all the children of a given resource in a single REST call +* "/tools/lookup" URI to map DICOM UIDs to Orthanc identifiers +* Support of index-only mode (using the "StoreDicom" option) +* Plugins can implement a custom storage area + +Minor +----- + +* Configuration option to enable HTTP Keep-Alive +* Configuration option to disable the logging of exported resources in "/exports" +* Plugins can retrieve the path to Orthanc and to its configuration file +* "/tools/create-dicom" now accepts the "PatientID" DICOM tag (+ updated sample) +* Possibility to set HTTP headers from plugins +* "LastUpdate" metadata is now always returned for patients, studies and series + +Maintenance +----------- + +* Refactoring of HttpOutput ("Content-Length" header is now always sent) +* Upgrade to Mongoose 3.8 +* Fixes for Visual Studio 2013 and Windows 64bit +* Fix issue 16: Handling of "AT" value representations in JSON +* Fix issue 17 + + +Version 0.8.2 (2014/08/07) +========================== + +* Support of the standard text encodings +* Hot restart of Orthanc by posting to "/tools/reset" +* More fault-tolerant commands in Lua scripts +* Parameter to set the default encoding for DICOM files without SpecificCharacterSet +* Fix of issue #14 (support of XCode 5.1) +* Upgrade to Google Test 1.7.0 + + +Version 0.8.1 (2014/07/29) +========================== + +General +------- + +* Access patient module at the study level to cope with PatientID collisions +* On-the-fly conversion of JSON to XML according to the HTTP Accept header +* C-Echo SCU in the REST API +* DICOM conformance statement available at URI "/tools/dicom-conformance" + +Lua scripts +----------- + +* Lua scripts can do HTTP requests, and thus can call Web services +* Lua scripts can invoke system commands, with CallSystem() + +Plugins +------- + +* Lookup for DICOM UIDs in the plugin SDK +* Plugins have access to the HTTP headers and can answer with HTTP status codes +* Callback to react to the incoming of DICOM instances + +Fixes +----- + +* Fix build of Google Log with Visual Studio >= 11.0 +* Fix automated generation of the list of resource children in the REST API + + +Version 0.8.0 (2014/07/10) +========================== + +Major changes +------------- + +* Routing images with Lua scripts +* Introduction of the Orthanc Plugin SDK +* Official support of OS X (Darwin) 10.8 + +Minor changes +------------- + +* Extraction of tags for the patient/study/series/instance DICOM modules +* Extraction of the tags shared by all the instances of a patient/study/series * Options to limit the number of results for an incoming C-FIND query * Support of kFreeBSD +* Several code refactorings +* Fix OrthancCppClient::GetVoxelSizeZ() Version 0.7.6 (2014/06/11) @@ -21,7 +205,7 @@ * Creation of DICOM instances using the REST API * Embedding of images within DICOM instances * Adding/removal/modification of remote modalities/peers through REST -* Reuse of the previous SCU connection to avoid unecessary handshakes +* Reuse of the previous SCU connection to avoid unnecessary handshakes * Fix problems with anonymization and modification * Fix missing licensing terms about reuse of some code from DCMTK * Various code refactorings @@ -150,7 +334,7 @@ ========================== * "Bulk" Store-SCU (send several DICOM instances with the same - DICOM connexion) + DICOM connection) * Store-SCU for patients and studies in Orthanc Explorer * Filtering of incoming DICOM instances (through Lua scripting) * Filtering of incoming HTTP requests (through Lua scripting) @@ -175,7 +359,7 @@ ------------- * Download of modified or anonymized DICOM instances -* Inplace modification and anymization of DICOM series, studies and patients +* Inplace modification and anonymization of DICOM series, studies and patients Minor changes ------------- diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/Instance.cpp --- a/OrthancCppClient/Instance.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/Instance.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/Instance.h --- a/OrthancCppClient/Instance.h Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/Instance.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/OrthancClientException.h --- a/OrthancCppClient/OrthancClientException.h Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/OrthancClientException.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/OrthancConnection.cpp --- a/OrthancCppClient/OrthancConnection.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/OrthancConnection.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/OrthancConnection.h --- a/OrthancCppClient/OrthancConnection.h Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/OrthancConnection.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/OrthancCppClient.cpp --- a/OrthancCppClient/OrthancCppClient.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/OrthancCppClient.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/Patient.cpp --- a/OrthancCppClient/Patient.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/Patient.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/Patient.h --- a/OrthancCppClient/Patient.h Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/Patient.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/Series.cpp --- a/OrthancCppClient/Series.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/Series.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -53,11 +53,12 @@ /** * Compute the slice normal from Image Orientation Patient. * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice + * http://dicomiseasy.blogspot.be/2013/06/getting-oriented-using-image-plane.html * http://www.itk.org/pipermail/insight-users/2003-September/004762.html **/ std::vector cosines; - someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient"); + someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient"); // 0020-0037 if (cosines.size() != 6) { @@ -76,7 +77,7 @@ float ComputeSliceLocation(Instance& instance) const { std::vector ipp; - instance.SplitVectorOfFloats(ipp, "ImagePositionPatient"); + instance.SplitVectorOfFloats(ipp, "ImagePositionPatient"); // 0020-0032 if (ipp.size() != 3) { throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); @@ -129,7 +130,7 @@ if (instance_.GetPixelFormat() == format_) { - memcpy(p, instance_.GetBuffer(y), 2 * instance_.GetWidth()); + memcpy(p, instance_.GetBuffer(y), GetBytesPerPixel(instance_.GetPixelFormat()) * instance_.GetWidth()); } else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 && format_ == PixelFormat_RGB24) @@ -212,32 +213,77 @@ { if (GetInstanceCount() == 0) { + // Empty image, use some default value (should never happen) + voxelSizeX_ = 1; + voxelSizeY_ = 1; + voxelSizeZ_ = 1; + sliceThickness_ = 1; + return true; } - Instance& i1 = GetInstance(0); + // Choose a reference slice + Instance& reference = GetInstance(0); + // Check that all the child instances share the same 3D parameters for (unsigned int i = 0; i < GetInstanceCount(); i++) { Instance& i2 = GetInstance(i); - if (std::string(i1.GetTagAsString("Columns")) != std::string(i2.GetTagAsString("Columns")) || - std::string(i1.GetTagAsString("Rows")) != std::string(i2.GetTagAsString("Rows")) || - std::string(i1.GetTagAsString("ImageOrientationPatient")) != std::string(i2.GetTagAsString("ImageOrientationPatient")) || - std::string(i1.GetTagAsString("SliceThickness")) != std::string(i2.GetTagAsString("SliceThickness")) || - std::string(i1.GetTagAsString("PixelSpacing")) != std::string(i2.GetTagAsString("PixelSpacing"))) + if (std::string(reference.GetTagAsString("Columns")) != std::string(i2.GetTagAsString("Columns")) || + std::string(reference.GetTagAsString("Rows")) != std::string(i2.GetTagAsString("Rows")) || + std::string(reference.GetTagAsString("ImageOrientationPatient")) != std::string(i2.GetTagAsString("ImageOrientationPatient")) || + std::string(reference.GetTagAsString("SliceThickness")) != std::string(i2.GetTagAsString("SliceThickness")) || + std::string(reference.GetTagAsString("PixelSpacing")) != std::string(i2.GetTagAsString("PixelSpacing"))) { return false; } } - SliceLocator locator(GetInstance(0)); + + // Extract X/Y voxel size and slice thickness + std::string s = GetInstance(0).GetTagAsString("PixelSpacing"); // 0028-0030 + size_t pos = s.find('\\'); + assert(pos != std::string::npos); + std::string sy = s.substr(0, pos); + std::string sx = s.substr(pos + 1); + + try + { + voxelSizeX_ = boost::lexical_cast(sx); + voxelSizeY_ = boost::lexical_cast(sy); + } + catch (boost::bad_lexical_cast) + { + throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); + } + + sliceThickness_ = GetInstance(0).GetTagAsFloat("SliceThickness"); // 0018-0050 + + + // Compute the location of each slice to extract the voxel size along Z + voxelSizeZ_ = std::numeric_limits::infinity(); + + SliceLocator locator(reference); + float referenceSliceLocation = locator.ComputeSliceLocation(reference); + std::set l; for (unsigned int i = 0; i < GetInstanceCount(); i++) { - l.insert(locator.ComputeSliceLocation(GetInstance(i))); + float location = locator.ComputeSliceLocation(GetInstance(i)); + float distanceToReferenceSlice = fabs(location - referenceSliceLocation); + + l.insert(location); + + if (distanceToReferenceSlice > std::numeric_limits::epsilon() && + distanceToReferenceSlice < voxelSizeZ_) + { + voxelSizeZ_ = distanceToReferenceSlice; + } } + + // Make sure that 2 slices do not share the same Z location return l.size() == GetInstanceCount(); } catch (OrthancClientException) @@ -275,10 +321,10 @@ status_ = Status3DImage_NotTested; url_ = std::string(connection_.GetOrthancUrl()) + "/series/" + id_; - isVoxelSizeRead_ = false; voxelSizeX_ = 0; voxelSizeY_ = 0; voxelSizeZ_ = 0; + sliceThickness_ = 0; instances_.SetThreadCount(connection.GetThreadCount()); } @@ -324,46 +370,6 @@ return GetInstance(0).GetTagAsInt("Rows"); } - void Series::LoadVoxelSize() - { - if (isVoxelSizeRead_) - { - return; - } - - Check3DImage(); - - if (GetInstanceCount() == 0) - { - // Empty image, use some default value - voxelSizeX_ = 1; - voxelSizeY_ = 1; - voxelSizeZ_ = 1; - } - else - { - try - { - std::string s = GetInstance(0).GetTagAsString("PixelSpacing"); - size_t pos = s.find('\\'); - assert(pos != std::string::npos); - std::string sy = s.substr(0, pos); - std::string sx = s.substr(pos + 1); - - voxelSizeX_ = boost::lexical_cast(sx); - voxelSizeY_ = boost::lexical_cast(sy); - voxelSizeZ_ = GetInstance(0).GetTagAsFloat("SliceThickness"); - } - catch (boost::bad_lexical_cast) - { - throw OrthancClientException(Orthanc::ErrorCode_NotImplemented); - } - } - - isVoxelSizeRead_ = true; - } - - const char* Series::GetMainDicomTag(const char* tag, const char* defaultValue) const { if (series_["MainDicomTags"].isMember(tag)) @@ -486,22 +492,28 @@ float Series::GetVoxelSizeX() { - LoadVoxelSize(); + Check3DImage(); // Is3DImageInternal() will compute the voxel sizes return voxelSizeX_; } float Series::GetVoxelSizeY() { - LoadVoxelSize(); + Check3DImage(); // Is3DImageInternal() will compute the voxel sizes return voxelSizeY_; } float Series::GetVoxelSizeZ() { - LoadVoxelSize(); + Check3DImage(); // Is3DImageInternal() will compute the voxel sizes return voxelSizeZ_; } + float Series::GetSliceThickness() + { + Check3DImage(); // Is3DImageInternal() will compute the voxel sizes + return sliceThickness_; + } + void Series::Load3DImage(void* target, Orthanc::PixelFormat format, int64_t lineStride, diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/Series.h --- a/OrthancCppClient/Series.h Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/Series.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -62,11 +62,11 @@ Orthanc::ArrayFilledByThreads instances_; Status3DImage status_; - bool isVoxelSizeRead_; float voxelSizeX_; float voxelSizeY_; float voxelSizeZ_; - + float sliceThickness_; + void Check3DImage(); bool Is3DImageInternal(); @@ -86,8 +86,6 @@ size_t stackStride, Orthanc::ThreadedCommandProcessor::IListener* listener); - void LoadVoxelSize(); - public: /** * {summary}{Create a connection to some series.} @@ -189,6 +187,13 @@ **/ float GetVoxelSizeZ(); + /** + * {summary}{Get the slice thickness.} + * {description}{Get the slice thickness. This call is only valid if this series corresponds to a 3D image.} + * {returns}{The slice thickness.} + **/ + float GetSliceThickness(); + LAAW_API_INTERNAL void Load3DImage(void* target, Orthanc::PixelFormat format, int64_t lineStride, diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Thu May 21 16:58:30 2015 +0200 @@ -795,6 +795,29 @@ } } + LAAW_EXPORT_DLL_API char* LAAW_CALL_CONVENTION LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497(void* thisObject, float* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast(thisObject); +*result = this_->GetSliceThickness(); + + return NULL; + } + catch (::Laaw::LaawException& e) + { + return LAAW_EXTERNC_CopyString(e.What()); + } + catch (...) + { + return LAAW_EXTERNC_CopyString("..."); + } + } + LAAW_EXPORT_DLL_API char* LAAW_CALL_CONVENTION LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5(void* thisObject, void* arg0, int32_t arg1, int64_t arg2, int64_t arg3) { try @@ -1466,7 +1489,7 @@ LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetCompany() { - return "CHU of Liege"; + return "University Hospital of Liege"; } LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetProduct() @@ -1476,22 +1499,22 @@ LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetCopyright() { - return "(c) 2012-2014, Sebastien Jodogne, CHU of Liege"; + return "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege"; } LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetVersion() { - return "0.7"; + return "0.8"; } LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFileVersion() { - return "0.7.0.6"; + return "0.8.0.6"; } LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFullVersion() { - return "0.7.6"; + return "0.8.6"; } LAAW_EXPORT_DLL_API void LAAW_CALL_CONVENTION LAAW_EXTERNC_FreeString(char* str) diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Thu May 21 16:58:30 2015 +0200 @@ -81,9 +81,9 @@ /* cf. http://sourceforge.net/p/predef/wiki/Architectures/ */ #ifdef __amd64__ -#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "libOrthancClient.so.0.7" +#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "libOrthancClient.so.0.8" #else -#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "libOrthancClient.so.0.7" +#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "libOrthancClient.so.0.8" #endif #define LAAW_ORTHANC_CLIENT_CALL_CONV @@ -203,7 +203,7 @@ { private: LAAW_ORTHANC_CLIENT_HANDLE_TYPE handle_; - LAAW_ORTHANC_CLIENT_FUNCTION_TYPE functionsIndex_[62 + 1]; + LAAW_ORTHANC_CLIENT_FUNCTION_TYPE functionsIndex_[63 + 1]; @@ -239,7 +239,7 @@ void FreeString(char* str) { typedef void (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (char*); - Function function = (Function) GetFunction(62); + Function function = (Function) GetFunction(63); function(str); } @@ -302,7 +302,15 @@ { if (handle_ != LAAW_ORTHANC_CLIENT_HANDLE_NULL) { +#if 0 + /** + * Do not explicitly unload the shared library, as it might + * interfere with the destruction of static objects declared + * inside the library (e.g. this is the case of gflags that is + * internally used by googlelog). + **/ LAAW_ORTHANC_CLIENT_CLOSER(handle_); +#endif handle_ = LAAW_ORTHANC_CLIENT_HANDLE_NULL; } } @@ -386,12 +394,12 @@ * It is assumed that the API does not change when the revision * number (MAJOR.MINOR.REVISION) changes. **/ - if (strcmp(getVersion(), "0.7")) + if (strcmp(getVersion(), "0.8")) { throw ::OrthancClient::OrthancClientException("Mismatch between the C++ header and the library version"); } - functionsIndex_[62] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_FreeString", "4"); + functionsIndex_[63] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_FreeString", "4"); functionsIndex_[3] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_557aee7b61817292a0f31269d3c35db7", "8"); functionsIndex_[4] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_0b8dff0ce67f10954a49b059e348837e", "8"); functionsIndex_[5] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e05097c153f676e5a5ee54dcfc78256f", "4"); @@ -423,40 +431,41 @@ functionsIndex_[30] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0", "8"); functionsIndex_[31] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab", "8"); functionsIndex_[32] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d", "8"); - functionsIndex_[33] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5", "28"); - functionsIndex_[34] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c", "32"); + functionsIndex_[33] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497", "8"); + functionsIndex_[34] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5", "28"); + functionsIndex_[35] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c", "32"); functionsIndex_[19] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342", "12"); functionsIndex_[20] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_7c97f17063a357d38c5fab1136ad12a0", "4"); - functionsIndex_[37] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7", "4"); - functionsIndex_[38] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321", "8"); - functionsIndex_[39] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05", "12"); - functionsIndex_[40] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7", "8"); - functionsIndex_[41] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654", "16"); - functionsIndex_[35] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678", "12"); - functionsIndex_[36] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376", "4"); - functionsIndex_[44] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb", "8"); - functionsIndex_[45] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146", "8"); - functionsIndex_[46] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda", "8"); - functionsIndex_[47] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484", "12"); - functionsIndex_[48] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb", "12"); - functionsIndex_[49] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1729a067d902771517388eedd7346b23", "12"); - functionsIndex_[50] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745", "8"); - functionsIndex_[51] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0", "8"); - functionsIndex_[52] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8", "8"); - functionsIndex_[53] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c", "8"); - functionsIndex_[54] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b", "8"); - functionsIndex_[55] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef", "12"); - functionsIndex_[56] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91", "8"); - functionsIndex_[57] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e", "8"); - functionsIndex_[58] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a", "4"); - functionsIndex_[59] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c", "4"); - functionsIndex_[60] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd", "8"); - functionsIndex_[61] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb", "8"); - functionsIndex_[42] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d", "12"); - functionsIndex_[43] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207", "4"); + functionsIndex_[38] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7", "4"); + functionsIndex_[39] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321", "8"); + functionsIndex_[40] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05", "12"); + functionsIndex_[41] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7", "8"); + functionsIndex_[42] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654", "16"); + functionsIndex_[36] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678", "12"); + functionsIndex_[37] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376", "4"); + functionsIndex_[45] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb", "8"); + functionsIndex_[46] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146", "8"); + functionsIndex_[47] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda", "8"); + functionsIndex_[48] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484", "12"); + functionsIndex_[49] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb", "12"); + functionsIndex_[50] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1729a067d902771517388eedd7346b23", "12"); + functionsIndex_[51] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745", "8"); + functionsIndex_[52] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0", "8"); + functionsIndex_[53] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8", "8"); + functionsIndex_[54] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c", "8"); + functionsIndex_[55] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b", "8"); + functionsIndex_[56] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef", "12"); + functionsIndex_[57] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91", "8"); + functionsIndex_[58] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e", "8"); + functionsIndex_[59] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a", "4"); + functionsIndex_[60] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c", "4"); + functionsIndex_[61] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd", "8"); + functionsIndex_[62] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb", "8"); + functionsIndex_[43] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d", "12"); + functionsIndex_[44] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207", "4"); /* Check whether the functions were properly loaded */ - for (unsigned int i = 0; i <= 62; i++) + for (unsigned int i = 0; i <= 63; i++) { if (functionsIndex_[i] == (LAAW_ORTHANC_CLIENT_FUNCTION_TYPE) NULL) { @@ -704,6 +713,7 @@ inline float GetVoxelSizeX(); inline float GetVoxelSizeY(); inline float GetVoxelSizeZ(); + inline float GetSliceThickness(); inline void Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride); inline void Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride, float progress[]); }; @@ -1323,6 +1333,22 @@ return result_; } /** + * @brief Get the slice thickness. + * + * Get the slice thickness. This call is only valid if this series corresponds to a 3D image. + * + * @return The slice thickness. + **/ + inline float Series::GetSliceThickness() + { + float result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, float*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(33); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** * @brief Load the 3D image into a memory buffer. * * Load the 3D image into a memory buffer. This call is only valid if this series corresponds to a 3D image. The "target" buffer must be wide enough to store all the voxels of the image. @@ -1335,7 +1361,7 @@ inline void Series::Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride) { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void*, LAAW_INT32, LAAW_INT64, LAAW_INT64); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(33); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(34); char* error = function(pimpl_, target, format, lineStride, stackStride); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1353,7 +1379,7 @@ inline void Series::Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride, float progress[]) { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void*, LAAW_INT32, LAAW_INT64, LAAW_INT64, float*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(34); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(35); char* error = function(pimpl_, target, format, lineStride, stackStride, progress); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1373,7 +1399,7 @@ { isReference_ = false; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(35); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(36); char* error = function(&pimpl_, connection.pimpl_, id.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1387,7 +1413,7 @@ { if (isReference_) return; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(36); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(37); char* error = function(pimpl_); error = error; // Remove warning about unused variable } @@ -1400,7 +1426,7 @@ inline void Study::Reload() { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(37); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(38); char* error = function(pimpl_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1415,7 +1441,7 @@ { LAAW_UINT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(38); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(39); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1432,7 +1458,7 @@ { void* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void**, LAAW_UINT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(39); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(40); char* error = function(pimpl_, &result_, index); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return ::OrthancClient::Series(result_); @@ -1448,7 +1474,7 @@ { const char* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(40); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(41); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return std::string(result_); @@ -1466,7 +1492,7 @@ { const char* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**, const char*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(41); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(42); char* error = function(pimpl_, &result_, tag.c_str(), defaultValue.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return std::string(result_); @@ -1487,7 +1513,7 @@ { isReference_ = false; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(42); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(43); char* error = function(&pimpl_, connection.pimpl_, id.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1501,7 +1527,7 @@ { if (isReference_) return; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(43); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(44); char* error = function(pimpl_); error = error; // Remove warning about unused variable } @@ -1516,7 +1542,7 @@ { const char* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(44); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(45); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return std::string(result_); @@ -1531,7 +1557,7 @@ inline void Instance::SetImageExtractionMode(::Orthanc::ImageExtractionMode mode) { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_INT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(45); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(46); char* error = function(pimpl_, mode); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1546,7 +1572,7 @@ { LAAW_INT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, LAAW_INT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(46); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(47); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return static_cast< ::Orthanc::ImageExtractionMode >(result_); @@ -1563,7 +1589,7 @@ { const char* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(47); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(48); char* error = function(pimpl_, &result_, tag.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return std::string(result_); @@ -1580,7 +1606,7 @@ { float result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, float*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(48); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(49); char* error = function(pimpl_, &result_, tag.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1597,7 +1623,7 @@ { LAAW_INT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, LAAW_INT32*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(49); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(50); char* error = function(pimpl_, &result_, tag.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1613,7 +1639,7 @@ { LAAW_UINT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(50); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(51); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1629,7 +1655,7 @@ { LAAW_UINT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(51); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(52); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1645,7 +1671,7 @@ { LAAW_UINT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(52); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(53); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1661,7 +1687,7 @@ { LAAW_INT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_INT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(53); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(54); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return static_cast< ::Orthanc::PixelFormat >(result_); @@ -1677,7 +1703,7 @@ { const void* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(54); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(55); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return reinterpret_cast< const void* >(result_); @@ -1694,7 +1720,7 @@ { const void* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**, LAAW_UINT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(55); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(56); char* error = function(pimpl_, &result_, y); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return reinterpret_cast< const void* >(result_); @@ -1710,7 +1736,7 @@ { LAAW_UINT64 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT64*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(56); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(57); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1726,7 +1752,7 @@ { const void* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(57); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(58); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return reinterpret_cast< const void* >(result_); @@ -1740,7 +1766,7 @@ inline void Instance::DiscardImage() { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(58); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(59); char* error = function(pimpl_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1753,7 +1779,7 @@ inline void Instance::DiscardDicom() { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(59); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(60); char* error = function(pimpl_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1767,7 +1793,7 @@ inline void Instance::LoadTagContent(const ::std::string& path) { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(60); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(61); char* error = function(pimpl_, path.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1782,7 +1808,7 @@ { const char* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(61); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(62); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return std::string(result_); diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def Thu May 21 16:58:30 2015 +0200 @@ -31,6 +31,7 @@ _LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0@8 = LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0@8 _LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab@8 = LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab@8 _LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d@8 = LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d@8 + _LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497@8 = LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497@8 _LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5@28 = LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5@28 _LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c@32 = LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c@32 _LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342@12 = LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342@12 diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Thu May 21 16:58:30 2015 +0200 @@ -1,8 +1,8 @@ #include VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,7,0,6 - PRODUCTVERSION 0,7,0,0 + FILEVERSION 0,8,0,6 + PRODUCTVERSION 0,8,0,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL BEGIN @@ -10,16 +10,16 @@ BEGIN BLOCK "040904E4" BEGIN - VALUE "Comments", "Release 0.7.6" - VALUE "CompanyName", "CHU of Liege" + VALUE "Comments", "Release 0.8.6" + VALUE "CompanyName", "University Hospital of Liege" VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.7.0.6" + VALUE "FileVersion", "0.8.0.6" VALUE "InternalName", "OrthancClient" - VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, CHU of Liege" - VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/" + VALUE "LegalCopyright", "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege" + VALUE "LegalTrademarks", "Licensing information is available at http://www.orthanc-server.com/" VALUE "OriginalFilename", "OrthancClient_Windows32.dll" VALUE "ProductName", "OrthancClient" - VALUE "ProductVersion", "0.7" + VALUE "ProductVersion", "0.8" END END diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def Thu May 21 16:58:30 2015 +0200 @@ -31,6 +31,7 @@ LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0 LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d + LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497 LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5 LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342 diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Thu May 21 16:58:30 2015 +0200 @@ -1,8 +1,8 @@ #include VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,7,0,6 - PRODUCTVERSION 0,7,0,0 + FILEVERSION 0,8,0,6 + PRODUCTVERSION 0,8,0,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL BEGIN @@ -10,16 +10,16 @@ BEGIN BLOCK "040904E4" BEGIN - VALUE "Comments", "Release 0.7.6" - VALUE "CompanyName", "CHU of Liege" + VALUE "Comments", "Release 0.8.6" + VALUE "CompanyName", "University Hospital of Liege" VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.7.0.6" + VALUE "FileVersion", "0.8.0.6" VALUE "InternalName", "OrthancClient" - VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, CHU of Liege" - VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/" + VALUE "LegalCopyright", "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege" + VALUE "LegalTrademarks", "Licensing information is available at http://www.orthanc-server.com/" VALUE "OriginalFilename", "OrthancClient_Windows64.dll" VALUE "ProductName", "OrthancClient" - VALUE "ProductVersion", "0.7" + VALUE "ProductVersion", "0.8" END END diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/SharedLibrary/Product.json --- a/OrthancCppClient/SharedLibrary/Product.json Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/Product.json Thu May 21 16:58:30 2015 +0200 @@ -1,8 +1,8 @@ { "Product" : "OrthancClient", "Description" : "Native client to the REST API of Orthanc", - "Company" : "CHU of Liege", - "Copyright" : "(c) 2012-2014, Sebastien Jodogne, CHU of Liege", - "Legal" : "Licensing information is available on https://code.google.com/p/orthanc/", - "Version" : "0.7.6" + "Company" : "University Hospital of Liege", + "Copyright" : "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege", + "Legal" : "Licensing information is available at http://www.orthanc-server.com/", + "Version" : "0.8.6" } diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/SharedLibrary/SharedLibrary.cpp --- a/OrthancCppClient/SharedLibrary/SharedLibrary.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/SharedLibrary.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/Study.cpp --- a/OrthancCppClient/Study.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/Study.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 OrthancCppClient/Study.h --- a/OrthancCppClient/Study.h Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancCppClient/Study.h Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff -r f894be6e7cc1 -r 111e23bb4904 OrthancExplorer/explorer.html --- a/OrthancExplorer/explorer.html Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancExplorer/explorer.html Thu May 21 16:58:30 2015 +0200 @@ -30,11 +30,13 @@ +

Find a patient

+ Plugins Upload DICOM
@@ -46,7 +48,7 @@

Upload DICOM files

- Find patient + Patients
@@ -72,7 +74,7 @@ @@ -125,7 +128,7 @@ Patient » Study - Find patient + Patients Upload DICOM
@@ -173,7 +177,7 @@ Series - Find patient + Patients Upload DICOM
@@ -222,7 +227,7 @@ Series » Instance - Find patient + Patients Upload DICOM
@@ -268,6 +273,18 @@
+
+
+

Plugins

+ Patients +
+
+
    +
+
+
+ +