Mercurial > hg > orthanc
changeset 762:45b16f67259c lua-scripting
integration mainline -> lua-scripting
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 22 Apr 2014 16:47:21 +0200 |
parents | 63f707278fc8 (current diff) b2a62f22fbe8 (diff) |
children | fc97f762834c |
files | Core/Cache/CacheIndex.h Core/PngWriter.cpp Core/PngWriter.h OrthancCppClient/CMakeLists.txt OrthancCppClient/HttpClient.cpp OrthancCppClient/HttpClient.h OrthancCppClient/HttpEnumerations.h OrthancCppClient/HttpException.cpp OrthancCppClient/HttpException.h OrthancCppClient/main.cpp OrthancServer/OrthancRestApi.cpp OrthancServer/OrthancRestApi.h Resources/Archives/MessageWithDestination.cpp Resources/Archives/PrepareDatabase-v1.sql Resources/Samples/RestApi/CMakeLists.txt Resources/Samples/RestApi/Sample.cpp Resources/Samples/RestApiLinuxDynamic/AutoGeneratedCode.cmake Resources/Samples/RestApiLinuxDynamic/CMakeLists.txt Resources/Samples/RestApiLinuxDynamic/README.txt Resources/Samples/RestApiLinuxDynamic/Sample.cpp Resources/sha1/Makefile Resources/sha1/Makefile.nt Resources/sha1/license.txt Resources/sha1/sha.cpp Resources/sha1/sha1.cpp Resources/sha1/sha1.h Resources/sha1/shacmp.cpp Resources/sha1/shatest.cpp UnitTests/FileStorage.cpp UnitTests/Lua.cpp UnitTests/MemoryCache.cpp UnitTests/PngWriter.cpp UnitTests/RestApi.cpp UnitTests/SQLite.cpp UnitTests/SQLiteChromium.cpp UnitTests/ServerIndex.cpp UnitTests/Versions.cpp UnitTests/Zip.cpp UnitTests/main.cpp |
diffstat | 292 files changed, 24450 insertions(+), 9383 deletions(-) [+] |
line wrap: on
line diff
--- a/AUTHORS Fri May 03 12:23:02 2013 +0200 +++ b/AUTHORS Tue Apr 22 16:47:21 2014 +0200 @@ -11,6 +11,19 @@ Overall design and main developper. +Client library +-------------- + +The client library of Orthanc is automatically wrapped from the C++ +code using the LAAW software. LAAW is the Lightweight, Automated API +Wrapper from the Jomago team, that comes from the following authors: + +* Sebastien Jodogne <s.jodogne@gmail.com> +* Alain Mazy <alain@mazy.be> +* Benjamin Golinvaux <golinvauxb@gmail.com> + +LAAW should be soon released as a separate open-source project. + Contributors ------------
--- a/CMakeLists.txt Fri May 03 12:23:02 2013 +0200 +++ b/CMakeLists.txt Tue Apr 22 16:47:21 2014 +0200 @@ -3,34 +3,39 @@ project(Orthanc) # Version of the build, should always be "mainline" except in release branches -add_definitions( - -DORTHANC_VERSION="mainline" - ) +set(ORTHANC_VERSION "mainline") + + +##################################################################### +## CMake parameters tunable at the command line +##################################################################### # Parameters of the build -SET(STATIC_BUILD ON CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") -SET(STANDALONE_BUILD OFF CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)") +SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +SET(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)") SET(ENABLE_SSL ON CACHE BOOL "Include support for SSL") -SET(BUILD_UNIT_TESTS ON CACHE BOOL "Build the unit tests") +SET(BUILD_CLIENT_LIBRARY ON CACHE BOOL "Build the client library") +SET(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (only when using system version of DCMTK)") +SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") +SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests") -# Advanced parameters (for Debian packaging) -SET(USE_DYNAMIC_JSONCPP OFF CACHE BOOL "Use the dynamic version of JsonCpp (only for Debian sid)") -SET(USE_DYNAMIC_GOOGLE_LOG ON CACHE BOOL "Use the dynamic version of Google Log") -SET(USE_DYNAMIC_GOOGLE_TEST ON CACHE BOOL "Use the dynamic version of Google Test (not for Debian sid)") -SET(USE_DYNAMIC_SQLITE ON CACHE BOOL "Use the dynamic version of SQLite") -SET(USE_DYNAMIC_MONGOOSE OFF CACHE BOOL "Use the dynamic version of Mongoose") -SET(USE_DYNAMIC_LUA OFF CACHE BOOL "Use the dynamic version of Lua") -SET(DEBIAN_FORCE_HARDENING OFF CACHE BOOL "Force the injection of Debian hardening flags (unrecommended)") -SET(DEBIAN_USE_GTEST_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (only for Debian sid)") -SET(ONLY_CORE_LIBRARY OFF CACHE BOOL "Only build the core library") +# Advanced parameters to fine-tune linking against system libraries +SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") +SET(USE_SYSTEM_GOOGLE_LOG ON CACHE BOOL "Use the system version of Google Log") +SET(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") +SET(USE_SYSTEM_SQLITE ON CACHE BOOL "Use the system version of SQLite") +SET(USE_SYSTEM_MONGOOSE ON CACHE BOOL "Use the system version of Mongoose") +SET(USE_SYSTEM_LUA ON CACHE BOOL "Use the system version of Lua") +SET(USE_SYSTEM_DCMTK ON CACHE BOOL "Use the system version of DCMTK") +SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") +SET(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of LibPng") +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") -mark_as_advanced(USE_DYNAMIC_JSONCPP) -mark_as_advanced(USE_DYNAMIC_GOOGLE_LOG) -mark_as_advanced(USE_DYNAMIC_GOOGLE_TEST) -mark_as_advanced(USE_DYNAMIC_SQLITE) -mark_as_advanced(DEBIAN_FORCE_HARDENING) -mark_as_advanced(DEBIAN_USE_STATIC_GOOGLE_TEST) -mark_as_advanced(ONLY_CORE_LIBRARY) +# Distribution-specific settings +SET(USE_GTEST_DEBIAN_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") +mark_as_advanced(USE_GTEST_DEBIAN_SOURCE_PACKAGE) # Some basic inclusions include(CheckIncludeFiles) @@ -40,8 +45,17 @@ include(${CMAKE_SOURCE_DIR}/Resources/CMake/DownloadPackage.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/Compiler.cmake) +set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}) + + + + +##################################################################### +## Inclusion of third-party dependencies +##################################################################### + # Configuration of the standalone builds -if (${CMAKE_CROSSCOMPILING}) +if (CMAKE_CROSSCOMPILING) # Cross-compilation implies the standalone build SET(STANDALONE_BUILD ON) endif() @@ -50,10 +64,19 @@ SET(THIRD_PARTY_SOURCES ${CMAKE_SOURCE_DIR}/Resources/md5/md5.c ${CMAKE_SOURCE_DIR}/Resources/base64/base64.cpp - ${CMAKE_SOURCE_DIR}/Resources/sha1/sha1.cpp ) include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/BoostConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/MongooseConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/ZlibConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/SQLiteConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/JsonCppConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/LuaConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibCurlConfiguration.cmake) + if (${ENABLE_SSL}) add_definitions(-DORTHANC_SSL_ENABLED=1) @@ -62,24 +85,16 @@ add_definitions(-DORTHANC_SSL_ENABLED=0) endif() -include(${CMAKE_SOURCE_DIR}/Resources/CMake/BoostConfiguration.cmake) -if(NOT ONLY_CORE_LIBRARY) - include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake) -endif() -include(${CMAKE_SOURCE_DIR}/Resources/CMake/MongooseConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/ZlibConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/SQLiteConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/JsonCppConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibCurlConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/LuaConfiguration.cmake) - +##################################################################### +## 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 ) @@ -104,11 +119,24 @@ -# The main instructions to build the Orthanc binaries +##################################################################### +## Build the core of Orthanc +##################################################################### + +add_definitions( + -DORTHANC_VERSION="${ORTHANC_VERSION}" + ) + +list(LENGTH OPENSSL_SOURCES OPENSSL_SOURCES_LENGTH) +if (${OPENSSL_SOURCES_LENGTH} GREATER 0) + add_library(OpenSSL STATIC ${OPENSSL_SOURCES}) +endif() + add_library(CoreLibrary STATIC ${AUTOGENERATED_SOURCES} ${THIRD_PARTY_SOURCES} + ${CURL_SOURCES} Core/Cache/MemoryCache.cpp Core/ChunkedBuffer.cpp @@ -122,10 +150,12 @@ Core/DicomFormat/DicomTag.cpp Core/DicomFormat/DicomIntegerPixelAccessor.cpp Core/DicomFormat/DicomInstanceHasher.cpp + Core/Enumerations.cpp Core/FileStorage/FileStorage.cpp Core/FileStorage/StorageAccessor.cpp Core/FileStorage/CompressedFileStorageAccessor.cpp Core/FileStorage/FileStorageAccessor.cpp + Core/HttpClient.cpp Core/HttpServer/EmbeddedResourceHttpHandler.cpp Core/HttpServer/FilesystemHttpHandler.cpp Core/HttpServer/HttpHandler.cpp @@ -136,8 +166,14 @@ Core/RestApi/RestApiPath.cpp Core/RestApi/RestApiOutput.cpp Core/RestApi/RestApi.cpp + Core/MultiThreading/ArrayFilledByThreads.cpp Core/MultiThreading/BagOfRunnablesBySteps.cpp - Core/PngWriter.cpp + Core/MultiThreading/Mutex.cpp + Core/MultiThreading/ReaderWriterLock.cpp + Core/MultiThreading/SharedMessageQueue.cpp + Core/MultiThreading/ThreadedCommandProcessor.cpp + Core/FileFormats/PngReader.cpp + Core/FileFormats/PngWriter.cpp Core/SQLite/Connection.cpp Core/SQLite/FunctionContext.cpp Core/SQLite/Statement.cpp @@ -149,84 +185,263 @@ Core/Lua/LuaContext.cpp Core/Lua/LuaFunctionCall.cpp - OrthancCppClient/HttpClient.cpp - OrthancCppClient/HttpException.cpp + OrthancCppClient/OrthancConnection.cpp + OrthancCppClient/Study.cpp + OrthancCppClient/Series.cpp + OrthancCppClient/Instance.cpp + OrthancCppClient/Patient.cpp ) +##################################################################### +## Build the Orthanc server +##################################################################### + +add_library(ServerLibrary + STATIC + ${DCMTK_SOURCES} + OrthancServer/DicomProtocol/DicomFindAnswers.cpp + OrthancServer/DicomProtocol/DicomServer.cpp + OrthancServer/DicomProtocol/DicomUserConnection.cpp + OrthancServer/FromDcmtkBridge.cpp + OrthancServer/Internals/CommandDispatcher.cpp + OrthancServer/Internals/FindScp.cpp + OrthancServer/Internals/MoveScp.cpp + OrthancServer/Internals/StoreScp.cpp + OrthancServer/OrthancInitialization.cpp + OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp + OrthancServer/OrthancRestApi/OrthancRestApi.cpp + OrthancServer/OrthancRestApi/OrthancRestArchive.cpp + OrthancServer/OrthancRestApi/OrthancRestChanges.cpp + OrthancServer/OrthancRestApi/OrthancRestModalities.cpp + OrthancServer/OrthancRestApi/OrthancRestResources.cpp + OrthancServer/OrthancRestApi/OrthancRestSystem.cpp + OrthancServer/ServerIndex.cpp + OrthancServer/ToDcmtkBridge.cpp + OrthancServer/DatabaseWrapper.cpp + OrthancServer/ServerContext.cpp + OrthancServer/ServerEnumerations.cpp + OrthancServer/ServerToolbox.cpp + OrthancServer/OrthancFindRequestHandler.cpp + OrthancServer/OrthancMoveRequestHandler.cpp + ) + +# Ensure autogenerated code is built before building ServerLibrary +add_dependencies(ServerLibrary CoreLibrary) + +add_executable(Orthanc + OrthancServer/main.cpp + ) + +target_link_libraries(Orthanc ServerLibrary CoreLibrary) + +if (${OPENSSL_SOURCES_LENGTH} GREATER 0) + target_link_libraries(Orthanc OpenSSL) +endif() + +install( + TARGETS Orthanc + RUNTIME DESTINATION sbin + ) + + + +##################################################################### +## Build the unit tests +##################################################################### + +if (UNIT_TESTS_WITH_HTTP_CONNEXIONS) + add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1) +else() + add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=0) +endif() + +add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake) +add_executable(UnitTests + ${GTEST_SOURCES} + UnitTestsSources/DicomMap.cpp + UnitTestsSources/FileStorage.cpp + UnitTestsSources/MemoryCache.cpp + UnitTestsSources/Png.cpp + UnitTestsSources/RestApi.cpp + UnitTestsSources/SQLite.cpp + UnitTestsSources/SQLiteChromium.cpp + UnitTestsSources/ServerIndexTests.cpp + UnitTestsSources/Versions.cpp + UnitTestsSources/Zip.cpp + UnitTestsSources/Lua.cpp + UnitTestsSources/MultiThreading.cpp + UnitTestsSources/UnitTestsMain.cpp + ) +target_link_libraries(UnitTests ServerLibrary CoreLibrary) + +if (${OPENSSL_SOURCES_LENGTH} GREATER 0) + target_link_libraries(UnitTests OpenSSL) +endif() + + -if(NOT ONLY_CORE_LIBRARY) - add_library(ServerLibrary - STATIC - ${DCMTK_SOURCES} - OrthancServer/DicomProtocol/DicomFindAnswers.cpp - OrthancServer/DicomProtocol/DicomServer.cpp - OrthancServer/DicomProtocol/DicomUserConnection.cpp - OrthancServer/FromDcmtkBridge.cpp - OrthancServer/Internals/CommandDispatcher.cpp - OrthancServer/Internals/FindScp.cpp - OrthancServer/Internals/MoveScp.cpp - OrthancServer/Internals/StoreScp.cpp - OrthancServer/OrthancInitialization.cpp - OrthancServer/OrthancRestApi.cpp - OrthancServer/ServerIndex.cpp - OrthancServer/ToDcmtkBridge.cpp - OrthancServer/DatabaseWrapper.cpp - OrthancServer/ServerContext.cpp - OrthancServer/ServerEnumerations.cpp - OrthancServer/ServerToolbox.cpp +##################################################################### +## Create the standalone DLL containing the Orthanc Client API +##################################################################### + +if (BUILD_CLIENT_LIBRARY) + include_directories(${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/Laaw) + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + if (CMAKE_CROSSCOMPILING) + # Remove the default "lib" prefix from "libOrthancClient.dll" if cross-compiling + set(CMAKE_SHARED_LIBRARY_PREFIX "") + + if (${CMAKE_SIZEOF_VOID_P} EQUAL 4) + set(ORTHANC_CPP_CLIENT_AUX ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def) + elseif (${CMAKE_SIZEOF_VOID_P} EQUAL 8) + set(ORTHANC_CPP_CLIENT_AUX ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def) + else() + message(FATAL_ERROR "Support your platform here") + endif() + else() + # Nothing to do if using Visual Studio + endif() + + if (${CMAKE_SIZEOF_VOID_P} EQUAL 4) + set(CMAKE_SHARED_LIBRARY_SUFFIX "_Windows32.dll") + list(APPEND ORTHANC_CPP_CLIENT_AUX ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc) + elseif (${CMAKE_SIZEOF_VOID_P} EQUAL 8) + set(CMAKE_SHARED_LIBRARY_SUFFIX "_Windows64.dll") + list(APPEND ORTHANC_CPP_CLIENT_AUX ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc) + else() + message(FATAL_ERROR "Support your platform here") + endif() + + else() + set(ORTHANC_CPP_CLIENT_AUX ${OPENSSL_SOURCES}) + endif() + + add_library(OrthancClient SHARED + ${ORTHANC_ROOT}/Core/OrthancException.cpp + ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/Toolbox.cpp + ${ORTHANC_ROOT}/Core/HttpClient.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/ArrayFilledByThreads.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/ThreadedCommandProcessor.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp + ${ORTHANC_ROOT}/Core/FileFormats/PngReader.cpp + ${ORTHANC_ROOT}/OrthancCppClient/OrthancConnection.cpp + ${ORTHANC_ROOT}/OrthancCppClient/Series.cpp + ${ORTHANC_ROOT}/OrthancCppClient/Study.cpp + ${ORTHANC_ROOT}/OrthancCppClient/Instance.cpp + ${ORTHANC_ROOT}/OrthancCppClient/Patient.cpp + ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/SharedLibrary.cpp + ${ORTHANC_ROOT}/Resources/md5/md5.c + ${ORTHANC_ROOT}/Resources/base64/base64.cpp + ${ORTHANC_CPP_CLIENT_AUX} + ${THIRD_PARTY_SOURCES} + ${CURL_SOURCES} ) - # Ensure autogenerated code is built before building ServerLibrary - add_dependencies(ServerLibrary CoreLibrary) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + 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" + ) + target_link_libraries(OrthancClient pthread) + + elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + target_link_libraries(OrthancClient OpenSSL ws2_32) + + if (CMAKE_CROSSCOMPILING) + set_target_properties(OrthancClient + PROPERTIES LINK_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++" + ) + endif() - add_executable(Orthanc - OrthancServer/main.cpp - ) + else() + message(FATAL_ERROR "Support your platform here") + endif() + - target_link_libraries(Orthanc ServerLibrary CoreLibrary) + # Set the version of the "Orthanc Client" shared library + file(STRINGS + ${CMAKE_SOURCE_DIR}/OrthancCppClient/SharedLibrary/Product.json + ORTHANC_CLIENT_VERSION_TMP + REGEX "^[ \t]*\"Version\"[ \t]*") + + string(REGEX REPLACE "^.*\"([0-9]+)\\.([0-9]+)\\.([0-9]+)\"" "\\1.\\2" + ORTHANC_CLIENT_VERSION ${ORTHANC_CLIENT_VERSION_TMP}) + + message("Setting the version of the library to ${ORTHANC_CLIENT_VERSION}") + + set_target_properties(OrthancClient PROPERTIES + VERSION ${ORTHANC_CLIENT_VERSION} + SOVERSION ${ORTHANC_CLIENT_VERSION}) + install( - TARGETS Orthanc - RUNTIME DESTINATION bin + TARGETS OrthancClient + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION lib # Destination for Linux ) - # Build the unit tests if required - if (BUILD_UNIT_TESTS) - add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1) - include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake) - add_executable(UnitTests - ${GTEST_SOURCES} - UnitTests/FileStorage.cpp - UnitTests/MemoryCache.cpp - UnitTests/PngWriter.cpp - UnitTests/RestApi.cpp - UnitTests/SQLite.cpp - UnitTests/SQLiteChromium.cpp - UnitTests/ServerIndex.cpp - UnitTests/Versions.cpp - UnitTests/Zip.cpp - UnitTests/Lua.cpp - UnitTests/main.cpp - ) - target_link_libraries(UnitTests ServerLibrary CoreLibrary) - endif() + install( + FILES ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h + DESTINATION include/orthanc + ) endif() -# Generate the Doxygen documentation if Doxygen is present + + +##################################################################### +## Generate the documentation if Doxygen is present +##################################################################### + find_package(Doxygen) if (DOXYGEN_FOUND) configure_file( ${CMAKE_SOURCE_DIR}/Resources/Orthanc.doxygen ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen @ONLY) + add_custom_target(doc ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating API documentation with Doxygen" VERBATIM + COMMENT "Generating internal documentation with Doxygen" VERBATIM ) + + if (BUILD_CLIENT_LIBRARY) + configure_file( + ${CMAKE_SOURCE_DIR}/Resources/OrthancClient.doxygen + ${CMAKE_CURRENT_BINARY_DIR}/OrthancClient.doxygen + @ONLY) + + add_custom_command(TARGET OrthancClient + POST_BUILD + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancClient.doxygen + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating client documentation with Doxygen" VERBATIM + ) + + install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/OrthancClientDocumentation/doc/ + DESTINATION share/doc/orthanc/OrthancClient + ) + endif() + else() message("Doxygen not found. The documentation will not be built.") endif() + +##################################################################### +## Prepare the "uninstall" target +## http://www.cmake.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F +##################################################################### + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/Resources/CMake/Uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
--- a/Core/Cache/CacheIndex.h Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,250 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 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 <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <list> -#include <map> -#include <boost/noncopyable.hpp> -#include <cassert> - -#include "../OrthancException.h" -#include "../Toolbox.h" - -namespace Orthanc -{ - /** - * This class implements the index of a cache with least recently - * used (LRU) recycling policy. All the items of the cache index - * can be associated with a payload. - * Reference: http://stackoverflow.com/a/2504317 - **/ - template <typename T, typename Payload = NullType> - class CacheIndex : public boost::noncopyable - { - private: - typedef std::list< std::pair<T, Payload> > Queue; - typedef std::map<T, typename Queue::iterator> Index; - - Index index_; - Queue queue_; - - /** - * Internal method for debug builds to check whether the internal - * data structures are not corrupted. - **/ - void CheckInvariants() const; - - public: - /** - * Add a new element to the cache index, and make it the most - * recent element. - * \param id The ID of the element. - * \param payload The payload of the element. - **/ - void Add(T id, Payload payload = Payload()); - - /** - * When accessing an element of the cache, this method tags the - * element as the most recently used. - * \param id The most recently accessed item. - **/ - void TagAsMostRecent(T id); - - /** - * Remove an element from the cache index. - * \param id The item to remove. - **/ - Payload Invalidate(T id); - - /** - * Get the oldest element in the cache and remove it. - * \return The oldest item. - **/ - T RemoveOldest() - { - Payload p; - return RemoveOldest(p); - } - - /** - * Get the oldest element in the cache, remove it and return the - * associated payload. - * \param payload Where to store the associated payload. - * \return The oldest item. - **/ - T RemoveOldest(Payload& payload); - - /** - * Check whether an element is contained in the cache. - * \param id The item. - * \return \c true iff the item is indexed by the cache. - **/ - bool Contains(T id) const - { - return index_.find(id) != index_.end(); - } - - bool Contains(T id, Payload& payload) const - { - typename Index::const_iterator it = index_.find(id); - if (it == index_.end()) - { - return false; - } - else - { - payload = it->second->second; - return true; - } - } - - /** - * Return the number of elements in the cache. - * \return The number of elements. - **/ - size_t GetSize() const - { - assert(index_.size() == queue_.size()); - return queue_.size(); - } - - /** - * Check whether the cache index is empty. - * \return \c true iff the cache is empty. - **/ - bool IsEmpty() const - { - return index_.empty(); - } - }; - - - - - /****************************************************************** - ** Implementation of the template - ******************************************************************/ - - template <typename T, typename Payload> - void CacheIndex<T, Payload>::CheckInvariants() const - { -#ifndef NDEBUG - assert(index_.size() == queue_.size()); - - for (typename Index::const_iterator - it = index_.begin(); it != index_.end(); it++) - { - assert(it->second != queue_.end()); - assert(it->second->first == it->first); - } -#endif - } - - - template <typename T, typename Payload> - void CacheIndex<T, Payload>::Add(T id, Payload payload) - { - if (Contains(id)) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - queue_.push_front(std::make_pair(id, payload)); - index_[id] = queue_.begin(); - - CheckInvariants(); - } - - - template <typename T, typename Payload> - void CacheIndex<T, Payload>::TagAsMostRecent(T id) - { - if (!Contains(id)) - { - throw OrthancException(ErrorCode_InexistentItem); - } - - typename Index::iterator it = index_.find(id); - assert(it != index_.end()); - - std::pair<T, Payload> item = *(it->second); - - queue_.erase(it->second); - queue_.push_front(item); - index_[id] = queue_.begin(); - - CheckInvariants(); - } - - - template <typename T, typename Payload> - Payload CacheIndex<T, Payload>::Invalidate(T id) - { - if (!Contains(id)) - { - throw OrthancException(ErrorCode_InexistentItem); - } - - typename Index::iterator it = index_.find(id); - assert(it != index_.end()); - - Payload payload = it->second->second; - queue_.erase(it->second); - index_.erase(it); - - CheckInvariants(); - return payload; - } - - - template <typename T, typename Payload> - T CacheIndex<T, Payload>::RemoveOldest(Payload& payload) - { - if (IsEmpty()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - std::pair<T, Payload> item = queue_.back(); - T oldest = item.first; - payload = item.second; - - queue_.pop_back(); - assert(index_.find(oldest) != index_.end()); - index_.erase(oldest); - - CheckInvariants(); - - return oldest; - } -}
--- a/Core/Cache/ICachePageProvider.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Cache/ICachePageProvider.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/LeastRecentlyUsedIndex.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,346 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <list> +#include <map> +#include <boost/noncopyable.hpp> +#include <cassert> + +#include "../OrthancException.h" +#include "../Toolbox.h" + +namespace Orthanc +{ + /** + * This class implements the index of a cache with least recently + * used (LRU) recycling policy. All the items of the cache index + * can be associated with a payload. + * Reference: http://stackoverflow.com/a/2504317 + **/ + template <typename T, typename Payload = NullType> + class LeastRecentlyUsedIndex : public boost::noncopyable + { + private: + typedef std::list< std::pair<T, Payload> > Queue; + typedef std::map<T, typename Queue::iterator> Index; + + Index index_; + Queue queue_; + + /** + * Internal method for debug builds to check whether the internal + * data structures are not corrupted. + **/ + void CheckInvariants() const; + + public: + /** + * Add a new element to the cache index, and make it the most + * recent element. + * \param id The ID of the element. + * \param payload The payload of the element. + **/ + void Add(T id, Payload payload = Payload()); + + void AddOrMakeMostRecent(T id, Payload payload = Payload()); + + /** + * When accessing an element of the cache, this method tags the + * element as the most recently used. + * \param id The most recently accessed item. + **/ + void MakeMostRecent(T id); + + void MakeMostRecent(T id, Payload updatedPayload); + + /** + * Remove an element from the cache index. + * \param id The item to remove. + **/ + Payload Invalidate(T id); + + /** + * Get the oldest element in the cache and remove it. + * \return The oldest item. + **/ + T RemoveOldest(); + + /** + * Get the oldest element in the cache, remove it and return the + * associated payload. + * \param payload Where to store the associated payload. + * \return The oldest item. + **/ + T RemoveOldest(Payload& payload); + + /** + * Check whether an element is contained in the cache. + * \param id The item. + * \return \c true iff the item is indexed by the cache. + **/ + bool Contains(T id) const + { + return index_.find(id) != index_.end(); + } + + bool Contains(T id, Payload& payload) const + { + typename Index::const_iterator it = index_.find(id); + if (it == index_.end()) + { + return false; + } + else + { + payload = it->second->second; + return true; + } + } + + /** + * Return the number of elements in the cache. + * \return The number of elements. + **/ + size_t GetSize() const + { + assert(index_.size() == queue_.size()); + return queue_.size(); + } + + /** + * Check whether the cache index is empty. + * \return \c true iff the cache is empty. + **/ + bool IsEmpty() const + { + return index_.empty(); + } + + const T& GetOldest() const; + + const Payload& GetOldestPayload() const; + }; + + + + + /****************************************************************** + ** Implementation of the template + ******************************************************************/ + + template <typename T, typename Payload> + void LeastRecentlyUsedIndex<T, Payload>::CheckInvariants() const + { +#ifndef NDEBUG + assert(index_.size() == queue_.size()); + + for (typename Index::const_iterator + it = index_.begin(); it != index_.end(); it++) + { + assert(it->second != queue_.end()); + assert(it->second->first == it->first); + } +#endif + } + + + template <typename T, typename Payload> + void LeastRecentlyUsedIndex<T, Payload>::Add(T id, Payload payload) + { + if (Contains(id)) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + queue_.push_front(std::make_pair(id, payload)); + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template <typename T, typename Payload> + void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id) + { + if (!Contains(id)) + { + throw OrthancException(ErrorCode_InexistentItem); + } + + typename Index::iterator it = index_.find(id); + assert(it != index_.end()); + + std::pair<T, Payload> item = *(it->second); + + queue_.erase(it->second); + queue_.push_front(item); + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template <typename T, typename Payload> + void LeastRecentlyUsedIndex<T, Payload>::AddOrMakeMostRecent(T id, Payload payload) + { + typename Index::iterator it = index_.find(id); + + if (it != index_.end()) + { + // Already existing. Make it most recent. + std::pair<T, Payload> item = *(it->second); + item.second = payload; + queue_.erase(it->second); + queue_.push_front(item); + } + else + { + // New item + queue_.push_front(std::make_pair(id, payload)); + } + + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template <typename T, typename Payload> + void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id, Payload updatedPayload) + { + if (!Contains(id)) + { + throw OrthancException(ErrorCode_InexistentItem); + } + + typename Index::iterator it = index_.find(id); + assert(it != index_.end()); + + std::pair<T, Payload> item = *(it->second); + item.second = updatedPayload; + + queue_.erase(it->second); + queue_.push_front(item); + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template <typename T, typename Payload> + Payload LeastRecentlyUsedIndex<T, Payload>::Invalidate(T id) + { + if (!Contains(id)) + { + throw OrthancException(ErrorCode_InexistentItem); + } + + typename Index::iterator it = index_.find(id); + assert(it != index_.end()); + + Payload payload = it->second->second; + queue_.erase(it->second); + index_.erase(it); + + CheckInvariants(); + return payload; + } + + + template <typename T, typename Payload> + T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest(Payload& payload) + { + if (IsEmpty()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + std::pair<T, Payload> item = queue_.back(); + T oldest = item.first; + payload = item.second; + + queue_.pop_back(); + assert(index_.find(oldest) != index_.end()); + index_.erase(oldest); + + CheckInvariants(); + + return oldest; + } + + + template <typename T, typename Payload> + T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest() + { + if (IsEmpty()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + std::pair<T, Payload> item = queue_.back(); + T oldest = item.first; + + queue_.pop_back(); + assert(index_.find(oldest) != index_.end()); + index_.erase(oldest); + + CheckInvariants(); + + return oldest; + } + + + template <typename T, typename Payload> + const T& LeastRecentlyUsedIndex<T, Payload>::GetOldest() const + { + if (IsEmpty()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + return queue_.back().first; + } + + + template <typename T, typename Payload> + const Payload& LeastRecentlyUsedIndex<T, Payload>::GetOldestPayload() const + { + if (IsEmpty()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + return queue_.back().second; + } +}
--- a/Core/Cache/MemoryCache.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/Cache/MemoryCache.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -46,7 +46,7 @@ { VLOG(1) << "Reusing a cache page"; assert(p != NULL); - index_.TagAsMostRecent(id); + index_.MakeMostRecent(id); return *p; }
--- a/Core/Cache/MemoryCache.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Cache/MemoryCache.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -33,7 +33,7 @@ #pragma once #include <memory> -#include "CacheIndex.h" +#include "LeastRecentlyUsedIndex.h" #include "ICachePageProvider.h" namespace Orthanc @@ -52,7 +52,7 @@ ICachePageProvider& provider_; size_t cacheSize_; - CacheIndex<std::string, Page*> index_; + LeastRecentlyUsedIndex<std::string, Page*> index_; Page& Load(const std::string& id);
--- a/Core/ChunkedBuffer.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/ChunkedBuffer.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -43,7 +43,7 @@ numBytes_ = 0; for (Chunks::iterator it = chunks_.begin(); - it != chunks_.end(); it++) + it != chunks_.end(); ++it) { delete *it; } @@ -70,7 +70,7 @@ size_t pos = 0; for (Chunks::iterator it = chunks_.begin(); - it != chunks_.end(); it++) + it != chunks_.end(); ++it) { assert(*it != NULL);
--- a/Core/ChunkedBuffer.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/ChunkedBuffer.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/BufferCompressor.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/Compression/BufferCompressor.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/BufferCompressor.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Compression/BufferCompressor.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/HierarchicalZipWriter.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/Compression/HierarchicalZipWriter.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -81,12 +81,12 @@ std::string result; Stack::const_iterator it = stack_.begin(); - it++; // Skip the root node (to avoid absolute paths) + ++it; // Skip the root node (to avoid absolute paths) while (it != stack_.end()) { result += (*it)->name_ + "/"; - it++; + ++it; } return result; @@ -118,7 +118,7 @@ HierarchicalZipWriter::Index::~Index() { - for (Stack::iterator it = stack_.begin(); it != stack_.end(); it++) + for (Stack::iterator it = stack_.begin(); it != stack_.end(); ++it) { delete *it; }
--- a/Core/Compression/HierarchicalZipWriter.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Compression/HierarchicalZipWriter.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -94,6 +94,16 @@ ~HierarchicalZipWriter(); + void SetZip64(bool isZip64) + { + writer_.SetZip64(isZip64); + } + + bool IsZip64() const + { + return writer_.IsZip64(); + } + void SetCompressionLevel(uint8_t level) { writer_.SetCompressionLevel(level);
--- a/Core/Compression/ZipWriter.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/Compression/ZipWriter.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -29,11 +29,15 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ +#ifndef NOMINMAX +#define NOMINMAX +#endif #include "ZipWriter.h" #include "../../Resources/minizip/zip.h" #include <boost/date_time/posix_time/posix_time.hpp> +#include <limits> #include "../OrthancException.h" @@ -76,6 +80,7 @@ { compressionLevel_ = 6; hasFileInZip_ = false; + isZip64_ = false; pimpl_->file_ = NULL; } @@ -113,7 +118,16 @@ } hasFileInZip_ = false; - pimpl_->file_ = zipOpen64(path_.c_str(), APPEND_STATUS_CREATE); + + if (isZip64_) + { + pimpl_->file_ = zipOpen64(path_.c_str(), APPEND_STATUS_CREATE); + } + else + { + pimpl_->file_ = zipOpen(path_.c_str(), APPEND_STATUS_CREATE); + } + if (!pimpl_->file_) { throw OrthancException(ErrorCode_CannotWriteFile); @@ -126,6 +140,12 @@ path_ = path; } + void ZipWriter::SetZip64(bool isZip64) + { + Close(); + isZip64_ = isZip64; + } + void ZipWriter::SetCompressionLevel(uint8_t level) { if (level >= 10) @@ -133,6 +153,7 @@ throw OrthancException("ZIP compression level must be between 0 (no compression) and 9 (highest compression"); } + Close(); compressionLevel_ = level; } @@ -143,13 +164,30 @@ zip_fileinfo zfi; PrepareFileInfo(zfi); - if (zipOpenNewFileInZip64(pimpl_->file_, path, - &zfi, - NULL, 0, - NULL, 0, - "", // Comment - Z_DEFLATED, - compressionLevel_, 1) != 0) + int result; + + if (isZip64_) + { + result = zipOpenNewFileInZip64(pimpl_->file_, path, + &zfi, + NULL, 0, + NULL, 0, + "", // Comment + Z_DEFLATED, + compressionLevel_, 1); + } + else + { + result = zipOpenNewFileInZip(pimpl_->file_, path, + &zfi, + NULL, 0, + NULL, 0, + "", // Comment + Z_DEFLATED, + compressionLevel_); + } + + if (result != 0) { throw OrthancException(ErrorCode_CannotWriteFile); }
--- a/Core/Compression/ZipWriter.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Compression/ZipWriter.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -48,6 +48,7 @@ struct PImpl; boost::shared_ptr<PImpl> pimpl_; + bool isZip64_; bool hasFileInZip_; uint8_t compressionLevel_; std::string path_; @@ -57,6 +58,13 @@ ~ZipWriter(); + void SetZip64(bool isZip64); + + bool IsZip64() const + { + return isZip64_; + } + void SetCompressionLevel(uint8_t level); uint8_t GetCompressionLevel() const
--- a/Core/Compression/ZlibCompressor.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/Compression/ZlibCompressor.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZlibCompressor.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Compression/ZlibCompressor.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomArray.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomArray.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -41,7 +41,7 @@ elements_.reserve(map.map_.size()); for (DicomMap::Map::const_iterator it = - map.map_.begin(); it != map.map_.end(); it++) + map.map_.begin(); it != map.map_.end(); ++it) { elements_.push_back(new DicomElement(it->first, *it->second)); }
--- a/Core/DicomFormat/DicomArray.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomArray.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomElement.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomElement.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomInstanceHasher.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomInstanceHasher.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomInstanceHasher.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomInstanceHasher.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -30,12 +30,12 @@ **/ -#include "DicomIntegerPixelAccessor.h" - #ifndef NOMINMAX #define NOMINMAX #endif +#include "DicomIntegerPixelAccessor.h" + #include "../OrthancException.h" #include <boost/lexical_cast.hpp> #include <limits> @@ -140,11 +140,13 @@ if (pixelRepresentation) { + // Pixels are signed mask_ = (1 << (bitsStored - 1)) - 1; signMask_ = (1 << (bitsStored - 1)); } else { + // Pixels are unsigned mask_ = (1 << bitsStored) - 1; signMask_ = 0; } @@ -231,25 +233,28 @@ pixel += channel * frameOffset_ / samplesPerPixel_ + x * bytesPerPixel_; } - int32_t v; + uint32_t v; v = pixel[0]; if (bytesPerPixel_ >= 2) - v = v + (static_cast<int32_t>(pixel[1]) << 8); + v = v + (static_cast<uint32_t>(pixel[1]) << 8); if (bytesPerPixel_ >= 3) - v = v + (static_cast<int32_t>(pixel[2]) << 16); + v = v + (static_cast<uint32_t>(pixel[2]) << 16); if (bytesPerPixel_ >= 4) - v = v + (static_cast<int32_t>(pixel[3]) << 24); + v = v + (static_cast<uint32_t>(pixel[3]) << 24); - v = (v >> shift_) & mask_; + v = v >> shift_; if (v & signMask_) { - // Signed value: Not implemented yet - //throw OrthancException(ErrorCode_NotImplemented); - v = 0; + // Signed value + // http://en.wikipedia.org/wiki/Two%27s_complement#Subtraction_from_2N + return -static_cast<int32_t>(mask_) + static_cast<int32_t>(v & mask_) - 1; } - - return v; + else + { + // Unsigned value + return static_cast<int32_t>(v & mask_); + } }
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomMap.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomMap.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -76,9 +76,11 @@ DicomTag(0x0018, 0x0024), // SequenceName DicomTag(0x0018, 0x1030), // ProtocolName DicomTag(0x0020, 0x0011), // SeriesNumber - //DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES, + DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES, DICOM_TAG_IMAGES_IN_ACQUISITION, + DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, DICOM_TAG_NUMBER_OF_SLICES, + DICOM_TAG_NUMBER_OF_TIME_SLICES, DICOM_TAG_SERIES_INSTANCE_UID }; @@ -90,6 +92,7 @@ DICOM_TAG_IMAGE_INDEX, DICOM_TAG_INSTANCE_NUMBER, DICOM_TAG_NUMBER_OF_FRAMES, + DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, DICOM_TAG_SOP_INSTANCE_UID }; @@ -125,7 +128,7 @@ void DicomMap::Clear() { - for (Map::iterator it = map_.begin(); it != map_.end(); it++) + for (Map::iterator it = map_.begin(); it != map_.end(); ++it) { delete it->second; } @@ -177,7 +180,7 @@ { std::auto_ptr<DicomMap> result(new DicomMap); - for (Map::const_iterator it = map_.begin(); it != map_.end(); it++) + for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it) { result->map_.insert(std::make_pair(it->first, it->second->Clone())); } @@ -196,7 +199,7 @@ } else { - throw OrthancException("Inexistent tag"); + throw OrthancException(ErrorCode_InexistentTag); } } @@ -277,4 +280,110 @@ SetValue(tag, source.GetValue(tag)); } } + + + bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level) + { + DicomTag *tags = NULL; + size_t size; + + switch (level) + { + case ResourceType_Patient: + tags = patientTags; + size = sizeof(patientTags) / sizeof(DicomTag); + break; + + case ResourceType_Study: + tags = studyTags; + size = sizeof(studyTags) / sizeof(DicomTag); + break; + + case ResourceType_Series: + tags = seriesTags; + size = sizeof(seriesTags) / sizeof(DicomTag); + break; + + case ResourceType_Instance: + tags = instanceTags; + size = sizeof(instanceTags) / sizeof(DicomTag); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + for (size_t i = 0; i < size; i++) + { + if (tags[i] == tag) + { + return true; + } + } + + return false; + } + + bool DicomMap::IsMainDicomTag(const DicomTag& tag) + { + return (IsMainDicomTag(tag, ResourceType_Patient) || + IsMainDicomTag(tag, ResourceType_Study) || + IsMainDicomTag(tag, ResourceType_Series) || + IsMainDicomTag(tag, ResourceType_Instance)); + } + + + void DicomMap::GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level) + { + DicomTag *tags = NULL; + size_t size; + + switch (level) + { + case ResourceType_Patient: + tags = patientTags; + size = sizeof(patientTags) / sizeof(DicomTag); + break; + + case ResourceType_Study: + tags = studyTags; + size = sizeof(studyTags) / sizeof(DicomTag); + break; + + case ResourceType_Series: + tags = seriesTags; + size = sizeof(seriesTags) / sizeof(DicomTag); + break; + + case ResourceType_Instance: + tags = instanceTags; + size = sizeof(instanceTags) / sizeof(DicomTag); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + for (size_t i = 0; i < size; i++) + { + result.insert(tags[i]); + } + } + + + void DicomMap::GetMainDicomTags(std::set<DicomTag>& result, ResourceType level) + { + result.clear(); + GetMainDicomTagsInternal(result, level); + } + + + void DicomMap::GetMainDicomTags(std::set<DicomTag>& result) + { + result.clear(); + GetMainDicomTagsInternal(result, ResourceType_Patient); + GetMainDicomTagsInternal(result, ResourceType_Study); + GetMainDicomTagsInternal(result, ResourceType_Series); + GetMainDicomTagsInternal(result, ResourceType_Instance); + } }
--- a/Core/DicomFormat/DicomMap.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomMap.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -35,7 +35,9 @@ #include "DicomTag.h" #include "DicomValue.h" #include "DicomString.h" +#include "../Enumerations.h" +#include <set> #include <map> #include <json/json.h> @@ -63,6 +65,8 @@ void ExtractTags(DicomMap& source, const DicomTag* tags, size_t count) const; + + static void GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level); public: DicomMap() @@ -148,5 +152,13 @@ void CopyTagIfExists(const DicomMap& source, const DicomTag& tag); + + static bool IsMainDicomTag(const DicomTag& tag, ResourceType level); + + static bool IsMainDicomTag(const DicomTag& tag); + + static void GetMainDicomTags(std::set<DicomTag>& result, ResourceType level); + + static void GetMainDicomTags(std::set<DicomTag>& result); }; }
--- a/Core/DicomFormat/DicomNullValue.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomNullValue.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomString.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomString.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomTag.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomTag.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomTag.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomTag.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -95,6 +95,7 @@ static const DicomTag DICOM_TAG_INSTANCE_NUMBER(0x0020, 0x0013); static const DicomTag DICOM_TAG_NUMBER_OF_SLICES(0x0054, 0x0081); + static const DicomTag DICOM_TAG_NUMBER_OF_TIME_SLICES(0x0054, 0x0101); static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008); static const DicomTag DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES(0x0018, 0x1090); static const DicomTag DICOM_TAG_IMAGES_IN_ACQUISITION(0x0020, 0x1002); @@ -105,4 +106,13 @@ 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); + + // DICOM tags used for fMRI (thanks to Will Ryder) + static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105); + static const DicomTag DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER(0x0020, 0x0100); + + // Tags for C-FIND and C-MOVE + static const DicomTag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005); + static const DicomTag DICOM_TAG_QUERY_RETRIEVE_LEVEL(0x0008, 0x0052); + static const DicomTag DICOM_TAG_MODALITIES_IN_STUDY(0x0008, 0x0061); }
--- a/Core/DicomFormat/DicomValue.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/DicomFormat/DicomValue.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/EnumerationDictionary.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,122 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "OrthancException.h" + +#include <boost/lexical_cast.hpp> +#include <string> +#include <map> + +namespace Orthanc +{ + namespace Toolbox + { + template <typename Enumeration> + class EnumerationDictionary + { + private: + typedef std::map<Enumeration, std::string> EnumerationToString; + typedef std::map<std::string, Enumeration> StringToEnumeration; + + EnumerationToString enumerationToString_; + StringToEnumeration stringToEnumeration_; + + public: + 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()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + // Prevent the registration of a number + try + { + boost::lexical_cast<int>(str); + throw OrthancException(ErrorCode_BadRequest); + } + catch (boost::bad_lexical_cast) + { + // OK, the string is not a number + } + + enumerationToString_[value] = str; + stringToEnumeration_[str] = value; + stringToEnumeration_[boost::lexical_cast<std::string>(static_cast<int>(value))] = value; + } + + Enumeration Translate(const std::string& str) const + { + try + { + int value = boost::lexical_cast<int>(str); + return static_cast<Enumeration>(value); + } + catch (boost::bad_lexical_cast) + { + } + + typename StringToEnumeration::const_iterator + found = stringToEnumeration_.find(str); + + if (found == stringToEnumeration_.end()) + { + throw OrthancException(ErrorCode_InexistentItem); + } + else + { + return found->second; + } + } + + std::string Translate(Enumeration e) const + { + typename EnumerationToString::const_iterator + found = enumerationToString_.find(e); + + if (found == enumerationToString_.end()) + { + // No name for this item + return boost::lexical_cast<std::string>(static_cast<int>(e)); + } + else + { + return found->second; + } + } + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Enumerations.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,277 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "Enumerations.h" + +#include "OrthancException.h" +#include "Toolbox.h" + +namespace Orthanc +{ + const char* EnumerationToString(HttpMethod method) + { + switch (method) + { + case HttpMethod_Get: + return "GET"; + + case HttpMethod_Post: + return "POST"; + + case HttpMethod_Delete: + return "DELETE"; + + case HttpMethod_Put: + return "PUT"; + + default: + return "?"; + } + } + + + const char* EnumerationToString(HttpStatus status) + { + switch (status) + { + case HttpStatus_100_Continue: + return "Continue"; + + case HttpStatus_101_SwitchingProtocols: + return "Switching Protocols"; + + case HttpStatus_102_Processing: + return "Processing"; + + case HttpStatus_200_Ok: + return "OK"; + + case HttpStatus_201_Created: + return "Created"; + + case HttpStatus_202_Accepted: + return "Accepted"; + + case HttpStatus_203_NonAuthoritativeInformation: + return "Non-Authoritative Information"; + + case HttpStatus_204_NoContent: + return "No Content"; + + case HttpStatus_205_ResetContent: + return "Reset Content"; + + case HttpStatus_206_PartialContent: + return "Partial Content"; + + case HttpStatus_207_MultiStatus: + return "Multi-Status"; + + case HttpStatus_208_AlreadyReported: + return "Already Reported"; + + case HttpStatus_226_IMUsed: + return "IM Used"; + + case HttpStatus_300_MultipleChoices: + return "Multiple Choices"; + + case HttpStatus_301_MovedPermanently: + return "Moved Permanently"; + + case HttpStatus_302_Found: + return "Found"; + + case HttpStatus_303_SeeOther: + return "See Other"; + + case HttpStatus_304_NotModified: + return "Not Modified"; + + case HttpStatus_305_UseProxy: + return "Use Proxy"; + + case HttpStatus_307_TemporaryRedirect: + return "Temporary Redirect"; + + case HttpStatus_400_BadRequest: + return "Bad Request"; + + case HttpStatus_401_Unauthorized: + return "Unauthorized"; + + case HttpStatus_402_PaymentRequired: + return "Payment Required"; + + case HttpStatus_403_Forbidden: + return "Forbidden"; + + case HttpStatus_404_NotFound: + return "Not Found"; + + case HttpStatus_405_MethodNotAllowed: + return "Method Not Allowed"; + + case HttpStatus_406_NotAcceptable: + return "Not Acceptable"; + + case HttpStatus_407_ProxyAuthenticationRequired: + return "Proxy Authentication Required"; + + case HttpStatus_408_RequestTimeout: + return "Request Timeout"; + + case HttpStatus_409_Conflict: + return "Conflict"; + + case HttpStatus_410_Gone: + return "Gone"; + + case HttpStatus_411_LengthRequired: + return "Length Required"; + + case HttpStatus_412_PreconditionFailed: + return "Precondition Failed"; + + case HttpStatus_413_RequestEntityTooLarge: + return "Request Entity Too Large"; + + case HttpStatus_414_RequestUriTooLong: + return "Request-URI Too Long"; + + case HttpStatus_415_UnsupportedMediaType: + return "Unsupported Media Type"; + + case HttpStatus_416_RequestedRangeNotSatisfiable: + return "Requested Range Not Satisfiable"; + + case HttpStatus_417_ExpectationFailed: + return "Expectation Failed"; + + case HttpStatus_422_UnprocessableEntity: + return "Unprocessable Entity"; + + case HttpStatus_423_Locked: + return "Locked"; + + case HttpStatus_424_FailedDependency: + return "Failed Dependency"; + + case HttpStatus_426_UpgradeRequired: + return "Upgrade Required"; + + case HttpStatus_500_InternalServerError: + return "Internal Server Error"; + + case HttpStatus_501_NotImplemented: + return "Not Implemented"; + + case HttpStatus_502_BadGateway: + return "Bad Gateway"; + + case HttpStatus_503_ServiceUnavailable: + return "Service Unavailable"; + + case HttpStatus_504_GatewayTimeout: + return "Gateway Timeout"; + + case HttpStatus_505_HttpVersionNotSupported: + return "HTTP Version Not Supported"; + + case HttpStatus_506_VariantAlsoNegotiates: + return "Variant Also Negotiates"; + + case HttpStatus_507_InsufficientStorage: + return "Insufficient Storage"; + + case HttpStatus_509_BandwidthLimitExceeded: + return "Bandwidth Limit Exceeded"; + + case HttpStatus_510_NotExtended: + return "Not Extended"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(ResourceType type) + { + switch (type) + { + case ResourceType_Patient: + return "Patient"; + + case ResourceType_Study: + return "Study"; + + case ResourceType_Series: + return "Series"; + + case ResourceType_Instance: + return "Instance"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + ResourceType StringToResourceType(const char* type) + { + std::string s(type); + Toolbox::ToUpperCase(s); + + if (s == "PATIENT" || s == "PATIENTS") + { + return ResourceType_Patient; + } + else if (s == "STUDY" || s == "STUDIES") + { + return ResourceType_Study; + } + else if (s == "SERIES") + { + return ResourceType_Series; + } + else if (s == "INSTANCE" || s == "IMAGE" || + s == "INSTANCES" || s == "IMAGES") + { + return ResourceType_Instance; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } +}
--- a/Core/Enumerations.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Enumerations.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,10 +32,17 @@ #pragma once -#include "../OrthancCppClient/HttpEnumerations.h" +#include <laaw/laaw.h> namespace Orthanc { + enum Endianness + { + Endianness_Unknown, + Endianness_Big, + Endianness_Little + }; + enum ErrorCode { // Generic error codes @@ -49,6 +56,7 @@ ErrorCode_BadSequenceOfCalls, ErrorCode_InexistentItem, ErrorCode_BadRequest, + ErrorCode_NetworkProtocol, // Specific error codes ErrorCode_UriSyntax, @@ -58,14 +66,149 @@ ErrorCode_Timeout, ErrorCode_UnknownResource, ErrorCode_IncompatibleDatabaseVersion, - ErrorCode_FullStorage + ErrorCode_FullStorage, + ErrorCode_CorruptedFile, + ErrorCode_InexistentTag + }; + + /** + * {summary}{The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image.} + **/ + enum LAAW_API PixelFormat + { + /** + * {summary}{Color image in RGB24 format.} + * {description}{This format describes a color image. The pixels are stored in 3 + * consecutive bytes. The memory layout is RGB. + **/ + PixelFormat_RGB24, + + /** + * {summary}{Graylevel 8bpp image.} + * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.} + **/ + PixelFormat_Grayscale8, + + /** + * {summary}{Graylevel, unsigned 16bpp image.} + * {description}{The image is graylevel. Each pixel is unsigned and stored in two bytes.} + **/ + PixelFormat_Grayscale16, + + /** + * {summary}{Graylevel, signed 16bpp image.} + * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.} + **/ + PixelFormat_SignedGrayscale16 + }; + + + /** + * {summary}{The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image.} + **/ + enum LAAW_API ImageExtractionMode + { + /** + * {summary}{Rescaled to 8bpp.} + * {description}{The minimum value of the image is set to 0, and its maximum value is set to 255.} + **/ + ImageExtractionMode_Preview, + + /** + * {summary}{Truncation to the [0, 255] range.} + **/ + ImageExtractionMode_UInt8, + + /** + * {summary}{Truncation to the [0, 65535] range.} + **/ + ImageExtractionMode_UInt16, + + /** + * {summary}{Truncation to the [-32768, 32767] range.} + **/ + ImageExtractionMode_Int16 }; - enum PixelFormat + + /** + * Most common, non-joke and non-experimental HTTP status codes + * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes + **/ + enum HttpStatus { - PixelFormat_RGB24, - PixelFormat_Grayscale8, - PixelFormat_Grayscale16 + HttpStatus_None = -1, + + // 1xx Informational + HttpStatus_100_Continue = 100, + HttpStatus_101_SwitchingProtocols = 101, + HttpStatus_102_Processing = 102, + + // 2xx Success + HttpStatus_200_Ok = 200, + HttpStatus_201_Created = 201, + HttpStatus_202_Accepted = 202, + HttpStatus_203_NonAuthoritativeInformation = 203, + HttpStatus_204_NoContent = 204, + HttpStatus_205_ResetContent = 205, + HttpStatus_206_PartialContent = 206, + HttpStatus_207_MultiStatus = 207, + HttpStatus_208_AlreadyReported = 208, + HttpStatus_226_IMUsed = 226, + + // 3xx Redirection + HttpStatus_300_MultipleChoices = 300, + HttpStatus_301_MovedPermanently = 301, + HttpStatus_302_Found = 302, + HttpStatus_303_SeeOther = 303, + HttpStatus_304_NotModified = 304, + HttpStatus_305_UseProxy = 305, + HttpStatus_307_TemporaryRedirect = 307, + + // 4xx Client Error + HttpStatus_400_BadRequest = 400, + HttpStatus_401_Unauthorized = 401, + HttpStatus_402_PaymentRequired = 402, + HttpStatus_403_Forbidden = 403, + HttpStatus_404_NotFound = 404, + HttpStatus_405_MethodNotAllowed = 405, + HttpStatus_406_NotAcceptable = 406, + HttpStatus_407_ProxyAuthenticationRequired = 407, + HttpStatus_408_RequestTimeout = 408, + HttpStatus_409_Conflict = 409, + HttpStatus_410_Gone = 410, + HttpStatus_411_LengthRequired = 411, + HttpStatus_412_PreconditionFailed = 412, + HttpStatus_413_RequestEntityTooLarge = 413, + HttpStatus_414_RequestUriTooLong = 414, + HttpStatus_415_UnsupportedMediaType = 415, + HttpStatus_416_RequestedRangeNotSatisfiable = 416, + HttpStatus_417_ExpectationFailed = 417, + HttpStatus_422_UnprocessableEntity = 422, + HttpStatus_423_Locked = 423, + HttpStatus_424_FailedDependency = 424, + HttpStatus_426_UpgradeRequired = 426, + + // 5xx Server Error + HttpStatus_500_InternalServerError = 500, + HttpStatus_501_NotImplemented = 501, + HttpStatus_502_BadGateway = 502, + HttpStatus_503_ServiceUnavailable = 503, + HttpStatus_504_GatewayTimeout = 504, + HttpStatus_505_HttpVersionNotSupported = 505, + HttpStatus_506_VariantAlsoNegotiates = 506, + HttpStatus_507_InsufficientStorage = 507, + HttpStatus_509_BandwidthLimitExceeded = 509, + HttpStatus_510_NotExtended = 510 + }; + + + enum HttpMethod + { + HttpMethod_Get = 0, + HttpMethod_Post = 1, + HttpMethod_Delete = 2, + HttpMethod_Put = 3 }; @@ -84,6 +227,27 @@ enum FileContentType { FileContentType_Dicom = 1, - FileContentType_Json = 2 + FileContentType_DicomAsJson = 2, + + // Make sure that the value "65535" can be stored into this enumeration + FileContentType_StartUser = 1024, + FileContentType_EndUser = 65535 }; + + enum ResourceType + { + ResourceType_Patient = 1, + ResourceType_Study = 2, + ResourceType_Series = 3, + ResourceType_Instance = 4 + }; + + + const char* EnumerationToString(HttpMethod method); + + const char* EnumerationToString(HttpStatus status); + + const char* EnumerationToString(ResourceType type); + + ResourceType StringToResourceType(const char* type); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileFormats/PngReader.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,305 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "PngReader.h" + +#include "../OrthancException.h" +#include "../Toolbox.h" + +#include <png.h> +#include <string.h> // For memcpy() + +namespace Orthanc +{ + namespace + { + struct FileRabi + { + FILE* fp_; + + FileRabi(const char* filename) + { + fp_ = fopen(filename, "rb"); + if (!fp_) + { + throw OrthancException(ErrorCode_InexistentFile); + } + } + + ~FileRabi() + { + if (fp_) + fclose(fp_); + } + }; + } + + + struct PngReader::PngRabi + { + png_structp png_; + png_infop info_; + png_infop endInfo_; + + void Destruct() + { + if (png_) + { + png_destroy_read_struct(&png_, &info_, &endInfo_); + + png_ = NULL; + info_ = NULL; + endInfo_ = NULL; + } + } + + PngRabi() + { + png_ = NULL; + info_ = NULL; + endInfo_ = NULL; + + png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + info_ = png_create_info_struct(png_); + if (!info_) + { + png_destroy_read_struct(&png_, NULL, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + endInfo_ = png_create_info_struct(png_); + if (!info_) + { + png_destroy_read_struct(&png_, &info_, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + ~PngRabi() + { + Destruct(); + } + + static void MemoryCallback(png_structp png_ptr, + png_bytep data, + png_size_t size); + }; + + + void PngReader::CheckHeader(const void* header) + { + int is_png = !png_sig_cmp((png_bytep) header, 0, 8); + if (!is_png) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + PngReader::PngReader() + { + width_ = 0; + height_ = 0; + pitch_ = 0; + format_ = PixelFormat_Grayscale8; + } + + void PngReader::Read(PngRabi& rabi) + { + png_set_sig_bytes(rabi.png_, 8); + + png_read_info(rabi.png_, rabi.info_); + + png_uint_32 width, height; + int bit_depth, color_type, interlace_type; + int compression_type, filter_method; + // get size and bit-depth of the PNG-image + png_get_IHDR(rabi.png_, rabi.info_, + &width, &height, + &bit_depth, &color_type, &interlace_type, + &compression_type, &filter_method); + + width_ = width; + height_ = height; + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8) + { + format_ = PixelFormat_Grayscale8; + pitch_ = width_; + } + else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16) + { + format_ = PixelFormat_Grayscale16; + pitch_ = 2 * width_; + + if (Toolbox::DetectEndianness() == Endianness_Little) + { + png_set_swap(rabi.png_); + } + } + else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8) + { + format_ = PixelFormat_Grayscale8; + pitch_ = 3 * width_; + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + + buffer_.resize(height_ * pitch_); + + if (height_ == 0 || width_ == 0) + { + // Empty image, we are done + return; + } + + png_read_update_info(rabi.png_, rabi.info_); + + std::vector<png_bytep> rows(height_); + for (size_t i = 0; i < height_; i++) + { + rows[i] = &buffer_[0] + i * pitch_; + } + + png_read_image(rabi.png_, &rows[0]); + } + + void PngReader::ReadFromFile(const char* filename) + { + FileRabi f(filename); + + char header[8]; + if (fread(header, 1, 8, f.fp_) != 8) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + CheckHeader(header); + + PngRabi rabi; + + if (setjmp(png_jmpbuf(rabi.png_))) + { + rabi.Destruct(); + throw OrthancException(ErrorCode_BadFileFormat); + } + + png_init_io(rabi.png_, f.fp_); + + Read(rabi); + } + + + + namespace + { + struct MemoryBuffer + { + const uint8_t* buffer_; + size_t size_; + size_t pos_; + bool ok_; + }; + } + + + void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, + png_bytep outBytes, + png_size_t byteCountToRead) + { + MemoryBuffer* from = reinterpret_cast<MemoryBuffer*>(png_get_io_ptr(png_ptr)); + + if (!from->ok_) + { + return; + } + + if (from->pos_ + byteCountToRead > from->size_) + { + from->ok_ = false; + return; + } + + memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead); + + from->pos_ += byteCountToRead; + } + + + void PngReader::ReadFromMemory(const void* buffer, + size_t size) + { + if (size < 8) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + CheckHeader(buffer); + + PngRabi rabi; + + if (setjmp(png_jmpbuf(rabi.png_))) + { + rabi.Destruct(); + throw OrthancException(ErrorCode_BadFileFormat); + } + + MemoryBuffer tmp; + tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8; // We skip the header + tmp.size_ = size - 8; + tmp.pos_ = 0; + tmp.ok_ = true; + + png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback); + + Read(rabi); + + if (!tmp.ok_) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + void PngReader::ReadFromMemory(const std::string& buffer) + { + if (buffer.size() != 0) + ReadFromMemory(&buffer[0], buffer.size()); + else + ReadFromMemory(NULL, 0); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileFormats/PngReader.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,109 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +#include <vector> +#include <stdint.h> +#include <boost/shared_ptr.hpp> + +namespace Orthanc +{ + class PngReader + { + private: + struct PngRabi; + + PixelFormat format_; + unsigned int width_; + unsigned int height_; + unsigned int pitch_; + std::vector<uint8_t> buffer_; + + void CheckHeader(const void* header); + + void Read(PngRabi& rabi); + + public: + PngReader(); + + PixelFormat GetFormat() const + { + return format_; + } + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetPitch() const + { + return pitch_; + } + + const void* GetBuffer() const + { + if (buffer_.size() > 0) + return &buffer_[0]; + else + return NULL; + } + + const void* GetBuffer(unsigned int y) const + { + if (buffer_.size() > 0) + return &buffer_[y * pitch_]; + else + return NULL; + } + + void ReadFromFile(const char* filename); + + void ReadFromFile(const std::string& filename) + { + ReadFromFile(filename.c_str()); + } + + void ReadFromMemory(const void* buffer, + size_t size); + + void ReadFromMemory(const std::string& buffer); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileFormats/PngWriter.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,263 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "PngWriter.h" + +#include <vector> +#include <stdint.h> +#include <png.h> +#include "../OrthancException.h" +#include "../ChunkedBuffer.h" +#include "../Toolbox.h" + + +// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4 +// http://zarb.org/~gc/html/libpng.html +/* + void write_row_callback(png_ptr, png_uint_32 row, int pass) + { + }*/ + + + + +/* bool isError_; + +// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2 + +static void ErrorHandler(png_structp png, png_const_charp message) +{ +printf("** [%s]\n", message); + +PngWriter* that = (PngWriter*) png_get_error_ptr(png); +that->isError_ = true; +printf("** %d\n", (int)that); + +//((PngWriter*) payload)->isError_ = true; +} + +static void WarningHandler(png_structp png, png_const_charp message) +{ + printf("++ %d\n", (int)message); +}*/ + + +namespace Orthanc +{ + struct PngWriter::PImpl + { + png_structp png_; + png_infop info_; + + // Filled by Prepare() + std::vector<uint8_t*> rows_; + int bitDepth_; + int colorType_; + }; + + + + PngWriter::PngWriter() : pimpl_(new PImpl) + { + pimpl_->png_ = NULL; + pimpl_->info_ = NULL; + + pimpl_->png_ = png_create_write_struct + (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler); + if (!pimpl_->png_) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + pimpl_->info_ = png_create_info_struct(pimpl_->png_); + if (!pimpl_->info_) + { + png_destroy_write_struct(&pimpl_->png_, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + PngWriter::~PngWriter() + { + if (pimpl_->info_) + { + png_destroy_info_struct(pimpl_->png_, &pimpl_->info_); + } + + if (pimpl_->png_) + { + png_destroy_write_struct(&pimpl_->png_, NULL); + } + } + + + + void PngWriter::Prepare(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + pimpl_->rows_.resize(height); + for (unsigned int y = 0; y < height; y++) + { + pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch; + } + + switch (format) + { + case PixelFormat_RGB24: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_RGB; + break; + + case PixelFormat_Grayscale8: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; + break; + + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + pimpl_->bitDepth_ = 16; + pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void PngWriter::Compress(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format) + { + png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height, + pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(pimpl_->png_, pimpl_->info_); + + if (height > 0) + { + switch (format) + { + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + int transforms = 0; + if (Toolbox::DetectEndianness() == Endianness_Little) + { + transforms = PNG_TRANSFORM_SWAP_ENDIAN; + } + + png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]); + png_write_png(pimpl_->png_, pimpl_->info_, transforms, NULL); + + break; + } + + default: + png_write_image(pimpl_->png_, &pimpl_->rows_[0]); + } + } + + png_write_end(pimpl_->png_, NULL); + } + + + void PngWriter::WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + Prepare(width, height, pitch, format, buffer); + + FILE* fp = fopen(filename, "wb"); + if (!fp) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + + png_init_io(pimpl_->png_, fp); + + if (setjmp(png_jmpbuf(pimpl_->png_))) + { + // Error during writing PNG + throw OrthancException(ErrorCode_CannotWriteFile); + } + + Compress(width, height, pitch, format); + + fclose(fp); + } + + + + + static void MemoryCallback(png_structp png_ptr, + png_bytep data, + png_size_t size) + { + ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr)); + buffer->AddChunk(reinterpret_cast<const char*>(data), size); + } + + + + void PngWriter::WriteToMemory(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + ChunkedBuffer chunks; + + Prepare(width, height, pitch, format, buffer); + + if (setjmp(png_jmpbuf(pimpl_->png_))) + { + // Error during writing PNG + throw OrthancException(ErrorCode_InternalError); + } + + png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL); + + Compress(width, height, pitch, format); + + chunks.Flatten(png); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileFormats/PngWriter.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,78 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +#include <boost/shared_ptr.hpp> +#include <string> + +namespace Orthanc +{ + class PngWriter + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + void Compress(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format); + + void Prepare(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + public: + PngWriter(); + + ~PngWriter(); + + void WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + void WriteToMemory(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + }; +}
--- a/Core/FileStorage/CompressedFileStorageAccessor.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -42,21 +42,36 @@ size_t size, FileContentType type) { + std::string md5; + + if (storeMD5_) + { + Toolbox::ComputeMD5(md5, data, size); + } + switch (compressionType_) { case CompressionType_None: { std::string uuid = storage_.Create(data, size); - return FileInfo(uuid, type, size); + return FileInfo(uuid, type, size, md5); } case CompressionType_Zlib: { std::string compressed; zlib_.Compress(compressed, data, size); + + std::string compressedMD5; + + if (storeMD5_) + { + Toolbox::ComputeMD5(compressedMD5, compressed); + } + std::string uuid = storage_.Create(compressed); - return FileInfo(uuid, type, size, - CompressionType_Zlib, compressed.size()); + return FileInfo(uuid, type, size, md5, + CompressionType_Zlib, compressed.size(), compressedMD5); } default:
--- a/Core/FileStorage/CompressedFileStorageAccessor.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/FileStorage/CompressedFileStorageAccessor.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileInfo.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/FileStorage/FileInfo.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -43,9 +43,13 @@ private: std::string uuid_; FileContentType contentType_; + uint64_t uncompressedSize_; + std::string uncompressedMD5_; + CompressionType compressionType_; uint64_t compressedSize_; + std::string compressedMD5_; public: FileInfo() @@ -57,12 +61,15 @@ **/ FileInfo(const std::string& uuid, FileContentType contentType, - uint64_t size) : + uint64_t size, + const std::string& md5) : uuid_(uuid), contentType_(contentType), uncompressedSize_(size), + uncompressedMD5_(md5), compressionType_(CompressionType_None), - compressedSize_(size) + compressedSize_(size), + compressedMD5_(md5) { } @@ -72,13 +79,17 @@ FileInfo(const std::string& uuid, FileContentType contentType, uint64_t uncompressedSize, + const std::string& uncompressedMD5, CompressionType compressionType, - uint64_t compressedSize) : + uint64_t compressedSize, + const std::string& compressedMD5) : uuid_(uuid), contentType_(contentType), uncompressedSize_(uncompressedSize), + uncompressedMD5_(uncompressedMD5), compressionType_(compressionType), - compressedSize_(compressedSize) + compressedSize_(compressedSize), + compressedMD5_(compressedMD5) { } @@ -106,5 +117,15 @@ { return compressedSize_; } + + const std::string& GetCompressedMD5() const + { + return compressedMD5_; + } + + const std::string& GetUncompressedMD5() const + { + return uncompressedMD5_; + } }; }
--- a/Core/FileStorage/FileStorage.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/FileStorage/FileStorage.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -267,7 +267,7 @@ List result; ListAllFiles(result); - for (List::const_iterator it = result.begin(); it != result.end(); it++) + for (List::const_iterator it = result.begin(); it != result.end(); ++it) { Remove(*it); }
--- a/Core/FileStorage/FileStorage.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/FileStorage/FileStorage.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorageAccessor.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/FileStorage/FileStorageAccessor.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -38,6 +38,13 @@ size_t size, FileContentType type) { - return FileInfo(storage_.Create(data, size), type, size); + std::string md5; + + if (storeMD5_) + { + Toolbox::ComputeMD5(md5, data, size); + } + + return FileInfo(storage_.Create(data, size), type, size, md5); } }
--- a/Core/FileStorage/FileStorageAccessor.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/FileStorage/FileStorageAccessor.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/StorageAccessor.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/FileStorage/StorageAccessor.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/StorageAccessor.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/FileStorage/StorageAccessor.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -45,15 +45,32 @@ class StorageAccessor : boost::noncopyable { protected: + bool storeMD5_; + virtual FileInfo WriteInternal(const void* data, size_t size, FileContentType type) = 0; public: + StorageAccessor() + { + storeMD5_ = true; + } + virtual ~StorageAccessor() { } + void SetStoreMD5(bool storeMD5) + { + storeMD5_ = storeMD5; + } + + bool IsStoreMD5() const + { + return storeMD5_; + } + FileInfo Write(const void* data, size_t size, FileContentType type)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpClient.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,257 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "HttpClient.h" + +#include "../Core/Toolbox.h" +#include "../Core/OrthancException.h" + +#include <string.h> +#include <curl/curl.h> + + +namespace Orthanc +{ + struct HttpClient::PImpl + { + CURL* curl_; + struct curl_slist *postHeaders_; + }; + + + static CURLcode CheckCode(CURLcode code) + { + if (code != CURLE_OK) + { + throw OrthancException("libCURL error: " + std::string(curl_easy_strerror(code))); + } + + return code; + } + + + static size_t CurlCallback(void *buffer, size_t size, size_t nmemb, void *payload) + { + std::string& target = *(static_cast<std::string*>(payload)); + + size_t length = size * nmemb; + if (length == 0) + return 0; + + size_t pos = target.size(); + + target.resize(pos + length); + memcpy(&target.at(pos), buffer, length); + + return length; + } + + + void HttpClient::Setup() + { + pimpl_->postHeaders_ = NULL; + if ((pimpl_->postHeaders_ = curl_slist_append(pimpl_->postHeaders_, "Expect:")) == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + pimpl_->curl_ = curl_easy_init(); + if (!pimpl_->curl_) + { + curl_slist_free_all(pimpl_->postHeaders_); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlCallback)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); + +#if ORTHANC_SSL_ENABLED == 1 + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); +#endif + + // This fixes the "longjmp causes uninitialized stack frame" crash + // that happens on modern Linux versions. + // http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOSIGNAL, 1)); + + url_ = ""; + method_ = HttpMethod_Get; + lastStatus_ = HttpStatus_200_Ok; + isVerbose_ = false; + } + + + HttpClient::HttpClient() : pimpl_(new PImpl) + { + Setup(); + } + + + HttpClient::HttpClient(const HttpClient& other) : pimpl_(new PImpl) + { + Setup(); + + if (other.IsVerbose()) + { + SetVerbose(true); + } + + if (other.credentials_.size() != 0) + { + credentials_ = other.credentials_; + } + } + + + HttpClient::~HttpClient() + { + curl_easy_cleanup(pimpl_->curl_); + curl_slist_free_all(pimpl_->postHeaders_); + } + + + void HttpClient::SetVerbose(bool isVerbose) + { + isVerbose_ = isVerbose; + + if (isVerbose_) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1)); + } + else + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 0)); + } + } + + + bool HttpClient::Apply(std::string& answer) + { + answer.clear(); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, NULL)); + + if (credentials_.size() != 0) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, credentials_.c_str())); + } + + switch (method_) + { + case HttpMethod_Get: + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L)); + break; + + case HttpMethod_Post: + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->postHeaders_)); + + if (postData_.size() > 0) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, postData_.c_str())); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, postData_.size())); + } + else + { + 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_)); + + long status; + CheckCode(curl_easy_getinfo(pimpl_->curl_, CURLINFO_RESPONSE_CODE, &status)); + + if (status == 0) + { + // This corresponds to a call to an inexistent host + lastStatus_ = HttpStatus_500_InternalServerError; + } + else + { + lastStatus_ = static_cast<HttpStatus>(status); + } + + return (status >= 200 && status < 300); + } + + + bool HttpClient::Apply(Json::Value& answer) + { + std::string s; + if (Apply(s)) + { + Json::Reader reader; + return reader.parse(s, answer); + } + else + { + return false; + } + } + + + void HttpClient::SetCredentials(const char* username, + const char* password) + { + credentials_ = std::string(username) + ":" + std::string(password); + } + + + void HttpClient::GlobalInitialize() + { + CheckCode(curl_global_init(CURL_GLOBAL_DEFAULT)); + } + + void HttpClient::GlobalFinalize() + { + curl_global_cleanup(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpClient.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,125 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Core/Enumerations.h" + +#include <string> +#include <boost/shared_ptr.hpp> +#include <json/json.h> + +namespace Orthanc +{ + class HttpClient + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + std::string url_; + std::string credentials_; + HttpMethod method_; + HttpStatus lastStatus_; + std::string postData_; + bool isVerbose_; + + void Setup(); + + void operator= (const HttpClient&); // Forbidden + + public: + HttpClient(const HttpClient& base); + + HttpClient(); + + ~HttpClient(); + + void SetUrl(const char* url) + { + url_ = std::string(url); + } + + void SetUrl(const std::string& url) + { + url_ = url; + } + + const std::string& GetUrl() const + { + return url_; + } + + void SetMethod(HttpMethod method) + { + method_ = method; + } + + HttpMethod GetMethod() const + { + return method_; + } + + std::string& AccessPostData() + { + return postData_; + } + + const std::string& AccessPostData() const + { + return postData_; + } + + void SetVerbose(bool isVerbose); + + bool IsVerbose() const + { + return isVerbose_; + } + + bool Apply(std::string& answer); + + bool Apply(Json::Value& answer); + + HttpStatus GetLastStatus() const + { + return lastStatus_; + } + + void SetCredentials(const char* username, + const char* password); + + static void GlobalInitialize(); + + static void GlobalFinalize(); + }; +}
--- a/Core/HttpServer/BufferHttpSender.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/BufferHttpSender.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -58,13 +58,13 @@ void EmbeddedResourceHttpHandler::Handle( HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& arguments, const std::string&) { - if (method != Orthanc_HttpMethod_Get) + if (method != HttpMethod_Get) { output.SendMethodNotAllowedError("GET"); return; @@ -79,10 +79,10 @@ size_t size = EmbeddedResources::GetDirectoryResourceSize(resourceId_, resourcePath.c_str()); output.AnswerBufferWithContentType(buffer, size, contentType); } - catch (OrthancException& e) + catch (OrthancException&) { LOG(WARNING) << "Unable to find HTTP resource: " << resourcePath; - output.SendHeader(Orthanc_HttpStatus_404_NotFound); + output.SendHeader(HttpStatus_404_NotFound); } } }
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -54,7 +54,7 @@ virtual void Handle( HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& arguments,
--- a/Core/HttpServer/FilesystemHttpHandler.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/FilesystemHttpHandler.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -127,13 +127,13 @@ void FilesystemHttpHandler::Handle( HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& arguments, const std::string&) { - if (method != Orthanc_HttpMethod_Get) + if (method != HttpMethod_Get) { output.SendMethodNotAllowedError("GET"); return; @@ -161,7 +161,7 @@ } else { - output.SendHeader(Orthanc_HttpStatus_404_NotFound); + output.SendHeader(HttpStatus_404_NotFound); } } }
--- a/Core/HttpServer/FilesystemHttpHandler.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/FilesystemHttpHandler.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -56,7 +56,7 @@ virtual void Handle( HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& arguments,
--- a/Core/HttpServer/FilesystemHttpSender.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/FilesystemHttpSender.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/FilesystemHttpSender.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/FilesystemHttpSender.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpFileSender.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/HttpFileSender.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -47,7 +47,7 @@ if (!SendData(output)) { - output.SendHeader(Orthanc_HttpStatus_500_InternalServerError); + output.SendHeader(HttpStatus_500_InternalServerError); } } }
--- a/Core/HttpServer/HttpFileSender.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/HttpFileSender.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpHandler.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/HttpHandler.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpHandler.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/HttpHandler.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -36,7 +36,6 @@ #include <vector> #include <stdint.h> #include "../Toolbox.h" -#include "../../OrthancCppClient/HttpEnumerations.h" namespace Orthanc { @@ -54,7 +53,7 @@ virtual bool IsServedUri(const UriComponents& uri) = 0; virtual void Handle(HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& getArguments,
--- a/Core/HttpServer/HttpOutput.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/HttpOutput.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -38,7 +38,6 @@ #include <boost/lexical_cast.hpp> #include "../OrthancException.h" #include "../Toolbox.h" -#include "../../OrthancCppClient/HttpException.h" namespace Orthanc { @@ -90,7 +89,7 @@ std::string s = "HTTP/1.1 200 OK\r\n"; for (Header::const_iterator - it = header.begin(); it != header.end(); it++) + it = header.begin(); it != header.end(); ++it) { s += it->first + ": " + it->second + "\r\n"; } @@ -104,17 +103,17 @@ void HttpOutput::SendMethodNotAllowedError(const std::string& allowed) { std::string s = - "HTTP/1.1 405 " + std::string(HttpException::GetDescription(Orthanc_HttpStatus_405_MethodNotAllowed)) + + "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(Orthanc_HttpStatus status) + void HttpOutput::SendHeader(HttpStatus status) { - if (status == Orthanc_HttpStatus_200_Ok || - status == Orthanc_HttpStatus_405_MethodNotAllowed) + if (status == HttpStatus_200_Ok || + status == HttpStatus_405_MethodNotAllowed) { throw OrthancException("Please use the dedicated methods to this HTTP status code in HttpOutput"); } @@ -123,11 +122,11 @@ } - void HttpOutput::SendHeaderInternal(Orthanc_HttpStatus status) + void HttpOutput::SendHeaderInternal(HttpStatus status) { std::string s = "HTTP/1.1 " + boost::lexical_cast<std::string>(status) + - " " + std::string(HttpException::GetDescription(status)) + + " " + std::string(EnumerationToString(status)) + "\r\n\r\n"; Send(&s[0], s.size()); } @@ -145,7 +144,7 @@ const HttpHandler::Arguments& cookies) { for (HttpHandler::Arguments::const_iterator it = cookies.begin(); - it != cookies.end(); it++) + it != cookies.end(); ++it) { header.push_back(std::make_pair("Set-Cookie", it->first + "=" + it->second)); } @@ -190,7 +189,7 @@ void HttpOutput::Redirect(const std::string& path) { std::string s = - "HTTP/1.1 301 " + std::string(HttpException::GetDescription(Orthanc_HttpStatus_301_MovedPermanently)) + + "HTTP/1.1 301 " + std::string(EnumerationToString(HttpStatus_301_MovedPermanently)) + "\r\nLocation: " + path + "\r\n\r\n"; Send(&s[0], s.size());
--- a/Core/HttpServer/HttpOutput.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/HttpOutput.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -45,7 +45,7 @@ private: typedef std::list< std::pair<std::string, std::string> > Header; - void SendHeaderInternal(Orthanc_HttpStatus status); + void SendHeaderInternal(HttpStatus status); void PrepareOkHeader(Header& header, const char* contentType, @@ -74,7 +74,7 @@ void SendMethodNotAllowedError(const std::string& allowed); - void SendHeader(Orthanc_HttpStatus status); + void SendHeader(HttpStatus status); void Redirect(const std::string& path);
--- a/Core/HttpServer/MongooseServer.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/MongooseServer.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -49,6 +49,9 @@ #include "HttpOutput.h" #include "mongoose.h" +#if ORTHANC_SSL_ENABLED == 1 +#include <openssl/opensslv.h> +#endif #define ORTHANC_REALM "Orthanc Secure Area" @@ -129,7 +132,7 @@ void Clear() { for (Content::iterator it = content_.begin(); - it != content_.end(); it++) + it != content_.end(); ++it) { delete *it; } @@ -138,7 +141,7 @@ Content::iterator Find(const std::string& filename) { for (Content::iterator it = content_.begin(); - it != content_.end(); it++) + it != content_.end(); ++it) { if ((*it)->GetFilename() == filename) { @@ -254,7 +257,7 @@ HttpHandler* MongooseServer::FindHandler(const UriComponents& forUri) const { for (Handlers::const_iterator it = - handlers_.begin(); it != handlers_.end(); it++) + handlers_.begin(); it != handlers_.end(); ++it) { if ((*it)->IsServedUri(forUri)) { @@ -268,9 +271,9 @@ - static PostDataStatus ReadPostData(std::string& postData, - struct mg_connection *connection, - const HttpHandler::Arguments& headers) + static PostDataStatus ReadBody(std::string& postData, + struct mg_connection *connection, + const HttpHandler::Arguments& headers) { HttpHandler::Arguments::const_iterator cs = headers.find("content-length"); if (cs == headers.end()) @@ -303,6 +306,7 @@ { return PostDataStatus_Failure; } + assert(r <= length); length -= r; pos += r; @@ -322,7 +326,7 @@ std::string boundary = "--" + contentType.substr(multipartLength); std::string postData; - PostDataStatus status = ReadPostData(postData, connection, headers); + PostDataStatus status = ReadBody(postData, connection, headers); if (status != PostDataStatus_Success) { @@ -485,6 +489,83 @@ } + static bool ExtractMethod(HttpMethod& method, + const struct mg_request_info *request, + const HttpHandler::Arguments& headers, + const HttpHandler::Arguments& argumentsGET) + { + std::string overriden; + + // Check whether some PUT/DELETE faking is done + + // 1. Faking with Google's approach + HttpHandler::Arguments::const_iterator methodOverride = + headers.find("x-http-method-override"); + + if (methodOverride != headers.end()) + { + overriden = methodOverride->second; + } + else if (!strcmp(request->request_method, "GET")) + { + // 2. Faking with Ruby on Rail's approach + // GET /my/resource?_method=delete <=> DELETE /my/resource + methodOverride = argumentsGET.find("_method"); + if (methodOverride != argumentsGET.end()) + { + overriden = methodOverride->second; + } + } + + if (overriden.size() > 0) + { + // A faking has been done within this request + Toolbox::ToUpperCase(overriden); + + LOG(INFO) << "HTTP method faking has been detected for " << overriden; + + if (overriden == "PUT") + { + method = HttpMethod_Put; + return true; + } + else if (overriden == "DELETE") + { + method = HttpMethod_Delete; + return true; + } + else + { + return false; + } + } + + // No PUT/DELETE faking was present + if (!strcmp(request->request_method, "GET")) + { + method = HttpMethod_Get; + } + else if (!strcmp(request->request_method, "POST")) + { + method = HttpMethod_Post; + } + else if (!strcmp(request->request_method, "DELETE")) + { + method = HttpMethod_Delete; + } + else if (!strcmp(request->request_method, "PUT")) + { + method = HttpMethod_Put; + } + else + { + return false; + } + + return true; + } + + static void* Callback(enum mg_event event, struct mg_connection *connection, @@ -492,33 +573,10 @@ { if (event == MG_NEW_REQUEST) { - MongooseServer* that = (MongooseServer*) (request->user_data); + MongooseServer* that = reinterpret_cast<MongooseServer*>(request->user_data); MongooseOutput output(connection); - // Compute the method - Orthanc_HttpMethod method; - if (!strcmp(request->request_method, "GET")) - { - method = Orthanc_HttpMethod_Get; - } - else if (!strcmp(request->request_method, "POST")) - { - method = Orthanc_HttpMethod_Post; - } - else if (!strcmp(request->request_method, "DELETE")) - { - method = Orthanc_HttpMethod_Delete; - } - else if (!strcmp(request->request_method, "PUT")) - { - method = Orthanc_HttpMethod_Put; - } - else - { - output.SendHeader(Orthanc_HttpStatus_405_MethodNotAllowed); - return (void*) ""; - } - + // Check remote calls if (!that->IsRemoteAccessAllowed() && request->remote_ip != LOCALHOST) { @@ -526,8 +584,9 @@ return (void*) ""; } - HttpHandler::Arguments arguments, headers; + // Extract the HTTP headers + HttpHandler::Arguments headers; for (int i = 0; i < request->num_headers; i++) { std::string name = request->http_headers[i].name; @@ -535,6 +594,24 @@ 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)) @@ -564,83 +641,93 @@ } - std::string postData; - - if (method == Orthanc_HttpMethod_Get) + // Extract the body of the request for PUT and POST + std::string body; + if (method == HttpMethod_Post || + method == HttpMethod_Put) { - HttpHandler::ParseGetQuery(arguments, request->query_string); - } - else if (method == Orthanc_HttpMethod_Post || - method == Orthanc_HttpMethod_Put) - { + PostDataStatus status; + HttpHandler::Arguments::const_iterator ct = headers.find("content-type"); if (ct == headers.end()) { - output.SendHeader(Orthanc_HttpStatus_400_BadRequest); - return (void*) ""; - } - - PostDataStatus status; - - std::string contentType = ct->second; - if (contentType.size() >= multipartLength && - !memcmp(contentType.c_str(), multipart, multipartLength)) - { - status = ParseMultipartPost(postData, connection, headers, contentType, that->GetChunkStore()); + // No content-type specified. Assume no multi-part content occurs at this point. + status = ReadBody(body, connection, headers); } else { - status = ReadPostData(postData, connection, headers); + 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(Orthanc_HttpStatus_411_LengthRequired); - return (void*) ""; + case PostDataStatus_NoLength: + output.SendHeader(HttpStatus_411_LengthRequired); + return (void*) ""; - case PostDataStatus_Failure: - output.SendHeader(Orthanc_HttpStatus_400_BadRequest); - return (void*) ""; + case PostDataStatus_Failure: + output.SendHeader(HttpStatus_400_BadRequest); + return (void*) ""; - case PostDataStatus_Pending: - output.AnswerBufferWithContentType(NULL, 0, ""); - return (void*) ""; + case PostDataStatus_Pending: + output.AnswerBufferWithContentType(NULL, 0, ""); + return (void*) ""; - default: - break; + default: + break; } } + + // Call the proper handler for this URI UriComponents uri; - Toolbox::SplitUriComponents(uri, request->uri); + try + { + Toolbox::SplitUriComponents(uri, request->uri); + } + catch (OrthancException) + { + output.SendHeader(HttpStatus_400_BadRequest); + return (void*) ""; + } + HttpHandler* handler = that->FindHandler(uri); if (handler) { try { - handler->Handle(output, method, uri, headers, arguments, postData); + 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(Orthanc_HttpStatus_500_InternalServerError); + output.SendHeader(HttpStatus_500_InternalServerError); } catch (boost::bad_lexical_cast&) { LOG(ERROR) << "MongooseServer Exception: Bad lexical cast"; - output.SendHeader(Orthanc_HttpStatus_400_BadRequest); + output.SendHeader(HttpStatus_400_BadRequest); } catch (std::runtime_error&) { LOG(ERROR) << "MongooseServer Exception: Presumably a bad JSON request"; - output.SendHeader(Orthanc_HttpStatus_400_BadRequest); + output.SendHeader(HttpStatus_400_BadRequest); } } else { - output.SendHeader(Orthanc_HttpStatus_404_NotFound); + output.SendHeader(HttpStatus_404_NotFound); } // Mark as processed @@ -666,6 +753,17 @@ authentication_ = false; ssl_ = false; port_ = 8000; + filter_ = NULL; + +#if ORTHANC_SSL_ENABLED == 1 + // Check for the Heartbleed exploit + // https://en.wikipedia.org/wiki/OpenSSL#Heartbleed_bug + if (OPENSSL_VERSION_NUMBER < 0x1000107fL /* openssl-1.0.1g */ && + OPENSSL_VERSION_NUMBER >= 0x1000100fL /* openssl-1.0.1 */) + { + LOG(WARNING) << "This version of OpenSSL is vulnerable to the Heartbleed exploit"; + } +#endif } @@ -731,7 +829,7 @@ Stop(); for (Handlers::iterator it = - handlers_.begin(); it != handlers_.end(); it++) + handlers_.begin(); it != handlers_.end(); ++it) { delete *it; }
--- a/Core/HttpServer/MongooseServer.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/HttpServer/MongooseServer.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -51,7 +51,7 @@ { } - virtual bool IsAllowed(Orthanc_HttpMethod method, + virtual bool IsAllowed(HttpMethod method, const char* uri, const char* ip, const char* username) const = 0;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ICommand.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,48 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IDynamicObject.h" + +namespace Orthanc +{ + /** + * This class is the base class for the "Command" design pattern. + * http://en.wikipedia.org/wiki/Command_pattern + **/ + class ICommand : public IDynamicObject + { + public: + virtual bool Execute() = 0; + }; +}
--- a/Core/IDynamicObject.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/IDynamicObject.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Lua/LuaContext.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/Lua/LuaContext.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -42,11 +42,18 @@ namespace Orthanc { - int LuaContext::PrintToLog(lua_State *L) + int LuaContext::PrintToLog(lua_State *state) { + // Get the pointer to the "LuaContext" underlying object + lua_getglobal(state, "_LuaContext"); + assert(lua_type(state, -1) == LUA_TLIGHTUSERDATA); + LuaContext* that = const_cast<LuaContext*>(reinterpret_cast<const LuaContext*>(lua_topointer(state, -1))); + assert(that != NULL); + lua_pop(state, 1); + // http://medek.wordpress.com/2009/02/03/wrapping-lua-errors-and-print-function/ - int nArgs = lua_gettop(L); - lua_getglobal(L, "tostring"); + int nArgs = lua_gettop(state); + lua_getglobal(state, "tostring"); // Make sure you start at 1 *NOT* 0 for arrays in Lua. std::string result; @@ -54,10 +61,10 @@ for (int i = 1; i <= nArgs; i++) { const char *s; - lua_pushvalue(L, -1); - lua_pushvalue(L, i); - lua_call(L, 1, 1); - s = lua_tostring(L, -1); + lua_pushvalue(state, -1); + lua_pushvalue(state, i); + lua_call(state, 1, 1); + s = lua_tostring(state, -1); if (result.size() > 0) result.append(", "); @@ -67,10 +74,12 @@ else result.append(s); - lua_pop(L, 1); + lua_pop(state, 1); } LOG(INFO) << "Lua says: " << result; + that->log_.append(result); + that->log_.append("\n"); return 0; } @@ -86,6 +95,9 @@ luaL_openlibs(lua_); lua_register(lua_, "print", PrintToLog); + + lua_pushlightuserdata(lua_, this); + lua_setglobal(lua_, "_LuaContext"); } @@ -95,12 +107,12 @@ } - void LuaContext::Execute(const std::string& command) + void LuaContext::Execute(std::string* output, + const std::string& command) { boost::mutex::scoped_lock lock(mutex_); - lua_settop(lua_, 0); - + log_.clear(); int error = (luaL_loadbuffer(lua_, command.c_str(), command.size(), "line") || lua_pcall(lua_, 0, 0, 0)); @@ -112,6 +124,11 @@ lua_pop(lua_, 1); /* pop error message from the stack */ throw LuaException(description); } + + if (output != NULL) + { + *output = log_; + } }
--- a/Core/Lua/LuaContext.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Lua/LuaContext.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -53,15 +53,28 @@ lua_State *lua_; boost::mutex mutex_; + std::string log_; static int PrintToLog(lua_State *L); + void Execute(std::string* output, + const std::string& command); + public: LuaContext(); ~LuaContext(); - void Execute(const std::string& command); + void Execute(const std::string& command) + { + Execute(NULL, command); + } + + void Execute(std::string& output, + const std::string& command) + { + Execute(&output, command); + } void Execute(EmbeddedResources::FileResourceId resource);
--- a/Core/Lua/LuaException.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Lua/LuaException.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Lua/LuaFunctionCall.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/Lua/LuaFunctionCall.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -130,7 +130,7 @@ Json::Value::Members members = value.getMemberNames(); for (Json::Value::Members::const_iterator - it = members.begin(); it != members.end(); it++) + it = members.begin(); it != members.end(); ++it) { // Push the index of the cell lua_pushstring(context_.lua_, it->c_str());
--- a/Core/Lua/LuaFunctionCall.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Lua/LuaFunctionCall.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ArrayFilledByThreads.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,121 @@ +#include "ArrayFilledByThreads.h" + +#include "../MultiThreading/ThreadedCommandProcessor.h" +#include "../OrthancException.h" + +namespace Orthanc +{ + class ArrayFilledByThreads::Command : public ICommand + { + private: + ArrayFilledByThreads& that_; + size_t index_; + + public: + Command(ArrayFilledByThreads& that, + size_t index) : + that_(that), + index_(index) + { + } + + virtual bool Execute() + { + std::auto_ptr<IDynamicObject> obj(that_.filler_.GetFillerItem(index_)); + if (obj.get() == NULL) + { + return false; + } + else + { + boost::mutex::scoped_lock lock(that_.mutex_); + that_.array_[index_] = obj.release(); + return true; + } + } + }; + + void ArrayFilledByThreads::Clear() + { + for (size_t i = 0; i < array_.size(); i++) + { + if (array_[i]) + delete array_[i]; + } + + array_.clear(); + filled_ = false; + } + + void ArrayFilledByThreads::Update() + { + if (!filled_) + { + array_.resize(filler_.GetFillerSize()); + + Orthanc::ThreadedCommandProcessor processor(threadCount_); + for (size_t i = 0; i < array_.size(); i++) + { + processor.Post(new Command(*this, i)); + } + + processor.Join(); + filled_ = true; + } + } + + + ArrayFilledByThreads::ArrayFilledByThreads(IFiller& filler) : filler_(filler) + { + filled_ = false; + threadCount_ = 4; + } + + + ArrayFilledByThreads::~ArrayFilledByThreads() + { + Clear(); + } + + + void ArrayFilledByThreads::Reload() + { + Clear(); + Update(); + } + + + void ArrayFilledByThreads::Invalidate() + { + Clear(); + } + + + void ArrayFilledByThreads::SetThreadCount(unsigned int t) + { + if (t < 1) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + threadCount_ = t; + } + + + size_t ArrayFilledByThreads::GetSize() + { + Update(); + return array_.size(); + } + + + IDynamicObject& ArrayFilledByThreads::GetItem(size_t index) + { + if (index >= GetSize()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + return *array_[index]; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ArrayFilledByThreads.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,54 @@ +#pragma once + +#include <boost/thread.hpp> + +#include "../IDynamicObject.h" + +namespace Orthanc +{ + class ArrayFilledByThreads + { + public: + class IFiller + { + public: + virtual size_t GetFillerSize() = 0; + + virtual IDynamicObject* GetFillerItem(size_t index) = 0; + }; + + private: + IFiller& filler_; + boost::mutex mutex_; + std::vector<IDynamicObject*> array_; + bool filled_; + unsigned int threadCount_; + + class Command; + + void Clear(); + + void Update(); + + public: + ArrayFilledByThreads(IFiller& filler); + + ~ArrayFilledByThreads(); + + void Reload(); + + void Invalidate(); + + void SetThreadCount(unsigned int t); + + unsigned int GetThreadCount() const + { + return threadCount_; + } + + size_t GetSize(); + + IDynamicObject& GetItem(size_t index); + }; +} +
--- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -102,7 +102,11 @@ assert(t.get() != NULL); assert(bag->pimpl_->activeThreads_.find(r.get()) == bag->pimpl_->activeThreads_.end()); - t->join(); + if (t->joinable()) + { + t->join(); + } + bag->pimpl_->oneThreadIsJoined_.notify_one(); } @@ -128,7 +132,11 @@ // Stop the finish listener pimpl_->stopFinishListener_ = true; pimpl_->oneThreadIsStopped_.notify_one(); // Awakens the listener - pimpl_->finishListener_->join(); + + if (pimpl_->finishListener_->joinable()) + { + pimpl_->finishListener_->join(); + } }
--- a/Core/MultiThreading/BagOfRunnablesBySteps.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/MultiThreading/BagOfRunnablesBySteps.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ILockable.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,50 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class ILockable : public boost::noncopyable + { + public: + virtual ~ILockable() + { + } + + virtual void Lock() = 0; + + virtual void Unlock() = 0; + }; +}
--- a/Core/MultiThreading/IRunnableBySteps.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/MultiThreading/IRunnableBySteps.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/Locker.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,55 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ILockable.h" + +namespace Orthanc +{ + class Locker + { + private: + ILockable& lockable_; + + public: + Locker(ILockable& lockable) : lockable_(lockable) + { + lockable_.Lock(); + } + + virtual ~Locker() + { + lockable_.Unlock(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/Mutex.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,120 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "Mutex.h" + +#include "../OrthancException.h" + +#if defined(_WIN32) +#include <windows.h> +#elif defined(__linux) +#include <pthread.h> +#else +#error Support your platform here +#endif + +namespace Orthanc +{ +#if defined (_WIN32) + + struct Mutex::PImpl + { + CRITICAL_SECTION criticalSection_; + }; + + Mutex::Mutex() + { + pimpl_ = new PImpl; + ::InitializeCriticalSection(&pimpl_->criticalSection_); + } + + Mutex::~Mutex() + { + ::DeleteCriticalSection(&pimpl_->criticalSection_); + delete pimpl_; + } + + void Mutex::Lock() + { + ::EnterCriticalSection(&pimpl_->criticalSection_); + } + + void Mutex::Unlock() + { + ::LeaveCriticalSection(&pimpl_->criticalSection_); + } + + +#elif defined(__linux) + + struct Mutex::PImpl + { + pthread_mutex_t mutex_; + }; + + Mutex::Mutex() + { + pimpl_ = new PImpl; + + if (pthread_mutex_init(&pimpl_->mutex_, NULL) != 0) + { + delete pimpl_; + throw OrthancException(ErrorCode_InternalError); + } + } + + Mutex::~Mutex() + { + pthread_mutex_destroy(&pimpl_->mutex_); + delete pimpl_; + } + + void Mutex::Lock() + { + if (pthread_mutex_lock(&pimpl_->mutex_) != 0) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + void Mutex::Unlock() + { + if (pthread_mutex_unlock(&pimpl_->mutex_) != 0) + { + throw OrthancException(ErrorCode_InternalError); + } + } + +#else +#error Support your plateform here +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/Mutex.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,55 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ILockable.h" + +namespace Orthanc +{ + class Mutex : public ILockable + { + private: + struct PImpl; + + PImpl *pimpl_; + + public: + Mutex(); + + ~Mutex(); + + virtual void Lock(); + + virtual void Unlock(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ReaderWriterLock.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,122 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "ReaderWriterLock.h" + +#include <boost/thread/shared_mutex.hpp> + +namespace Orthanc +{ + namespace + { + // Anonymous namespace to avoid clashes between compilation + // modules. + + class ReaderLockable : public ILockable + { + private: + boost::shared_mutex& lock_; + + public: + ReaderLockable(boost::shared_mutex& lock) : lock_(lock) + { + } + + virtual void Lock() + { + lock_.lock_shared(); + } + + virtual void Unlock() + { + lock_.unlock_shared(); + } + }; + + + class WriterLockable : public ILockable + { + private: + boost::shared_mutex& lock_; + + public: + WriterLockable(boost::shared_mutex& lock) : lock_(lock) + { + } + + virtual void Lock() + { + lock_.lock(); + } + + virtual void Unlock() + { + lock_.unlock(); + } + }; + } + + struct ReaderWriterLock::PImpl + { + boost::shared_mutex lock_; + ReaderLockable reader_; + WriterLockable writer_; + + PImpl() : reader_(lock_), writer_(lock_) + { + } + }; + + + ReaderWriterLock::ReaderWriterLock() + { + pimpl_ = new PImpl; + } + + + ReaderWriterLock::~ReaderWriterLock() + { + delete pimpl_; + } + + + ILockable& ReaderWriterLock::ForReader() + { + return pimpl_->reader_; + } + + + ILockable& ReaderWriterLock::ForWriter() + { + return pimpl_->writer_; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ReaderWriterLock.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,55 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ILockable.h" + +namespace Orthanc +{ + class ReaderWriterLock + { + private: + struct PImpl; + + PImpl *pimpl_; + + public: + ReaderWriterLock(); + + virtual ~ReaderWriterLock(); + + ILockable& ForReader(); + + ILockable& ForWriter(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/SharedMessageQueue.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,126 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "SharedMessageQueue.h" + +namespace Orthanc +{ + SharedMessageQueue::SharedMessageQueue(unsigned int maxSize) + { + maxSize_ = maxSize; + } + + + SharedMessageQueue::~SharedMessageQueue() + { + for (Queue::iterator it = queue_.begin(); it != queue_.end(); ++it) + { + delete *it; + } + } + + + void SharedMessageQueue::Enqueue(IDynamicObject* message) + { + boost::mutex::scoped_lock lock(mutex_); + + if (maxSize_ != 0 && queue_.size() > maxSize_) + { + // Too many elements in the queue: First remove the oldest + delete queue_.front(); + queue_.pop_front(); + } + + queue_.push_back(message); + elementAvailable_.notify_one(); + } + + + IDynamicObject* SharedMessageQueue::Dequeue(int32_t millisecondsTimeout) + { + boost::mutex::scoped_lock lock(mutex_); + + // Wait for a message to arrive in the FIFO queue + while (queue_.empty()) + { + if (millisecondsTimeout == 0) + { + elementAvailable_.wait(lock); + } + else + { + bool success = elementAvailable_.timed_wait + (lock, boost::posix_time::milliseconds(millisecondsTimeout)); + if (!success) + { + return NULL; + } + } + } + + std::auto_ptr<IDynamicObject> message(queue_.front()); + queue_.pop_front(); + + if (queue_.empty()) + { + emptied_.notify_all(); + } + + return message.release(); + } + + + + bool SharedMessageQueue::WaitEmpty(int32_t millisecondsTimeout) + { + boost::mutex::scoped_lock lock(mutex_); + + // Wait for the queue to become empty + while (!queue_.empty()) + { + if (millisecondsTimeout == 0) + { + emptied_.wait(lock); + } + else + { + if (!emptied_.timed_wait + (lock, boost::posix_time::milliseconds(millisecondsTimeout))) + { + return false; + } + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/SharedMessageQueue.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,67 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../IDynamicObject.h" + +#include <stdint.h> +#include <list> +#include <boost/thread.hpp> + +namespace Orthanc +{ + class SharedMessageQueue + { + private: + typedef std::list<IDynamicObject*> Queue; + + unsigned int maxSize_; + Queue queue_; + boost::mutex mutex_; + boost::condition_variable elementAvailable_; + boost::condition_variable emptied_; + + public: + SharedMessageQueue(unsigned int maxSize = 0); + + ~SharedMessageQueue(); + + // This transfers the ownership of the message + void Enqueue(IDynamicObject* message); + + // The caller is responsible to delete the dequeud message! + IDynamicObject* Dequeue(int32_t millisecondsTimeout); + + bool WaitEmpty(int32_t millisecondsTimeout); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ThreadedCommandProcessor.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,210 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "ThreadedCommandProcessor.h" + +#include "../OrthancException.h" + +namespace Orthanc +{ + static const int32_t TIMEOUT = 10; + + + void ThreadedCommandProcessor::Processor(ThreadedCommandProcessor* that) + { + while (!that->done_) + { + std::auto_ptr<IDynamicObject> command(that->queue_.Dequeue(TIMEOUT)); + + if (command.get() != NULL) + { + bool success = false; + + try + { + if (that->success_) + { + // No command has failed so far + + if (that->cancel_) + { + // The commands have been canceled. Skip the execution + // of this command, yet mark it as succeeded. + success = true; + } + else + { + success = dynamic_cast<ICommand&>(*command).Execute(); + } + } + else + { + // A command has already failed. Skip the execution of this command. + } + } + catch (OrthancException) + { + } + + { + boost::mutex::scoped_lock lock(that->mutex_); + assert(that->remainingCommands_ > 0); + that->remainingCommands_--; + + if (!success) + { + if (!that->cancel_ && that->listener_ && that->success_) + { + // This is the first command that fails + that->listener_->SignalFailure(); + } + + that->success_ = false; + } + else + { + if (!that->cancel_ && that->listener_) + { + if (that->remainingCommands_ == 0) + { + that->listener_->SignalSuccess(that->totalCommands_); + } + else + { + that->listener_->SignalProgress(that->totalCommands_ - that->remainingCommands_, + that->totalCommands_); + } + } + } + + that->processedCommand_.notify_all(); + } + } + } + } + + + ThreadedCommandProcessor::ThreadedCommandProcessor(unsigned int numThreads) + { + if (numThreads < 1) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + listener_ = NULL; + success_ = true; + done_ = false; + cancel_ = false; + threads_.resize(numThreads); + remainingCommands_ = 0; + totalCommands_ = 0; + + for (unsigned int i = 0; i < numThreads; i++) + { + threads_[i] = new boost::thread(Processor, this); + } + } + + + ThreadedCommandProcessor::~ThreadedCommandProcessor() + { + done_ = true; + + for (unsigned int i = 0; i < threads_.size(); i++) + { + boost::thread* t = threads_[i]; + + if (t != NULL) + { + if (t->joinable()) + { + t->join(); + } + + delete t; + } + } + } + + + void ThreadedCommandProcessor::Post(ICommand* command) + { + if (command == NULL) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + boost::mutex::scoped_lock lock(mutex_); + queue_.Enqueue(command); + remainingCommands_++; + totalCommands_++; + } + + + bool ThreadedCommandProcessor::Join() + { + boost::mutex::scoped_lock lock(mutex_); + + while (!remainingCommands_ == 0) + { + processedCommand_.wait(lock); + } + + if (cancel_ && listener_) + { + listener_->SignalCancel(); + } + + // Reset the sequence counters for subsequent commands + bool hasSucceeded = success_; + success_ = true; + totalCommands_ = 0; + cancel_ = false; + + return hasSucceeded; + } + + + void ThreadedCommandProcessor::Cancel() + { + boost::mutex::scoped_lock lock(mutex_); + + cancel_ = true; + } + + + void ThreadedCommandProcessor::SetListener(IListener& listener) + { + boost::mutex::scoped_lock lock(mutex_); + listener_ = &listener; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ThreadedCommandProcessor.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,94 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../ICommand.h" + +#include "SharedMessageQueue.h" + +namespace Orthanc +{ + class ThreadedCommandProcessor + { + public: + class IListener + { + public: + virtual ~IListener() + { + } + + virtual void SignalProgress(unsigned int current, + unsigned int total) = 0; + + virtual void SignalSuccess(unsigned int total) = 0; + + virtual void SignalFailure() = 0; + + virtual void SignalCancel() = 0; + }; + + private: + SharedMessageQueue queue_; + bool done_; + bool cancel_; + std::vector<boost::thread*> threads_; + IListener* listener_; + + boost::mutex mutex_; + bool success_; + unsigned int remainingCommands_, totalCommands_; + boost::condition_variable processedCommand_; + + static void Processor(ThreadedCommandProcessor* that); + + public: + ThreadedCommandProcessor(unsigned int numThreads); + + ~ThreadedCommandProcessor(); + + // This takes the ownership of the command + void Post(ICommand* command); + + bool Join(); + + void Cancel(); + + void SetListener(IListener& listener); + + IListener& GetListener() const + { + return *listener_; + } + }; +}
--- a/Core/OrthancException.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/OrthancException.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -102,6 +102,15 @@ case ErrorCode_BadRequest: return "Bad request"; + case ErrorCode_NetworkProtocol: + return "Error in the network protocol"; + + case ErrorCode_CorruptedFile: + return "Corrupted file (inconsistent MD5 hash)"; + + case ErrorCode_InexistentTag: + return "Inexistent tag"; + case ErrorCode_Custom: default: return "???";
--- a/Core/OrthancException.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/OrthancException.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -46,21 +46,20 @@ public: static const char* GetDescription(ErrorCode error); - OrthancException(const char* custom) + OrthancException(const char* custom) : + error_(ErrorCode_Custom), + custom_(custom) { - error_ = ErrorCode_Custom; - custom_ = custom; } - OrthancException(const std::string& custom) + OrthancException(const std::string& custom) : + error_(ErrorCode_Custom), + custom_(custom) { - error_ = ErrorCode_Custom; - custom_ = custom; } - OrthancException(ErrorCode error) + OrthancException(ErrorCode error) : error_(error) { - error_ = error; } ErrorCode GetErrorCode() const
--- a/Core/PngWriter.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,252 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 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 <http://www.gnu.org/licenses/>. - **/ - - -#include "PngWriter.h" - -#include <vector> -#include <stdint.h> -#include <png.h> -#include "OrthancException.h" -#include "ChunkedBuffer.h" - - -// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4 -// http://zarb.org/~gc/html/libpng.html -/* - void write_row_callback(png_ptr, png_uint_32 row, int pass) - { - }*/ - - - - -/* bool isError_; - -// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2 - -static void ErrorHandler(png_structp png, png_const_charp message) -{ -printf("** [%s]\n", message); - -PngWriter* that = (PngWriter*) png_get_error_ptr(png); -that->isError_ = true; -printf("** %d\n", (int)that); - -//((PngWriter*) payload)->isError_ = true; -} - -static void WarningHandler(png_structp png, png_const_charp message) -{ - printf("++ %d\n", (int)message); -}*/ - - -namespace Orthanc -{ - struct PngWriter::PImpl - { - png_structp png_; - png_infop info_; - - // Filled by Prepare() - std::vector<uint8_t*> rows_; - int bitDepth_; - int colorType_; - }; - - - - PngWriter::PngWriter() : pimpl_(new PImpl) - { - pimpl_->png_ = NULL; - pimpl_->info_ = NULL; - - pimpl_->png_ = png_create_write_struct - (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler); - if (!pimpl_->png_) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - pimpl_->info_ = png_create_info_struct(pimpl_->png_); - if (!pimpl_->info_) - { - png_destroy_write_struct(&pimpl_->png_, NULL); - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - PngWriter::~PngWriter() - { - if (pimpl_->info_) - { - png_destroy_info_struct(pimpl_->png_, &pimpl_->info_); - } - - if (pimpl_->png_) - { - png_destroy_write_struct(&pimpl_->png_, NULL); - } - } - - - - void PngWriter::Prepare(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - pimpl_->rows_.resize(height); - for (unsigned int y = 0; y < height; y++) - { - pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch; - } - - switch (format) - { - case PixelFormat_RGB24: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_RGB; - break; - - case PixelFormat_Grayscale8: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; - break; - - case PixelFormat_Grayscale16: - pimpl_->bitDepth_ = 16; - pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void PngWriter::Compress(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format) - { - png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height, - pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - png_write_info(pimpl_->png_, pimpl_->info_); - - if (height > 0) - { - switch (format) - { - case PixelFormat_Grayscale16: - // Must swap the endianness!! - png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]); - png_write_png(pimpl_->png_, pimpl_->info_, PNG_TRANSFORM_SWAP_ENDIAN, NULL); - break; - - default: - png_write_image(pimpl_->png_, &pimpl_->rows_[0]); - } - } - - png_write_end(pimpl_->png_, NULL); - } - - - void PngWriter::WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - Prepare(width, height, pitch, format, buffer); - - FILE* fp = fopen(filename, "wb"); - if (!fp) - { - throw OrthancException(ErrorCode_CannotWriteFile); - } - - png_init_io(pimpl_->png_, fp); - - if (setjmp(png_jmpbuf(pimpl_->png_))) - { - // Error during writing PNG - throw OrthancException(ErrorCode_CannotWriteFile); - } - - Compress(width, height, pitch, format); - - fclose(fp); - } - - - - - static void MemoryCallback(png_structp png_ptr, - png_bytep data, - png_size_t size) - { - ChunkedBuffer* buffer = (ChunkedBuffer*) png_get_io_ptr(png_ptr); - buffer->AddChunk(reinterpret_cast<const char*>(data), size); - } - - - - void PngWriter::WriteToMemory(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - ChunkedBuffer chunks; - - Prepare(width, height, pitch, format, buffer); - - if (setjmp(png_jmpbuf(pimpl_->png_))) - { - // Error during writing PNG - throw OrthancException(ErrorCode_InternalError); - } - - png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL); - - Compress(width, height, pitch, format); - - chunks.Flatten(png); - } -}
--- a/Core/PngWriter.h Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 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 <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "Enumerations.h" - -#include <boost/shared_ptr.hpp> -#include <string> - -namespace Orthanc -{ - class PngWriter - { - private: - struct PImpl; - boost::shared_ptr<PImpl> pimpl_; - - void Compress(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format); - - void Prepare(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - public: - PngWriter(); - - ~PngWriter(); - - void WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - void WriteToMemory(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - }; -}
--- a/Core/RestApi/RestApi.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/RestApi/RestApi.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -51,7 +51,7 @@ result.clear(); for (HttpHandler::Arguments::const_iterator - it = getArguments_->begin(); it != getArguments_->end(); it++) + it = getArguments_.begin(); it != getArguments_.end(); ++it) { result[it->first] = it->second; } @@ -63,7 +63,7 @@ bool RestApi::IsGetAccepted(const UriComponents& uri) { for (GetHandlers::const_iterator it = getHandlers_.begin(); - it != getHandlers_.end(); it++) + it != getHandlers_.end(); ++it) { if (it->first->Match(uri)) { @@ -77,7 +77,7 @@ bool RestApi::IsPutAccepted(const UriComponents& uri) { for (PutHandlers::const_iterator it = putHandlers_.begin(); - it != putHandlers_.end(); it++) + it != putHandlers_.end(); ++it) { if (it->first->Match(uri)) { @@ -91,7 +91,7 @@ bool RestApi::IsPostAccepted(const UriComponents& uri) { for (PostHandlers::const_iterator it = postHandlers_.begin(); - it != postHandlers_.end(); it++) + it != postHandlers_.end(); ++it) { if (it->first->Match(uri)) { @@ -105,7 +105,7 @@ bool RestApi::IsDeleteAccepted(const UriComponents& uri) { for (DeleteHandlers::const_iterator it = deleteHandlers_.begin(); - it != deleteHandlers_.end(); it++) + it != deleteHandlers_.end(); ++it) { if (it->first->Match(uri)) { @@ -147,25 +147,25 @@ RestApi::~RestApi() { for (GetHandlers::iterator it = getHandlers_.begin(); - it != getHandlers_.end(); it++) + it != getHandlers_.end(); ++it) { delete it->first; } for (PutHandlers::iterator it = putHandlers_.begin(); - it != putHandlers_.end(); it++) + it != putHandlers_.end(); ++it) { delete it->first; } for (PostHandlers::iterator it = postHandlers_.begin(); - it != postHandlers_.end(); it++) + it != postHandlers_.end(); ++it) { delete it->first; } for (DeleteHandlers::iterator it = deleteHandlers_.begin(); - it != deleteHandlers_.end(); it++) + it != deleteHandlers_.end(); ++it) { delete it->first; } @@ -180,7 +180,7 @@ } void RestApi::Handle(HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& getArguments, @@ -191,88 +191,58 @@ RestApiPath::Components components; UriComponents trailing; - if (method == Orthanc_HttpMethod_Get) + if (method == HttpMethod_Get) { for (GetHandlers::const_iterator it = getHandlers_.begin(); - it != getHandlers_.end(); it++) + it != getHandlers_.end(); ++it) { if (it->first->Match(components, trailing, uri)) { - LOG(INFO) << "REST GET call on: " << Toolbox::FlattenUri(uri); + //LOG(INFO) << "REST GET call on: " << Toolbox::FlattenUri(uri); ok = true; - GetCall call; - call.output_ = &restOutput; - call.context_ = this; - call.httpHeaders_ = &headers; - call.uriComponents_ = &components; - call.trailing_ = &trailing; - call.fullUri_ = &uri; - - call.getArguments_ = &getArguments; + GetCall call(restOutput, *this, headers, components, trailing, uri, getArguments); it->second(call); } } } - else if (method == Orthanc_HttpMethod_Put) + else if (method == HttpMethod_Put) { for (PutHandlers::const_iterator it = putHandlers_.begin(); - it != putHandlers_.end(); it++) + it != putHandlers_.end(); ++it) { if (it->first->Match(components, trailing, uri)) { - LOG(INFO) << "REST PUT call on: " << Toolbox::FlattenUri(uri); + //LOG(INFO) << "REST PUT call on: " << Toolbox::FlattenUri(uri); ok = true; - PutCall call; - call.output_ = &restOutput; - call.context_ = this; - call.httpHeaders_ = &headers; - call.uriComponents_ = &components; - call.trailing_ = &trailing; - call.fullUri_ = &uri; - - call.data_ = &postData; + PutCall call(restOutput, *this, headers, components, trailing, uri, postData); it->second(call); } } } - else if (method == Orthanc_HttpMethod_Post) + else if (method == HttpMethod_Post) { for (PostHandlers::const_iterator it = postHandlers_.begin(); - it != postHandlers_.end(); it++) + it != postHandlers_.end(); ++it) { if (it->first->Match(components, trailing, uri)) { - LOG(INFO) << "REST POST call on: " << Toolbox::FlattenUri(uri); + //LOG(INFO) << "REST POST call on: " << Toolbox::FlattenUri(uri); ok = true; - PostCall call; - call.output_ = &restOutput; - call.context_ = this; - call.httpHeaders_ = &headers; - call.uriComponents_ = &components; - call.trailing_ = &trailing; - call.fullUri_ = &uri; - - call.data_ = &postData; + PostCall call(restOutput, *this, headers, components, trailing, uri, postData); it->second(call); } } } - else if (method == Orthanc_HttpMethod_Delete) + else if (method == HttpMethod_Delete) { for (DeleteHandlers::const_iterator it = deleteHandlers_.begin(); - it != deleteHandlers_.end(); it++) + it != deleteHandlers_.end(); ++it) { if (it->first->Match(components, trailing, uri)) { - LOG(INFO) << "REST DELETE call on: " << Toolbox::FlattenUri(uri); + //LOG(INFO) << "REST DELETE call on: " << Toolbox::FlattenUri(uri); ok = true; - DeleteCall call; - call.output_ = &restOutput; - call.context_ = this; - call.httpHeaders_ = &headers; - call.uriComponents_ = &components; - call.trailing_ = &trailing; - call.fullUri_ = &uri; + DeleteCall call(restOutput, *this, headers, components, trailing, uri); it->second(call); } } @@ -280,7 +250,8 @@ if (!ok) { - LOG(INFO) << "REST method " << method << " not allowed on: " << Toolbox::FlattenUri(uri); + LOG(INFO) << "REST method " << EnumerationToString(method) + << " not allowed on: " << Toolbox::FlattenUri(uri); output.SendMethodNotAllowedError(GetAcceptedMethods(uri)); } }
--- a/Core/RestApi/RestApi.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/RestApi/RestApi.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -48,12 +48,27 @@ friend class RestApi; private: - RestApiOutput* output_; - RestApi* context_; - const HttpHandler::Arguments* httpHeaders_; - const RestApiPath::Components* uriComponents_; - const UriComponents* trailing_; - const UriComponents* fullUri_; + 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, @@ -62,44 +77,44 @@ public: RestApiOutput& GetOutput() { - return *output_; + return output_; } RestApi& GetContext() { - return *context_; + return context_; } const UriComponents& GetFullUri() const { - return *fullUri_; + return fullUri_; } const UriComponents& GetTrailingUri() const { - return *trailing_; + return trailing_; } std::string GetUriComponent(const std::string& name, const std::string& defaultValue) const { - return HttpHandler::GetArgument(*uriComponents_, name, defaultValue); + return HttpHandler::GetArgument(uriComponents_, name, defaultValue); } std::string GetHttpHeader(const std::string& name, const std::string& defaultValue) const { - return HttpHandler::GetArgument(*httpHeaders_, name, defaultValue); + return HttpHandler::GetArgument(httpHeaders_, name, defaultValue); } const HttpHandler::Arguments& GetHttpHeaders() const { - return *httpHeaders_; + return httpHeaders_; } void ParseCookies(HttpHandler::Arguments& result) const { - HttpHandler::ParseCookies(result, *httpHeaders_); + HttpHandler::ParseCookies(result, httpHeaders_); } virtual bool ParseJsonRequest(Json::Value& result) const = 0; @@ -111,34 +126,59 @@ friend class RestApi; private: - const HttpHandler::Arguments* getArguments_; + 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); + return HttpHandler::GetArgument(getArguments_, name, defaultValue); } bool HasArgument(const std::string& name) const { - return getArguments_->find(name) != getArguments_->end(); + 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_; + 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_; + return data_; } virtual bool ParseJsonRequest(Json::Value& result) const @@ -152,12 +192,24 @@ friend class RestApi; private: - const std::string* data_; + 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_; + return data_; } virtual bool ParseJsonRequest(Json::Value& result) const @@ -169,6 +221,16 @@ 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(); @@ -212,7 +274,7 @@ virtual bool IsServedUri(const UriComponents& uri); virtual void Handle(HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& getArguments,
--- a/Core/RestApi/RestApiOutput.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/RestApi/RestApiOutput.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -48,7 +48,7 @@ { if (!alreadySent_) { - output_.SendHeader(Orthanc_HttpStatus_400_BadRequest); + output_.SendHeader(HttpStatus_400_BadRequest); } } @@ -100,10 +100,10 @@ alreadySent_ = true; } - void RestApiOutput::SignalError(Orthanc_HttpStatus status) + void RestApiOutput::SignalError(HttpStatus status) { - if (status != Orthanc_HttpStatus_403_Forbidden && - status != Orthanc_HttpStatus_415_UnsupportedMediaType) + if (status != HttpStatus_403_Forbidden && + status != HttpStatus_415_UnsupportedMediaType) { throw OrthancException("This HTTP status is not allowed in a REST API"); }
--- a/Core/RestApi/RestApiOutput.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/RestApi/RestApiOutput.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -74,7 +74,7 @@ size_t length, const std::string& contentType); - void SignalError(Orthanc_HttpStatus status); + void SignalError(HttpStatus status); void Redirect(const std::string& path);
--- a/Core/RestApi/RestApiPath.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/RestApi/RestApiPath.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/RestApi/RestApiPath.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/RestApi/RestApiPath.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/SQLite/Connection.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/Connection.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -111,7 +111,7 @@ { for (CachedStatements::iterator it = cachedStatements_.begin(); - it != cachedStatements_.end(); it++) + it != cachedStatements_.end(); ++it) { delete it->second; } @@ -331,7 +331,7 @@ void* payload = sqlite3_user_data(rawContext); assert(payload != NULL); - IScalarFunction& func = *(IScalarFunction*) payload; + IScalarFunction& func = *reinterpret_cast<IScalarFunction*>(payload); func.Compute(context); } @@ -339,7 +339,7 @@ static void ScalarFunctionDestroyer(void* payload) { assert(payload != NULL); - delete (IScalarFunction*) payload; + delete reinterpret_cast<IScalarFunction*>(payload); }
--- a/Core/SQLite/Connection.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/Connection.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/FunctionContext.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/FunctionContext.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Redistribution and use in source and binary forms, with or without @@ -89,6 +89,12 @@ CheckIndex(index); return std::string(reinterpret_cast<const char*>(sqlite3_value_text(argv_[index]))); } + + bool FunctionContext::IsNullValue(unsigned int index) const + { + CheckIndex(index); + return sqlite3_value_type(argv_[index]) == SQLITE_NULL; + } void FunctionContext::SetNullResult() {
--- a/Core/SQLite/FunctionContext.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/FunctionContext.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Redistribution and use in source and binary forms, with or without @@ -74,6 +74,8 @@ double GetDoubleValue(unsigned int index) const; std::string GetStringValue(unsigned int index) const; + + bool IsNullValue(unsigned int index) const; void SetNullResult();
--- a/Core/SQLite/IScalarFunction.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/IScalarFunction.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Redistribution and use in source and binary forms, with or without
--- a/Core/SQLite/Statement.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/Statement.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -295,7 +295,7 @@ return true; }*/ - bool Statement::ColumnBlobAsVector(int col, std::vector<char>* val) const + /*bool Statement::ColumnBlobAsVector(int col, std::vector<char>* val) const { val->clear(); @@ -306,14 +306,14 @@ memcpy(&(*val)[0], data, len); } return true; - } + }*/ - bool Statement::ColumnBlobAsVector( + /*bool Statement::ColumnBlobAsVector( int col, std::vector<unsigned char>* val) const { return ColumnBlobAsVector(col, reinterpret_cast< std::vector<char>* >(val)); - } + }*/ } }
--- a/Core/SQLite/Statement.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/Statement.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -89,10 +89,6 @@ return reference_.GetWrappedObject(); } - // Resets the statement to its initial condition. This includes any current - // result row, and also the bound variables if the |clear_bound_vars| is true. - void Reset(bool clear_bound_vars = true); - public: Statement(Connection& database, const std::string& sql); @@ -166,9 +162,12 @@ const void* ColumnBlob(int col) const; bool ColumnBlobAsString(int col, std::string* blob); //bool ColumnBlobAsString16(int col, string16* val) const; - bool ColumnBlobAsVector(int col, std::vector<char>* val) const; - bool ColumnBlobAsVector(int col, std::vector<unsigned char>* val) const; + //bool ColumnBlobAsVector(int col, std::vector<char>* val) const; + //bool ColumnBlobAsVector(int col, std::vector<unsigned char>* val) const; + // Resets the statement to its initial condition. This includes any current + // result row, and also the bound variables if the |clear_bound_vars| is true. + void Reset(bool clear_bound_vars = true); }; } }
--- a/Core/SQLite/StatementId.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/StatementId.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementId.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/StatementId.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementReference.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/StatementReference.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -39,6 +39,7 @@ #include "../OrthancException.h" #include <cassert> +#include <glog/logging.h> #include "sqlite3.h" namespace Orthanc @@ -103,8 +104,11 @@ { if (refCount_ != 0) { - // There remain references to this object - throw OrthancException(ErrorCode_InternalError); + // There remain references to this object. We cannot throw + // an exception because: + // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html + + LOG(ERROR) << "Bad value of the reference counter"; } else if (statement_ != NULL) { @@ -115,7 +119,11 @@ { if (root_->refCount_ == 0) { - throw OrthancException(ErrorCode_InternalError); + // There remain references to this object. We cannot throw + // an exception because: + // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html + + LOG(ERROR) << "Bad value of the reference counter"; } else {
--- a/Core/SQLite/StatementReference.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/StatementReference.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Transaction.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/Transaction.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Transaction.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/SQLite/Transaction.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/Toolbox.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/Toolbox.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -34,12 +34,15 @@ #include "OrthancException.h" +#include <stdint.h> #include <string.h> #include <boost/filesystem.hpp> #include <boost/filesystem/fstream.hpp> #include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/uuid/sha1.hpp> #include <algorithm> #include <ctype.h> +#include <boost/regex.hpp> #if defined(_WIN32) #include <windows.h> @@ -64,7 +67,19 @@ #include "../Resources/md5/md5.h" #include "../Resources/base64/base64.h" -#include "../Resources/sha1/sha1.h" + + +#ifdef _MSC_VER +// Patch for the missing "_strtoll" symbol when compiling with Visual Studio +extern "C" +{ +int64_t _strtoi64(const char *nptr, char **endptr, int base); +int64_t strtoll(const char *nptr, char **endptr, int base) +{ + return _strtoi64(nptr, endptr, base); +} +} +#endif #if BOOST_HAS_LOCALE == 0 @@ -131,9 +146,9 @@ #if defined(_WIN32) static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType) { - // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx - finish = true; - return true; + // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx + finish = true; + return true; } #else static void SignalHandler(int) @@ -142,17 +157,6 @@ } #endif - void Toolbox::Sleep(uint32_t seconds) - { -#if defined(_WIN32) - ::Sleep(static_cast<DWORD>(seconds) * static_cast<DWORD>(1000)); -#elif defined(__linux) - usleep(static_cast<uint64_t>(seconds) * static_cast<uint64_t>(1000000)); -#else -#error Support your platform here -#endif - } - void Toolbox::USleep(uint64_t microSeconds) { #if defined(_WIN32) @@ -168,10 +172,11 @@ void Toolbox::ServerBarrier() { #if defined(_WIN32) - SetConsoleCtrlHandler(ConsoleControlHandler, true); + SetConsoleCtrlHandler(ConsoleControlHandler, true); #else signal(SIGINT, SignalHandler); signal(SIGQUIT, SignalHandler); + signal(SIGTERM, SignalHandler); #endif finish = false; @@ -181,10 +186,11 @@ } #if defined(_WIN32) - SetConsoleCtrlHandler(ConsoleControlHandler, false); + SetConsoleCtrlHandler(ConsoleControlHandler, false); #else signal(SIGINT, NULL); signal(SIGQUIT, NULL); + signal(SIGTERM, NULL); #endif } @@ -202,15 +208,29 @@ } + void Toolbox::ToUpperCase(std::string& result, + const std::string& source) + { + result = source; + ToUpperCase(result); + } + + void Toolbox::ToLowerCase(std::string& result, + const std::string& source) + { + result = source; + ToLowerCase(result); + } + void Toolbox::ReadFile(std::string& content, const std::string& path) { boost::filesystem::ifstream f; - f.open(path, std::ifstream::in | std::ios::binary); + f.open(path, std::ifstream::in | std::ifstream::binary); if (!f.good()) { - throw OrthancException("Unable to open a file"); + throw OrthancException(ErrorCode_InexistentFile); } // http://www.cplusplus.com/reference/iostream/istream/tellg/ @@ -228,6 +248,26 @@ } + void Toolbox::WriteFile(const std::string& content, + const std::string& path) + { + boost::filesystem::ofstream f; + f.open(path, std::ofstream::binary); + if (!f.good()) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + + if (content.size() != 0) + { + f.write(content.c_str(), content.size()); + } + + f.close(); + } + + + void Toolbox::RemoveFile(const std::string& path) { if (boost::filesystem::exists(path)) @@ -414,13 +454,29 @@ void Toolbox::ComputeMD5(std::string& result, const std::string& data) { + if (data.size() > 0) + { + ComputeMD5(result, &data[0], data.size()); + } + else + { + ComputeMD5(result, NULL, 0); + } + } + + + void Toolbox::ComputeMD5(std::string& result, + const void* data, + size_t length) + { md5_state_s state; md5_init(&state); - if (data.size() > 0) + if (length > 0) { - md5_append(&state, reinterpret_cast<const md5_byte_t*>(&data[0]), - static_cast<int>(data.size())); + md5_append(&state, + reinterpret_cast<const md5_byte_t*>(data), + static_cast<int>(length)); } md5_byte_t actualHash[16]; @@ -538,31 +594,27 @@ void Toolbox::ComputeSHA1(std::string& result, const std::string& data) { - SHA1 sha1; + boost::uuids::detail::sha1 sha1; + if (data.size() > 0) { - sha1.Input(&data[0], data.size()); + sha1.process_bytes(&data[0], data.size()); } - unsigned digest[5]; + unsigned int digest[5]; // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide - assert(sizeof(unsigned) == 4 && sizeof(digest) == (160 / 8)); + assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8)); - if (sha1.Result(digest)) - { - result.resize(8 * 5 + 4); - sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x", - digest[0], - digest[1], - digest[2], - digest[3], - digest[4]); - } - else - { - throw OrthancException(ErrorCode_InternalError); - } + sha1.get_digest(digest); + + result.resize(8 * 5 + 4); + sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x", + digest[0], + digest[1], + digest[2], + digest[3], + digest[4]); } bool Toolbox::IsSHA1(const std::string& str) @@ -671,4 +723,85 @@ s.resize(target); } + + + Endianness Toolbox::DetectEndianness() + { + // http://sourceforge.net/p/predef/wiki/Endianness/ + + uint8_t buffer[4]; + + buffer[0] = 0x00; + buffer[1] = 0x01; + buffer[2] = 0x02; + buffer[3] = 0x03; + + switch (*((uint32_t *)buffer)) + { + case 0x00010203: + return Endianness_Big; + + case 0x03020100: + return Endianness_Little; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + std::string Toolbox::WildcardToRegularExpression(const std::string& source) + { + // TODO - Speed up this with a regular expression + + std::string result = source; + + // Escape all special characters + boost::replace_all(result, "\\", "\\\\"); + boost::replace_all(result, "^", "\\^"); + boost::replace_all(result, ".", "\\."); + boost::replace_all(result, "$", "\\$"); + boost::replace_all(result, "|", "\\|"); + boost::replace_all(result, "(", "\\("); + boost::replace_all(result, ")", "\\)"); + boost::replace_all(result, "[", "\\["); + boost::replace_all(result, "]", "\\]"); + boost::replace_all(result, "+", "\\+"); + boost::replace_all(result, "/", "\\/"); + boost::replace_all(result, "{", "\\{"); + boost::replace_all(result, "}", "\\}"); + + // Convert wildcards '*' and '?' to their regex equivalents + boost::replace_all(result, "?", "."); + boost::replace_all(result, "*", ".*"); + + return result; + } + + + + void Toolbox::TokenizeString(std::vector<std::string>& result, + const std::string& value, + char separator) + { + result.clear(); + + std::string currentItem; + + for (size_t i = 0; i < value.size(); i++) + { + if (value[i] == separator) + { + result.push_back(currentItem); + currentItem.clear(); + } + else + { + currentItem.push_back(value[i]); + } + } + + result.push_back(currentItem); + } } +
--- a/Core/Toolbox.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Toolbox.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,6 +32,8 @@ #pragma once +#include "Enumerations.h" + #include <stdint.h> #include <vector> #include <string> @@ -48,14 +50,21 @@ { void ServerBarrier(); - void ToUpperCase(std::string& s); + void ToUpperCase(std::string& s); // Inplace version + + void ToLowerCase(std::string& s); // Inplace version - void ToLowerCase(std::string& s); + void ToUpperCase(std::string& result, + const std::string& source); + + void ToLowerCase(std::string& result, + const std::string& source); void ReadFile(std::string& content, const std::string& path); - void Sleep(uint32_t seconds); + void WriteFile(const std::string& content, + const std::string& path); void USleep(uint64_t microSeconds); @@ -77,6 +86,10 @@ void ComputeMD5(std::string& result, const std::string& data); + void ComputeMD5(std::string& result, + const void* data, + size_t length); + void ComputeSHA1(std::string& result, const std::string& data); @@ -101,5 +114,13 @@ // In-place percent-decoding for URL void UrlDecode(std::string& s); + + Endianness DetectEndianness(); + + std::string WildcardToRegularExpression(const std::string& s); + + void TokenizeString(std::vector<std::string>& result, + const std::string& source, + char separator); } }
--- a/Core/Uuid.cpp Fri May 03 12:23:02 2013 +0200 +++ b/Core/Uuid.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -141,15 +141,15 @@ } - TemporaryFile::TemporaryFile() + TemporaryFile::TemporaryFile() : + path_(CreateTemporaryPath(NULL)) { - path_ = CreateTemporaryPath(NULL); } - TemporaryFile::TemporaryFile(const char* extension) + TemporaryFile::TemporaryFile(const char* extension) : + path_(CreateTemporaryPath(extension)) { - path_ = CreateTemporaryPath(extension); }
--- a/Core/Uuid.h Fri May 03 12:23:02 2013 +0200 +++ b/Core/Uuid.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/INSTALL Fri May 03 12:23:02 2013 +0200 +++ b/INSTALL Tue Apr 22 16:47:21 2014 +0200 @@ -43,29 +43,7 @@ Native Linux Compilation ------------------------ -To build binaries with debug information: - -# cd ~/OrthancBuild -# cmake -DSTATIC_BUILD=ON -DCMAKE_BUILD_TYPE=Debug ~/Orthanc -# make -# make doc - - -To build a release version: - -# cd ~/OrthancBuild -# cmake -DSTATIC_BUILD=ON -DCMAKE_BUILD_TYPE=Release ~/Orthanc -# make -# make doc - - -Under Linux, you have the possibility to dynamically link Orthanc -against the shared libraries of your system, provided their version is -recent enough. This greatly speeds up the compilation: - -# cd ~/OrthancBuild -# cmake -DSTATIC_BUILD=OFF -DCMAKE_BUILD_TYPE=Debug ~/Orthanc -# make +See the file "LinuxCompilation.txt". @@ -104,23 +82,3 @@ # cd [...]\OrthancBuild # cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Debug [...]\Orthanc # mingw32-make - - - -Using ccache ------------- - -Under Linux, you have the opportunity to use "ccache" to dramatically -decrease the compilation time when rebuilding Orthanc. This is -especially useful for developers. Under Debian/Ubuntu, you would use: - -# CC="ccache gcc" CXX="ccache g++" cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ - -DSTATIC_BUILD=OFF -DCMAKE_BUILD_TYPE=Debug ~/Orthanc - - - -Troubleshooting ---------------- - -The build instructions for specific Linux distributions are available at the following place: -https://code.google.com/p/orthanc/wiki/FAQ#I_use_the_Linux_distribution_XXX,_how_can_I_build_Orthanc?
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LinuxCompilation.txt Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,205 @@ +This file is a complement to "INSTALL", which contains instructions +that are specific to Linux. + + +Static linking for Linux +======================== + +The most simple way of building Orthanc under Linux consists in +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. + + +To build binaries with debug information: + +# cd ~/OrthancBuild +# cmake -DSTATIC_BUILD=ON -DCMAKE_BUILD_TYPE=Debug ~/Orthanc +# make +# make doc + + +To build a release version: + +# cd ~/OrthancBuild +# cmake -DSTATIC_BUILD=ON -DCMAKE_BUILD_TYPE=Release ~/Orthanc +# make +# make doc + + +Note: When the "STATIC_BUILD" option is set to "ON", the build tool +will not ask you the permission to download packages from the +Internet. + + +Use system-wide libraries under Linux +===================================== + +Under Linux, by default, Orthanc links against the shared libraries of +your system (the "STATIC_BUILD" option is set to "OFF"). This greatly +speeds up the compilation. This is also required when building +packages for Linux distributions. Because using system libraries is +the default behavior, you just have to use: + +# cd ~/OrthancBuild +# cmake -DCMAKE_BUILD_TYPE=Debug ~/Orthanc +# make + + +However, on some Linux distributions, it is still required to download +and static link against some third-party dependencies, e.g. when the +system-wide library is not shipped or is outdated. Because of +difference in the packaging of the various Linux distribution, it is +also sometimes required to fine-tune some options. + +You will find below build instructions for specific Linux +distributions. Distributions tagged by "SUPPORTED" are tested by +Sébastien Jodogne. Distributions tagged by "CONTRIBUTED" come from +Orthanc users. + + +SUPPORTED - Debian Squeeze (6.x) +-------------------------------- + +# sudo apt-get install build-essential unzip cmake mercurial \ + uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ + libgoogle-glog-dev libpng-dev libgtest-dev \ + libsqlite3-dev libssl-dev zlib1g-dev + +# cmake -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_BOOST=OFF \ + -DUSE_SYSTEM_DCMTK=OFF \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_JSONCPP=OFF \ + ~/Orthanc + + +SUPPORTED - Debian Wheezy (7.x) +------------------------------- + +# 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 libjsoncpp-dev + +# cmake -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_GOOGLE_LOG=OFF \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + ~/Orthanc + + +SUPPORTED - Debian Jessie/Sid +----------------------------- + +# sudo apt-get install build-essential unzip cmake mercurial \ + 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 + +# cmake -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + ~/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 +---------------------------- + +# 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 + +# cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ + -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_JSONCPP=OFF \ + -DUSE_SYSTEM_GOOGLE_LOG=OFF \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + ~/Orthanc + + +SUPPORTED - Ubuntu 12.10 +------------------------ + +# sudo apt-get install build-essential unzip cmake mercurial \ + 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 + +# cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ + -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_JSONCPP=OFF \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + ~/Orthanc + + +SUPPORTED - Ubuntu 13.10 +------------------------ + +# sudo apt-get install build-essential unzip cmake mercurial \ + 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 + +# cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ + -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + ~/Orthanc + + + +SUPPORTED - Fedora 18/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 + +# cmake ~/Orthanc + +Note: Have also a look at the official package: +http://pkgs.fedoraproject.org/cgit/orthanc.git/tree/?h=f18 + + + + + +Other Linux distributions? +-------------------------- + +Please send us your build instructions (by a mail to +s.jodogne@gmail.com)! + +You can find build instructions for Orthanc up to 0.7.0 on the +following Wiki page: +https://code.google.com/p/orthanc/wiki/LinuxCompilationUpTo070 + +These instructions will not work as such beyond Orthanc 0.7.0, but +they might give indications. + + + + +Using ccache +============ + +Under Linux, you also have the opportunity to use "ccache" to +dramatically decrease the compilation time when rebuilding +Orthanc. This is especially useful for developers. To this end, you +would use: + +# CC="ccache gcc" CXX="ccache g++" cmake ~/Orthanc [Other Options]
--- a/NEWS Fri May 03 12:23:02 2013 +0200 +++ b/NEWS Tue Apr 22 16:47:21 2014 +0200 @@ -2,11 +2,137 @@ =============================== -* Store-SCU for patients and studies in Orthanc Explorer. + +Version 0.7.4 (2014/04/16) +========================== + +* Switch to openssl-1.0.1g in static builds (cf. Heartbleed exploit) +* Switch to boost 1.55.0 in static builds (to solve compiling errors) +* Better logging about nonexistent tags +* Dcm4Chee manufacturer +* Automatic discovering of the path to the DICOM dictionaries +* In the "DicomModalities" config, the port number can be a string + + +Version 0.7.3 (2014/02/14) +========================== + +Major changes +------------- + +* Fixes in the implementation of the C-FIND handler for Query/Retrieve +* Custom attachment of files to patients, studies, series or instances +* Access to lowlevel info about the attached files through the REST API +* Recover pixel data for more transfer syntaxes (notably JPEG) + +Minor changes +------------- + +* AET comparison is now case-insensitive by default +* Possibility to disable the HTTP server or the DICOM server +* Automatic computation of MD5 hashes for the stored DICOM files +* Maintenance tool to recover DICOM files compressed by Orthanc +* The newline characters in the configuration file are fixed for Linux +* Capture of the SIGTERM signal in Linux + + +Version 0.7.2 (2013/11/08) +========================== + +* Support of Query/Retrieve from medInria +* Accept more transfer syntaxes for C-Store SCP and SCU (notably JPEG) +* Create the meta-header when receiving files through C-Store SCP +* Fixes and improvements thanks to the static analyzer cppcheck + + +Version 0.7.1 (2013/10/30) +========================== + +* Use ZIP64 only when required to improve compatibility (cf. issue #7) +* Refactoring of the CMake options +* Fix for big-endian architectures (RedHat bug #985748) +* Use filenames with 8 characters in ZIP files for maximum compatibility +* Possibility to build Orthanc inplace (in the source directory) + + +Version 0.7.0 (2013/10/25) +========================== + +Major changes +------------- + +* DICOM Query/Retrieve is supported + +Minor changes +------------- + +* Possibility to keep the PatientID during an anonymization +* Check whether "unzip", "tar" and/or "7-zip" are installed from CMake + + +Version 0.6.2 (2013/10/04) +========================== + +* Build of the C++ client as a shared library +* Improvements and documentation of the C++ client API +* Fix of Debian bug #724947 (licensing issue with the SHA-1 library) +* Switch to Boost 1.54.0 (cf. issue #9) +* "make uninstall" is now possible + + +Version 0.6.1 (2013/09/16) +========================== + +* Detection of stable patients/studies/series +* C-Find SCU at the instance level +* Link from modified to original resource in Orthanc Explorer +* Fix of issue #8 +* Anonymization of the medical alerts tag (0010,2000) + + +Version 0.6.0 (2013/07/16) +========================== + +Major changes +------------- + +* Introduction of the C++ client +* Send DICOM resources to other Orthanc instances through HTTP +* Access to signed images (instances/.../image-int16) + (Closes: Debian #716958) + +Minor changes +------------- + +* Export of DICOM files to the host filesystem (instances/.../export) +* Statistics about patients, studies, series and instances +* Link from anonymized to original resource in Orthanc Explorer +* Fixes for Red Hat and Debian packaging +* Fixes for history in Orthanc Explorer +* Fixes for boost::thread, as reported by Cyril Paulus +* Fix licensing (Closes: Debian #712038) + +Metadata +-------- + +* Access to the metadata through the REST API (.../metadata) +* Support of user-defined metadata +* "LastUpdate" metadata for patients, studies and series +* "/tools/now" to be used in combination with "LastUpdate" +* Improved support of series with temporal positions + + +Version 0.5.2 (2013/05/07) +========================== + * "Bulk" Store-SCU (send several DICOM instances with the same - DICOM connexion). -* Filtering of incoming DICOM instances (through Lua scripting). -* Filtering of incoming HTTP requests (through Lua scripting). + DICOM connexion) +* 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) +* Clearing of "/exports" and "/changes" +* Check MD5 of third party downloads +* Faking of the HTTP methods PUT and DELETE Version 0.5.1 (2013/04/17)
--- a/OrthancCppClient/CMakeLists.txt Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# Mini-project to check whether "OrthancCppClient" can compile in a -# standalone fashion - -cmake_minimum_required(VERSION 2.8) - -project(OrthancCppClientTest) - -SET(STATIC_BUILD OFF) - -include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DownloadPackage.cmake) -include(${CMAKE_SOURCE_DIR}/../Resources/CMake/JsonCppConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/../Resources/CMake/LibCurlConfiguration.cmake) - -if (${CMAKE_COMPILER_IS_GNUCXX}) - set(CMAKE_C_FLAGS "-Wall -pedantic -Wno-implicit-function-declaration") # --std=c99 makes libcurl not to compile - set(CMAKE_CXX_FLAGS "-Wall -pedantic -Wno-long-long -Wno-variadic-macros") - set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed") - set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined") -elseif (${MSVC}) - add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) -endif() - -add_library(OrthancCppClient - SHARED - - ${THIRD_PARTY_SOURCES} - HttpException.cpp - HttpClient.cpp - ) - -add_executable(Test - main.cpp - ) - -target_link_libraries(Test OrthancCppClient)
--- a/OrthancCppClient/HttpClient.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,208 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#include "HttpClient.h" - -#include <string.h> -#include <curl/curl.h> - - -namespace Orthanc -{ - struct HttpClient::PImpl - { - CURL* curl_; - struct curl_slist *postHeaders_; - }; - - - static CURLcode CheckCode(CURLcode code) - { - if (code != CURLE_OK) - { - printf("ICI: %s\n", curl_easy_strerror(code)); - throw HttpException("CURL: " + std::string(curl_easy_strerror(code))); - } - - return code; - } - - - static size_t CurlCallback(void *buffer, size_t size, size_t nmemb, void *payload) - { - std::string& target = *(static_cast<std::string*>(payload)); - - size_t length = size * nmemb; - if (length == 0) - return 0; - - size_t pos = target.size(); - - target.resize(pos + length); - memcpy(&target.at(pos), buffer, length); - - return length; - } - - - HttpClient::HttpClient() : pimpl_(new PImpl) - { - pimpl_->postHeaders_ = NULL; - if ((pimpl_->postHeaders_ = curl_slist_append(pimpl_->postHeaders_, "Expect:")) == NULL) - { - throw HttpException("HttpClient: Not enough memory"); - } - - pimpl_->curl_ = curl_easy_init(); - if (!pimpl_->curl_) - { - curl_slist_free_all(pimpl_->postHeaders_); - throw HttpException("HttpClient: Not enough memory"); - } - - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlCallback)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); - -#if ORTHANC_SSL_ENABLED == 1 - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); -#endif - - url_ = ""; - method_ = Orthanc_HttpMethod_Get; - lastStatus_ = Orthanc_HttpStatus_200_Ok; - isVerbose_ = false; - } - - - HttpClient::~HttpClient() - { - curl_easy_cleanup(pimpl_->curl_); - curl_slist_free_all(pimpl_->postHeaders_); - } - - - void HttpClient::SetVerbose(bool isVerbose) - { - isVerbose_ = isVerbose; - - if (isVerbose_) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1)); - } - else - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 0)); - } - } - - - bool HttpClient::Apply(std::string& answer) - { - answer.clear(); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, NULL)); - - switch (method_) - { - case Orthanc_HttpMethod_Get: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L)); - break; - - case Orthanc_HttpMethod_Post: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->postHeaders_)); - - if (postData_.size() > 0) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, postData_.c_str())); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, postData_.size())); - } - else - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0)); - } - - break; - - case Orthanc_HttpMethod_Delete: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE")); - break; - - case Orthanc_HttpMethod_Put: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L)); - break; - - default: - throw HttpException("HttpClient: Internal error"); - } - - // Do the actual request - CheckCode(curl_easy_perform(pimpl_->curl_)); - - long status; - CheckCode(curl_easy_getinfo(pimpl_->curl_, CURLINFO_RESPONSE_CODE, &status)); - - if (status == 0) - { - // This corresponds to a call to an inexistent host - lastStatus_ = Orthanc_HttpStatus_500_InternalServerError; - } - else - { - lastStatus_ = static_cast<Orthanc_HttpStatus>(status); - } - - return (status >= 200 && status < 300); - } - - - bool HttpClient::Apply(Json::Value& answer) - { - std::string s; - if (Apply(s)) - { - Json::Reader reader; - return reader.parse(s, answer); - } - else - { - return false; - } - } - - - void HttpClient::SetPassword(const char* username, - const char* password) - { - std::string s = std::string(username) + ":" + std::string(password); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, s.c_str())); - } -}
--- a/OrthancCppClient/HttpClient.h Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#pragma once - -#include "HttpEnumerations.h" -#include "HttpException.h" - -#include <string> -#include <boost/shared_ptr.hpp> -#include <json/json.h> - -namespace Orthanc -{ - class HttpClient - { - private: - struct PImpl; - boost::shared_ptr<PImpl> pimpl_; - - std::string url_; - Orthanc_HttpMethod method_; - Orthanc_HttpStatus lastStatus_; - std::string postData_; - bool isVerbose_; - - public: - HttpClient(); - - ~HttpClient(); - - void SetUrl(const char* url) - { - url_ = std::string(url); - } - - void SetUrl(const std::string& url) - { - url_ = url; - } - - const std::string& GetUrl() const - { - return url_; - } - - void SetMethod(Orthanc_HttpMethod method) - { - method_ = method; - } - - Orthanc_HttpMethod GetMethod() const - { - return method_; - } - - std::string& AccessPostData() - { - return postData_; - } - - const std::string& AccessPostData() const - { - return postData_; - } - - void SetVerbose(bool isVerbose); - - bool IsVerbose() const - { - return isVerbose_; - } - - bool Apply(std::string& answer); - - bool Apply(Json::Value& answer); - - Orthanc_HttpStatus GetLastStatus() const - { - return lastStatus_; - } - - const char* GetLastStatusText() const - { - return HttpException::GetDescription(lastStatus_); - } - - void SetPassword(const char* username, - const char* password); - }; -}
--- a/OrthancCppClient/HttpEnumerations.h Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#pragma once - - -/** - * This file contains the enumerations for the access to the Orthanc - * REST API in C and C++. Namespaces are not used, in order to enable - * the access in C. - **/ - -// Most common, non-joke and non-experimental HTTP status codes -// http://en.wikipedia.org/wiki/List_of_HTTP_status_codes -enum Orthanc_HttpStatus -{ - Orthanc_HttpStatus_None = -1, - - // 1xx Informational - Orthanc_HttpStatus_100_Continue = 100, - Orthanc_HttpStatus_101_SwitchingProtocols = 101, - Orthanc_HttpStatus_102_Processing = 102, - - // 2xx Success - Orthanc_HttpStatus_200_Ok = 200, - Orthanc_HttpStatus_201_Created = 201, - Orthanc_HttpStatus_202_Accepted = 202, - Orthanc_HttpStatus_203_NonAuthoritativeInformation = 203, - Orthanc_HttpStatus_204_NoContent = 204, - Orthanc_HttpStatus_205_ResetContent = 205, - Orthanc_HttpStatus_206_PartialContent = 206, - Orthanc_HttpStatus_207_MultiStatus = 207, - Orthanc_HttpStatus_208_AlreadyReported = 208, - Orthanc_HttpStatus_226_IMUsed = 226, - - // 3xx Redirection - Orthanc_HttpStatus_300_MultipleChoices = 300, - Orthanc_HttpStatus_301_MovedPermanently = 301, - Orthanc_HttpStatus_302_Found = 302, - Orthanc_HttpStatus_303_SeeOther = 303, - Orthanc_HttpStatus_304_NotModified = 304, - Orthanc_HttpStatus_305_UseProxy = 305, - Orthanc_HttpStatus_307_TemporaryRedirect = 307, - - // 4xx Client Error - Orthanc_HttpStatus_400_BadRequest = 400, - Orthanc_HttpStatus_401_Unauthorized = 401, - Orthanc_HttpStatus_402_PaymentRequired = 402, - Orthanc_HttpStatus_403_Forbidden = 403, - Orthanc_HttpStatus_404_NotFound = 404, - Orthanc_HttpStatus_405_MethodNotAllowed = 405, - Orthanc_HttpStatus_406_NotAcceptable = 406, - Orthanc_HttpStatus_407_ProxyAuthenticationRequired = 407, - Orthanc_HttpStatus_408_RequestTimeout = 408, - Orthanc_HttpStatus_409_Conflict = 409, - Orthanc_HttpStatus_410_Gone = 410, - Orthanc_HttpStatus_411_LengthRequired = 411, - Orthanc_HttpStatus_412_PreconditionFailed = 412, - Orthanc_HttpStatus_413_RequestEntityTooLarge = 413, - Orthanc_HttpStatus_414_RequestUriTooLong = 414, - Orthanc_HttpStatus_415_UnsupportedMediaType = 415, - Orthanc_HttpStatus_416_RequestedRangeNotSatisfiable = 416, - Orthanc_HttpStatus_417_ExpectationFailed = 417, - Orthanc_HttpStatus_422_UnprocessableEntity = 422, - Orthanc_HttpStatus_423_Locked = 423, - Orthanc_HttpStatus_424_FailedDependency = 424, - Orthanc_HttpStatus_426_UpgradeRequired = 426, - - // 5xx Server Error - Orthanc_HttpStatus_500_InternalServerError = 500, - Orthanc_HttpStatus_501_NotImplemented = 501, - Orthanc_HttpStatus_502_BadGateway = 502, - Orthanc_HttpStatus_503_ServiceUnavailable = 503, - Orthanc_HttpStatus_504_GatewayTimeout = 504, - Orthanc_HttpStatus_505_HttpVersionNotSupported = 505, - Orthanc_HttpStatus_506_VariantAlsoNegotiates = 506, - Orthanc_HttpStatus_507_InsufficientStorage = 507, - Orthanc_HttpStatus_509_BandwidthLimitExceeded = 509, - Orthanc_HttpStatus_510_NotExtended = 510 -}; - - -enum Orthanc_HttpMethod -{ - Orthanc_HttpMethod_Get = 0, - Orthanc_HttpMethod_Post = 1, - Orthanc_HttpMethod_Delete = 2, - Orthanc_HttpMethod_Put = 3 -};
--- a/OrthancCppClient/HttpException.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,208 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#include "HttpException.h" - -namespace Orthanc -{ - const char* HttpException::What() const - { - if (status_ == Orthanc_HttpStatus_None) - { - return custom_.c_str(); - } - else - { - return GetDescription(status_); - } - } - - const char* HttpException::GetDescription(Orthanc_HttpStatus status) - { - switch (status) - { - case Orthanc_HttpStatus_100_Continue: - return "Continue"; - - case Orthanc_HttpStatus_101_SwitchingProtocols: - return "Switching Protocols"; - - case Orthanc_HttpStatus_102_Processing: - return "Processing"; - - case Orthanc_HttpStatus_200_Ok: - return "OK"; - - case Orthanc_HttpStatus_201_Created: - return "Created"; - - case Orthanc_HttpStatus_202_Accepted: - return "Accepted"; - - case Orthanc_HttpStatus_203_NonAuthoritativeInformation: - return "Non-Authoritative Information"; - - case Orthanc_HttpStatus_204_NoContent: - return "No Content"; - - case Orthanc_HttpStatus_205_ResetContent: - return "Reset Content"; - - case Orthanc_HttpStatus_206_PartialContent: - return "Partial Content"; - - case Orthanc_HttpStatus_207_MultiStatus: - return "Multi-Status"; - - case Orthanc_HttpStatus_208_AlreadyReported: - return "Already Reported"; - - case Orthanc_HttpStatus_226_IMUsed: - return "IM Used"; - - case Orthanc_HttpStatus_300_MultipleChoices: - return "Multiple Choices"; - - case Orthanc_HttpStatus_301_MovedPermanently: - return "Moved Permanently"; - - case Orthanc_HttpStatus_302_Found: - return "Found"; - - case Orthanc_HttpStatus_303_SeeOther: - return "See Other"; - - case Orthanc_HttpStatus_304_NotModified: - return "Not Modified"; - - case Orthanc_HttpStatus_305_UseProxy: - return "Use Proxy"; - - case Orthanc_HttpStatus_307_TemporaryRedirect: - return "Temporary Redirect"; - - case Orthanc_HttpStatus_400_BadRequest: - return "Bad Request"; - - case Orthanc_HttpStatus_401_Unauthorized: - return "Unauthorized"; - - case Orthanc_HttpStatus_402_PaymentRequired: - return "Payment Required"; - - case Orthanc_HttpStatus_403_Forbidden: - return "Forbidden"; - - case Orthanc_HttpStatus_404_NotFound: - return "Not Found"; - - case Orthanc_HttpStatus_405_MethodNotAllowed: - return "Method Not Allowed"; - - case Orthanc_HttpStatus_406_NotAcceptable: - return "Not Acceptable"; - - case Orthanc_HttpStatus_407_ProxyAuthenticationRequired: - return "Proxy Authentication Required"; - - case Orthanc_HttpStatus_408_RequestTimeout: - return "Request Timeout"; - - case Orthanc_HttpStatus_409_Conflict: - return "Conflict"; - - case Orthanc_HttpStatus_410_Gone: - return "Gone"; - - case Orthanc_HttpStatus_411_LengthRequired: - return "Length Required"; - - case Orthanc_HttpStatus_412_PreconditionFailed: - return "Precondition Failed"; - - case Orthanc_HttpStatus_413_RequestEntityTooLarge: - return "Request Entity Too Large"; - - case Orthanc_HttpStatus_414_RequestUriTooLong: - return "Request-URI Too Long"; - - case Orthanc_HttpStatus_415_UnsupportedMediaType: - return "Unsupported Media Type"; - - case Orthanc_HttpStatus_416_RequestedRangeNotSatisfiable: - return "Requested Range Not Satisfiable"; - - case Orthanc_HttpStatus_417_ExpectationFailed: - return "Expectation Failed"; - - case Orthanc_HttpStatus_422_UnprocessableEntity: - return "Unprocessable Entity"; - - case Orthanc_HttpStatus_423_Locked: - return "Locked"; - - case Orthanc_HttpStatus_424_FailedDependency: - return "Failed Dependency"; - - case Orthanc_HttpStatus_426_UpgradeRequired: - return "Upgrade Required"; - - case Orthanc_HttpStatus_500_InternalServerError: - return "Internal Server Error"; - - case Orthanc_HttpStatus_501_NotImplemented: - return "Not Implemented"; - - case Orthanc_HttpStatus_502_BadGateway: - return "Bad Gateway"; - - case Orthanc_HttpStatus_503_ServiceUnavailable: - return "Service Unavailable"; - - case Orthanc_HttpStatus_504_GatewayTimeout: - return "Gateway Timeout"; - - case Orthanc_HttpStatus_505_HttpVersionNotSupported: - return "HTTP Version Not Supported"; - - case Orthanc_HttpStatus_506_VariantAlsoNegotiates: - return "Variant Also Negotiates"; - - case Orthanc_HttpStatus_507_InsufficientStorage: - return "Insufficient Storage"; - - case Orthanc_HttpStatus_509_BandwidthLimitExceeded: - return "Bandwidth Limit Exceeded"; - - case Orthanc_HttpStatus_510_NotExtended: - return "Not Extended"; - - default: - throw HttpException("Unknown HTTP status"); - } - } -}
--- a/OrthancCppClient/HttpException.h Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#pragma once - -#include "HttpEnumerations.h" - -#include <string> - -namespace Orthanc -{ - class HttpException - { - private: - Orthanc_HttpStatus status_; - std::string custom_; - - public: - static const char* GetDescription(Orthanc_HttpStatus status); - - HttpException(const std::string& custom) - { - status_ = Orthanc_HttpStatus_None; - custom_ = custom; - } - - HttpException(Orthanc_HttpStatus status) - { - status_ = status; - } - - Orthanc_HttpStatus GetHttpStatus() const - { - return status_; - } - - const char* What() const; - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Instance.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,285 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "Instance.h" + +#include "OrthancConnection.h" + +#include <boost/lexical_cast.hpp> + +namespace OrthancClient +{ + void Instance::DownloadImage() + { + if (reader_.get() == NULL) + { + const char* suffix; + switch (mode_) + { + case Orthanc::ImageExtractionMode_Preview: + suffix = "preview"; + break; + + case Orthanc::ImageExtractionMode_UInt8: + suffix = "image-uint8"; + break; + + case Orthanc::ImageExtractionMode_UInt16: + suffix = "image-uint16"; + break; + + case Orthanc::ImageExtractionMode_Int16: + suffix = "image-int16"; + break; + + default: + throw OrthancClientException(Orthanc::ErrorCode_NotImplemented); + } + + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/" + suffix); + std::string png; + + if (!client.Apply(png)) + { + throw OrthancClientException(Orthanc::ErrorCode_NotImplemented); + } + + reader_.reset(new Orthanc::PngReader); + reader_->ReadFromMemory(png); + } + } + + void Instance::DownloadDicom() + { + if (dicom_.get() == NULL) + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/file"); + + dicom_.reset(new std::string); + + if (!client.Apply(*dicom_)) + { + throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); + } + } + } + + Instance::Instance(const OrthancConnection& connection, + const char* id) : + connection_(connection), + id_(id), + mode_(Orthanc::ImageExtractionMode_Int16) + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + + client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/simplified-tags"); + Json::Value v; + if (!client.Apply(tags_)) + { + throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + const char* Instance::GetTagAsString(const char* tag) const + { + if (tags_.isMember(tag)) + { + return tags_[tag].asCString(); + } + else + { + throw OrthancClientException(Orthanc::ErrorCode_InexistentItem); + } + } + + float Instance::GetTagAsFloat(const char* tag) const + { + std::string value = GetTagAsString(tag); + + try + { + return boost::lexical_cast<float>(value); + } + catch (boost::bad_lexical_cast) + { + throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); + } + } + + int Instance::GetTagAsInt(const char* tag) const + { + std::string value = GetTagAsString(tag); + + try + { + return boost::lexical_cast<int>(value); + } + catch (boost::bad_lexical_cast) + { + throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); + } + } + + unsigned int Instance::GetWidth() + { + DownloadImage(); + return reader_->GetWidth(); + } + + unsigned int Instance::GetHeight() + { + DownloadImage(); + return reader_->GetHeight(); + } + + unsigned int Instance::GetPitch() + { + DownloadImage(); + return reader_->GetPitch(); + } + + Orthanc::PixelFormat Instance::GetPixelFormat() + { + DownloadImage(); + return reader_->GetFormat(); + } + + const void* Instance::GetBuffer() + { + DownloadImage(); + return reader_->GetBuffer(); + } + + const void* Instance::GetBuffer(unsigned int y) + { + DownloadImage(); + return reader_->GetBuffer(y); + } + + void Instance::DiscardImage() + { + reader_.reset(); + } + + void Instance::DiscardDicom() + { + dicom_.reset(); + } + + + void Instance::SetImageExtractionMode(Orthanc::ImageExtractionMode mode) + { + if (mode_ == mode) + { + return; + } + + DiscardImage(); + mode_ = mode; + } + + + void Instance::SplitVectorOfFloats(std::vector<float>& target, + const char* tag) + { + const std::string value = GetTagAsString(tag); + + target.clear(); + + try + { + std::string tmp; + for (size_t i = 0; i < value.size(); i++) + { + if (value[i] == '\\') + { + target.push_back(boost::lexical_cast<float>(tmp)); + tmp.clear(); + } + else + { + tmp.push_back(value[i]); + } + } + + target.push_back(boost::lexical_cast<float>(tmp)); + } + catch (boost::bad_lexical_cast) + { + // Unable to parse the Image Orientation Patient. + throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + const uint64_t Instance::GetDicomSize() + { + DownloadDicom(); + assert(dicom_.get() != NULL); + return dicom_->size(); + } + + const void* Instance::GetDicom() + { + DownloadDicom(); + assert(dicom_.get() != NULL); + + if (dicom_->size() == 0) + { + return NULL; + } + else + { + return &((*dicom_) [0]); + } + } + + + void Instance::LoadTagContent(const char* path) + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/content/" + path); + + if (!client.Apply(content_)) + { + throw OrthancClientException(Orthanc::ErrorCode_UnknownResource); + } + } + + + const char* Instance::GetLoadedTagContent() const + { + return content_.c_str(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Instance.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,202 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <string> +#include <json/value.h> + +#include "OrthancClientException.h" +#include "../Core/IDynamicObject.h" +#include "../Core/FileFormats/PngReader.h" + +namespace OrthancClient +{ + class OrthancConnection; + + /** + * {summary}{Connection to an instance stored in %Orthanc.} + * {description}{This class encapsulates a connection to an image instance + * from a remote instance of %Orthanc.} + **/ + class LAAW_API Instance : public Orthanc::IDynamicObject + { + private: + const OrthancConnection& connection_; + std::string id_; + Json::Value tags_; + std::auto_ptr<Orthanc::PngReader> reader_; + Orthanc::ImageExtractionMode mode_; + std::auto_ptr<std::string> dicom_; + std::string content_; + + void DownloadImage(); + + void DownloadDicom(); + + public: + /** + * {summary}{Create a connection to some image instance.} + * {param}{connection The remote instance of %Orthanc.} + * {param}{id The %Orthanc identifier of the image instance.} + **/ + Instance(const OrthancConnection& connection, + const char* id); + + + /** + * {summary}{Get the %Orthanc identifier of this identifier.} + * {returns}{The identifier.} + **/ + const char* GetId() const + { + return id_.c_str(); + } + + + /** + * {summary}{Set the extraction mode for the 2D image corresponding to this instance.} + * {param}{mode The extraction mode.} + **/ + void SetImageExtractionMode(Orthanc::ImageExtractionMode mode); + + /** + * {summary}{Get the extraction mode for the 2D image corresponding to this instance.} + * {returns}{The extraction mode.} + **/ + Orthanc::ImageExtractionMode GetImageExtractionMode() const + { + return mode_; + } + + + /** + * {summary}{Get the string value of some DICOM tag of this instance.} + * {param}{tag The name of the tag of interest.} + * {returns}{The value of the tag.} + **/ + const char* GetTagAsString(const char* tag) const; + + /** + * {summary}{Get the floating point value that is stored in some DICOM tag of this instance.} + * {param}{tag The name of the tag of interest.} + * {returns}{The value of the tag.} + **/ + float GetTagAsFloat(const char* tag) const; + + /** + * {summary}{Get the integer value that is stored in some DICOM tag of this instance.} + * {param}{tag The name of the tag of interest.} + * {returns}{The value of the tag.} + **/ + int32_t GetTagAsInt(const char* tag) const; + + + /** + * {summary}{Get the width of the 2D image.} + * {description}{Get the width of the 2D image that is encoded by this DICOM instance.} + * {returns}{The width.} + **/ + uint32_t GetWidth(); + + /** + * {summary}{Get the height of the 2D image.} + * {description}{Get the height of the 2D image that is encoded by this DICOM instance.} + * {returns}{The height.} + **/ + uint32_t GetHeight(); + + /** + * {summary}{Get the number of bytes between two lines of the image (pitch).} + * {description}{Get the number of bytes between two lines of the image in the memory buffer returned by GetBuffer(). This value depends on the extraction mode for the image.} + * {returns}{The pitch.} + **/ + uint32_t GetPitch(); + + /** + * {summary}{Get the format of the pixels of the 2D image.} + * {description}{Return the memory layout that is used for the 2D image that is encoded by this DICOM instance. This value depends on the extraction mode for the image.} + * {returns}{The pixel format.} + **/ + Orthanc::PixelFormat GetPixelFormat(); + + /** + * {summary}{Access the memory buffer in which the raw pixels of the 2D image are stored.} + * {returns}{A pointer to the memory buffer.} + **/ + const void* GetBuffer(); + + /** + * {summary}{Access the memory buffer in which the raw pixels of some line of the 2D image are stored.} + * {param}{y The line of interest.} + * {returns}{A pointer to the memory buffer.} + **/ + const void* GetBuffer(uint32_t y); + + /** + * {summary}{Get the size of the DICOM file corresponding to this instance.} + * {returns}{The file size.} + **/ + const uint64_t GetDicomSize(); + + /** + * {summary}{Get a pointer to the content of the DICOM file corresponding to this instance.} + * {returns}{The DICOM file.} + **/ + const void* GetDicom(); + + /** + * {summary}{Discard the downloaded 2D image, so as to make room in memory.} + **/ + void DiscardImage(); + + /** + * {summary}{Discard the downloaded DICOM file, so as to make room in memory.} + **/ + void DiscardDicom(); + + LAAW_API_INTERNAL void SplitVectorOfFloats(std::vector<float>& target, + const char* tag); + + /** + * {summary}{Load a raw tag from the DICOM file.} + * {param}{path The path to the tag of interest (e.g. "0020-000d").} + **/ + void LoadTagContent(const char* path); + + /** + * {summary}{Return the value of the raw tag that was loaded by LoadContent.} + * {returns}{The tag value.} + **/ + const char* GetLoadedTagContent() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/OrthancClientException.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,58 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Core/OrthancException.h" +#include <laaw/laaw.h> + +namespace OrthancClient +{ + class OrthancClientException : public ::Laaw::LaawException + { + public: + OrthancClientException(Orthanc::ErrorCode code) : + LaawException(Orthanc::OrthancException::GetDescription(code)) + { + } + + OrthancClientException(const char* message) : + LaawException(message) + { + } + + OrthancClientException(const std::string& message) : + LaawException(message) + { + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/OrthancConnection.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,114 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancConnection.h" + +#include "../Core/Toolbox.h" + +namespace OrthancClient +{ + void OrthancConnection::ReadPatients() + { + client_.SetMethod(Orthanc::HttpMethod_Get); + client_.SetUrl(orthancUrl_ + "/patients"); + + Json::Value v; + if (!client_.Apply(content_)) + { + throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* OrthancConnection::GetFillerItem(size_t index) + { + Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); + std::string id = content_[tmp].asString(); + return new Patient(*this, id.c_str()); + } + + Patient& OrthancConnection::GetPatient(unsigned int index) + { + return dynamic_cast<Patient&>(patients_.GetItem(index)); + } + + OrthancConnection::OrthancConnection(const char* orthancUrl) : + orthancUrl_(orthancUrl), patients_(*this) + { + ReadPatients(); + } + + OrthancConnection::OrthancConnection(const char* orthancUrl, + const char* username, + const char* password) : + orthancUrl_(orthancUrl), patients_(*this) + { + client_.SetCredentials(username, password); + ReadPatients(); + } + + + void OrthancConnection::Store(const void* dicom, uint64_t size) + { + if (size == 0) + { + return; + } + + client_.SetMethod(Orthanc::HttpMethod_Post); + client_.SetUrl(orthancUrl_ + "/instances"); + + // Copy the DICOM file in the POST body. TODO - Avoid memory copy + client_.AccessPostData().resize(static_cast<size_t>(size)); + memcpy(&client_.AccessPostData()[0], dicom, static_cast<size_t>(size)); + + Json::Value v; + if (!client_.Apply(v)) + { + throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); + } + + Reload(); + } + + + void OrthancConnection::StoreFile(const char* filename) + { + std::string content; + Orthanc::Toolbox::ReadFile(content, filename); + + if (content.size() != 0) + { + Store(&content[0], content.size()); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/OrthancConnection.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,180 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Core/HttpClient.h" + +#include "Patient.h" + +namespace OrthancClient +{ + /** + * {summary}{Connection to an instance of %Orthanc.} + * {description}{This class encapsulates a connection to a remote instance + * of %Orthanc through its REST API.} + **/ + class LAAW_API OrthancConnection : + public boost::noncopyable, + private Orthanc::ArrayFilledByThreads::IFiller + { + private: + Orthanc::HttpClient client_; + std::string orthancUrl_; + Orthanc::ArrayFilledByThreads patients_; + Json::Value content_; + + void ReadPatients(); + + virtual size_t GetFillerSize() + { + return content_.size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + public: + /** + * {summary}{Create a connection to an instance of %Orthanc.} + * {param}{orthancUrl URL to which the REST API of %Orthanc is listening.} + **/ + OrthancConnection(const char* orthancUrl); + + /** + * {summary}{Create a connection to an instance of %Orthanc, with authentication.} + * {param}{orthancUrl URL to which the REST API of %Orthanc is listening.} + * {param}{username The username.} + * {param}{password The password.} + **/ + OrthancConnection(const char* orthancUrl, + const char* username, + const char* password); + + virtual ~OrthancConnection() + { + } + + /** + * {summary}{Returns the number of threads for this connection.} + * {description}{Returns the number of simultaneous connections + * that are used when downloading information from this instance + * of %Orthanc.} + * {returns}{The number of threads.} + **/ + uint32_t GetThreadCount() const + { + return patients_.GetThreadCount(); + } + + /** + * {summary}{Sets the number of threads for this connection.} + * {description}{Sets the number of simultaneous connections + * that are used when downloading information from this instance + * of %Orthanc.} + * {param}{threadCount The number of threads.} + **/ + void SetThreadCount(uint32_t threadCount) + { + patients_.SetThreadCount(threadCount); + } + + /** + * {summary}{Reload the list of the patients.} + * {description}{This method will reload the list of the patients from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.} + **/ + void Reload() + { + ReadPatients(); + patients_.Invalidate(); + } + + LAAW_API_INTERNAL const Orthanc::HttpClient& GetHttpClient() const + { + return client_; + } + + /** + * {summary}{Returns the URL of this instance of %Orthanc.} + * {description}{Returns the URL of the remote %Orthanc instance to which this object is connected.} + * {returns}{The URL.} + **/ + const char* GetOrthancUrl() const + { + return orthancUrl_.c_str(); + } + + /** + * {summary}{Returns the number of patients.} + * {description}{Returns the number of patients that are stored in the remote instance of %Orthanc.} + * {returns}{The number of patients.} + **/ + uint32_t GetPatientCount() + { + return patients_.GetSize(); + } + + /** + * {summary}{Get some patient.} + * {description}{This method will return an object that contains information about some patient. The patients are indexed by a number between 0 (inclusive) and the result of GetPatientCount() (exclusive).} + * {param}{index The index of the patient of interest.} + * {returns}{The patient.} + **/ + Patient& GetPatient(uint32_t index); + + /** + * {summary}{Delete some patient.} + * {description}{Delete some patient from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.} + * {param}{index The index of the patient of interest.} + * {returns}{The patient.} + **/ + void DeletePatient(uint32_t index) + { + GetPatient(index).Delete(); + Reload(); + } + + /** + * {summary}{Send a DICOM file.} + * {description}{This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.} + * {param}{filename Path to the DICOM file} + **/ + void StoreFile(const char* filename); + + /** + * {summary}{Send a DICOM file that is contained inside a memory buffer.} + * {description}{This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.} + * {param}{dicom The memory buffer containing the DICOM file.} + * {param}{size The size of the DICOM file.} + **/ + void Store(const void* dicom, uint64_t size); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Patient.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,92 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "Patient.h" + +#include "OrthancConnection.h" + +namespace OrthancClient +{ + void Patient::ReadPatient() + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/patients/" + id_); + + Json::Value v; + if (!client.Apply(patient_)) + { + throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* Patient::GetFillerItem(size_t index) + { + Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); + std::string id = patient_["Studies"][tmp].asString(); + return new Study(connection_, id.c_str()); + } + + Patient::Patient(const OrthancConnection& connection, + const char* id) : + connection_(connection), + id_(id), + studies_(*this) + { + studies_.SetThreadCount(connection.GetThreadCount()); + ReadPatient(); + } + + const char* Patient::GetMainDicomTag(const char* tag, const char* defaultValue) const + { + if (patient_["MainDicomTags"].isMember(tag)) + { + return patient_["MainDicomTags"][tag].asCString(); + } + else + { + return defaultValue; + } + } + + void Patient::Delete() + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetMethod(Orthanc::HttpMethod_Delete); + client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/patients/" + id_); + + std::string s; + if (!client.Apply(s)) + { + throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Patient.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,121 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Study.h" + +namespace OrthancClient +{ + /** + * {summary}{Connection to a patient stored in %Orthanc.} + * {description}{This class encapsulates a connection to a patient + * from a remote instance of %Orthanc.} + **/ + class LAAW_API Patient : + public Orthanc::IDynamicObject, + private Orthanc::ArrayFilledByThreads::IFiller + { + private: + const OrthancConnection& connection_; + std::string id_; + Json::Value patient_; + Orthanc::ArrayFilledByThreads studies_; + + void ReadPatient(); + + virtual size_t GetFillerSize() + { + return patient_["Studies"].size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + public: + /** + * {summary}{Create a connection to some patient.} + * {param}{connection The remote instance of %Orthanc.} + * {param}{id The %Orthanc identifier of the patient.} + **/ + Patient(const OrthancConnection& connection, + const char* id); + + /** + * {summary}{Reload the studies of this patient.} + * {description}{This method will reload the list of the studies of this patient. Pay attention to the fact that the studies that have been previously returned by GetStudy() will be invalidated.} + **/ + void Reload() + { + studies_.Reload(); + } + + /** + * {summary}{Return the number of studies for this patient.} + * {returns}{The number of studies.} + **/ + uint32_t GetStudyCount() + { + return studies_.GetSize(); + } + + /** + * {summary}{Get some study of this patient.} + * {description}{This method will return an object that contains information about some study. The studies are indexed by a number between 0 (inclusive) and the result of GetStudyCount() (exclusive).} + * {param}{index The index of the study of interest.} + * {returns}{The study.} + **/ + Study& GetStudy(uint32_t index) + { + return dynamic_cast<Study&>(studies_.GetItem(index)); + } + + /** + * {summary}{Get the %Orthanc identifier of this patient.} + * {returns}{The identifier.} + **/ + const char* GetId() const + { + return id_.c_str(); + } + + /** + * {summary}{Get the value of one of the main DICOM tags for this patient.} + * {param}{tag The name of the tag of interest ("PatientName", "PatientID", "PatientSex" or "PatientBirthDate").} + * {param}{defaultValue The default value to be returned if this tag does not exist.} + * {returns}{The value of the tag.} + **/ + const char* GetMainDicomTag(const char* tag, + const char* defaultValue) const; + + LAAW_API_INTERNAL void Delete(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Series.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,514 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "Series.h" + +#include "OrthancConnection.h" + +#include <set> +#include <boost/lexical_cast.hpp> + +namespace OrthancClient +{ + namespace + { + class SliceLocator + { + private: + float normal_[3]; + + public: + SliceLocator(Instance& someSlice) + { + /** + * Compute the slice normal from Image Orientation Patient. + * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice + * http://www.itk.org/pipermail/insight-users/2003-September/004762.html + **/ + + std::vector<float> cosines; + someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient"); + + if (cosines.size() != 6) + { + throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); + } + + normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4]; + normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5]; + normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3]; + } + + + /** + * Compute the distance of some slice along the slice normal. + **/ + float ComputeSliceLocation(Instance& instance) const + { + std::vector<float> ipp; + instance.SplitVectorOfFloats(ipp, "ImagePositionPatient"); + if (ipp.size() != 3) + { + throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); + } + + float dist = 0; + + for (int i = 0; i < 3; i++) + { + dist += normal_[i] * ipp[i]; + } + + return dist; + } + }; + + class ImageDownloadCommand : public Orthanc::ICommand + { + private: + Orthanc::PixelFormat format_; + Orthanc::ImageExtractionMode mode_; + Instance& instance_; + void* target_; + size_t lineStride_; + + public: + ImageDownloadCommand(Instance& instance, + Orthanc::PixelFormat format, + Orthanc::ImageExtractionMode mode, + void* target, + size_t lineStride) : + format_(format), + mode_(mode), + instance_(instance), + target_(target), + lineStride_(lineStride) + { + instance_.SetImageExtractionMode(mode); + } + + virtual bool Execute() + { + using namespace Orthanc; + + unsigned int width = instance_.GetHeight(); + + for (unsigned int y = 0; y < instance_.GetHeight(); y++) + { + uint8_t* p = reinterpret_cast<uint8_t*>(target_) + y * lineStride_; + + if (instance_.GetPixelFormat() == format_) + { + memcpy(p, instance_.GetBuffer(y), 2 * instance_.GetWidth()); + } + else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 && + format_ == PixelFormat_RGB24) + { + const uint8_t* s = reinterpret_cast<const uint8_t*>(instance_.GetBuffer(y)); + for (unsigned int x = 0; x < width; x++, s++, p += 3) + { + p[0] = *s; + p[1] = *s; + p[2] = *s; + } + } + else + { + throw OrthancClientException(ErrorCode_NotImplemented); + } + } + + // Do not keep the image in memory, as we are loading 3D images + instance_.DiscardImage(); + + return true; + } + }; + + + class ProgressToFloatListener : public Orthanc::ThreadedCommandProcessor::IListener + { + private: + float* target_; + + public: + ProgressToFloatListener(float* target) : target_(target) + { + } + + virtual void SignalProgress(unsigned int current, + unsigned int total) + { + if (total == 0) + { + *target_ = 0; + } + else + { + *target_ = static_cast<float>(current) / static_cast<float>(total); + } + } + + virtual void SignalSuccess(unsigned int total) + { + *target_ = 1; + } + + virtual void SignalFailure() + { + *target_ = 0; + } + + virtual void SignalCancel() + { + *target_ = 0; + } + }; + + } + + + void Series::Check3DImage() + { + if (!Is3DImage()) + { + throw OrthancClientException(Orthanc::ErrorCode_NotImplemented); + } + } + + bool Series::Is3DImageInternal() + { + try + { + if (GetInstanceCount() == 0) + { + return true; + } + + Instance& i1 = GetInstance(0); + + 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"))) + { + return false; + } + } + + SliceLocator locator(GetInstance(0)); + std::set<float> l; + for (unsigned int i = 0; i < GetInstanceCount(); i++) + { + l.insert(locator.ComputeSliceLocation(GetInstance(i))); + } + + return l.size() == GetInstanceCount(); + } + catch (OrthancClientException) + { + return false; + } + } + + void Series::ReadSeries() + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + + client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/series/" + id_); + Json::Value v; + if (!client.Apply(series_)) + { + throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* Series::GetFillerItem(size_t index) + { + Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); + std::string id = series_["Instances"][tmp].asString(); + return new Instance(connection_, id.c_str()); + } + + Series::Series(const OrthancConnection& connection, + const char* id) : + connection_(connection), + id_(id), + instances_(*this) + { + ReadSeries(); + status_ = Status3DImage_NotTested; + url_ = std::string(connection_.GetOrthancUrl()) + "/series/" + id_; + + isVoxelSizeRead_ = false; + voxelSizeX_ = 0; + voxelSizeY_ = 0; + voxelSizeZ_ = 0; + + instances_.SetThreadCount(connection.GetThreadCount()); + } + + + bool Series::Is3DImage() + { + if (status_ == Status3DImage_NotTested) + { + status_ = Is3DImageInternal() ? Status3DImage_True : Status3DImage_False; + } + + return status_ == Status3DImage_True; + } + + unsigned int Series::GetInstanceCount() + { + return instances_.GetSize(); + } + + Instance& Series::GetInstance(unsigned int index) + { + return dynamic_cast<Instance&>(instances_.GetItem(index)); + } + + unsigned int Series::GetWidth() + { + Check3DImage(); + + if (GetInstanceCount() == 0) + return 0; + else + return GetInstance(0).GetTagAsInt("Columns"); + } + + unsigned int Series::GetHeight() + { + Check3DImage(); + + if (GetInstanceCount() == 0) + return 0; + else + 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<float>(sx); + voxelSizeY_ = boost::lexical_cast<float>(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)) + { + return series_["MainDicomTags"][tag].asCString(); + } + else + { + return defaultValue; + } + } + + + + void Series::Load3DImageInternal(void* target, + Orthanc::PixelFormat format, + size_t lineStride, + size_t stackStride, + Orthanc::ThreadedCommandProcessor::IListener* listener) + { + using namespace Orthanc; + + // Choose the extraction mode, depending on the format of the + // target image. + + uint8_t bytesPerPixel; + ImageExtractionMode mode; + + switch (format) + { + case PixelFormat_RGB24: + bytesPerPixel = 3; + mode = ImageExtractionMode_Preview; + break; + + case PixelFormat_Grayscale8: + bytesPerPixel = 1; + mode = ImageExtractionMode_UInt8; // Preview ??? + break; + + case PixelFormat_Grayscale16: + bytesPerPixel = 2; + mode = ImageExtractionMode_UInt16; + break; + + case PixelFormat_SignedGrayscale16: + bytesPerPixel = 2; + mode = ImageExtractionMode_UInt16; + format = PixelFormat_Grayscale16; + break; + + default: + throw OrthancClientException(ErrorCode_NotImplemented); + } + + + // Check that the target image is properly sized + unsigned int sx = GetWidth(); + unsigned int sy = GetHeight(); + + if (lineStride < sx * bytesPerPixel || + stackStride < sx * sy * bytesPerPixel) + { + throw OrthancClientException(ErrorCode_BadRequest); + } + + if (sx == 0 || sy == 0 || GetInstanceCount() == 0) + { + // Empty image, nothing to do + if (listener) + listener->SignalSuccess(0); + return; + } + + + /** + * Order the stacks according to their distance along the slice + * normal (using the "Image Position Patient" tag). This works + * even if the "SliceLocation" tag is absent. + **/ + SliceLocator locator(GetInstance(0)); + + typedef std::map<float, Instance*> Instances; + Instances instances; + for (unsigned int i = 0; i < GetInstanceCount(); i++) + { + float dist = locator.ComputeSliceLocation(GetInstance(i)); + instances[dist] = &GetInstance(i); + } + + if (instances.size() != GetInstanceCount()) + { + // Several instances have the same Z coordinate + throw OrthancClientException(ErrorCode_NotImplemented); + } + + + // Submit the download of each stack as a set of commands + ThreadedCommandProcessor processor(connection_.GetThreadCount()); + + if (listener != NULL) + { + processor.SetListener(*listener); + } + + uint8_t* stackTarget = reinterpret_cast<uint8_t*>(target); + for (Instances::iterator it = instances.begin(); it != instances.end(); ++it) + { + processor.Post(new ImageDownloadCommand(*it->second, format, mode, stackTarget, lineStride)); + stackTarget += stackStride; + } + + + // Wait for all the stacks to be downloaded + if (!processor.Join()) + { + throw OrthancClientException(ErrorCode_NetworkProtocol); + } + } + + float Series::GetVoxelSizeX() + { + LoadVoxelSize(); + return voxelSizeX_; + } + + float Series::GetVoxelSizeY() + { + LoadVoxelSize(); + return voxelSizeY_; + } + + float Series::GetVoxelSizeZ() + { + LoadVoxelSize(); + return voxelSizeZ_; + } + + void Series::Load3DImage(void* target, + Orthanc::PixelFormat format, + int64_t lineStride, + int64_t stackStride, + float* progress) + { + ProgressToFloatListener listener(progress); + Load3DImageInternal(target, format, static_cast<size_t>(lineStride), + static_cast<size_t>(stackStride), &listener); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Series.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,234 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Instance.h" + +#include "../Core/MultiThreading/ArrayFilledByThreads.h" +#include "../Core/MultiThreading/ThreadedCommandProcessor.h" + +namespace OrthancClient +{ + /** + * {summary}{Connection to a series stored in %Orthanc.} + * {description}{This class encapsulates a connection to a series + * from a remote instance of %Orthanc.} + **/ + class LAAW_API Series : + public Orthanc::IDynamicObject, + private Orthanc::ArrayFilledByThreads::IFiller + { + private: + enum Status3DImage + { + Status3DImage_NotTested, + Status3DImage_True, + Status3DImage_False + }; + + const OrthancConnection& connection_; + std::string id_, url_; + Json::Value series_; + Orthanc::ArrayFilledByThreads instances_; + Status3DImage status_; + + bool isVoxelSizeRead_; + float voxelSizeX_; + float voxelSizeY_; + float voxelSizeZ_; + + void Check3DImage(); + + bool Is3DImageInternal(); + + void ReadSeries(); + + virtual size_t GetFillerSize() + { + return series_["Instances"].size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + void Load3DImageInternal(void* target, + Orthanc::PixelFormat format, + size_t lineStride, + size_t stackStride, + Orthanc::ThreadedCommandProcessor::IListener* listener); + + void LoadVoxelSize(); + + public: + /** + * {summary}{Create a connection to some series.} + * {param}{connection The remote instance of %Orthanc.} + * {param}{id The %Orthanc identifier of the series.} + **/ + Series(const OrthancConnection& connection, + const char* id); + + /** + * {summary}{Reload the instances of this series.} + * {description}{This method will reload the list of the instances of this series. Pay attention to the fact that the instances that have been previously returned by GetInstance() will be invalidated.} + **/ + void Reload() + { + instances_.Reload(); + } + + /** + * {summary}{Return the number of instances for this series.} + * {returns}{The number of instances.} + **/ + uint32_t GetInstanceCount(); + + /** + * {summary}{Get some instance of this series.} + * {description}{This method will return an object that contains information about some instance. The instances are indexed by a number between 0 (inclusive) and the result of GetInstanceCount() (exclusive).} + * {param}{index The index of the instance of interest.} + * {returns}{The instance.} + **/ + Instance& GetInstance(uint32_t index); + + /** + * {summary}{Get the %Orthanc identifier of this series.} + * {returns}{The identifier.} + **/ + const char* GetId() const + { + return id_.c_str(); + } + + /** + * {summary}{Returns the URL to this series.} + * {returns}{The URL.} + **/ + const char* GetUrl() const + { + return url_.c_str(); + } + + + /** + * {summary}{Get the value of one of the main DICOM tags for this series.} + * {param}{tag The name of the tag of interest ("Modality", "Manufacturer", "SeriesDate", "SeriesDescription", "SeriesInstanceUID"...).} + * {param}{defaultValue The default value to be returned if this tag does not exist.} + * {returns}{The value of the tag.} + **/ + const char* GetMainDicomTag(const char* tag, + const char* defaultValue) const; + + /** + * {summary}{Test whether this series encodes a 3D image that can be downloaded from %Orthanc.} + * {returns}{"true" if and only if this is a 3D image.} + **/ + bool Is3DImage(); + + /** + * {summary}{Get the width of the 3D image.} + * {description}{Get the width of the 3D image (i.e. along the X-axis). This call is only valid if this series corresponds to a 3D image.} + * {returns}{The width.} + **/ + uint32_t GetWidth(); + + /** + * {summary}{Get the height of the 3D image.} + * {description}{Get the height of the 3D image (i.e. along the Y-axis). This call is only valid if this series corresponds to a 3D image.} + * {returns}{The height.} + **/ + uint32_t GetHeight(); + + /** + * {summary}{Get the physical size of a voxel along the X-axis.} + * {description}{Get the physical size of a voxel along the X-axis. This call is only valid if this series corresponds to a 3D image.} + * {returns}{The voxel size.} + **/ + float GetVoxelSizeX(); + + /** + * {summary}{Get the physical size of a voxel along the Y-axis.} + * {description}{Get the physical size of a voxel along the Y-axis. This call is only valid if this series corresponds to a 3D image.} + * {returns}{The voxel size.} + **/ + float GetVoxelSizeY(); + + /** + * {summary}{Get the physical size of a voxel along the Z-axis.} + * {description}{Get the physical size of a voxel along the Z-axis. This call is only valid if this series corresponds to a 3D image.} + * {returns}{The voxel size.} + **/ + float GetVoxelSizeZ(); + + LAAW_API_INTERNAL void Load3DImage(void* target, + Orthanc::PixelFormat format, + int64_t lineStride, + int64_t stackStride, + Orthanc::ThreadedCommandProcessor::IListener& listener) + { + Load3DImageInternal(target, format, static_cast<size_t>(lineStride), + static_cast<size_t>(stackStride), &listener); + } + + /** + * {summary}{Load the 3D image into a memory buffer.} + * {description}{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.} + * {param}{target The target memory buffer.} + * {param}{format The memory layout of the voxels.} + * {param}{lineStride The number of bytes between two lines in the target memory buffer.} + * {param}{stackStride The number of bytes between two 2D slices in the target memory buffer.} + **/ + void Load3DImage(void* target, + Orthanc::PixelFormat format, + int64_t lineStride, + int64_t stackStride) + { + Load3DImageInternal(target, format, static_cast<size_t>(lineStride), + static_cast<size_t>(stackStride), NULL); + } + + /** + * {summary}{Load the 3D image into a memory buffer.} + * {description}{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. This method will also update a progress indicator to monitor the loading of the image.} + * {param}{target The target memory buffer.} + * {param}{format The memory layout of the voxels.} + * {param}{lineStride The number of bytes between two lines in the target memory buffer.} + * {param}{stackStride The number of bytes between two 2D slices in the target memory buffer.} + * {param}{progress A pointer to a floating-point number that is continuously updated by the download threads to reflect the percentage of completion (between 0 and 1). This value can be read from a separate thread.} + **/ + void Load3DImage(void* target, + Orthanc::PixelFormat format, + int64_t lineStride, + int64_t stackStride, + float* progress); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,1502 @@ +/** + * Laaw - Lightweight, Automated API Wrapper + * Copyright (C) 2010-2013 Jomago - Alain Mazy, Benjamin Golinvaux, + * Sebastien Jodogne + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include <laaw/laaw.h> +#include <string.h> // For strcpy() and strlen() +#include <stdlib.h> // For free() + +static char* LAAW_EXTERNC_CopyString(const char* str) +{ + char* copy = reinterpret_cast<char*>(malloc(strlen(str) + 1)); + strcpy(copy, str); + return copy; +} + +extern "C" +{ + LAAW_EXPORT_DLL_API char* LAAW_CALL_CONVENTION LAAW_EXTERNC_1f1acb322ea4d0aad65172824607673c(void** newObject, const char* arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + *newObject = new OrthancClient::OrthancConnection(reinterpret_cast< const char* >(arg0)); + + 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_f3fd272e4636f6a531aabb72ee01cd5b(void** newObject, const char* arg0, const char* arg1, const char* arg2) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + *newObject = new OrthancClient::OrthancConnection(reinterpret_cast< const char* >(arg0), reinterpret_cast< const char* >(arg1), reinterpret_cast< const char* >(arg2)); + + 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_12d3de0a96e9efb11136a9811bb9ed38(void* thisObject) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + delete static_cast<OrthancClient::OrthancConnection*>(thisObject); + + 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_557aee7b61817292a0f31269d3c35db7(const void* thisObject, uint32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::OrthancConnection* this_ = static_cast<const OrthancClient::OrthancConnection*>(thisObject); +*result = this_->GetThreadCount(); + + 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_0b8dff0ce67f10954a49b059e348837e(void* thisObject, uint32_t arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); +this_->SetThreadCount(arg0); + + 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_e05097c153f676e5a5ee54dcfc78256f(void* thisObject) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); +this_->Reload(); + + 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_e840242bf58d17d3c1d722da09ce88e0(const void* thisObject, const char** result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::OrthancConnection* this_ = static_cast<const OrthancClient::OrthancConnection*>(thisObject); +*result = this_->GetOrthancUrl(); + + 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_c9af31433001b5dfc012a552dc6d0050(void* thisObject, uint32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); +*result = this_->GetPatientCount(); + + 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_3fba4d6b818180a44cd1cae6046334dc(void* thisObject, void** result, uint32_t arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); +*result = &this_->GetPatient(arg0); + + 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_aeb20dc75b9246188db857317e5e0ce7(void* thisObject, uint32_t arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); +this_->DeletePatient(arg0); + + 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_62689803d9871e4d9c51a648640b320b(void* thisObject, const char* arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); +this_->StoreFile(reinterpret_cast< const char* >(arg0)); + + 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_2fb64c9e5a67eccd413b0e913469a421(void* thisObject, const void* arg0, uint64_t arg1) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); +this_->Store(reinterpret_cast< const void* >(arg0), arg1); + + 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_6cf0d7268667f9b0aa4511bacf184919(void** newObject, void* arg0, const char* arg1) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + *newObject = new OrthancClient::Patient(*reinterpret_cast< ::OrthancClient::OrthancConnection* >(arg0), reinterpret_cast< const char* >(arg1)); + + 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_7d81cd502ee27e859735d0ea7112b5a1(void* thisObject) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + delete static_cast<OrthancClient::Patient*>(thisObject); + + 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_f756172daf04516eec3a566adabb4335(void* thisObject) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Patient* this_ = static_cast<OrthancClient::Patient*>(thisObject); +this_->Reload(); + + 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_ddb68763ec902a97d579666a73a20118(void* thisObject, uint32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Patient* this_ = static_cast<OrthancClient::Patient*>(thisObject); +*result = this_->GetStudyCount(); + + 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_fba3c68b4be7558dbc65f7ce1ab57d63(void* thisObject, void** result, uint32_t arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Patient* this_ = static_cast<OrthancClient::Patient*>(thisObject); +*result = &this_->GetStudy(arg0); + + 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_b4ca99d958f843493e58d1ef967340e1(const void* thisObject, const char** result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Patient* this_ = static_cast<const OrthancClient::Patient*>(thisObject); +*result = this_->GetId(); + + 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_78d5cc76d282437b6f93ec3b82c35701(const void* thisObject, const char** result, const char* arg0, const char* arg1) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Patient* this_ = static_cast<const OrthancClient::Patient*>(thisObject); +*result = this_->GetMainDicomTag(reinterpret_cast< const char* >(arg0), reinterpret_cast< const char* >(arg1)); + + 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_193599b9e345384fcdfcd47c29c55342(void** newObject, void* arg0, const char* arg1) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + *newObject = new OrthancClient::Series(*reinterpret_cast< ::OrthancClient::OrthancConnection* >(arg0), reinterpret_cast< const char* >(arg1)); + + 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_7c97f17063a357d38c5fab1136ad12a0(void* thisObject) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + delete static_cast<OrthancClient::Series*>(thisObject); + + 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_48a2a1a9d68c047e22bfba23014643d2(void* thisObject) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); +this_->Reload(); + + 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_852bf8296ca21c5fde5ec565cc10721d(void* thisObject, uint32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); +*result = this_->GetInstanceCount(); + + 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_efd04574e0779faa83df1f2d8f9888db(void* thisObject, void** result, uint32_t arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); +*result = &this_->GetInstance(arg0); + + 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_736247ff5e8036dac38163da6f666ed5(const void* thisObject, const char** result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Series* this_ = static_cast<const OrthancClient::Series*>(thisObject); +*result = this_->GetId(); + + 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_d82d2598a7a73f4b6fcc0c09c25b08ca(const void* thisObject, const char** result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Series* this_ = static_cast<const OrthancClient::Series*>(thisObject); +*result = this_->GetUrl(); + + 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_88134b978f9acb2aecdadf54aeab3c64(const void* thisObject, const char** result, const char* arg0, const char* arg1) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Series* this_ = static_cast<const OrthancClient::Series*>(thisObject); +*result = this_->GetMainDicomTag(reinterpret_cast< const char* >(arg0), reinterpret_cast< const char* >(arg1)); + + 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_152cb1b704c053d24b0dab7461ba6ea3(void* thisObject, int32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); +*result = this_->Is3DImage(); + + 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_eee03f337ec81d9f1783cd41e5238757(void* thisObject, uint32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); +*result = this_->GetWidth(); + + 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_006f08237bd7611636fc721baebfb4c5(void* thisObject, uint32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); +*result = this_->GetHeight(); + + 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_b794f5cd3dad7d7b575dd1fd902afdd0(void* thisObject, float* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); +*result = this_->GetVoxelSizeX(); + + 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_8ee2e50dd9df8f66a3c1766090dd03ab(void* thisObject, float* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); +*result = this_->GetVoxelSizeY(); + + 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_046aed35bbe4751691f4c34cc249a61d(void* thisObject, float* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); +*result = this_->GetVoxelSizeZ(); + + 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 + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); +this_->Load3DImage(reinterpret_cast< void* >(arg0), static_cast< ::Orthanc::PixelFormat >(arg1), arg2, arg3); + + 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_b2601a161c24ad0a1d3586246f87452c(void* thisObject, void* arg0, int32_t arg1, int64_t arg2, int64_t arg3, float* arg4) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); +this_->Load3DImage(reinterpret_cast< void* >(arg0), static_cast< ::Orthanc::PixelFormat >(arg1), arg2, arg3, reinterpret_cast< float* >(arg4)); + + 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_b01c6003238eb46c8db5dc823d7ca678(void** newObject, void* arg0, const char* arg1) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + *newObject = new OrthancClient::Study(*reinterpret_cast< ::OrthancClient::OrthancConnection* >(arg0), reinterpret_cast< const char* >(arg1)); + + 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_0147007fb99bad8cd95a139ec8795376(void* thisObject) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + delete static_cast<OrthancClient::Study*>(thisObject); + + 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_e65b20b7e0170b67544cd6664a4639b7(void* thisObject) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Study* this_ = static_cast<OrthancClient::Study*>(thisObject); +this_->Reload(); + + 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_470e981b0e41f17231ba0ae6f3033321(void* thisObject, uint32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Study* this_ = static_cast<OrthancClient::Study*>(thisObject); +*result = this_->GetSeriesCount(); + + 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_04cefd138b6ea15ad909858f2a0a8f05(void* thisObject, void** result, uint32_t arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Study* this_ = static_cast<OrthancClient::Study*>(thisObject); +*result = &this_->GetSeries(arg0); + + 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_aee5b1f6f0c082f2c3b0986f9f6a18c7(const void* thisObject, const char** result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Study* this_ = static_cast<const OrthancClient::Study*>(thisObject); +*result = this_->GetId(); + + 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_93965682bace75491413e1f0b8d5a654(const void* thisObject, const char** result, const char* arg0, const char* arg1) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Study* this_ = static_cast<const OrthancClient::Study*>(thisObject); +*result = this_->GetMainDicomTag(reinterpret_cast< const char* >(arg0), reinterpret_cast< const char* >(arg1)); + + 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_6c5ad02f91b583e29cebd0bd319ce21d(void** newObject, void* arg0, const char* arg1) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + *newObject = new OrthancClient::Instance(*reinterpret_cast< ::OrthancClient::OrthancConnection* >(arg0), reinterpret_cast< const char* >(arg1)); + + 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_4068241c44a9c1367fe0e57be523f207(void* thisObject) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + delete static_cast<OrthancClient::Instance*>(thisObject); + + 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_236ee8b403bc99535a8a4695c0cd45cb(const void* thisObject, const char** result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Instance* this_ = static_cast<const OrthancClient::Instance*>(thisObject); +*result = this_->GetId(); + + 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_2a437b7aba6bb01e81113835be8f0146(void* thisObject, int32_t arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +this_->SetImageExtractionMode(static_cast< ::Orthanc::ImageExtractionMode >(arg0)); + + 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_2bcbcb850934ae0bb4c6f0cc940e6cda(const void* thisObject, int32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Instance* this_ = static_cast<const OrthancClient::Instance*>(thisObject); +*result = this_->GetImageExtractionMode(); + + 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_8d415c3a78a48e7e61d9fd24e7c79484(const void* thisObject, const char** result, const char* arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Instance* this_ = static_cast<const OrthancClient::Instance*>(thisObject); +*result = this_->GetTagAsString(reinterpret_cast< const char* >(arg0)); + + 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_70d2f8398bbc63b5f792b69b4ad5fecb(const void* thisObject, float* result, const char* arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Instance* this_ = static_cast<const OrthancClient::Instance*>(thisObject); +*result = this_->GetTagAsFloat(reinterpret_cast< const char* >(arg0)); + + 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_1729a067d902771517388eedd7346b23(const void* thisObject, int32_t* result, const char* arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Instance* this_ = static_cast<const OrthancClient::Instance*>(thisObject); +*result = this_->GetTagAsInt(reinterpret_cast< const char* >(arg0)); + + 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_72e2aeee66cd3abd8ab7e987321c3745(void* thisObject, uint32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +*result = this_->GetWidth(); + + 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_1ea3df5a1ac1a1a687fe7325adddb6f0(void* thisObject, uint32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +*result = this_->GetHeight(); + + 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_99b4f370e4f532d8b763e2cb49db92f8(void* thisObject, uint32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +*result = this_->GetPitch(); + + 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_c41c742b68617f1c0590577a0a5ebc0c(void* thisObject, int32_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +*result = this_->GetPixelFormat(); + + 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_142dd2feba0fc1d262bbd0baeb441a8b(void* thisObject, const void** result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +*result = this_->GetBuffer(); + + 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_5f5c9f81a4dff8daa6c359f1d0488fef(void* thisObject, const void** result, uint32_t arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +*result = this_->GetBuffer(arg0); + + 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_9ca979fffd08fa256306d4e68d8b0e91(void* thisObject, uint64_t* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +*result = this_->GetDicomSize(); + + 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_6f2d77a26edc91c28d89408dbc3c271e(void* thisObject, const void** result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +*result = this_->GetDicom(); + + 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_c0f494b80d4ff8b232df7a75baa0700a(void* thisObject) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +this_->DiscardImage(); + + 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_d604f44bd5195e082e745e9cbc164f4c(void* thisObject) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +this_->DiscardDicom(); + + 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_1710299d1c5f3b1f2b7cf3962deebbfd(void* thisObject, const char* arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +this_->LoadTagContent(reinterpret_cast< const char* >(arg0)); + + 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_bb55aaf772ddceaadee36f4e54136bcb(const void* thisObject, const char** result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Instance* this_ = static_cast<const OrthancClient::Instance*>(thisObject); +*result = this_->GetLoadedTagContent(); + + return NULL; + } + catch (::Laaw::LaawException& e) + { + return LAAW_EXTERNC_CopyString(e.What()); + } + catch (...) + { + return LAAW_EXTERNC_CopyString("..."); + } + } + + + LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetDescription() + { + return "Native client to the REST API of Orthanc"; + } + + LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetCompany() + { + return "CHU of Liege"; + } + + LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetProduct() + { + return "OrthancClient"; + } + + LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetCopyright() + { + return "(c) 2012-2014, Sebastien Jodogne, CHU of Liege"; + } + + LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetVersion() + { + return "0.7"; + } + + LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFileVersion() + { + return "0.7.0.4"; + } + + LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFullVersion() + { + return "0.7.4"; + } + + LAAW_EXPORT_DLL_API void LAAW_CALL_CONVENTION LAAW_EXTERNC_FreeString(char* str) + { + if (str != NULL) + free(str); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,1784 @@ +/** + * Laaw - Lightweight, Automated API Wrapper + * Copyright (C) 2010-2013 Jomago - Alain Mazy, Benjamin Golinvaux, + * Sebastien Jodogne + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +/** + * @file + **/ + +#pragma once + +#include <stdexcept> +#include <memory> +#include <string> +#include <string.h> + +#if defined(_WIN32) + +/******************************************************************** + ** This is the Windows-specific section + ********************************************************************/ + +#include <windows.h> + +/* cf. http://sourceforge.net/p/predef/wiki/Architectures/ */ +#ifdef _M_X64 +/* 64 bits target */ +#define LAAW_ORTHANC_CLIENT_CALL_CONV __fastcall +#define LAAW_ORTHANC_CLIENT_CALL_DECORATION(Name, StdCallSuffix) Name +#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "OrthancClient_Windows64.dll" +#else +/* 32 bits target */ +#define LAAW_ORTHANC_CLIENT_CALL_CONV __stdcall +#define LAAW_ORTHANC_CLIENT_CALL_DECORATION(Name, StdCallSuffix) "_" Name "@" StdCallSuffix +#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "OrthancClient_Windows32.dll" +#endif + +#define LAAW_ORTHANC_CLIENT_HANDLE_TYPE HINSTANCE +#define LAAW_ORTHANC_CLIENT_HANDLE_NULL 0 +#define LAAW_ORTHANC_CLIENT_FUNCTION_TYPE FARPROC +#define LAAW_ORTHANC_CLIENT_LOADER(path) LoadLibraryA(path) +#define LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle, name, decoration) GetProcAddress(handle, LAAW_ORTHANC_CLIENT_CALL_DECORATION(name, decoration)) +#define LAAW_ORTHANC_CLIENT_CLOSER(handle) FreeLibrary(handle) + + +/******************************************************************** + ** This is the Linux-specific section + ********************************************************************/ + +#elif defined (__linux) + +#include <stdlib.h> +#include <dlfcn.h> + +/* cf. http://sourceforge.net/p/predef/wiki/Architectures/ */ +#ifdef __amd64__ +#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "libOrthancClient.so.0.7" +#else +#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "libOrthancClient.so.0.7" +#endif + +#define LAAW_ORTHANC_CLIENT_CALL_CONV +#define LAAW_ORTHANC_CLIENT_HANDLE_TYPE void* +#define LAAW_ORTHANC_CLIENT_HANDLE_NULL NULL +#define LAAW_ORTHANC_CLIENT_FUNCTION_TYPE intptr_t +#define LAAW_ORTHANC_CLIENT_LOADER(path) dlopen(path, RTLD_LAZY) +#define LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle, name, decoration) (LAAW_ORTHANC_CLIENT_FUNCTION_TYPE) dlsym(handle, name) +#define LAAW_ORTHANC_CLIENT_CLOSER(handle) dlclose(handle) + + +#else +#error Please support your platform here +#endif + + +/******************************************************************** + ** Definition of the integer types + ********************************************************************/ + +#ifndef LAAW_INT8 // Only define the integer types once + +#if defined(__GNUC__) + +// Under GCC (including MinGW), the stdint.h standard header is used. + +#include <stdint.h> + +#define LAAW_INT8 int8_t +#define LAAW_UINT8 uint8_t +#define LAAW_INT16 int16_t +#define LAAW_UINT16 uint16_t +#define LAAW_INT32 int32_t +#define LAAW_UINT32 uint32_t +#define LAAW_INT64 int64_t +#define LAAW_UINT64 uint64_t + +#elif defined(_MSC_VER) + +// Under Visual Studio, it is required to define the various integer +// types by hand. + +#if (_MSC_VER < 1300) +typedef signed char LAAW_INT8; +typedef signed short LAAW_INT16; +typedef signed int LAAW_INT32; +typedef unsigned char LAAW_UINT8; +typedef unsigned short LAAW_UINT16; +typedef unsigned int LAAW_UINT32; +#else +typedef signed __int8 LAAW_INT8; +typedef signed __int16 LAAW_INT16; +typedef signed __int32 LAAW_INT32; +typedef unsigned __int8 LAAW_UINT8; +typedef unsigned __int16 LAAW_UINT16; +typedef unsigned __int32 LAAW_UINT32; +#endif + +typedef signed __int64 LAAW_INT64; +typedef unsigned __int64 LAAW_UINT64; + +#else +#error "Please support your compiler here" +#endif + +#endif + + + + + +/******************************************************************** + ** This is a shared section between Windows and Linux + ********************************************************************/ + +namespace OrthancClient { +/** + * @brief Exception class that is thrown by the functions of this shared library. + **/ +class OrthancClientException : public std::exception + { + private: + std::string message_; + + public: + /** + * @brief Constructs an exception. + * @param message The error message. + **/ + OrthancClientException(std::string message) : message_(message) + { + } + + ~OrthancClientException() throw() + { + } + + /** + * @brief Get the error message associated with this exception. + * @returns The error message. + **/ + const std::string& What() const throw() + { + return message_; + } +}; +} + + +namespace OrthancClient { namespace Internals { +/** + * This internal class implements a Singleton design pattern that will + * store a reference to the shared library handle, together with a + * pointer to each function in the shared library. + **/ +class Library + { + private: + LAAW_ORTHANC_CLIENT_HANDLE_TYPE handle_; + LAAW_ORTHANC_CLIENT_FUNCTION_TYPE functionsIndex_[62 + 1]; + + + + void Load(const char* sharedLibraryPath) + { + + if (handle_ != LAAW_ORTHANC_CLIENT_HANDLE_NULL) + { + // Do nothing if the library is already loaded + return; + } + + /* Setup the path to the default shared library if not provided */ + if (sharedLibraryPath == NULL) + { + sharedLibraryPath = LAAW_ORTHANC_CLIENT_DEFAULT_PATH; + } + + /* Load the shared library */ + handle_ = LAAW_ORTHANC_CLIENT_LOADER(sharedLibraryPath); + + + if (handle_ == LAAW_ORTHANC_CLIENT_HANDLE_NULL) + { + throw ::OrthancClient::OrthancClientException("Error loading shared library"); + } + + LoadFunctions(); + } + + inline void LoadFunctions(); + + void FreeString(char* str) + { + typedef void (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (char*); + Function function = (Function) GetFunction(62); + function(str); + } + + Library() + { + handle_ = LAAW_ORTHANC_CLIENT_HANDLE_NULL; + } + + ~Library() + { + Finalize(); + } + + public: + LAAW_ORTHANC_CLIENT_FUNCTION_TYPE GetFunction(unsigned int index) + { + /** + * If the library has not been manually initialized by a call to + * ::OrthancClient::Initialize(), it is loaded from + * the default location (lazy initialization). + **/ + if (handle_ == NULL) + { + Load(NULL); + } + + return functionsIndex_[index]; + } + + void ThrowExceptionIfNeeded(char* message) + { + if (message != NULL) + { + std::string tmp(message); + FreeString(message); + throw ::OrthancClient::OrthancClientException(tmp); + } + } + + static inline Library& GetInstance() + { + /** + * This function defines a "static variable" inside a "static + * inline method" of a class. This ensures that a single + * instance of this variable will be used across all the + * compilation modules of the software. + * http://stackoverflow.com/a/1389403/881731 + **/ + + static Library singleton; + return singleton; + } + + static void Initialize(const char* sharedLibraryPath) + { + GetInstance().Load(sharedLibraryPath); + } + + void Finalize() + { + if (handle_ != LAAW_ORTHANC_CLIENT_HANDLE_NULL) + { + LAAW_ORTHANC_CLIENT_CLOSER(handle_); + handle_ = LAAW_ORTHANC_CLIENT_HANDLE_NULL; + } + } +}; +}} + + +/*! + * \addtogroup Global Global definitions. + * @{ + * @} + */ + + +namespace OrthancClient { +/*! + * \addtogroup Initialization Initialization of the shared library. + * @{ + */ + +/** + * @brief Manually initialize the shared library, using the default library name. + * + * Call this method before using the library to ensure correct + * behaviour in multi-threaded applications. This method is also + * useful to control the time at which the shared library is + * loaded (e.g. for real-time applications). + **/ +inline void Initialize() +{ + ::OrthancClient::Internals::Library::Initialize(NULL); +} + +/** + * @brief Manually initialize the shared library. + * + * Call this method before using the library to ensure correct + * behaviour in multi-threaded applications. This method is also + * useful to control the time at which the shared library is + * loaded (e.g. for real-time applications). + * + * @param sharedLibraryPath The path to the shared library that + * contains the module. + **/ +inline void Initialize(const std::string& sharedLibraryPath) +{ + ::OrthancClient::Internals::Library::Initialize(sharedLibraryPath.c_str()); +} + +/** + * @brief Manually finalize the shared library. + * + * Calling explicitly this function is not mandatory. It is useful to + * force the release of the resources acquired by the shared library, + * or to manually control the order in which the global variables get + * deleted. + **/ +inline void Finalize() +{ + ::OrthancClient::Internals::Library::GetInstance().Finalize(); +} + + +/** + * @} + */ +} + + +namespace OrthancClient { namespace Internals { +inline void Library::LoadFunctions() +{ + typedef const char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (); + Function getVersion = (Function) LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_GetVersion", "0"); + if (getVersion == NULL) + { + throw ::OrthancClient::OrthancClientException("Unable to get the library version"); + } + + /** + * It is assumed that the API does not change when the revision + * number (MAJOR.MINOR.REVISION) changes. + **/ + if (strcmp(getVersion(), "0.7")) + { + 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_[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"); + functionsIndex_[6] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e840242bf58d17d3c1d722da09ce88e0", "8"); + functionsIndex_[7] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c9af31433001b5dfc012a552dc6d0050", "8"); + functionsIndex_[8] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_3fba4d6b818180a44cd1cae6046334dc", "12"); + functionsIndex_[9] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_aeb20dc75b9246188db857317e5e0ce7", "8"); + functionsIndex_[10] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_62689803d9871e4d9c51a648640b320b", "8"); + functionsIndex_[11] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2fb64c9e5a67eccd413b0e913469a421", "16"); + functionsIndex_[0] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1f1acb322ea4d0aad65172824607673c", "8"); + functionsIndex_[1] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_f3fd272e4636f6a531aabb72ee01cd5b", "16"); + functionsIndex_[2] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_12d3de0a96e9efb11136a9811bb9ed38", "4"); + functionsIndex_[14] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_f756172daf04516eec3a566adabb4335", "4"); + functionsIndex_[15] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_ddb68763ec902a97d579666a73a20118", "8"); + functionsIndex_[16] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_fba3c68b4be7558dbc65f7ce1ab57d63", "12"); + functionsIndex_[17] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b4ca99d958f843493e58d1ef967340e1", "8"); + functionsIndex_[18] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_78d5cc76d282437b6f93ec3b82c35701", "16"); + functionsIndex_[12] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6cf0d7268667f9b0aa4511bacf184919", "12"); + functionsIndex_[13] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_7d81cd502ee27e859735d0ea7112b5a1", "4"); + functionsIndex_[21] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_48a2a1a9d68c047e22bfba23014643d2", "4"); + functionsIndex_[22] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_852bf8296ca21c5fde5ec565cc10721d", "8"); + functionsIndex_[23] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_efd04574e0779faa83df1f2d8f9888db", "12"); + functionsIndex_[24] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_736247ff5e8036dac38163da6f666ed5", "8"); + functionsIndex_[25] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_d82d2598a7a73f4b6fcc0c09c25b08ca", "8"); + functionsIndex_[26] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_88134b978f9acb2aecdadf54aeab3c64", "16"); + functionsIndex_[27] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_152cb1b704c053d24b0dab7461ba6ea3", "8"); + functionsIndex_[28] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_eee03f337ec81d9f1783cd41e5238757", "8"); + functionsIndex_[29] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_006f08237bd7611636fc721baebfb4c5", "8"); + 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_[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"); + + /* Check whether the functions were properly loaded */ + for (unsigned int i = 0; i <= 62; i++) + { + if (functionsIndex_[i] == (LAAW_ORTHANC_CLIENT_FUNCTION_TYPE) NULL) + { + throw ::OrthancClient::OrthancClientException("Unable to load the functions of the shared library"); + } + } +} +}} +namespace OrthancClient +{ + class OrthancConnection; +} + +namespace OrthancClient +{ + class Patient; +} + +namespace OrthancClient +{ + class Series; +} + +namespace OrthancClient +{ + class Study; +} + +namespace OrthancClient +{ + class Instance; +} + +namespace Orthanc +{ + /** + * @brief The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image. + * + * The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image. + * + * @ingroup Global + **/ + enum PixelFormat + { + /** + * @brief Graylevel, signed 16bpp image. + * + * The image is graylevel. Each pixel is signed and stored in two bytes. + * + **/ + PixelFormat_SignedGrayscale16 = 3, + /** + * @brief Color image in RGB24 format. + * + * Color image in RGB24 format. + * + **/ + PixelFormat_RGB24 = 0, + /** + * @brief Graylevel 8bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in one byte. + * + **/ + PixelFormat_Grayscale8 = 1, + /** + * @brief Graylevel, unsigned 16bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in two bytes. + * + **/ + PixelFormat_Grayscale16 = 2 + }; +} + +namespace Orthanc +{ + /** + * @brief The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image. + * + * The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image. + * + * @ingroup Global + **/ + enum ImageExtractionMode + { + /** + * @brief Truncation to the [-32768, 32767] range. + * + * Truncation to the [-32768, 32767] range. + * + **/ + ImageExtractionMode_Int16 = 3, + /** + * @brief Rescaled to 8bpp. + * + * The minimum value of the image is set to 0, and its maximum value is set to 255. + * + **/ + ImageExtractionMode_Preview = 0, + /** + * @brief Truncation to the [0, 255] range. + * + * Truncation to the [0, 255] range. + * + **/ + ImageExtractionMode_UInt8 = 1, + /** + * @brief Truncation to the [0, 65535] range. + * + * Truncation to the [0, 65535] range. + * + **/ + ImageExtractionMode_UInt16 = 2 + }; +} + +namespace OrthancClient +{ + /** + * @brief Connection to an instance of %Orthanc. + * + * This class encapsulates a connection to a remote instance of %Orthanc through its REST API. + * + **/ + class OrthancConnection + { + friend class ::OrthancClient::Patient; + friend class ::OrthancClient::Series; + friend class ::OrthancClient::Study; + friend class ::OrthancClient::Instance; + private: + bool isReference_; + OrthancConnection& operator= (const OrthancConnection&); // Assignment is forbidden + void* pimpl_; + OrthancConnection(void* pimpl) : isReference_(true), pimpl_(pimpl) {} + public: + /** + * @brief Construct a new reference to this object. + * + * Construct a new reference to this object. Pay attention to the fact that when the referenced object is deleted, the content of this object will be invalid. + * + * @param other The original object. + **/ + OrthancConnection(const OrthancConnection& other) : isReference_(true), pimpl_(other.pimpl_) { } + inline OrthancConnection(const ::std::string& orthancUrl); + inline OrthancConnection(const ::std::string& orthancUrl, const ::std::string& username, const ::std::string& password); + inline ~OrthancConnection(); + inline LAAW_UINT32 GetThreadCount() const; + inline void SetThreadCount(LAAW_UINT32 threadCount); + inline void Reload(); + inline ::std::string GetOrthancUrl() const; + inline LAAW_UINT32 GetPatientCount(); + inline ::OrthancClient::Patient GetPatient(LAAW_UINT32 index); + inline void DeletePatient(LAAW_UINT32 index); + inline void StoreFile(const ::std::string& filename); + inline void Store(const void* dicom, LAAW_UINT64 size); + }; +} + +namespace OrthancClient +{ + /** + * @brief Connection to a patient stored in %Orthanc. + * + * This class encapsulates a connection to a patient from a remote instance of %Orthanc. + * + **/ + class Patient + { + friend class ::OrthancClient::OrthancConnection; + friend class ::OrthancClient::Series; + friend class ::OrthancClient::Study; + friend class ::OrthancClient::Instance; + private: + bool isReference_; + Patient& operator= (const Patient&); // Assignment is forbidden + void* pimpl_; + Patient(void* pimpl) : isReference_(true), pimpl_(pimpl) {} + public: + /** + * @brief Construct a new reference to this object. + * + * Construct a new reference to this object. Pay attention to the fact that when the referenced object is deleted, the content of this object will be invalid. + * + * @param other The original object. + **/ + Patient(const Patient& other) : isReference_(true), pimpl_(other.pimpl_) { } + inline Patient(::OrthancClient::OrthancConnection& connection, const ::std::string& id); + inline ~Patient(); + inline void Reload(); + inline LAAW_UINT32 GetStudyCount(); + inline ::OrthancClient::Study GetStudy(LAAW_UINT32 index); + inline ::std::string GetId() const; + inline ::std::string GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const; + }; +} + +namespace OrthancClient +{ + /** + * @brief Connection to a series stored in %Orthanc. + * + * This class encapsulates a connection to a series from a remote instance of %Orthanc. + * + **/ + class Series + { + friend class ::OrthancClient::OrthancConnection; + friend class ::OrthancClient::Patient; + friend class ::OrthancClient::Study; + friend class ::OrthancClient::Instance; + private: + bool isReference_; + Series& operator= (const Series&); // Assignment is forbidden + void* pimpl_; + Series(void* pimpl) : isReference_(true), pimpl_(pimpl) {} + public: + /** + * @brief Construct a new reference to this object. + * + * Construct a new reference to this object. Pay attention to the fact that when the referenced object is deleted, the content of this object will be invalid. + * + * @param other The original object. + **/ + Series(const Series& other) : isReference_(true), pimpl_(other.pimpl_) { } + inline Series(::OrthancClient::OrthancConnection& connection, const ::std::string& id); + inline ~Series(); + inline void Reload(); + inline LAAW_UINT32 GetInstanceCount(); + inline ::OrthancClient::Instance GetInstance(LAAW_UINT32 index); + inline ::std::string GetId() const; + inline ::std::string GetUrl() const; + inline ::std::string GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const; + inline bool Is3DImage(); + inline LAAW_UINT32 GetWidth(); + inline LAAW_UINT32 GetHeight(); + inline float GetVoxelSizeX(); + inline float GetVoxelSizeY(); + inline float GetVoxelSizeZ(); + 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[]); + }; +} + +namespace OrthancClient +{ + /** + * @brief Connection to a study stored in %Orthanc. + * + * This class encapsulates a connection to a study from a remote instance of %Orthanc. + * + **/ + class Study + { + friend class ::OrthancClient::OrthancConnection; + friend class ::OrthancClient::Patient; + friend class ::OrthancClient::Series; + friend class ::OrthancClient::Instance; + private: + bool isReference_; + Study& operator= (const Study&); // Assignment is forbidden + void* pimpl_; + Study(void* pimpl) : isReference_(true), pimpl_(pimpl) {} + public: + /** + * @brief Construct a new reference to this object. + * + * Construct a new reference to this object. Pay attention to the fact that when the referenced object is deleted, the content of this object will be invalid. + * + * @param other The original object. + **/ + Study(const Study& other) : isReference_(true), pimpl_(other.pimpl_) { } + inline Study(::OrthancClient::OrthancConnection& connection, const ::std::string& id); + inline ~Study(); + inline void Reload(); + inline LAAW_UINT32 GetSeriesCount(); + inline ::OrthancClient::Series GetSeries(LAAW_UINT32 index); + inline ::std::string GetId() const; + inline ::std::string GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const; + }; +} + +namespace OrthancClient +{ + /** + * @brief Connection to an instance stored in %Orthanc. + * + * This class encapsulates a connection to an image instance from a remote instance of %Orthanc. + * + **/ + class Instance + { + friend class ::OrthancClient::OrthancConnection; + friend class ::OrthancClient::Patient; + friend class ::OrthancClient::Series; + friend class ::OrthancClient::Study; + private: + bool isReference_; + Instance& operator= (const Instance&); // Assignment is forbidden + void* pimpl_; + Instance(void* pimpl) : isReference_(true), pimpl_(pimpl) {} + public: + /** + * @brief Construct a new reference to this object. + * + * Construct a new reference to this object. Pay attention to the fact that when the referenced object is deleted, the content of this object will be invalid. + * + * @param other The original object. + **/ + Instance(const Instance& other) : isReference_(true), pimpl_(other.pimpl_) { } + inline Instance(::OrthancClient::OrthancConnection& connection, const ::std::string& id); + inline ~Instance(); + inline ::std::string GetId() const; + inline void SetImageExtractionMode(::Orthanc::ImageExtractionMode mode); + inline ::Orthanc::ImageExtractionMode GetImageExtractionMode() const; + inline ::std::string GetTagAsString(const ::std::string& tag) const; + inline float GetTagAsFloat(const ::std::string& tag) const; + inline LAAW_INT32 GetTagAsInt(const ::std::string& tag) const; + inline LAAW_UINT32 GetWidth(); + inline LAAW_UINT32 GetHeight(); + inline LAAW_UINT32 GetPitch(); + inline ::Orthanc::PixelFormat GetPixelFormat(); + inline const void* GetBuffer(); + inline const void* GetBuffer(LAAW_UINT32 y); + inline LAAW_UINT64 GetDicomSize(); + inline const void* GetDicom(); + inline void DiscardImage(); + inline void DiscardDicom(); + inline void LoadTagContent(const ::std::string& path); + inline ::std::string GetLoadedTagContent() const; + }; +} + +namespace OrthancClient +{ + /** + * @brief Create a connection to an instance of %Orthanc. + * + * Create a connection to an instance of %Orthanc. + * + * @param orthancUrl URL to which the REST API of %Orthanc is listening. + **/ + inline OrthancConnection::OrthancConnection(const ::std::string& orthancUrl) + { + isReference_ = false; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, const char*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(0); + char* error = function(&pimpl_, orthancUrl.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Create a connection to an instance of %Orthanc, with authentication. + * + * Create a connection to an instance of %Orthanc, with authentication. + * + * @param orthancUrl URL to which the REST API of %Orthanc is listening. + * @param username The username. + * @param password The password. + **/ + inline OrthancConnection::OrthancConnection(const ::std::string& orthancUrl, const ::std::string& username, const ::std::string& password) + { + isReference_ = false; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, const char*, const char*, const char*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(1); + char* error = function(&pimpl_, orthancUrl.c_str(), username.c_str(), password.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Destructs the object. + * + * Destructs the object. + * + **/ + inline OrthancConnection::~OrthancConnection() + { + if (isReference_) return; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(2); + char* error = function(pimpl_); + error = error; // Remove warning about unused variable + } + /** + * @brief Returns the number of threads for this connection. + * + * Returns the number of simultaneous connections that are used when downloading information from this instance of %Orthanc. + * + * @return The number of threads. + **/ + inline LAAW_UINT32 OrthancConnection::GetThreadCount() const + { + LAAW_UINT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, LAAW_UINT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(3); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Sets the number of threads for this connection. + * + * Sets the number of simultaneous connections that are used when downloading information from this instance of %Orthanc. + * + * @param threadCount The number of threads. + **/ + inline void OrthancConnection::SetThreadCount(LAAW_UINT32 threadCount) + { + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(4); + char* error = function(pimpl_, threadCount); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Reload the list of the patients. + * + * This method will reload the list of the patients from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated. + * + **/ + inline void OrthancConnection::Reload() + { + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(5); + char* error = function(pimpl_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Returns the URL of this instance of %Orthanc. + * + * Returns the URL of the remote %Orthanc instance to which this object is connected. + * + * @return The URL. + **/ + inline ::std::string OrthancConnection::GetOrthancUrl() const + { + const char* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(6); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } + /** + * @brief Returns the number of patients. + * + * Returns the number of patients that are stored in the remote instance of %Orthanc. + * + * @return The number of patients. + **/ + inline LAAW_UINT32 OrthancConnection::GetPatientCount() + { + LAAW_UINT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(7); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get some patient. + * + * This method will return an object that contains information about some patient. The patients are indexed by a number between 0 (inclusive) and the result of GetPatientCount() (exclusive). + * + * @param index The index of the patient of interest. + * @return The patient. + **/ + inline ::OrthancClient::Patient OrthancConnection::GetPatient(LAAW_UINT32 index) + { + void* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void**, LAAW_UINT32); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(8); + char* error = function(pimpl_, &result_, index); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return ::OrthancClient::Patient(result_); + } + /** + * @brief Delete some patient. + * + * Delete some patient from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated. + * + * @param index The index of the patient of interest. + * @return The patient. + **/ + inline void OrthancConnection::DeletePatient(LAAW_UINT32 index) + { + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(9); + char* error = function(pimpl_, index); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Send a DICOM file. + * + * This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated. + * + * @param filename Path to the DICOM file + **/ + inline void OrthancConnection::StoreFile(const ::std::string& filename) + { + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const char*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(10); + char* error = function(pimpl_, filename.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Send a DICOM file that is contained inside a memory buffer. + * + * This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated. + * + * @param dicom The memory buffer containing the DICOM file. + * @param size The size of the DICOM file. + **/ + inline void OrthancConnection::Store(const void* dicom, LAAW_UINT64 size) + { + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void*, LAAW_UINT64); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(11); + char* error = function(pimpl_, dicom, size); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } +} + +namespace OrthancClient +{ + /** + * @brief Create a connection to some patient. + * + * Create a connection to some patient. + * + * @param connection The remote instance of %Orthanc. + * @param id The %Orthanc identifier of the patient. + **/ + inline Patient::Patient(::OrthancClient::OrthancConnection& connection, const ::std::string& id) + { + isReference_ = false; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(12); + char* error = function(&pimpl_, connection.pimpl_, id.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Destructs the object. + * + * Destructs the object. + * + **/ + inline Patient::~Patient() + { + if (isReference_) return; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(13); + char* error = function(pimpl_); + error = error; // Remove warning about unused variable + } + /** + * @brief Reload the studies of this patient. + * + * This method will reload the list of the studies of this patient. Pay attention to the fact that the studies that have been previously returned by GetStudy() will be invalidated. + * + **/ + inline void Patient::Reload() + { + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(14); + char* error = function(pimpl_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Return the number of studies for this patient. + * + * Return the number of studies for this patient. + * + * @return The number of studies. + **/ + inline LAAW_UINT32 Patient::GetStudyCount() + { + LAAW_UINT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(15); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get some study of this patient. + * + * This method will return an object that contains information about some study. The studies are indexed by a number between 0 (inclusive) and the result of GetStudyCount() (exclusive). + * + * @param index The index of the study of interest. + * @return The study. + **/ + inline ::OrthancClient::Study Patient::GetStudy(LAAW_UINT32 index) + { + void* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void**, LAAW_UINT32); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(16); + char* error = function(pimpl_, &result_, index); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return ::OrthancClient::Study(result_); + } + /** + * @brief Get the %Orthanc identifier of this patient. + * + * Get the %Orthanc identifier of this patient. + * + * @return The identifier. + **/ + inline ::std::string Patient::GetId() const + { + const char* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(17); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } + /** + * @brief Get the value of one of the main DICOM tags for this patient. + * + * Get the value of one of the main DICOM tags for this patient. + * + * @param tag The name of the tag of interest ("PatientName", "PatientID", "PatientSex" or "PatientBirthDate"). + * @param defaultValue The default value to be returned if this tag does not exist. + * @return The value of the tag. + **/ + inline ::std::string Patient::GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const + { + 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(18); + char* error = function(pimpl_, &result_, tag.c_str(), defaultValue.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } +} + +namespace OrthancClient +{ + /** + * @brief Create a connection to some series. + * + * Create a connection to some series. + * + * @param connection The remote instance of %Orthanc. + * @param id The %Orthanc identifier of the series. + **/ + inline Series::Series(::OrthancClient::OrthancConnection& connection, const ::std::string& id) + { + isReference_ = false; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(19); + char* error = function(&pimpl_, connection.pimpl_, id.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Destructs the object. + * + * Destructs the object. + * + **/ + inline Series::~Series() + { + if (isReference_) return; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(20); + char* error = function(pimpl_); + error = error; // Remove warning about unused variable + } + /** + * @brief Reload the instances of this series. + * + * This method will reload the list of the instances of this series. Pay attention to the fact that the instances that have been previously returned by GetInstance() will be invalidated. + * + **/ + inline void Series::Reload() + { + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(21); + char* error = function(pimpl_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Return the number of instances for this series. + * + * Return the number of instances for this series. + * + * @return The number of instances. + **/ + inline LAAW_UINT32 Series::GetInstanceCount() + { + LAAW_UINT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(22); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get some instance of this series. + * + * This method will return an object that contains information about some instance. The instances are indexed by a number between 0 (inclusive) and the result of GetInstanceCount() (exclusive). + * + * @param index The index of the instance of interest. + * @return The instance. + **/ + inline ::OrthancClient::Instance Series::GetInstance(LAAW_UINT32 index) + { + void* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void**, LAAW_UINT32); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(23); + char* error = function(pimpl_, &result_, index); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return ::OrthancClient::Instance(result_); + } + /** + * @brief Get the %Orthanc identifier of this series. + * + * Get the %Orthanc identifier of this series. + * + * @return The identifier. + **/ + inline ::std::string Series::GetId() const + { + const char* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(24); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } + /** + * @brief Returns the URL to this series. + * + * Returns the URL to this series. + * + * @return The URL. + **/ + inline ::std::string Series::GetUrl() const + { + const char* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(25); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } + /** + * @brief Get the value of one of the main DICOM tags for this series. + * + * Get the value of one of the main DICOM tags for this series. + * + * @param tag The name of the tag of interest ("Modality", "Manufacturer", "SeriesDate", "SeriesDescription", "SeriesInstanceUID"...). + * @param defaultValue The default value to be returned if this tag does not exist. + * @return The value of the tag. + **/ + inline ::std::string Series::GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const + { + 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(26); + char* error = function(pimpl_, &result_, tag.c_str(), defaultValue.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } + /** + * @brief Test whether this series encodes a 3D image that can be downloaded from %Orthanc. + * + * Test whether this series encodes a 3D image that can be downloaded from %Orthanc. + * + * @return "true" if and only if this is a 3D image. + **/ + inline bool Series::Is3DImage() + { + LAAW_INT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_INT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(27); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_ != 0; + } + /** + * @brief Get the width of the 3D image. + * + * Get the width of the 3D image (i.e. along the X-axis). This call is only valid if this series corresponds to a 3D image. + * + * @return The width. + **/ + inline LAAW_UINT32 Series::GetWidth() + { + LAAW_UINT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(28); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get the height of the 3D image. + * + * Get the height of the 3D image (i.e. along the Y-axis). This call is only valid if this series corresponds to a 3D image. + * + * @return The height. + **/ + inline LAAW_UINT32 Series::GetHeight() + { + LAAW_UINT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(29); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get the physical size of a voxel along the X-axis. + * + * Get the physical size of a voxel along the X-axis. This call is only valid if this series corresponds to a 3D image. + * + * @return The voxel size. + **/ + inline float Series::GetVoxelSizeX() + { + float result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, float*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(30); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get the physical size of a voxel along the Y-axis. + * + * Get the physical size of a voxel along the Y-axis. This call is only valid if this series corresponds to a 3D image. + * + * @return The voxel size. + **/ + inline float Series::GetVoxelSizeY() + { + float result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, float*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(31); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get the physical size of a voxel along the Z-axis. + * + * Get the physical size of a voxel along the Z-axis. This call is only valid if this series corresponds to a 3D image. + * + * @return The voxel size. + **/ + inline float Series::GetVoxelSizeZ() + { + float result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, float*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(32); + 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. + * + * @param target The target memory buffer. + * @param format The memory layout of the voxels. + * @param lineStride The number of bytes between two lines in the target memory buffer. + * @param stackStride The number of bytes between two 2D slices in the target memory buffer. + **/ + 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); + char* error = function(pimpl_, target, format, lineStride, stackStride); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @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. This method will also update a progress indicator to monitor the loading of the image. + * + * @param target The target memory buffer. + * @param format The memory layout of the voxels. + * @param lineStride The number of bytes between two lines in the target memory buffer. + * @param stackStride The number of bytes between two 2D slices in the target memory buffer. + * @param progress A pointer to a floating-point number that is continuously updated by the download threads to reflect the percentage of completion (between 0 and 1). This value can be read from a separate thread. + **/ + 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); + char* error = function(pimpl_, target, format, lineStride, stackStride, progress); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } +} + +namespace OrthancClient +{ + /** + * @brief Create a connection to some study. + * + * Create a connection to some study. + * + * @param connection The remote instance of %Orthanc. + * @param id The %Orthanc identifier of the study. + **/ + inline Study::Study(::OrthancClient::OrthancConnection& connection, const ::std::string& id) + { + isReference_ = false; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(35); + char* error = function(&pimpl_, connection.pimpl_, id.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Destructs the object. + * + * Destructs the object. + * + **/ + inline Study::~Study() + { + if (isReference_) return; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(36); + char* error = function(pimpl_); + error = error; // Remove warning about unused variable + } + /** + * @brief Reload the series of this study. + * + * This method will reload the list of the series of this study. Pay attention to the fact that the series that have been previously returned by GetSeries() will be invalidated. + * + **/ + inline void Study::Reload() + { + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(37); + char* error = function(pimpl_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Return the number of series for this study. + * + * Return the number of series for this study. + * + * @return The number of series. + **/ + inline LAAW_UINT32 Study::GetSeriesCount() + { + LAAW_UINT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(38); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get some series of this study. + * + * This method will return an object that contains information about some series. The series are indexed by a number between 0 (inclusive) and the result of GetSeriesCount() (exclusive). + * + * @param index The index of the series of interest. + * @return The series. + **/ + inline ::OrthancClient::Series Study::GetSeries(LAAW_UINT32 index) + { + void* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void**, LAAW_UINT32); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(39); + char* error = function(pimpl_, &result_, index); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return ::OrthancClient::Series(result_); + } + /** + * @brief Get the %Orthanc identifier of this study. + * + * Get the %Orthanc identifier of this study. + * + * @return The identifier. + **/ + inline ::std::string Study::GetId() const + { + const char* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(40); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } + /** + * @brief Get the value of one of the main DICOM tags for this study. + * + * Get the value of one of the main DICOM tags for this study. + * + * @param tag The name of the tag of interest ("StudyDate", "StudyDescription", "StudyInstanceUID" or "StudyTime"). + * @param defaultValue The default value to be returned if this tag does not exist. + * @return The value of the tag. + **/ + inline ::std::string Study::GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const + { + 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); + char* error = function(pimpl_, &result_, tag.c_str(), defaultValue.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } +} + +namespace OrthancClient +{ + /** + * @brief Create a connection to some image instance. + * + * Create a connection to some image instance. + * + * @param connection The remote instance of %Orthanc. + * @param id The %Orthanc identifier of the image instance. + **/ + inline Instance::Instance(::OrthancClient::OrthancConnection& connection, const ::std::string& id) + { + isReference_ = false; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(42); + char* error = function(&pimpl_, connection.pimpl_, id.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Destructs the object. + * + * Destructs the object. + * + **/ + inline Instance::~Instance() + { + if (isReference_) return; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(43); + char* error = function(pimpl_); + error = error; // Remove warning about unused variable + } + /** + * @brief Get the %Orthanc identifier of this identifier. + * + * Get the %Orthanc identifier of this identifier. + * + * @return The identifier. + **/ + inline ::std::string Instance::GetId() const + { + const char* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(44); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } + /** + * @brief Set the extraction mode for the 2D image corresponding to this instance. + * + * Set the extraction mode for the 2D image corresponding to this instance. + * + * @param mode The extraction mode. + **/ + 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); + char* error = function(pimpl_, mode); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Get the extraction mode for the 2D image corresponding to this instance. + * + * Get the extraction mode for the 2D image corresponding to this instance. + * + * @return The extraction mode. + **/ + inline ::Orthanc::ImageExtractionMode Instance::GetImageExtractionMode() const + { + LAAW_INT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, LAAW_INT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(46); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return static_cast< ::Orthanc::ImageExtractionMode >(result_); + } + /** + * @brief Get the string value of some DICOM tag of this instance. + * + * Get the string value of some DICOM tag of this instance. + * + * @param tag The name of the tag of interest. + * @return The value of the tag. + **/ + inline ::std::string Instance::GetTagAsString(const ::std::string& tag) const + { + 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); + char* error = function(pimpl_, &result_, tag.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } + /** + * @brief Get the floating point value that is stored in some DICOM tag of this instance. + * + * Get the floating point value that is stored in some DICOM tag of this instance. + * + * @param tag The name of the tag of interest. + * @return The value of the tag. + **/ + inline float Instance::GetTagAsFloat(const ::std::string& tag) const + { + float result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, float*, const char*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(48); + char* error = function(pimpl_, &result_, tag.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get the integer value that is stored in some DICOM tag of this instance. + * + * Get the integer value that is stored in some DICOM tag of this instance. + * + * @param tag The name of the tag of interest. + * @return The value of the tag. + **/ + inline LAAW_INT32 Instance::GetTagAsInt(const ::std::string& tag) const + { + 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); + char* error = function(pimpl_, &result_, tag.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get the width of the 2D image. + * + * Get the width of the 2D image that is encoded by this DICOM instance. + * + * @return The width. + **/ + inline LAAW_UINT32 Instance::GetWidth() + { + LAAW_UINT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(50); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get the height of the 2D image. + * + * Get the height of the 2D image that is encoded by this DICOM instance. + * + * @return The height. + **/ + inline LAAW_UINT32 Instance::GetHeight() + { + LAAW_UINT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(51); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get the number of bytes between two lines of the image (pitch). + * + * Get the number of bytes between two lines of the image in the memory buffer returned by GetBuffer(). This value depends on the extraction mode for the image. + * + * @return The pitch. + **/ + inline LAAW_UINT32 Instance::GetPitch() + { + LAAW_UINT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(52); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get the format of the pixels of the 2D image. + * + * Return the memory layout that is used for the 2D image that is encoded by this DICOM instance. This value depends on the extraction mode for the image. + * + * @return The pixel format. + **/ + inline ::Orthanc::PixelFormat Instance::GetPixelFormat() + { + LAAW_INT32 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_INT32*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(53); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return static_cast< ::Orthanc::PixelFormat >(result_); + } + /** + * @brief Access the memory buffer in which the raw pixels of the 2D image are stored. + * + * Access the memory buffer in which the raw pixels of the 2D image are stored. + * + * @return A pointer to the memory buffer. + **/ + inline const void* Instance::GetBuffer() + { + const void* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(54); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return reinterpret_cast< const void* >(result_); + } + /** + * @brief Access the memory buffer in which the raw pixels of some line of the 2D image are stored. + * + * Access the memory buffer in which the raw pixels of some line of the 2D image are stored. + * + * @param y The line of interest. + * @return A pointer to the memory buffer. + **/ + inline const void* Instance::GetBuffer(LAAW_UINT32 y) + { + 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); + char* error = function(pimpl_, &result_, y); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return reinterpret_cast< const void* >(result_); + } + /** + * @brief Get the size of the DICOM file corresponding to this instance. + * + * Get the size of the DICOM file corresponding to this instance. + * + * @return The file size. + **/ + inline LAAW_UINT64 Instance::GetDicomSize() + { + LAAW_UINT64 result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT64*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(56); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** + * @brief Get a pointer to the content of the DICOM file corresponding to this instance. + * + * Get a pointer to the content of the DICOM file corresponding to this instance. + * + * @return The DICOM file. + **/ + inline const void* Instance::GetDicom() + { + const void* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(57); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return reinterpret_cast< const void* >(result_); + } + /** + * @brief Discard the downloaded 2D image, so as to make room in memory. + * + * Discard the downloaded 2D image, so as to make room in memory. + * + **/ + inline void Instance::DiscardImage() + { + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(58); + char* error = function(pimpl_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Discard the downloaded DICOM file, so as to make room in memory. + * + * Discard the downloaded DICOM file, so as to make room in memory. + * + **/ + inline void Instance::DiscardDicom() + { + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(59); + char* error = function(pimpl_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Load a raw tag from the DICOM file. + * + * Load a raw tag from the DICOM file. + * + * @param path The path to the tag of interest (e.g. "0020-000d"). + **/ + 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); + char* error = function(pimpl_, path.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Return the value of the raw tag that was loaded by LoadContent. + * + * Return the value of the raw tag that was loaded by LoadContent. + * + * @return The tag value. + **/ + inline ::std::string Instance::GetLoadedTagContent() const + { + const char* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(61); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,72 @@ +LIBRARY some.dll +EXPORTS + _LAAW_EXTERNC_557aee7b61817292a0f31269d3c35db7@8 = LAAW_EXTERNC_557aee7b61817292a0f31269d3c35db7@8 + _LAAW_EXTERNC_0b8dff0ce67f10954a49b059e348837e@8 = LAAW_EXTERNC_0b8dff0ce67f10954a49b059e348837e@8 + _LAAW_EXTERNC_e05097c153f676e5a5ee54dcfc78256f@4 = LAAW_EXTERNC_e05097c153f676e5a5ee54dcfc78256f@4 + _LAAW_EXTERNC_e840242bf58d17d3c1d722da09ce88e0@8 = LAAW_EXTERNC_e840242bf58d17d3c1d722da09ce88e0@8 + _LAAW_EXTERNC_c9af31433001b5dfc012a552dc6d0050@8 = LAAW_EXTERNC_c9af31433001b5dfc012a552dc6d0050@8 + _LAAW_EXTERNC_3fba4d6b818180a44cd1cae6046334dc@12 = LAAW_EXTERNC_3fba4d6b818180a44cd1cae6046334dc@12 + _LAAW_EXTERNC_aeb20dc75b9246188db857317e5e0ce7@8 = LAAW_EXTERNC_aeb20dc75b9246188db857317e5e0ce7@8 + _LAAW_EXTERNC_62689803d9871e4d9c51a648640b320b@8 = LAAW_EXTERNC_62689803d9871e4d9c51a648640b320b@8 + _LAAW_EXTERNC_2fb64c9e5a67eccd413b0e913469a421@16 = LAAW_EXTERNC_2fb64c9e5a67eccd413b0e913469a421@16 + _LAAW_EXTERNC_1f1acb322ea4d0aad65172824607673c@8 = LAAW_EXTERNC_1f1acb322ea4d0aad65172824607673c@8 + _LAAW_EXTERNC_f3fd272e4636f6a531aabb72ee01cd5b@16 = LAAW_EXTERNC_f3fd272e4636f6a531aabb72ee01cd5b@16 + _LAAW_EXTERNC_12d3de0a96e9efb11136a9811bb9ed38@4 = LAAW_EXTERNC_12d3de0a96e9efb11136a9811bb9ed38@4 + _LAAW_EXTERNC_f756172daf04516eec3a566adabb4335@4 = LAAW_EXTERNC_f756172daf04516eec3a566adabb4335@4 + _LAAW_EXTERNC_ddb68763ec902a97d579666a73a20118@8 = LAAW_EXTERNC_ddb68763ec902a97d579666a73a20118@8 + _LAAW_EXTERNC_fba3c68b4be7558dbc65f7ce1ab57d63@12 = LAAW_EXTERNC_fba3c68b4be7558dbc65f7ce1ab57d63@12 + _LAAW_EXTERNC_b4ca99d958f843493e58d1ef967340e1@8 = LAAW_EXTERNC_b4ca99d958f843493e58d1ef967340e1@8 + _LAAW_EXTERNC_78d5cc76d282437b6f93ec3b82c35701@16 = LAAW_EXTERNC_78d5cc76d282437b6f93ec3b82c35701@16 + _LAAW_EXTERNC_6cf0d7268667f9b0aa4511bacf184919@12 = LAAW_EXTERNC_6cf0d7268667f9b0aa4511bacf184919@12 + _LAAW_EXTERNC_7d81cd502ee27e859735d0ea7112b5a1@4 = LAAW_EXTERNC_7d81cd502ee27e859735d0ea7112b5a1@4 + _LAAW_EXTERNC_48a2a1a9d68c047e22bfba23014643d2@4 = LAAW_EXTERNC_48a2a1a9d68c047e22bfba23014643d2@4 + _LAAW_EXTERNC_852bf8296ca21c5fde5ec565cc10721d@8 = LAAW_EXTERNC_852bf8296ca21c5fde5ec565cc10721d@8 + _LAAW_EXTERNC_efd04574e0779faa83df1f2d8f9888db@12 = LAAW_EXTERNC_efd04574e0779faa83df1f2d8f9888db@12 + _LAAW_EXTERNC_736247ff5e8036dac38163da6f666ed5@8 = LAAW_EXTERNC_736247ff5e8036dac38163da6f666ed5@8 + _LAAW_EXTERNC_d82d2598a7a73f4b6fcc0c09c25b08ca@8 = LAAW_EXTERNC_d82d2598a7a73f4b6fcc0c09c25b08ca@8 + _LAAW_EXTERNC_88134b978f9acb2aecdadf54aeab3c64@16 = LAAW_EXTERNC_88134b978f9acb2aecdadf54aeab3c64@16 + _LAAW_EXTERNC_152cb1b704c053d24b0dab7461ba6ea3@8 = LAAW_EXTERNC_152cb1b704c053d24b0dab7461ba6ea3@8 + _LAAW_EXTERNC_eee03f337ec81d9f1783cd41e5238757@8 = LAAW_EXTERNC_eee03f337ec81d9f1783cd41e5238757@8 + _LAAW_EXTERNC_006f08237bd7611636fc721baebfb4c5@8 = LAAW_EXTERNC_006f08237bd7611636fc721baebfb4c5@8 + _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_4dcc7a0fd025efba251ac6e9b701c2c5@28 = LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5@28 + _LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c@32 = LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c@32 + _LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342@12 = LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342@12 + _LAAW_EXTERNC_7c97f17063a357d38c5fab1136ad12a0@4 = LAAW_EXTERNC_7c97f17063a357d38c5fab1136ad12a0@4 + _LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7@4 = LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7@4 + _LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321@8 = LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321@8 + _LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05@12 = LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05@12 + _LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7@8 = LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7@8 + _LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654@16 = LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654@16 + _LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678@12 = LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678@12 + _LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376@4 = LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376@4 + _LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb@8 = LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb@8 + _LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146@8 = LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146@8 + _LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda@8 = LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda@8 + _LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484@12 = LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484@12 + _LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb@12 = LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb@12 + _LAAW_EXTERNC_1729a067d902771517388eedd7346b23@12 = LAAW_EXTERNC_1729a067d902771517388eedd7346b23@12 + _LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745@8 = LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745@8 + _LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0@8 = LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0@8 + _LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8@8 = LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8@8 + _LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c@8 = LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c@8 + _LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b@8 = LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b@8 + _LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef@12 = LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef@12 + _LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91@8 = LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91@8 + _LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e@8 = LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e@8 + _LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a@4 = LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a@4 + _LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c@4 = LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c@4 + _LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd@8 = LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd@8 + _LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb@8 = LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb@8 + _LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d@12 = LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d@12 + _LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207@4 = LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207@4 + _LAAW_EXTERNC_GetDescription@0 = LAAW_EXTERNC_GetDescription@0 + _LAAW_EXTERNC_GetCompany@0 = LAAW_EXTERNC_GetCompany@0 + _LAAW_EXTERNC_GetProduct@0 = LAAW_EXTERNC_GetProduct@0 + _LAAW_EXTERNC_GetCopyright@0 = LAAW_EXTERNC_GetCopyright@0 + _LAAW_EXTERNC_GetVersion@0 = LAAW_EXTERNC_GetVersion@0 + _LAAW_EXTERNC_GetFileVersion@0 = LAAW_EXTERNC_GetFileVersion@0 + _LAAW_EXTERNC_GetFullVersion@0 = LAAW_EXTERNC_GetFullVersion@0 + _LAAW_EXTERNC_FreeString@4 = LAAW_EXTERNC_FreeString@4
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,30 @@ +#include <winver.h> + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,7,0,4 + PRODUCTVERSION 0,7,0,0 + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "Comments", "Release 0.7.4" + VALUE "CompanyName", "CHU of Liege" + VALUE "FileDescription", "Native client to the REST API of Orthanc" + VALUE "FileVersion", "0.7.0.4" + 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 "OriginalFilename", "OrthancClient_Windows32.dll" + VALUE "ProductName", "OrthancClient" + VALUE "ProductVersion", "0.7" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 // U.S. English + END + END
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,72 @@ +LIBRARY some.dll +EXPORTS + LAAW_EXTERNC_557aee7b61817292a0f31269d3c35db7 + LAAW_EXTERNC_0b8dff0ce67f10954a49b059e348837e + LAAW_EXTERNC_e05097c153f676e5a5ee54dcfc78256f + LAAW_EXTERNC_e840242bf58d17d3c1d722da09ce88e0 + LAAW_EXTERNC_c9af31433001b5dfc012a552dc6d0050 + LAAW_EXTERNC_3fba4d6b818180a44cd1cae6046334dc + LAAW_EXTERNC_aeb20dc75b9246188db857317e5e0ce7 + LAAW_EXTERNC_62689803d9871e4d9c51a648640b320b + LAAW_EXTERNC_2fb64c9e5a67eccd413b0e913469a421 + LAAW_EXTERNC_1f1acb322ea4d0aad65172824607673c + LAAW_EXTERNC_f3fd272e4636f6a531aabb72ee01cd5b + LAAW_EXTERNC_12d3de0a96e9efb11136a9811bb9ed38 + LAAW_EXTERNC_f756172daf04516eec3a566adabb4335 + LAAW_EXTERNC_ddb68763ec902a97d579666a73a20118 + LAAW_EXTERNC_fba3c68b4be7558dbc65f7ce1ab57d63 + LAAW_EXTERNC_b4ca99d958f843493e58d1ef967340e1 + LAAW_EXTERNC_78d5cc76d282437b6f93ec3b82c35701 + LAAW_EXTERNC_6cf0d7268667f9b0aa4511bacf184919 + LAAW_EXTERNC_7d81cd502ee27e859735d0ea7112b5a1 + LAAW_EXTERNC_48a2a1a9d68c047e22bfba23014643d2 + LAAW_EXTERNC_852bf8296ca21c5fde5ec565cc10721d + LAAW_EXTERNC_efd04574e0779faa83df1f2d8f9888db + LAAW_EXTERNC_736247ff5e8036dac38163da6f666ed5 + LAAW_EXTERNC_d82d2598a7a73f4b6fcc0c09c25b08ca + LAAW_EXTERNC_88134b978f9acb2aecdadf54aeab3c64 + LAAW_EXTERNC_152cb1b704c053d24b0dab7461ba6ea3 + LAAW_EXTERNC_eee03f337ec81d9f1783cd41e5238757 + LAAW_EXTERNC_006f08237bd7611636fc721baebfb4c5 + LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0 + LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab + LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d + LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5 + LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c + LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342 + LAAW_EXTERNC_7c97f17063a357d38c5fab1136ad12a0 + LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7 + LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321 + LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05 + LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7 + LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654 + LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678 + LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376 + LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb + LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146 + LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda + LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484 + LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb + LAAW_EXTERNC_1729a067d902771517388eedd7346b23 + LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745 + LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0 + LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8 + LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c + LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b + LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef + LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91 + LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e + LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a + LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c + LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd + LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb + LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d + LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207 + LAAW_EXTERNC_GetDescription + LAAW_EXTERNC_GetCompany + LAAW_EXTERNC_GetProduct + LAAW_EXTERNC_GetCopyright + LAAW_EXTERNC_GetVersion + LAAW_EXTERNC_GetFileVersion + LAAW_EXTERNC_GetFullVersion + LAAW_EXTERNC_FreeString
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,30 @@ +#include <winver.h> + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,7,0,4 + PRODUCTVERSION 0,7,0,0 + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "Comments", "Release 0.7.4" + VALUE "CompanyName", "CHU of Liege" + VALUE "FileDescription", "Native client to the REST API of Orthanc" + VALUE "FileVersion", "0.7.0.4" + 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 "OriginalFilename", "OrthancClient_Windows64.dll" + VALUE "ProductName", "OrthancClient" + VALUE "ProductVersion", "0.7" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 // U.S. English + END + END
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/ConfigurationCpp.json Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,5 @@ +{ + "InternalsNamespace" : [ "OrthancClient", "Internals" ], + "PublicNamespace" : [ "OrthancClient" ], + "ExceptionClassName" : "OrthancClientException" +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/Generate.sh Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +mkdir -p AUTOGENERATED +LAAW_ROOT=~/Subversion/Jomago/Src/Labo/Laaw + +${LAAW_ROOT}/Parser/Build/LaawParser.exe AUTOGENERATED/CodeModelRaw.json ../OrthancConnection.h -I`pwd`/../../s/jsoncpp-src-0.6.0-rc2/include -fms-extensions +python ${LAAW_ROOT}/Generators/CodeModelPostProcessing.py AUTOGENERATED/CodeModel.json AUTOGENERATED/CodeModelRaw.json Product.json +python ${LAAW_ROOT}/Generators/GenerateWrapperCpp.py AUTOGENERATED/OrthancCppClient.h AUTOGENERATED/CodeModel.json Product.json ConfigurationCpp.json +python ${LAAW_ROOT}/Generators/GenerateExternC.py AUTOGENERATED/ExternC.cpp AUTOGENERATED/CodeModel.json Product.json +python ${LAAW_ROOT}/Generators/GenerateWindows32Def.py AUTOGENERATED/Windows32.def AUTOGENERATED/CodeModel.json +python ${LAAW_ROOT}/Generators/GenerateWindows64Def.py AUTOGENERATED/Windows64.def AUTOGENERATED/CodeModel.json +python ${LAAW_ROOT}/Generators/ApplyProductSubstitutions.py AUTOGENERATED/Windows32.rc ${LAAW_ROOT}/Resources/DllResources.rc.mustache Product.json PLATFORM_SUFFIX "_Windows32" +python ${LAAW_ROOT}/Generators/ApplyProductSubstitutions.py AUTOGENERATED/Windows64.rc ${LAAW_ROOT}/Resources/DllResources.rc.mustache Product.json PLATFORM_SUFFIX "_Windows64"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/Laaw/VersionScript.map Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,9 @@ +# This is a version-script + +{ +global: + LAAW_EXTERNC_*; + +local: + *; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/Laaw/laaw/laaw-exports.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,85 @@ +/** + * Laaw - Lightweight, Automated API Wrapper + * Copyright (C) 2010-2013 Jomago - Alain Mazy, Benjamin Golinvaux, + * Sebastien Jodogne + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +/******************************************************************** + ** Windows target + ********************************************************************/ + +#if defined _WIN32 + +#include <windows.h> + +#if defined(__GNUC__) +// This is Mingw +#define LAAW_EXPORT_DLL_API // The exports are handled by the .DEF file +#else +// This is MSVC +#define LAAW_EXPORT_DLL_API __declspec(dllexport) +#endif + +#ifdef _M_X64 +// 64 bits target +#define LAAW_CALL_CONVENTION +#else +// 32 bits target +#define LAAW_CALL_CONVENTION __stdcall // Use the StdCall in Windows32 (for VB6) +#endif + + +/******************************************************************** + ** Linux target + ********************************************************************/ + +#elif defined(__linux) + +// Try the gcc visibility support +// http://gcc.gnu.org/wiki/Visibility +#if ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) +#define LAAW_EXPORT_DLL_API __attribute__ ((visibility("default"))) +#define LAAW_CALL_CONVENTION +#else +#error No support for visibility in your version of GCC +#endif + + +/******************************************************************** + ** Max OS X target + ********************************************************************/ + +#else + +#define LAAW_EXPORT_DLL_API __attribute__ ((visibility("default"))) +#define LAAW_CALL_CONVENTION + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/Laaw/laaw/laaw.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,89 @@ +/** + * Laaw - Lightweight, Automated API Wrapper + * Copyright (C) 2010-2013 Jomago - Alain Mazy, Benjamin Golinvaux, + * Sebastien Jodogne + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "laaw-exports.h" +#include <stddef.h> +#include <string> + +#if (LAAW_PARSING == 1) + +#define LAAW_API __attribute__((deprecated(""))) +#define LAAW_API_INTERNAL __attribute__((deprecated(""))) +#define LAAW_API_OVERLOAD(name) __attribute__((deprecated(""))) +#define LAAW_API_PROPERTY __attribute__((deprecated(""))) +#define LAAW_API_STATIC_CLASS __attribute__((deprecated(""))) +#define LAAW_API_CUSTOM(name, value) __attribute__((deprecated(""))) + +#else + +#define LAAW_API +#define LAAW_API_INTERNAL +#define LAAW_API_OVERLOAD(name) +#define LAAW_API_PROPERTY +#define LAAW_API_STATIC_CLASS +#define LAAW_API_CUSTOM(name, value) + +#endif + + +namespace Laaw +{ + /** + * This is the base class from which all the public exceptions in + * the SDK should derive. + **/ + class LaawException + { + private: + std::string what_; + + public: + LaawException() + { + } + + LaawException(const std::string& what) : what_(what) + { + } + + LaawException(const char* what) : what_(what) + { + } + + virtual const char* What() const + { + return what_.c_str(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/Product.json Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +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.4" +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/SharedLibrary.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,56 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + + +#include "../../Core/HttpClient.h" +#include "../OrthancConnection.h" + + +class SharedLibrarySingleton +{ +public: + SharedLibrarySingleton() + { + Orthanc::HttpClient::GlobalInitialize(); + } + + ~SharedLibrarySingleton() + { + Orthanc::HttpClient::GlobalFinalize(); + } +}; + + +static SharedLibrarySingleton singleton_; + + +#include "AUTOGENERATED/ExternC.cpp"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Study.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,79 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "Study.h" + +#include "OrthancConnection.h" + +namespace OrthancClient +{ + void Study::ReadStudy() + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/studies/" + id_); + + Json::Value v; + if (!client.Apply(study_)) + { + throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* Study::GetFillerItem(size_t index) + { + Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); + std::string id = study_["Series"][tmp].asString(); + return new Series(connection_, id.c_str()); + } + + Study::Study(const OrthancConnection& connection, + const char* id) : + connection_(connection), + id_(id), + series_(*this) + { + series_.SetThreadCount(connection.GetThreadCount()); + ReadStudy(); + } + + const char* Study::GetMainDicomTag(const char* tag, const char* defaultValue) const + { + if (study_["MainDicomTags"].isMember(tag)) + { + return study_["MainDicomTags"][tag].asCString(); + } + else + { + return defaultValue; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Study.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,119 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Series.h" + +namespace OrthancClient +{ + /** + * {summary}{Connection to a study stored in %Orthanc.} + * {description}{This class encapsulates a connection to a study + * from a remote instance of %Orthanc.} + **/ + class LAAW_API Study : + public Orthanc::IDynamicObject, + private Orthanc::ArrayFilledByThreads::IFiller + { + private: + const OrthancConnection& connection_; + std::string id_; + Json::Value study_; + Orthanc::ArrayFilledByThreads series_; + + void ReadStudy(); + + virtual size_t GetFillerSize() + { + return study_["Series"].size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + public: + /** + * {summary}{Create a connection to some study.} + * {param}{connection The remote instance of %Orthanc.} + * {param}{id The %Orthanc identifier of the study.} + **/ + Study(const OrthancConnection& connection, + const char* id); + + /** + * {summary}{Reload the series of this study.} + * {description}{This method will reload the list of the series of this study. Pay attention to the fact that the series that have been previously returned by GetSeries() will be invalidated.} + **/ + void Reload() + { + series_.Reload(); + } + + /** + * {summary}{Return the number of series for this study.} + * {returns}{The number of series.} + **/ + uint32_t GetSeriesCount() + { + return series_.GetSize(); + } + + /** + * {summary}{Get some series of this study.} + * {description}{This method will return an object that contains information about some series. The series are indexed by a number between 0 (inclusive) and the result of GetSeriesCount() (exclusive).} + * {param}{index The index of the series of interest.} + * {returns}{The series.} + **/ + Series& GetSeries(uint32_t index) + { + return dynamic_cast<Series&>(series_.GetItem(index)); + } + + /** + * {summary}{Get the %Orthanc identifier of this study.} + * {returns}{The identifier.} + **/ + const char* GetId() const + { + return id_.c_str(); + } + + /** + * {summary}{Get the value of one of the main DICOM tags for this study.} + * {param}{tag The name of the tag of interest ("StudyDate", "StudyDescription", "StudyInstanceUID" or "StudyTime").} + * {param}{defaultValue The default value to be returned if this tag does not exist.} + * {returns}{The value of the tag.} + **/ + const char* GetMainDicomTag(const char* tag, + const char* defaultValue) const; + }; +}
--- a/OrthancCppClient/main.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#include "HttpClient.h" - -#include <iostream> - -int main() -{ - // Prepare a simple call to a Web service - Orthanc::HttpClient c; - c.SetUrl("http://nominatim.openstreetmap.org/search?format=json&q=chu+liege+belgium"); - - // Do the request and store the result in a JSON structure - Json::Value result; - c.Apply(result); - - // Display the JSON answer - std::cout << result << std::endl; - - return 0; -}
--- a/OrthancExplorer/explorer.html Fri May 03 12:23:02 2013 +0200 +++ b/OrthancExplorer/explorer.html Tue Apr 22 16:47:21 2014 +0200 @@ -88,12 +88,24 @@ <option value="on">Protected</option> </select> </div> - <!--a href="#find-patients" data-role="button" data-icon="search">Go to patient finder</a--> - <a href="#" data-role="button" data-icon="delete" id="patient-delete">Delete this patient</a> - <a href="#" data-role="button" data-icon="forward" id="patient-store">Store in another DICOM modality</a> - <a href="#" data-role="button" data-icon="gear" id="patient-archive">Download ZIP</a> - <a href="#" data-role="button" data-icon="star" id="patient-anonymize">Anonymize</a> </p> + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Interact</li> + <li data-icon="delete"><a href="#" id="patient-delete">Delete this patient</a></li> + <li data-icon="forward"><a href="#" id="patient-store">Send to remote modality</a></li> + <li data-icon="star"><a href="#" id="patient-anonymize">Anonymize</a></li> + </ul> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Access</li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="patient-anonymized-from">Before anonymization</a> + </li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="patient-modified-from">Before modification</a> + </li> + <li data-icon="gear"><a href="#" id="patient-archive">Download ZIP</a></li> + </ul> </div> </div> <div class="ui-block-b" style="width:70%"> @@ -122,12 +134,24 @@ <div style="padding-right:10px"> <ul data-role="listview" data-inset="true" data-theme="a" id="study-info"> </ul> - <p> - <a href="#" data-role="button" data-icon="delete" id="study-delete">Delete this study</a> - <a href="#" data-role="button" data-icon="forward" id="study-store">Store in another DICOM modality</a> - <a href="#" data-role="button" data-icon="gear" id="study-archive">Download ZIP</a> - <a href="#" data-role="button" data-icon="star" id="study-anonymize">Anonymize</a> - </p> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Interact</li> + <li data-icon="delete"><a href="#" id="study-delete">Delete this study</a></li> + <li data-icon="forward"><a href="#" id="study-store">Send to DICOM modality</a></li> + <li data-icon="star"><a href="#" id="study-anonymize">Anonymize</a></li> + </ul> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Access</li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="study-anonymized-from">Before anonymization</a> + </li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="study-modified-from">Before modification</a> + </li> + <li data-icon="gear"><a href="#" id="study-archive">Download ZIP</a></li> + </ul> </div> </div> <div class="ui-block-b" style="width:70%"> @@ -156,15 +180,27 @@ <div class="ui-grid-a"> <div class="ui-block-a" style="width:30%"> <div style="padding-right:10px"> - <ul data-role="listview" data-inset="true" data-theme="a" id="series-info"> + <ul data-role="listview" data-inset="true" data-theme="a" id="series-info"> + </ul> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Interact</li> + <li data-icon="delete"><a href="#" id="series-delete">Delete this series</a></li> + <li data-icon="forward"><a href="#" id="series-store">Send to DICOM modality</a></li> + <li data-icon="star"><a href="#" id="series-anonymize">Anonymize</a></li> </ul> - <p> - <a href="#" data-role="button" data-icon="delete" id="series-delete">Delete this series</a> - <a href="#" data-role="button" data-icon="search" id="series-preview">Preview this series</a> - <a href="#" data-role="button" data-icon="forward" id="series-store">Store in another DICOM modality</a> - <a href="#" data-role="button" data-icon="gear" id="series-archive">Download ZIP</a> - <a href="#" data-role="button" data-icon="star" id="series-anonymize">Anonymize</a> - </p> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Access</li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="series-anonymized-from">Before anonymization</a> + </li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="series-modified-from">Before modification</a> + </li> + <li data-icon="search"><a href="#" id="series-preview">Preview this series</a></li> + <li data-icon="gear"><a href="#" id="series-archive">Download ZIP</a></li> + </ul> </div> </div> <div class="ui-block-b" style="width:70%"> @@ -195,13 +231,25 @@ <div style="padding-right:10px"> <ul data-role="listview" data-inset="true" data-theme="a" id="instance-info"> </ul> - <p> - <a href="#" data-role="button" data-icon="delete" id="instance-delete">Delete this instance</a> - <a href="#" data-role="button" data-icon="arrow-d" id="instance-download-dicom">Download the DICOM file</a> - <a href="#" data-role="button" data-icon="arrow-d" id="instance-download-json">Download the JSON file</a> - <a href="#" data-role="button" data-icon="search" id="instance-preview">Preview the instance</a> - <a href="#" data-role="button" data-icon="forward" id="instance-store">Store in another DICOM modality</a> - </p> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Interact</li> + <li data-icon="delete"><a href="#" id="instance-delete">Delete this instance</a></li> + <li data-icon="forward"><a href="#" id="instance-store">Send to DICOM modality</a></li> + </ul> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Access</li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="instance-anonymized-from">Before anonymization</a> + </li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="instance-modified-from">Before modification</a> + </li> + <li data-icon="arrow-d"><a href="#" id="instance-download-dicom">Download the DICOM file</a></li> + <li data-icon="arrow-d"><a href="#" id="instance-download-json">Download the JSON file</a></li> + <li data-icon="search"><a href="#" id="instance-preview">Preview the instance</a></li> + </ul> </div> </div> <div class="ui-block-b" style="width:70%"> @@ -220,7 +268,12 @@ </div> </div> - <div id="loading" style="display:none;" class="ui-body-c"> + <div id="peer-store" style="display:none;" class="ui-body-c"> + <p align="center"><b>Sending to Orthanc peer...</b></p> + <p><img src="libs/images/ajax-loader2.gif" alt="" /></p> + </div> + + <div id="dicom-store" style="display:none;" class="ui-body-c"> <p align="center"><b>Sending to DICOM modality...</b></p> <p><img src="libs/images/ajax-loader2.gif" alt="" /></p> </div>
--- a/OrthancExplorer/explorer.js Fri May 03 12:23:02 2013 +0200 +++ b/OrthancExplorer/explorer.js Tue Apr 22 16:47:21 2014 +0200 @@ -12,6 +12,11 @@ //$.mobile.page.prototype.options.addBackBtn = true; //$.mobile.defaultPageTransition = 'slide'; + +var currentPage = ''; +var currentUuid = ''; + + // http://stackoverflow.com/a/4673436 String.prototype.format = function() { var args = arguments; @@ -356,7 +361,25 @@ -$('#patient').live('pagebeforeshow', function() { +function SetupAnonymizedOrModifiedFrom(buttonSelector, resource, resourceType, field) +{ + if (field in resource) + { + $(buttonSelector).closest('li').show(); + $(buttonSelector).click(function(e) { + window.location.assign('explorer.html#' + resourceType + '?uuid=' + resource[field]); + }); + } + else + { + $(buttonSelector).closest('li').hide(); + } +} + + + +function RefreshPatient() +{ if ($.mobile.pageData) { GetSingleResource('patients', $.mobile.pageData.uuid, function(patient) { GetMultipleResources('studies', patient.Studies, function(studies) { @@ -381,6 +404,9 @@ target.append(FormatStudy(studies[i], '#study?uuid=' + studies[i].ID)); } + SetupAnonymizedOrModifiedFrom('#patient-anonymized-from', patient, 'patient', 'AnonymizedFrom'); + SetupAnonymizedOrModifiedFrom('#patient-modified-from', patient, 'patient', 'ModifiedFrom'); + target.listview('refresh'); // Check whether this patient is protected @@ -395,13 +421,17 @@ $('#protection').val(v).slider('refresh'); } }); + + currentPage = 'patient'; + currentUuid = $.mobile.pageData.uuid; }); }); } -}); +} -$('#study').live('pagebeforeshow', function() { +function RefreshStudy() +{ if ($.mobile.pageData) { GetSingleResource('studies', $.mobile.pageData.uuid, function(study) { GetSingleResource('patients', study.ParentPatient, function(patient) { @@ -417,6 +447,9 @@ .append(FormatStudy(study)) .listview('refresh'); + SetupAnonymizedOrModifiedFrom('#study-anonymized-from', study, 'study', 'AnonymizedFrom'); + SetupAnonymizedOrModifiedFrom('#study-modified-from', study, 'study', 'ModifiedFrom'); + var target = $('#list-series'); $('li', target).remove(); for (var i = 0; i < series.length; i++) { @@ -428,14 +461,18 @@ target.append(FormatSeries(series[i], '#series?uuid=' + series[i].ID)); } target.listview('refresh'); + + currentPage = 'study'; + currentUuid = $.mobile.pageData.uuid; }); }); }); } -}); +} -$('#series').live('pagebeforeshow', function() { +function RefreshSeries() +{ if ($.mobile.pageData) { GetSingleResource('series', $.mobile.pageData.uuid, function(series) { GetSingleResource('studies', series.ParentStudy, function(study) { @@ -456,18 +493,24 @@ .append(FormatSeries(series)) .listview('refresh'); + SetupAnonymizedOrModifiedFrom('#series-anonymized-from', series, 'series', 'AnonymizedFrom'); + SetupAnonymizedOrModifiedFrom('#series-modified-from', series, 'series', 'ModifiedFrom'); + var target = $('#list-instances'); $('li', target).remove(); for (var i = 0; i < instances.length; i++) { target.append(FormatInstance(instances[i], '#instance?uuid=' + instances[i].ID)); } target.listview('refresh'); + + currentPage = 'series'; + currentUuid = $.mobile.pageData.uuid; }); }); }); }); } -}); +} @@ -522,7 +565,8 @@ } -$('#instance').live('pagebeforeshow', function() { +function RefreshInstance() +{ if ($.mobile.pageData) { GetSingleResource('instances', $.mobile.pageData.uuid, function(instance) { GetSingleResource('series', instance.ParentSeries, function(series) { @@ -554,15 +598,52 @@ } }); + SetupAnonymizedOrModifiedFrom('#instance-anonymized-from', instance, 'instance', 'AnonymizedFrom'); + SetupAnonymizedOrModifiedFrom('#instance-modified-from', instance, 'instance', 'ModifiedFrom'); + + currentPage = 'instance'; + currentUuid = $.mobile.pageData.uuid; }); }); }); }); } +} + +$(document).live('pagebeforehide', function() { + currentPage = ''; + currentUuid = ''; }); +$('#patient').live('pagebeforeshow', RefreshPatient); +$('#study').live('pagebeforeshow', RefreshStudy); +$('#series').live('pagebeforeshow', RefreshSeries); +$('#instance').live('pagebeforeshow', RefreshInstance); + +$(function() { + $(window).hashchange(function(e, data) { + // This fixes the navigation with the back button and with the anonymization + if ('uuid' in $.mobile.pageData && + currentPage == $.mobile.pageData.active && + currentUuid != $.mobile.pageData.uuid) { + if (currentPage == 'patient') + RefreshPatient(); + else if (currentPage == 'study') + RefreshStudy(); + else if (currentPage == 'series') + RefreshSeries(); + else if (currentPage == 'instance') + RefreshInstance(); + } + }); +}); + + + + + function DeleteResource(path) { $.ajax({ @@ -708,6 +789,13 @@ function ChooseDicomModality(callback) { + var clickedModality = ''; + var clickedPeer = ''; + var items = $('<ul>') + .attr('data-divider-theme', 'd') + .attr('data-role', 'listview'); + + // Retrieve the list of the known DICOM modalities $.ajax({ url: '../modalities', type: 'GET', @@ -715,37 +803,66 @@ async: false, cache: false, success: function(modalities) { - var clickedModality = ''; - var items = $('<ul>') - .attr('data-role', 'listview'); + if (modalities.length > 0) + { + items.append('<li data-role="list-divider">DICOM modalities</li>'); - for (var i = 0; i < modalities.length; i++) { - var modality = modalities[i]; - var item = $('<li>') - .html('<a href="#" rel="close">' + modality + '</a>') - .attr('modality', modality) - .click(function() { - clickedModality = $(this).attr('modality'); - }); - items.append(item); + for (var i = 0; i < modalities.length; i++) { + var name = modalities[i]; + var item = $('<li>') + .html('<a href="#" rel="close">' + name + '</a>') + .attr('name', name) + .click(function() { + clickedModality = $(this).attr('name'); + }); + items.append(item); + } } - $('#dialog').simpledialog2({ - mode: 'blank', - animate: false, - headerText: 'DICOM modality', - headerClose: true, - width: '100%', - blankContent: items, - callbackClose: function() { - var timer; - function WaitForDialogToClose() { - if (!$('#dialog').is(':visible')) { - clearInterval(timer); - callback(clickedModality); + // Retrieve the list of the known Orthanc peers + $.ajax({ + url: '../peers', + type: 'GET', + dataType: 'json', + async: false, + cache: false, + success: function(peers) { + if (peers.length > 0) + { + items.append('<li data-role="list-divider">Orthanc peers</li>'); + + for (var i = 0; i < peers.length; i++) { + var name = peers[i]; + var item = $('<li>') + .html('<a href="#" rel="close">' + name + '</a>') + .attr('name', name) + .click(function() { + clickedPeer = $(this).attr('name'); + }); + items.append(item); } } - timer = setInterval(WaitForDialogToClose, 100); + + // Launch the dialog + $('#dialog').simpledialog2({ + mode: 'blank', + animate: false, + headerText: 'Choose target', + headerClose: true, + forceInput: false, + width: '100%', + blankContent: items, + callbackClose: function() { + var timer; + function WaitForDialogToClose() { + if (!$('#dialog').is(':visible')) { + clearInterval(timer); + callback(clickedModality, clickedPeer); + } + } + timer = setInterval(WaitForDialogToClose, 100); + } + }); } }); } @@ -754,16 +871,31 @@ $('#instance-store,#series-store,#study-store,#patient-store').live('click', function(e) { - ChooseDicomModality(function(modality) { - if (modality != '') { + ChooseDicomModality(function(modality, peer) { + var url; + var loading; + + if (modality != '') + { + url = '../modalities/' + modality + '/store'; + loading = '#dicom-store'; + } + + if (peer != '') + { + url = '../peers/' + peer + '/store'; + loading = '#peer-store'; + } + + if (url != '') { $.ajax({ - url: '../modalities/' + modality + '/store', + url: url, type: 'POST', dataType: 'text', data: $.mobile.pageData.uuid, async: true, // Necessary to block UI beforeSend: function() { - $.blockUI({ message: $('#loading') }); + $.blockUI({ message: $(loading) }); }, complete: function(s) { $.unblockUI(); @@ -771,10 +903,9 @@ success: function(s) { }, error: function() { - alert('Error during C-Store'); + alert('Error during store'); } - }); - + }); } }); }); @@ -840,7 +971,7 @@ //$.mobile.changePage('explorer.html#patient?uuid=' + s.PatientID); window.location.assign('explorer.html#patient?uuid=' + s.PatientID); - window.location.reload(); + //window.location.reload(); } }); },
--- a/OrthancExplorer/libs/jqm.page.params.js Fri May 03 12:23:02 2013 +0200 +++ b/OrthancExplorer/libs/jqm.page.params.js Tue Apr 22 16:47:21 2014 +0200 @@ -105,7 +105,9 @@ // http://stackoverflow.com/a/8295488 $(document).bind("pagebeforechange", function( event, data ) { - $.mobile.pageData = (data && data.options && data.options.pageData) + $.mobile.pageData = (data && data.options && data.options.pageData) ? data.options.pageData : {}; + + $.mobile.pageData.active = data.toPage[0].id; });
--- a/OrthancExplorer/libs/jquery.mobile.simpledialog2.js Fri May 03 12:23:02 2013 +0200 +++ b/OrthancExplorer/libs/jquery.mobile.simpledialog2.js Tue Apr 22 16:47:21 2014 +0200 @@ -3,8 +3,6 @@ * Copyright (c) JTSage * CC 3.0 Attribution. May be relicensed without permission/notifcation. * https://github.com/jtsage/jquery-mobile-simpledialog - * - * Modifications by Sebastien Jodogne */ (function($, undefined ) {
--- a/OrthancServer/DatabaseWrapper.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DatabaseWrapper.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -62,16 +62,30 @@ virtual unsigned int GetCardinality() const { - return 5; + return 7; } virtual void Compute(SQLite::FunctionContext& context) { + std::string uncompressedMD5, compressedMD5; + + if (!context.IsNullValue(5)) + { + uncompressedMD5 = context.GetStringValue(5); + } + + if (!context.IsNullValue(6)) + { + compressedMD5 = context.GetStringValue(6); + } + FileInfo info(context.GetStringValue(0), static_cast<FileContentType>(context.GetIntValue(1)), static_cast<uint64_t>(context.GetInt64Value(2)), + uncompressedMD5, static_cast<CompressionType>(context.GetIntValue(3)), - static_cast<uint64_t>(context.GetInt64Value(4))); + static_cast<uint64_t>(context.GetInt64Value(4)), + compressedMD5); listener_.SignalFileDeleted(info); } @@ -85,6 +99,11 @@ ResourceType remainingType_; public: + SignalRemainingAncestor() : + hasRemainingAncestor_(false) + { + } + void Reset() { hasRemainingAncestor_ = false; @@ -243,7 +262,7 @@ { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT parentId FROM Resources WHERE internalId=?"); - s.BindInt(0, resourceId); + s.BindInt64(0, resourceId); if (!s.Step()) { @@ -265,7 +284,7 @@ { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE internalId=?"); - s.BindInt(0, resourceId); + s.BindInt64(0, resourceId); if (!s.Step()) { @@ -280,7 +299,7 @@ { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT resourceType FROM Resources WHERE internalId=?"); - s.BindInt(0, resourceId); + s.BindInt64(0, resourceId); if (!s.Step()) { @@ -295,8 +314,8 @@ int64_t child) { SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?"); - s.BindInt(0, parent); - s.BindInt(1, child); + s.BindInt64(0, parent); + s.BindInt64(1, child); s.Run(); } @@ -304,7 +323,7 @@ int64_t id) { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?"); - s.BindInt(0, id); + s.BindInt64(0, id); childrenPublicIds = Json::arrayValue; while (s.Step()) @@ -319,7 +338,7 @@ signalRemainingAncestor_->Reset(); SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?"); - s.BindInt(0, id); + s.BindInt64(0, id); s.Run(); if (signalRemainingAncestor_->HasRemainingAncestor()) @@ -334,19 +353,28 @@ const std::string& value) { SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)"); - s.BindInt(0, id); + s.BindInt64(0, id); s.BindInt(1, type); s.BindString(2, value); s.Run(); } + void DatabaseWrapper::DeleteMetadata(int64_t id, + MetadataType type) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?"); + s.BindInt64(0, id); + s.BindInt(1, type); + s.Run(); + } + bool DatabaseWrapper::LookupMetadata(std::string& target, int64_t id, MetadataType type) { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM Metadata WHERE id=? AND type=?"); - s.BindInt(0, id); + s.BindInt64(0, id); s.BindInt(1, type); if (!s.Step()) @@ -360,6 +388,21 @@ } } + void DatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target, + int64_t id) + { + target.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?"); + s.BindInt64(0, id); + + while (s.Step()) + { + target.push_back(static_cast<MetadataType>(s.ColumnInt(0))); + } + } + + std::string DatabaseWrapper::GetMetadata(int64_t id, MetadataType type, const std::string& defaultValue) @@ -402,23 +445,52 @@ void DatabaseWrapper::AddAttachment(int64_t id, const FileInfo& attachment) { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?)"); - s.BindInt(0, id); + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); + s.BindInt64(0, id); s.BindInt(1, attachment.GetContentType()); s.BindString(2, attachment.GetUuid()); - s.BindInt(3, attachment.GetCompressedSize()); - s.BindInt(4, attachment.GetUncompressedSize()); + s.BindInt64(3, attachment.GetCompressedSize()); + s.BindInt64(4, attachment.GetUncompressedSize()); s.BindInt(5, attachment.GetCompressionType()); + s.BindString(6, attachment.GetUncompressedMD5()); + s.BindString(7, attachment.GetCompressedMD5()); s.Run(); } + + void DatabaseWrapper::DeleteAttachment(int64_t id, + FileContentType attachment) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?"); + s.BindInt64(0, id); + s.BindInt(1, attachment); + s.Run(); + } + + + + void DatabaseWrapper::ListAvailableAttachments(std::list<FileContentType>& result, + int64_t id) + { + result.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT fileType FROM AttachedFiles WHERE id=?"); + s.BindInt64(0, id); + + while (s.Step()) + { + result.push_back(static_cast<FileContentType>(s.ColumnInt(0))); + } + } + bool DatabaseWrapper::LookupAttachment(FileInfo& attachment, int64_t id, FileContentType contentType) { SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT uuid, uncompressedSize, compressionType, compressedSize FROM AttachedFiles WHERE id=? AND fileType=?"); - s.BindInt(0, id); + "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?"); + s.BindInt64(0, id); s.BindInt(1, contentType); if (!s.Step()) @@ -429,9 +501,11 @@ { attachment = FileInfo(s.ColumnString(0), contentType, - s.ColumnInt(1), + s.ColumnInt64(1), + s.ColumnString(4), static_cast<CompressionType>(s.ColumnInt(2)), - s.ColumnInt(3)); + s.ColumnInt64(3), + s.ColumnString(5)); return true; } } @@ -443,7 +517,7 @@ for (size_t i = 0; i < flattened.GetSize(); i++) { SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); - s.BindInt(0, id); + s.BindInt64(0, id); s.BindInt(1, flattened.GetElement(i).GetTag().GetGroup()); s.BindInt(2, flattened.GetElement(i).GetTag().GetElement()); s.BindString(3, flattened.GetElement(i).GetValue().AsString()); @@ -457,7 +531,7 @@ map.Clear(); SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?"); - s.BindInt(0, id); + s.BindInt64(0, id); while (s.Step()) { map.SetValue(s.ColumnInt(1), @@ -472,7 +546,7 @@ { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " "WHERE a.internalId = b.parentId AND b.internalId = ?"); - s.BindInt(0, id); + s.BindInt64(0, id); if (s.Step()) { @@ -491,7 +565,7 @@ { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " "WHERE a.parentId = b.internalId AND b.internalId = ?"); - s.BindInt(0, id); + s.BindInt64(0, id); result.clear(); @@ -507,13 +581,13 @@ { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b " "WHERE a.parentId = b.internalId AND b.internalId = ?"); - s.BindInt(0, id); + s.BindInt64(0, id); result.clear(); while (s.Step()) { - result.push_back(s.ColumnInt(0)); + result.push_back(s.ColumnInt64(0)); } } @@ -525,7 +599,7 @@ { SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); s.BindInt(0, changeType); - s.BindInt(1, internalId); + s.BindInt64(1, internalId); s.BindInt(2, resourceType); s.BindString(3, boost::posix_time::to_iso_string(date)); s.Run(); @@ -542,17 +616,17 @@ while (changes.size() < maxResults && s.Step()) { - int64_t seq = s.ColumnInt(0); + int64_t seq = s.ColumnInt64(0); ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1)); - int64_t internalId = s.ColumnInt(2); + int64_t internalId = s.ColumnInt64(2); ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3)); const std::string& date = s.ColumnString(4); std::string publicId = GetPublicId(internalId); Json::Value item = Json::objectValue; item["Seq"] = static_cast<int>(seq); - item["ChangeType"] = ToString(changeType); - item["ResourceType"] = ToString(resourceType); + item["ChangeType"] = EnumerationToString(changeType); + item["ResourceType"] = EnumerationToString(resourceType); item["ID"] = publicId; item["Path"] = GetBasePath(resourceType, publicId); item["Date"] = date; @@ -573,7 +647,7 @@ unsigned int maxResults) { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?"); - s.BindInt(0, since); + s.BindInt64(0, since); s.BindInt(1, maxResults + 1); GetChangesInternal(target, s, since, maxResults); } @@ -610,23 +684,23 @@ } - void DatabaseWrapper::GetExportedResources(Json::Value& target, - SQLite::Statement& s, - int64_t since, - unsigned int maxResults) + void DatabaseWrapper::GetExportedResourcesInternal(Json::Value& target, + SQLite::Statement& s, + int64_t since, + unsigned int maxResults) { Json::Value changes = Json::arrayValue; int64_t last = since; while (changes.size() < maxResults && s.Step()) { - int64_t seq = s.ColumnInt(0); + int64_t seq = s.ColumnInt64(0); ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1)); std::string publicId = s.ColumnString(2); Json::Value item = Json::objectValue; item["Seq"] = static_cast<int>(seq); - item["ResourceType"] = ToString(resourceType); + item["ResourceType"] = EnumerationToString(resourceType); item["ID"] = publicId; item["Path"] = GetBasePath(resourceType, publicId); item["RemoteModality"] = s.ColumnString(3); @@ -670,9 +744,9 @@ { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?"); - s.BindInt(0, since); + s.BindInt64(0, since); s.BindInt(1, maxResults + 1); - GetExportedResources(target, s, since, maxResults); + GetExportedResourcesInternal(target, s, since, maxResults); } @@ -680,7 +754,7 @@ { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1"); - GetExportedResources(target, s, 0, 1); + GetExportedResourcesInternal(target, s, 0, 1); } @@ -766,7 +840,7 @@ db_.Execute(query); } - // Sanity check of the version of the database + // Check the version of the database std::string version = GetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "Unknown"); bool ok = false; try @@ -774,9 +848,24 @@ LOG(INFO) << "Version of the Orthanc database: " << version; unsigned int v = boost::lexical_cast<unsigned int>(version); - // This version of Orthanc is only compatible with version 3 of - // the DB schema (since Orthanc 0.3.2) - ok = (v == 3); + /** + * History of the database versions: + * - Version 3: from Orthanc 0.3.2 to Orthanc 0.7.2 (inclusive) + * - Version 4: from Orthanc 0.7.3 (inclusive) + **/ + + // This version of Orthanc is only compatible with versions 3 of 4 of the DB schema + ok = (v == 3 || v == 4); + + if (v == 3) + { + LOG(WARNING) << "Upgrading database version from 3 to 4"; + std::string upgrade; + EmbeddedResources::GetFileResource(upgrade, EmbeddedResources::UPGRADE_DATABASE_3_TO_4); + db_.BeginTransaction(); + db_.Execute(upgrade); + db_.CommitTransaction(); + } } catch (boost::bad_lexical_cast&) { @@ -784,6 +873,7 @@ if (!ok) { + LOG(ERROR) << "Incompatible version of the Orthanc database: " << version; throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } @@ -832,7 +922,7 @@ SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT patientId FROM PatientRecyclingOrder " "WHERE patientId != ? ORDER BY seq ASC LIMIT 1"); - s.BindInt(0, patientIdToAvoid); + s.BindInt64(0, patientIdToAvoid); if (!s.Step()) { @@ -850,7 +940,7 @@ { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?"); - s.BindInt(0, internalId); + s.BindInt64(0, internalId); return !s.Step(); } @@ -860,13 +950,13 @@ if (isProtected) { SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); - s.BindInt(0, internalId); + s.BindInt64(0, internalId); s.Run(); } else if (IsProtectedPatient(internalId)) { SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); - s.BindInt(0, internalId); + s.BindInt64(0, internalId); s.Run(); } else @@ -902,4 +992,56 @@ return 1; } } + + + void DatabaseWrapper::ClearTable(const std::string& tableName) + { + db_.Execute("DELETE FROM " + tableName); + } + + + bool DatabaseWrapper::IsExistingResource(int64_t internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM Resources WHERE internalId=?"); + s.BindInt64(0, internalId); + return s.Step(); + } + + + void DatabaseWrapper::LookupTagValue(std::list<int64_t>& result, + DicomTag tag, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT id FROM MainDicomTags WHERE tagGroup=? AND tagElement=? and value=?"); + + s.BindInt(0, tag.GetGroup()); + s.BindInt(1, tag.GetElement()); + s.BindString(2, value); + + result.clear(); + + while (s.Step()) + { + result.push_back(s.ColumnInt64(0)); + } + } + + + void DatabaseWrapper::LookupTagValue(std::list<int64_t>& result, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT id FROM MainDicomTags WHERE value=?"); + + s.BindString(0, value); + + result.clear(); + + while (s.Step()) + { + result.push_back(s.ColumnInt64(0)); + } + } }
--- a/OrthancServer/DatabaseWrapper.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DatabaseWrapper.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -67,10 +67,10 @@ int64_t since, unsigned int maxResults); - void GetExportedResources(Json::Value& target, - SQLite::Statement& s, - int64_t since, - unsigned int maxResults); + void GetExportedResourcesInternal(Json::Value& target, + SQLite::Statement& s, + int64_t since, + unsigned int maxResults); public: void SetGlobalProperty(GlobalProperty property, @@ -108,10 +108,16 @@ MetadataType type, const std::string& value); + void DeleteMetadata(int64_t id, + MetadataType type); + bool LookupMetadata(std::string& target, int64_t id, MetadataType type); + void ListAvailableMetadata(std::list<MetadataType>& target, + int64_t id); + std::string GetMetadata(int64_t id, MetadataType type, const std::string& defaultValue = ""); @@ -123,6 +129,12 @@ void AddAttachment(int64_t id, const FileInfo& attachment); + void DeleteAttachment(int64_t id, + FileContentType attachment); + + void ListAvailableAttachments(std::list<FileContentType>& result, + int64_t id); + bool LookupAttachment(FileInfo& attachment, int64_t id, FileContentType contentType); @@ -212,5 +224,16 @@ } uint64_t IncrementGlobalSequence(GlobalProperty property); + + void ClearTable(const std::string& tableName); + + bool IsExistingResource(int64_t internalId); + + void LookupTagValue(std::list<int64_t>& result, + DicomTag tag, + const std::string& value); + + void LookupTagValue(std::list<int64_t>& result, + const std::string& value); }; }
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomServer.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomServer.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -36,6 +36,7 @@ #include "../../Core/Toolbox.h" #include "../../Core/Uuid.h" #include "../Internals/CommandDispatcher.h" +#include "../OrthancInitialization.h" #include "EmbeddedResources.h" #include <boost/thread.hpp> @@ -58,6 +59,7 @@ }; +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, EmbeddedResources::FileResourceId resource) { @@ -74,7 +76,7 @@ } } - +#else static void LoadExternalDictionary(DcmDataDictionary& dictionary, const std::string& directory, const std::string& filename) @@ -82,15 +84,18 @@ boost::filesystem::path p = directory; p = p / filename; + LOG(WARNING) << "Loading the external DICOM dictionary " << p; + if (!dictionary.loadDictionary(p.string().c_str())) { throw OrthancException(ErrorCode_InternalError); } } - + +#endif - void DicomServer::ServerThread(DicomServer* server) + void DicomServer::InitializeDictionary() { /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */ dcmDisableGethostbyaddr.set(OFTrue); @@ -111,7 +116,7 @@ LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_PRIVATE); #elif defined(__linux) - std::string path = "/usr/share/dcmtk"; + std::string path = DCMTK_DICTIONARY_DIR; const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); if (env != NULL) @@ -144,7 +149,11 @@ throw OrthancException(ErrorCode_InternalError); } } + } + + void DicomServer::ServerThread(DicomServer* server) + { /* initialize network, i.e. create an instance of T_ASC_Network*. */ T_ASC_Network *net; OFCondition cond = ASC_initializeNetwork @@ -190,9 +199,10 @@ } - DicomServer::DicomServer() : pimpl_(new PImpl) + DicomServer::DicomServer() : + pimpl_(new PImpl), + aet_("ANY-SCP") { - aet_ = "ANY-SCP"; port_ = 104; findRequestHandlerFactory_ = NULL; moveRequestHandlerFactory_ = NULL; @@ -201,6 +211,8 @@ checkCalledAet_ = true; clientTimeout_ = 30; isThreaded_ = true; + continue_ = false; + started_ = false; } DicomServer::~DicomServer() @@ -385,8 +397,24 @@ void DicomServer::Stop() { continue_ = false; - pimpl_->thread_.join(); + + if (pimpl_->thread_.joinable()) + { + pimpl_->thread_.join(); + } bagOfDispatchers_.StopAll(); } + + bool DicomServer::IsMyAETitle(const std::string& aet) const + { + if (!HasCalledApplicationEntityTitleCheck()) + { + // OK, no check on the AET. + return true; + } + + return Orthanc::IsSameAETitle(aet, GetApplicationEntityTitle()); + } + }
--- a/OrthancServer/DicomProtocol/DicomServer.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomServer.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -66,6 +66,8 @@ static void ServerThread(DicomServer* server); public: + static void InitializeDictionary(); + DicomServer(); ~DicomServer(); @@ -104,6 +106,8 @@ void Start(); void Stop(); + + bool IsMyAETitle(const std::string& aet) const; }; }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -39,9 +39,11 @@ #include <dcmtk/dcmdata/dcistrmb.h> #include <dcmtk/dcmdata/dcistrmf.h> #include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcmetinf.h> #include <dcmtk/dcmnet/diutil.h> #include <set> +#include <glog/logging.h> @@ -56,6 +58,8 @@ #endif +static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax; + namespace Orthanc { struct DicomUserConnection::PImpl @@ -72,7 +76,7 @@ void CheckIsOpen() const; - void Store(DcmInputStream& is); + void Store(DcmInputStream& is, DicomUserConnection& connection); }; @@ -106,21 +110,18 @@ distantAet_ = other.distantAet_; distantHost_ = other.distantHost_; distantPort_ = other.distantPort_; + manufacturer_ = other.manufacturer_; + preferredTransferSyntax_ = other.preferredTransferSyntax_; } - void DicomUserConnection::SetupPresentationContexts() + void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) { - // The preferred abstract syntax - std::string preferredSyntax = UID_LittleEndianImplicitTransferSyntax; - - // Fallback abstract syntaxes - std::set<std::string> abstractSyntaxes; - abstractSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); - abstractSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); - abstractSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); - abstractSyntaxes.erase(preferredSyntax); - assert(abstractSyntaxes.size() == 2); + // Fallback transfer syntaxes + std::set<std::string> fallbackSyntaxes; + fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); + fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); + fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); // Transfer syntaxes for C-ECHO, C-FIND and C-MOVE std::vector<std::string> transferSyntaxes; @@ -146,13 +147,18 @@ } } - // Flatten the fallback abstract syntaxes array - const char* asPreferred[1] = { preferredSyntax.c_str() }; - const char* asFallback[2]; - std::set<std::string>::const_iterator it = abstractSyntaxes.begin(); - asFallback[0] = it->c_str(); - it++; - asFallback[1] = it->c_str(); + // Flatten the fallback transfer syntaxes array + const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; + + fallbackSyntaxes.erase(preferredTransferSyntax); + + std::vector<const char*> asFallback; + asFallback.reserve(fallbackSyntaxes.size()); + for (std::set<std::string>::const_iterator + it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it) + { + asFallback.push_back(it->c_str()); + } unsigned int presentationContextId = 1; for (size_t i = 0; i < transferSyntaxes.size(); i++) @@ -161,20 +167,55 @@ transferSyntaxes[i].c_str(), asPreferred, 1)); presentationContextId += 2; - Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, - transferSyntaxes[i].c_str(), asFallback, 2)); - presentationContextId += 2; + if (asFallback.size() > 0) + { + Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, + transferSyntaxes[i].c_str(), &asFallback[0], asFallback.size())); + presentationContextId += 2; + } } } - void DicomUserConnection::PImpl::Store(DcmInputStream& is) + static bool IsGenericTransferSyntax(const std::string& syntax) + { + return (syntax == UID_LittleEndianExplicitTransferSyntax || + syntax == UID_BigEndianExplicitTransferSyntax || + syntax == UID_LittleEndianImplicitTransferSyntax); + } + + + void DicomUserConnection::PImpl::Store(DcmInputStream& is, DicomUserConnection& connection) { CheckIsOpen(); DcmFileFormat dcmff; Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); + // Determine whether a new presentation context must be + // negociated, depending on the transfer syntax of this instance + DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); + const std::string syntax(xfer.getXferID()); + bool isGeneric = IsGenericTransferSyntax(syntax); + + if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax())) + { + // Making a generic-to-specific or specific-to-generic change of + // the transfer syntax. Renegociate the connection. + LOG(INFO) << "Renegociating a C-Store association due to a change in the transfer syntax"; + + if (isGeneric) + { + connection.ResetPreferredTransferSyntax(); + } + else + { + connection.SetPreferredTransferSyntax(syntax); + } + + connection.Open(); + } + // Figure out which SOP class and SOP instance is encapsulated in the file DIC_UI sopClass; DIC_UI sopInstance; @@ -227,7 +268,7 @@ DcmDataset *responseIdentifiers /* pending response identifiers */ ) { - DicomFindAnswers& answers = *(DicomFindAnswers*) callbackData; + DicomFindAnswers& answers = *reinterpret_cast<DicomFindAnswers*>(callbackData); if (responseIdentifiers != NULL) { @@ -294,7 +335,19 @@ break; case FindRootModel_Instance: - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); + if (manufacturer_ == ModalityManufacturer_ClearCanvas || + manufacturer_ == ModalityManufacturer_Dcm4Chee) + { + // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>. + // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J + // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE"); + } + else + { + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); + } + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; // Accession number @@ -367,6 +420,7 @@ s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID); s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER); + s.CopyTagIfExists(fields, DICOM_TAG_MODALITIES_IN_STUDY); Find(result, FindRootModel_Study, s); } @@ -447,12 +501,15 @@ } - DicomUserConnection::DicomUserConnection() : pimpl_(new PImpl) + DicomUserConnection::DicomUserConnection() : + pimpl_(new PImpl), + preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX), + localAet_("STORESCU"), + distantAet_("ANY-SCP"), + distantHost_("127.0.0.1") { - localAet_ = "STORESCU"; - distantAet_ = "ANY-SCP"; distantPort_ = 104; - distantHost_ = "127.0.0.1"; + manufacturer_ = ModalityManufacturer_Generic; pimpl_->net_ = NULL; pimpl_->params_ = NULL; @@ -466,37 +523,76 @@ void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) { - Close(); - localAet_ = aet; + if (localAet_ != aet) + { + Close(); + localAet_ = aet; + } } void DicomUserConnection::SetDistantApplicationEntityTitle(const std::string& aet) { - Close(); - distantAet_ = aet; + if (distantAet_ != aet) + { + Close(); + distantAet_ = aet; + } + } + + void DicomUserConnection::SetDistantManufacturer(ModalityManufacturer manufacturer) + { + if (manufacturer_ != manufacturer) + { + Close(); + manufacturer_ = manufacturer; + } + } + + void DicomUserConnection::ResetPreferredTransferSyntax() + { + SetPreferredTransferSyntax(DEFAULT_PREFERRED_TRANSFER_SYNTAX); + } + + void DicomUserConnection::SetPreferredTransferSyntax(const std::string& preferredTransferSyntax) + { + if (preferredTransferSyntax_ != preferredTransferSyntax) + { + Close(); + preferredTransferSyntax_ = preferredTransferSyntax; + } } void DicomUserConnection::SetDistantHost(const std::string& host) { - if (host.size() > HOST_NAME_MAX - 10) + if (distantHost_ != host) { - throw OrthancException("Distant host name is too long"); + if (host.size() > HOST_NAME_MAX - 10) + { + throw OrthancException("Distant host name is too long"); + } + + Close(); + distantHost_ = host; } - - Close(); - distantHost_ = host; } void DicomUserConnection::SetDistantPort(uint16_t port) { - Close(); - distantPort_ = port; + if (distantPort_ != port) + { + Close(); + distantPort_ = port; + } } void DicomUserConnection::Open() { - Close(); + if (IsOpen()) + { + // Don't reopen the connection + return; + } Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ 30, &pimpl_->net_)); Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); @@ -522,7 +618,7 @@ // Set various options Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false)); - SetupPresentationContexts(); + SetupPresentationContexts(preferredTransferSyntax_); // Do the association Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_)); @@ -571,7 +667,7 @@ is.setBuffer(buffer, size); is.setEos(); - pimpl_->Store(is); + pimpl_->Store(is, *this); } void DicomUserConnection::Store(const std::string& buffer) @@ -586,7 +682,7 @@ { // Prepare an input stream for the file DcmInputFileStream is(path.c_str()); - pimpl_->Store(is); + pimpl_->Store(is, *this); } bool DicomUserConnection::Echo()
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -33,6 +33,7 @@ #pragma once #include "DicomFindAnswers.h" +#include "../ServerEnumerations.h" #include <stdint.h> #include <boost/shared_ptr.hpp> @@ -55,14 +56,16 @@ boost::shared_ptr<PImpl> pimpl_; // Connection parameters + std::string preferredTransferSyntax_; std::string localAet_; std::string distantAet_; std::string distantHost_; uint16_t distantPort_; + ModalityManufacturer manufacturer_; void CheckIsOpen() const; - void SetupPresentationContexts(); + void SetupPresentationContexts(const std::string& preferredTransferSyntax); void Find(DicomFindAnswers& result, FindRootModel model, @@ -106,6 +109,22 @@ return distantPort_; } + void SetDistantManufacturer(ModalityManufacturer manufacturer); + + ModalityManufacturer GetDistantManufacturer() const + { + return manufacturer_; + } + + void ResetPreferredTransferSyntax(); + + void SetPreferredTransferSyntax(const std::string& preferredTransferSyntax); + + const std::string& GetPreferredTransferSyntax() const + { + return preferredTransferSyntax_; + } + void Open(); void Close();
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,6 +32,8 @@ #pragma once +#include "../ServerEnumerations.h" + #include <string> namespace Orthanc @@ -43,7 +45,11 @@ { } - virtual bool IsAllowed(const std::string& callingIp, - const std::string& callingAet) = 0; + virtual bool IsAllowedConnection(const std::string& callingIp, + const std::string& callingAet) = 0; + + virtual bool IsAllowedRequest(const std::string& callingIp, + const std::string& callingAet, + DicomRequestType type) = 0; }; }
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -47,7 +47,8 @@ { } - virtual void Handle(const DicomMap& input, - DicomFindAnswers& answers) = 0; + virtual void Handle(DicomFindAnswers& answers, + const DicomMap& input, + const std::string& callingAETitle) = 0; }; }
--- a/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/FromDcmtkBridge.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -29,6 +29,49 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ + + +/*========================================================================= + + This file is based on portions of the following project: + + Program: GDCM (Grassroots DICOM). A DICOM library + Module: http://gdcm.sourceforge.net/Copyright.html + +Copyright (c) 2006-2011 Mathieu Malaterre +Copyright (c) 1993-2005 CREATIS +(CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) +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 name of Mathieu Malaterre, or CREATIS, nor the names of any + contributors (CNRS, INSERM, UCB, Universite Lyon I), 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 AUTHORS 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. + +=========================================================================*/ + + #ifndef NOMINMAX #define NOMINMAX #endif @@ -38,7 +81,7 @@ #include "ToDcmtkBridge.h" #include "../Core/Toolbox.h" #include "../Core/OrthancException.h" -#include "../Core/PngWriter.h" +#include "../Core/FileFormats/PngWriter.h" #include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomString.h" #include "../Core/DicomFormat/DicomNullValue.h" @@ -55,6 +98,7 @@ #include <dcmtk/dcmdata/dcfilefo.h> #include <dcmtk/dcmdata/dcistrmb.h> #include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcmetinf.h> #include <dcmtk/dcmdata/dcvrae.h> #include <dcmtk/dcmdata/dcvras.h> @@ -77,11 +121,20 @@ #include <dcmtk/dcmdata/dcvrul.h> #include <dcmtk/dcmdata/dcvrus.h> #include <dcmtk/dcmdata/dcvrut.h> +#include <dcmtk/dcmdata/dcpixel.h> +#include <dcmtk/dcmdata/dcpixseq.h> +#include <dcmtk/dcmdata/dcpxitem.h> + #include <boost/math/special_functions/round.hpp> #include <glog/logging.h> #include <dcmtk/dcmdata/dcostrmb.h> + +static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; + + + namespace Orthanc { void ParsedDicomFile::Setup(const char* buffer, size_t size) @@ -165,16 +218,34 @@ output.AnswerJson(v); } + + static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData, + E_TransferSyntax transferSyntax) + { + DcmPixelSequence* pixelSequence = NULL; + if (pixelData.getEncapsulatedRepresentation + (transferSyntax, NULL, pixelSequence).good() && pixelSequence) + { + return pixelSequence->card(); + } + else + { + return 1; + } + } + + static void AnswerDicomField(RestApiOutput& output, - DcmElement& element) + DcmElement& element, + E_TransferSyntax transferSyntax) { - // This element is not a sequence + // This element is nor a sequence, neither a pixel-data std::string buffer; buffer.resize(65536); - Uint32 length = element.getLength(); + Uint32 length = element.getLength(transferSyntax); Uint32 offset = 0; - output.GetLowLevelOutput().SendOkHeader("application/octet-stream", true, length, NULL); + output.GetLowLevelOutput().SendOkHeader(CONTENT_TYPE_OCTET_STREAM, true, length, NULL); while (offset < length) { @@ -188,14 +259,16 @@ nbytes = buffer.size(); } - if (element.getPartialValue(&buffer[0], offset, nbytes).good()) + OFCondition cond = element.getPartialValue(&buffer[0], offset, nbytes); + + if (cond.good()) { output.GetLowLevelOutput().Send(&buffer[0], nbytes); offset += nbytes; } else { - LOG(ERROR) << "Error while sending a DICOM field"; + LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); return; } } @@ -203,9 +276,96 @@ output.MarkLowLevelOutputDone(); } + + static bool AnswerPixelData(RestApiOutput& output, + DcmItem& dicom, + E_TransferSyntax transferSyntax, + const std::string* blockUri) + { + DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(), + DICOM_TAG_PIXEL_DATA.GetElement()); + + DcmElement *element = NULL; + if (!dicom.findAndGetElement(k, element).good() || + element == NULL) + { + return false; + } + + try + { + DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); + if (blockUri == NULL) + { + // The user asks how many blocks are presents in this pixel data + unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax); + + Json::Value result(Json::arrayValue); + for (unsigned int i = 0; i < blocks; i++) + { + result.append(boost::lexical_cast<std::string>(i)); + } + + output.AnswerJson(result); + return true; + } + + + unsigned int block = boost::lexical_cast<unsigned int>(*blockUri); + + if (block < GetPixelDataBlockCount(pixelData, transferSyntax)) + { + DcmPixelSequence* pixelSequence = NULL; + if (pixelData.getEncapsulatedRepresentation + (transferSyntax, NULL, pixelSequence).good() && pixelSequence) + { + // This is the case for JPEG transfer syntaxes + if (block < pixelSequence->card()) + { + DcmPixelItem* pixelItem = NULL; + if (pixelSequence->getItem(pixelItem, block).good() && pixelItem) + { + if (pixelItem->getLength() == 0) + { + output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM); + return true; + } + + Uint8* buffer = NULL; + if (pixelItem->getUint8Array(buffer).good() && buffer) + { + output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM); + return true; + } + } + } + } + else + { + // This is the case for raw, uncompressed image buffers + assert(*blockUri == "0"); + AnswerDicomField(output, *element, transferSyntax); + } + } + } + catch (boost::bad_lexical_cast&) + { + // The URI entered by the user is not a number + } + catch (std::bad_cast&) + { + // This should never happen + } + + return false; + } + + + static void SendPathValueForLeaf(RestApiOutput& output, const std::string& tag, - DcmItem& dicom) + DcmItem& dicom, + E_TransferSyntax transferSyntax) { DcmTagKey k; ParseTagAndGroup(k, tag); @@ -225,7 +385,7 @@ //element->getVR() != EVR_UNKNOWN && // This would forbid private tags element->getVR() != EVR_SQ) { - AnswerDicomField(output, *element); + AnswerDicomField(output, *element, transferSyntax); } } @@ -233,6 +393,22 @@ const UriComponents& uri) { DcmItem* dicom = file_->getDataset(); + E_TransferSyntax transferSyntax = file_->getDataset()->getOriginalXfer(); + + // Special case: Accessing the pixel data + if (uri.size() == 1 || + uri.size() == 2) + { + DcmTagKey tag; + ParseTagAndGroup(tag, uri[0]); + + if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() && + tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement()) + { + AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]); + return; + } + } // Go down in the tag hierarchy according to the URI for (size_t pos = 0; pos < uri.size() / 2; pos++) @@ -266,7 +442,7 @@ } else { - SendPathValueForLeaf(output, uri.back(), *dicom); + SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax); } } @@ -580,7 +756,7 @@ } for (Tags::iterator it = privateTags.begin(); - it != privateTags.end(); it++) + it != privateTags.end(); ++it) { DcmElement* tmp = dataset.remove(*it); if (tmp != NULL) @@ -657,7 +833,7 @@ std::string serialized; if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, file_->getDataset())) { - output.AnswerBuffer(serialized, "application/octet-stream"); + output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM); } } @@ -1097,9 +1273,9 @@ for (unsigned int x = 0; x < accessor.GetWidth(); x++, pixel++) { int32_t v = accessor.GetValue(x, y); - if (v < std::numeric_limits<T>::min()) + if (v < static_cast<int32_t>(std::numeric_limits<T>::min())) *pixel = std::numeric_limits<T>::min(); - else if (v > std::numeric_limits<T>::max()) + else if (v > static_cast<int32_t>(std::numeric_limits<T>::max())) *pixel = std::numeric_limits<T>::max(); else *pixel = static_cast<T>(v); @@ -1240,6 +1416,11 @@ accessor.reset(new DicomIntegerPixelAccessor(m, pixData, privateContent.size())); accessor->SetCurrentFrame(frame); } + + if (accessor.get() == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } PixelFormat format; bool supported = false; @@ -1263,6 +1444,11 @@ format = PixelFormat_Grayscale16; break; + case ImageExtractionMode_Int16: + supported = true; + format = PixelFormat_SignedGrayscale16; + break; + default: supported = false; break; @@ -1314,6 +1500,10 @@ ExtractPngImageTruncate<uint16_t>(result, *accessor, format); break; + case ImageExtractionMode_Int16: + ExtractPngImageTruncate<int16_t>(result, *accessor, format); + break; + default: throw OrthancException(ErrorCode_NotImplemented); } @@ -1436,7 +1626,7 @@ void FromDcmtkBridge::Print(FILE* fp, const DicomMap& m) { for (DicomMap::Map::const_iterator - it = m.map_.begin(); it != m.map_.end(); it++) + it = m.map_.begin(); it != m.map_.end(); ++it) { DicomTag t = it->first; std::string s = it->second->AsString(); @@ -1456,7 +1646,7 @@ result.clear(); for (DicomMap::Map::const_iterator - it = values.map_.begin(); it != values.map_.end(); it++) + it = values.map_.begin(); it != values.map_.end(); ++it) { result[GetName(it->first)] = it->second->AsString(); } @@ -1497,25 +1687,41 @@ // syntax, with explicit length. // http://support.dcmtk.org/docs/dcxfer_8h-source.html - E_TransferSyntax xfer = EXS_LittleEndianExplicit; + + + /** + * Note that up to Orthanc 0.7.1 (inclusive), the + * "EXS_LittleEndianExplicit" was always used to save the DICOM + * dataset into memory. We now keep the original transfer syntax + * (if available). + **/ + E_TransferSyntax xfer = dataSet->getOriginalXfer(); + if (xfer == EXS_Unknown) + { + // No information about the original transfer syntax: This is + // most probably a DICOM dataset that was read from memory. + xfer = EXS_LittleEndianExplicit; + } + E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; - uint32_t s = dataSet->getLength(xfer, encodingType); + // Create the meta-header information + DcmFileFormat ff(dataSet); + ff.validateMetaInfo(xfer); + // Create a memory buffer with the proper size + uint32_t s = ff.calcElementLength(xfer, encodingType); buffer.resize(s); DcmOutputBufferStream ob(&buffer[0], s); - dataSet->transferInit(); + // Fill the memory buffer with the meta-header and the dataset + ff.transferInit(); + OFCondition c = ff.write(ob, xfer, encodingType, NULL, + /*opt_groupLength*/ EGL_recalcGL, + /*opt_paddingType*/ EPD_withoutPadding); + ff.transferEnd(); -#if DCMTK_VERSION_NUMBER >= 360 - OFCondition c = dataSet->write(ob, xfer, encodingType, NULL, - /*opt_groupLength*/ EGL_recalcGL, - /*opt_paddingType*/ EPD_withoutPadding); -#else - OFCondition c = dataSet->write(ob, xfer, encodingType, NULL); -#endif - - dataSet->transferEnd(); + // Handle errors if (c.good()) { return true; @@ -1525,15 +1731,5 @@ buffer.clear(); return false; } - -#if 0 - OFCondition cond = cbdata->dcmff->saveFile(fileName.c_str(), xfer, - encodingType, - /*opt_groupLength*/ EGL_recalcGL, - /*opt_paddingType*/ EPD_withoutPadding, - OFstatic_cast(Uint32, /*opt_filepad*/ 0), - OFstatic_cast(Uint32, /*opt_itempad*/ 0), - (opt_useMetaheader) ? EWM_fileformat : EWM_dataset); -#endif } }
--- a/OrthancServer/FromDcmtkBridge.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -43,13 +43,6 @@ namespace Orthanc { - enum ImageExtractionMode - { - ImageExtractionMode_Preview, - ImageExtractionMode_UInt8, - ImageExtractionMode_UInt16 - }; - enum DicomRootLevel { DicomRootLevel_Patient,
--- a/OrthancServer/IServerIndexListener.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/IServerIndexListener.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/CommandDispatcher.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/Internals/CommandDispatcher.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -41,9 +41,149 @@ #include <boost/lexical_cast.hpp> #include <glog/logging.h> +#define ORTHANC_PROMISCUOUS 1 + static OFBool opt_rejectWithoutImplementationUID = OFFalse; + +#if ORTHANC_PROMISCUOUS == 1 +static +DUL_PRESENTATIONCONTEXT * +findPresentationContextID(LST_HEAD * head, + T_ASC_PresentationContextID presentationContextID) +{ + DUL_PRESENTATIONCONTEXT *pc; + LST_HEAD **l; + OFBool found = OFFalse; + + if (head == NULL) + return NULL; + + l = &head; + if (*l == NULL) + return NULL; + + pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l)); + (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc)); + + while (pc && !found) { + if (pc->presentationContextID == presentationContextID) { + found = OFTrue; + } else { + pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l)); + } + } + return pc; +} + + +/** accept all presenstation contexts for unknown SOP classes, + * i.e. UIDs appearing in the list of abstract syntaxes + * where no corresponding name is defined in the UID dictionary. + * @param params pointer to association parameters structure + * @param transferSyntax transfer syntax to accept + * @param acceptedRole SCU/SCP role to accept + */ +static OFCondition acceptUnknownContextsWithTransferSyntax( + T_ASC_Parameters * params, + const char* transferSyntax, + T_ASC_SC_ROLE acceptedRole) +{ + OFCondition cond = EC_Normal; + int n, i, k; + DUL_PRESENTATIONCONTEXT *dpc; + T_ASC_PresentationContext pc; + OFBool accepted = OFFalse; + OFBool abstractOK = OFFalse; + + n = ASC_countPresentationContexts(params); + for (i = 0; i < n; i++) + { + cond = ASC_getPresentationContext(params, i, &pc); + if (cond.bad()) return cond; + abstractOK = OFFalse; + accepted = OFFalse; + + if (dcmFindNameOfUID(pc.abstractSyntax) == NULL) + { + abstractOK = OFTrue; + + /* check the transfer syntax */ + for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++) + { + if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0) + { + accepted = OFTrue; + } + } + } + + if (accepted) + { + cond = ASC_acceptPresentationContext( + params, pc.presentationContextID, + transferSyntax, acceptedRole); + if (cond.bad()) return cond; + } else { + T_ASC_P_ResultReason reason; + + /* do not refuse if already accepted */ + dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext, + pc.presentationContextID); + if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE))) + { + + if (abstractOK) { + reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; + } else { + reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED; + } + /* + * If previously this presentation context was refused + * because of bad transfer syntax let it stay that way. + */ + if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED)) + reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; + + cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason); + if (cond.bad()) return cond; + } + } + } + return EC_Normal; +} + + +/** accept all presenstation contexts for unknown SOP classes, + * i.e. UIDs appearing in the list of abstract syntaxes + * where no corresponding name is defined in the UID dictionary. + * This method is passed a list of "preferred" transfer syntaxes. + * @param params pointer to association parameters structure + * @param transferSyntax transfer syntax to accept + * @param acceptedRole SCU/SCP role to accept + */ +static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes( + T_ASC_Parameters * params, + const char* transferSyntaxes[], int transferSyntaxCount, + T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT) +{ + OFCondition cond = EC_Normal; + /* + ** Accept in the order "least wanted" to "most wanted" transfer + ** syntax. Accepting a transfer syntax will override previously + ** accepted transfer syntaxes. + */ + for (int i = transferSyntaxCount - 1; i >= 0; i--) + { + cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole); + if (cond.bad()) return cond; + } + return cond; +} +#endif + + namespace Orthanc { namespace Internals @@ -98,11 +238,9 @@ if (server.HasMoveRequestHandlerFactory()) { knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); + knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel); } - const char* transferSyntaxes[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; - int numTransferSyntaxes = 0; - cond = ASC_receiveAssociation(net, &assoc, /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, NULL, NULL, @@ -127,13 +265,47 @@ LOG(INFO) << "Association Received"; - transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax; - transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax; - transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax; - numTransferSyntaxes = 3; + std::vector<const char*> transferSyntaxes; + + // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1 + transferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); + transferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); + transferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); + + // New transfer syntaxes supported since Orthanc 0.7.2 + transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax); + transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); + transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax); + transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax); + transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax); + transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax); + transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); + transferSyntaxes.push_back(UID_RLELosslessTransferSyntax); /* accept the Verification SOP Class if presented */ - cond = ASC_acceptContextsWithPreferredTransferSyntaxes( assoc->params, &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), transferSyntaxes, numTransferSyntaxes); + cond = ASC_acceptContextsWithPreferredTransferSyntaxes( assoc->params, &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), &transferSyntaxes[0], transferSyntaxes.size()); if (cond.bad()) { LOG(INFO) << cond.text(); @@ -142,7 +314,7 @@ } /* the array of Storage SOP Class UIDs comes from dcuid.h */ - cond = ASC_acceptContextsWithPreferredTransferSyntaxes( assoc->params, dcmAllStorageSOPClassUIDs, numberOfAllDcmStorageSOPClassUIDs, transferSyntaxes, numTransferSyntaxes); + cond = ASC_acceptContextsWithPreferredTransferSyntaxes( assoc->params, dcmAllStorageSOPClassUIDs, numberOfAllDcmStorageSOPClassUIDs, &transferSyntaxes[0], transferSyntaxes.size()); if (cond.bad()) { LOG(INFO) << cond.text(); @@ -150,6 +322,18 @@ return NULL; } +#if ORTHANC_PROMISCUOUS == 1 + /* accept everything not known not to be a storage SOP class */ + cond = acceptUnknownContextsWithPreferredTransferSyntaxes( + assoc->params, &transferSyntaxes[0], transferSyntaxes.size()); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + AssociationCleanup(assoc); + return NULL; + } +#endif + /* set our app title */ ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str()); @@ -174,6 +358,9 @@ AssociationCleanup(assoc); return NULL; } + + std::string callingIP; + std::string callingTitle; /* check the AETs */ { @@ -195,15 +382,11 @@ return NULL; } - std::string callingIP(/*OFSTRING_GUARD*/(callingIP_C)); - std::string callingTitle(/*OFSTRING_GUARD*/(callingTitle_C)); + callingIP = std::string(/*OFSTRING_GUARD*/(callingIP_C)); + callingTitle = std::string(/*OFSTRING_GUARD*/(callingTitle_C)); std::string calledTitle(/*OFSTRING_GUARD*/(calledTitle_C)); - Toolbox::ToUpperCase(callingIP); - Toolbox::ToUpperCase(callingTitle); - Toolbox::ToUpperCase(calledTitle); - if (server.HasCalledApplicationEntityTitleCheck() && - calledTitle != server.GetApplicationEntityTitle()) + if (!server.IsMyAETitle(calledTitle)) { T_ASC_RejectParameters rej = { @@ -217,7 +400,7 @@ } if (server.HasApplicationEntityFilter() && - !server.GetApplicationEntityFilter().IsAllowed(callingIP, callingTitle)) + !server.GetApplicationEntityFilter().IsAllowedConnection(callingIP, callingTitle)) { T_ASC_RejectParameters rej = { @@ -264,7 +447,8 @@ LOG(INFO) << " (but no valid presentation contexts)"; } - return new CommandDispatcher(server, assoc); + IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL; + return new CommandDispatcher(server, assoc, callingIP, callingTitle, filter); } bool CommandDispatcher::Step() @@ -311,56 +495,94 @@ // Reset the client timeout counter elapsedTimeSinceLastCommand_ = 0; - // in case we received a valid message, process this command - // note that storescp can only process a C-ECHO-RQ and a C-STORE-RQ + // Convert the type of request to Orthanc's internal type + bool supported = false; + DicomRequestType request; switch (msg.CommandField) { - case DIMSE_C_ECHO_RQ: - // process C-ECHO-Request - cond = EchoScp(assoc_, &msg, presID); - break; + case DIMSE_C_ECHO_RQ: + request = DicomRequestType_Echo; + supported = true; + break; + + case DIMSE_C_STORE_RQ: + request = DicomRequestType_Store; + supported = true; + break; + + case DIMSE_C_MOVE_RQ: + request = DicomRequestType_Move; + supported = true; + break; + + case DIMSE_C_FIND_RQ: + request = DicomRequestType_Find; + supported = true; + break; - case DIMSE_C_STORE_RQ: - // process C-STORE-Request - if (server_.HasStoreRequestHandlerFactory()) - { - std::auto_ptr<IStoreRequestHandler> handler - (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); - cond = Internals::storeScp(assoc_, &msg, presID, *handler); - } - else - cond = DIMSE_BADCOMMANDTYPE; // Should never happen - break; + default: + // we cannot handle this kind of message + cond = DIMSE_BADCOMMANDTYPE; + LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; + break; + } + - case DIMSE_C_MOVE_RQ: - // process C-MOVE-Request - if (server_.HasMoveRequestHandlerFactory()) + // Check whether this request is allowed by the security filter + if (supported && + filter_ != NULL && + !filter_->IsAllowedRequest(callingIP_, callingAETitle_, request)) + { + LOG(ERROR) << EnumerationToString(request) + << " requests are disallowed for the AET \"" + << callingAETitle_ << "\""; + cond = DIMSE_BADCOMMANDTYPE; + supported = false; + } + + // in case we received a supported message, process this command + if (supported) + { + // If anything goes wrong, there will be a "BADCOMMANDTYPE" answer + cond = DIMSE_BADCOMMANDTYPE; + + switch (request) { - std::auto_ptr<IMoveRequestHandler> handler - (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); - cond = Internals::moveScp(assoc_, &msg, presID, *handler); - } - else - cond = DIMSE_BADCOMMANDTYPE; // Should never happen - break; + case DicomRequestType_Echo: + cond = EchoScp(assoc_, &msg, presID); + break; + + case DicomRequestType_Store: + if (server_.HasStoreRequestHandlerFactory()) // Should always be true + { + std::auto_ptr<IStoreRequestHandler> handler + (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); + cond = Internals::storeScp(assoc_, &msg, presID, *handler); + } + break; - case DIMSE_C_FIND_RQ: - // process C-FIND-Request - if (server_.HasFindRequestHandlerFactory()) - { - std::auto_ptr<IFindRequestHandler> handler - (server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); - cond = Internals::findScp(assoc_, &msg, presID, *handler); + case DicomRequestType_Move: + if (server_.HasMoveRequestHandlerFactory()) // Should always be true + { + std::auto_ptr<IMoveRequestHandler> handler + (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); + cond = Internals::moveScp(assoc_, &msg, presID, *handler); + } + break; + + case DicomRequestType_Find: + if (server_.HasFindRequestHandlerFactory()) // Should always be true + { + std::auto_ptr<IFindRequestHandler> handler + (server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); + cond = Internals::findScp(assoc_, &msg, presID, *handler, callingAETitle_); + } + break; + + default: + // Should never happen + break; } - else - cond = DIMSE_BADCOMMANDTYPE; // Should never happen - break; - - default: - // we cannot handle this kind of message - cond = DIMSE_BADCOMMANDTYPE; - LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; - break; } } else @@ -368,6 +590,8 @@ // Bad status, which indicates the closing of the connection by // the peer or a network error finished = true; + + LOG(INFO) << cond.text(); } if (finished)
--- a/OrthancServer/Internals/CommandDispatcher.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/Internals/CommandDispatcher.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -50,12 +50,21 @@ uint32_t elapsedTimeSinceLastCommand_; const DicomServer& server_; T_ASC_Association* assoc_; + std::string callingIP_; + std::string callingAETitle_; + IApplicationEntityFilter* filter_; public: CommandDispatcher(const DicomServer& server, - T_ASC_Association* assoc) : + T_ASC_Association* assoc, + const std::string& callingIP, + const std::string& callingAETitle, + IApplicationEntityFilter* filter) : server_(server), - assoc_(assoc) + assoc_(assoc), + callingIP_(callingIP), + callingAETitle_(callingAETitle), + filter_(filter) { clientTimeout_ = server.GetClientTimeout(); elapsedTimeSinceLastCommand_ = 0;
--- a/OrthancServer/Internals/FindScp.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/Internals/FindScp.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -49,6 +49,7 @@ DicomMap input_; DicomFindAnswers answers_; DcmDataset* lastRequest_; + const std::string* callingAETitle_; }; @@ -67,14 +68,14 @@ bzero(response, sizeof(T_DIMSE_C_FindRSP)); *statusDetail = NULL; - FindScpData& data = *(FindScpData*) callbackData; + FindScpData& data = *reinterpret_cast<FindScpData*>(callbackData); if (data.lastRequest_ == NULL) { FromDcmtkBridge::Convert(data.input_, *requestIdentifiers); try { - data.handler_->Handle(data.input_, data.answers_); + data.handler_->Handle(data.answers_, data.input_, *data.callingAETitle_); } catch (OrthancException& e) { @@ -94,7 +95,7 @@ *responseIdentifiers = NULL; return; } - + if (responseCount <= static_cast<int>(data.answers_.GetSize())) { response->DimseStatus = STATUS_Pending; @@ -112,11 +113,13 @@ OFCondition Internals::findScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID, - IFindRequestHandler& handler) + IFindRequestHandler& handler, + const std::string& callingAETitle) { FindScpData data; data.lastRequest_ = NULL; data.handler_ = &handler; + data.callingAETitle_ = &callingAETitle; OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, FindScpCallback, &data,
--- a/OrthancServer/Internals/FindScp.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/Internals/FindScp.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -43,6 +43,7 @@ OFCondition findScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID, - IFindRequestHandler& handler); + IFindRequestHandler& handler, + const std::string& callingAETitle); } }
--- a/OrthancServer/Internals/MoveScp.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/Internals/MoveScp.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -74,7 +74,7 @@ *statusDetail = NULL; *responseIdentifiers = NULL; - MoveScpData& data = *(MoveScpData*) callbackData; + MoveScpData& data = *reinterpret_cast<MoveScpData*>(callbackData); if (data.lastRequest_ == NULL) { FromDcmtkBridge::Convert(data.input_, *requestIdentifiers); @@ -82,6 +82,14 @@ try { data.iterator_.reset(data.handler_->Handle(data.target_, data.input_)); + + if (data.iterator_.get() == NULL) + { + // Internal error! + response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; + return; + } + data.subOperationCount_ = data.iterator_->GetSubOperationCount(); data.failureCount_ = 0; data.warningCount_ = 0;
--- a/OrthancServer/Internals/MoveScp.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/Internals/MoveScp.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/StoreScp.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/Internals/StoreScp.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -33,6 +33,7 @@ #include "StoreScp.h" #include "../FromDcmtkBridge.h" +#include "../ServerToolbox.h" #include "../ToDcmtkBridge.h" #include "../../Core/OrthancException.h" @@ -57,7 +58,7 @@ uint32_t messageID; }; - + static void storeScpCallback( void *callbackData, @@ -117,7 +118,7 @@ FromDcmtkBridge::Convert(summary, **imageDataSet); FromDcmtkBridge::ToJson(dicomJson, **imageDataSet); - if (FromDcmtkBridge::SaveToMemoryBuffer(buffer, *imageDataSet) < 0) + if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, *imageDataSet)) { LOG(ERROR) << "cannot write DICOM file to memory"; rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; @@ -155,7 +156,15 @@ catch (OrthancException& e) { rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; - LOG(ERROR) << "Exception while storing DICOM: " << e.What(); + + if (e.GetErrorCode() == ErrorCode_InexistentTag) + { + LogMissingRequiredTag(summary); + } + else + { + LOG(ERROR) << "Exception while storing DICOM: " << e.What(); + } } } }
--- a/OrthancServer/Internals/StoreScp.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/Internals/StoreScp.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,601 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + +#include "OrthancFindRequestHandler.h" + +#include <glog/logging.h> +#include <boost/regex.hpp> + +#include "../Core/DicomFormat/DicomArray.h" +#include "ServerToolbox.h" +#include "OrthancInitialization.h" + +namespace Orthanc +{ + static bool IsWildcard(const std::string& constraint) + { + return (constraint.find('-') != std::string::npos || + constraint.find('*') != std::string::npos || + constraint.find('\\') != std::string::npos || + constraint.find('?') != std::string::npos); + } + + static bool ApplyRangeConstraint(const std::string& value, + const std::string& constraint) + { + size_t separator = constraint.find('-'); + std::string lower, upper, v; + Toolbox::ToLowerCase(lower, constraint.substr(0, separator)); + Toolbox::ToLowerCase(upper, constraint.substr(separator + 1)); + Toolbox::ToLowerCase(v, value); + + if (lower.size() == 0 && upper.size() == 0) + { + return false; + } + + if (lower.size() == 0) + { + return v <= upper; + } + + if (upper.size() == 0) + { + return v >= lower; + } + + return (v >= lower && v <= upper); + } + + + static bool ApplyListConstraint(const std::string& value, + const std::string& constraint) + { + std::string v1; + Toolbox::ToLowerCase(v1, value); + + std::vector<std::string> items; + Toolbox::TokenizeString(items, constraint, '\\'); + + for (size_t i = 0; i < items.size(); i++) + { + std::string lower; + Toolbox::ToLowerCase(lower, items[i]); + if (lower == v1) + { + return true; + } + } + + return false; + } + + + static bool Matches(const std::string& value, + const std::string& constraint) + { + // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained + // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html + + if (constraint.find('-') != std::string::npos) + { + return ApplyRangeConstraint(value, constraint); + } + + if (constraint.find('\\') != std::string::npos) + { + return ApplyListConstraint(value, constraint); + } + + if (constraint.find('*') != std::string::npos || + constraint.find('?') != std::string::npos) + { + // TODO - Cache the constructed regular expression + boost::regex pattern(Toolbox::WildcardToRegularExpression(constraint), + boost::regex::icase /* case insensitive search */); + return boost::regex_match(value, pattern); + } + else + { + std::string v, c; + Toolbox::ToLowerCase(v, value); + Toolbox::ToLowerCase(c, constraint); + return v == c; + } + } + + + static bool LookupOneInstance(std::string& result, + ServerIndex& index, + const std::string& id, + ResourceType type) + { + if (type == ResourceType_Instance) + { + result = id; + return true; + } + + std::string childId; + + { + std::list<std::string> children; + index.GetChildInstances(children, id); + + if (children.empty()) + { + return false; + } + + childId = children.front(); + } + + return LookupOneInstance(result, index, childId, GetChildResourceType(type)); + } + + + static bool Matches(const Json::Value& resource, + const DicomArray& query) + { + for (size_t i = 0; i < query.GetSize(); i++) + { + if (query.GetElement(i).GetValue().IsNull() || + query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL || + query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET || + query.GetElement(i).GetTag() == DICOM_TAG_MODALITIES_IN_STUDY) + { + continue; + } + + std::string tag = query.GetElement(i).GetTag().Format(); + std::string value; + if (resource.isMember(tag)) + { + value = resource.get(tag, Json::arrayValue).get("Value", "").asString(); + } + + if (!Matches(value, query.GetElement(i).GetValue().AsString())) + { + return false; + } + } + + return true; + } + + + static void AddAnswer(DicomFindAnswers& answers, + const Json::Value& resource, + const DicomArray& query) + { + DicomMap result; + + for (size_t i = 0; i < query.GetSize(); i++) + { + if (query.GetElement(i).GetTag() != DICOM_TAG_QUERY_RETRIEVE_LEVEL && + query.GetElement(i).GetTag() != DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + std::string tag = query.GetElement(i).GetTag().Format(); + std::string value; + if (resource.isMember(tag)) + { + value = resource.get(tag, Json::arrayValue).get("Value", "").asString(); + result.SetValue(query.GetElement(i).GetTag(), value); + } + else + { + result.SetValue(query.GetElement(i).GetTag(), ""); + } + } + } + + answers.Add(result); + } + + + static bool ApplyModalitiesInStudyFilter(std::list<std::string>& filteredStudies, + const std::list<std::string>& studies, + const DicomMap& input, + ServerIndex& index) + { + filteredStudies.clear(); + + const DicomValue& v = input.GetValue(DICOM_TAG_MODALITIES_IN_STUDY); + if (v.IsNull()) + { + return false; + } + + // Move the allowed modalities into a "std::set" + std::vector<std::string> tmp; + Toolbox::TokenizeString(tmp, v.AsString(), '\\'); + + std::set<std::string> modalities; + for (size_t i = 0; i < tmp.size(); i++) + { + modalities.insert(tmp[i]); + } + + // Loop over the studies + for (std::list<std::string>::const_iterator + it = studies.begin(); it != studies.end(); ++it) + { + try + { + // We are considering a single study. Check whether one of + // its child series matches one of the modalities. + Json::Value study; + if (index.LookupResource(study, *it, ResourceType_Study)) + { + // Loop over the series of the considered study. + for (Json::Value::ArrayIndex j = 0; j < study["Series"].size(); j++) // (*) + { + Json::Value series; + if (index.LookupResource(series, study["Series"][j].asString(), ResourceType_Series)) + { + // Get the modality of this series + if (series["MainDicomTags"].isMember("Modality")) + { + std::string modality = series["MainDicomTags"]["Modality"].asString(); + if (modalities.find(modality) != modalities.end()) + { + // This series of the considered study matches one + // of the required modalities. Take the study into + // consideration for future filtering. + filteredStudies.push_back(*it); + + // We have finished considering this study. Break the study loop at (*). + break; + } + } + } + } + } + } + catch (OrthancException&) + { + // This resource has probably been deleted during the find request + } + } + + return true; + } + + + namespace + { + class CandidateResources + { + private: + ServerIndex& index_; + ModalityManufacturer manufacturer_; + ResourceType level_; + bool isFilterApplied_; + std::set<std::string> filtered_; + + static void ListToSet(std::set<std::string>& target, + const std::list<std::string>& source) + { + for (std::list<std::string>::const_iterator + it = source.begin(); it != source.end(); ++it) + { + target.insert(*it); + } + } + + void ApplyExactFilter(const DicomTag& tag, const std::string& value) + { + LOG(INFO) << "Applying exact filter on tag " + << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")"; + + std::list<std::string> resources; + index_.LookupTagValue(resources, tag, value, level_); + + if (isFilterApplied_) + { + std::set<std::string> s; + ListToSet(s, resources); + + std::set<std::string> tmp = filtered_; + filtered_.clear(); + + for (std::set<std::string>::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) + { + if (s.find(*it) != s.end()) + { + filtered_.insert(*it); + } + } + } + else + { + assert(filtered_.empty()); + isFilterApplied_ = true; + ListToSet(filtered_, resources); + } + } + + public: + CandidateResources(ServerIndex& index, + ModalityManufacturer manufacturer) : + index_(index), + manufacturer_(manufacturer), + level_(ResourceType_Patient), + isFilterApplied_(false) + { + } + + ResourceType GetLevel() const + { + return level_; + } + + void GoDown() + { + assert(level_ != ResourceType_Instance); + + if (isFilterApplied_) + { + std::set<std::string> tmp = filtered_; + + filtered_.clear(); + + for (std::set<std::string>::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) + { + std::list<std::string> children; + index_.GetChildren(children, *it); + ListToSet(filtered_, children); + } + } + + switch (level_) + { + case ResourceType_Patient: + level_ = ResourceType_Study; + break; + + case ResourceType_Study: + level_ = ResourceType_Series; + break; + + case ResourceType_Series: + level_ = ResourceType_Instance; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + void Flatten(std::list<std::string>& resources) const + { + resources.clear(); + + if (isFilterApplied_) + { + for (std::set<std::string>::const_iterator + it = filtered_.begin(); it != filtered_.end(); ++it) + { + resources.push_back(*it); + } + } + else + { + Json::Value tmp; + index_.GetAllUuids(tmp, level_); + for (Json::Value::ArrayIndex i = 0; i < tmp.size(); i++) + { + resources.push_back(tmp[i].asString()); + } + } + } + + void ApplyFilter(const DicomTag& tag, const DicomMap& query) + { + if (query.HasTag(tag)) + { + const DicomValue& value = query.GetValue(tag); + if (!value.IsNull()) + { + std::string value = query.GetValue(tag).AsString(); + if (!IsWildcard(value)) + { + ApplyExactFilter(tag, value); + } + } + } + } + }; + } + + + void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, + const DicomMap& input, + const std::string& callingAETitle) + { + /** + * Retrieve the manufacturer of this modality. + **/ + + ModalityManufacturer manufacturer; + + { + std::string symbolicName, address; + int port; + + if (!LookupDicomModalityUsingAETitle(callingAETitle, symbolicName, address, port, manufacturer)) + { + throw OrthancException("Unknown modality"); + } + } + + + /** + * Retrieve the query level. + **/ + + const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); + if (levelTmp == NULL) + { + throw OrthancException(ErrorCode_BadRequest); + } + + ResourceType level = StringToResourceType(levelTmp->AsString().c_str()); + + if (level != ResourceType_Patient && + level != ResourceType_Study && + level != ResourceType_Series && + level != ResourceType_Instance) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + + DicomArray query(input); + LOG(INFO) << "DICOM C-Find request at level: " << EnumerationToString(level); + + for (size_t i = 0; i < query.GetSize(); i++) + { + if (!query.GetElement(i).GetValue().IsNull()) + { + LOG(INFO) << " " << query.GetElement(i).GetTag() + << " " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag()) + << " = " << query.GetElement(i).GetValue().AsString(); + } + } + + + /** + * Retrieve the candidate resources for this query level. Whenever + * possible, we avoid returning ALL the resources for this query + * level, as it would imply reading the JSON file on the harddisk + * for each of them. + **/ + + CandidateResources candidates(context_.GetIndex(), manufacturer); + + for (;;) + { + switch (candidates.GetLevel()) + { + case ResourceType_Patient: + candidates.ApplyFilter(DICOM_TAG_PATIENT_ID, input); + break; + + case ResourceType_Study: + candidates.ApplyFilter(DICOM_TAG_STUDY_INSTANCE_UID, input); + candidates.ApplyFilter(DICOM_TAG_ACCESSION_NUMBER, input); + break; + + case ResourceType_Series: + candidates.ApplyFilter(DICOM_TAG_SERIES_INSTANCE_UID, input); + break; + + case ResourceType_Instance: + candidates.ApplyFilter(DICOM_TAG_SOP_INSTANCE_UID, input); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (candidates.GetLevel() == level) + { + break; + } + + candidates.GoDown(); + } + + std::list<std::string> resources; + candidates.Flatten(resources); + + LOG(INFO) << "Number of candidate resources after exact filtering: " << resources.size(); + + /** + * Apply filtering on modalities for studies, if asked (this is an + * extension to standard DICOM) + * http://www.medicalconnections.co.uk/kb/Filtering_on_and_Retrieving_the_Modality_in_a_C_FIND + **/ + + if (level == ResourceType_Study && + input.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) + { + std::list<std::string> filtered; + if (ApplyModalitiesInStudyFilter(filtered, resources, input, context_.GetIndex())) + { + resources = filtered; + } + } + + + /** + * Loop over all the resources for this query level. + **/ + + for (std::list<std::string>::const_iterator + resource = resources.begin(); resource != resources.end(); ++resource) + { + try + { + std::string instance; + if (LookupOneInstance(instance, context_.GetIndex(), *resource, level)) + { + Json::Value info; + context_.ReadJson(info, instance); + + if (Matches(info, query)) + { + AddAnswer(answers, info, query); + } + } + } + catch (OrthancException&) + { + // This resource has probably been deleted during the find request + } + } + } +} + + + +/** + * TODO : Case-insensitive match for PN value representation (Patient + * Name). Case-senstive match for all the other value representations. + * + * Reference: DICOM PS 3.4 + * - C.2.2.2.1 ("Single Value Matching") + * - C.2.2.2.4 ("Wild Card Matching") + * http://medical.nema.org/Dicom/2011/11_04pu.pdf ( + **/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancFindRequestHandler.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,55 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + +#pragma once + +#include "DicomProtocol/IFindRequestHandler.h" + +#include "ServerContext.h" + +namespace Orthanc +{ + class OrthancFindRequestHandler : public IFindRequestHandler + { + private: + ServerContext& context_; + + public: + OrthancFindRequestHandler(ServerContext& context) : + context_(context) + { + } + + virtual void Handle(DicomFindAnswers& answers, + const DicomMap& input, + const std::string& callingAETitle); + }; +}
--- a/OrthancServer/OrthancInitialization.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,8 +32,11 @@ #include "OrthancInitialization.h" +#include "../Core/HttpClient.h" #include "../Core/OrthancException.h" #include "../Core/Toolbox.h" +#include "DicomProtocol/DicomServer.h" +#include "ServerEnumerations.h" #include <boost/lexical_cast.hpp> #include <boost/filesystem.hpp> @@ -43,13 +46,10 @@ namespace Orthanc { - static const char* CONFIGURATION_FILE = "Configuration.json"; - static boost::mutex globalMutex_; static std::auto_ptr<Json::Value> configuration_; static boost::filesystem::path defaultDirectory_; - static void ReadGlobalConfiguration(const char* configurationFile) { configuration_.reset(new Json::Value); @@ -60,51 +60,31 @@ { Toolbox::ReadFile(content, configurationFile); defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path(); - LOG(INFO) << "Using the configuration from: " << configurationFile; + LOG(WARNING) << "Using the configuration from: " << configurationFile; } else { -#if 0 && ORTHANC_STANDALONE == 1 && defined(__linux) - // Unused anymore - // Under Linux, try and open "../../etc/orthanc/Configuration.json" +#if ORTHANC_STANDALONE == 1 + // No default path for the standalone configuration + LOG(WARNING) << "Using the default Orthanc configuration"; + return; + +#else + // In a non-standalone build, we use the + // "Resources/Configuration.json" from the Orthanc source code + try { - boost::filesystem::path p = Toolbox::GetDirectoryOfExecutable(); - p = p.parent_path().parent_path(); - p /= "etc"; - p /= "orthanc"; - p /= CONFIGURATION_FILE; - + boost::filesystem::path p = ORTHANC_PATH; + p /= "Resources"; + p /= "Configuration.json"; Toolbox::ReadFile(content, p.string()); - LOG(INFO) << "Using the configuration from: " << p.string(); + LOG(WARNING) << "Using the configuration from: " << p.string(); } catch (OrthancException&) { // No configuration file found, give up with empty configuration - LOG(INFO) << "Using the default Orthanc configuration"; - return; - } - -#elif ORTHANC_STANDALONE == 1 - // No default path for the standalone configuration - LOG(INFO) << "Using the default Orthanc configuration"; - return; - -#else - // In a non-standalone build, we use the - // "Resources/Configuration.json" from the Orthanc distribution - try - { - boost::filesystem::path p = ORTHANC_PATH; - p /= "Resources"; - p /= CONFIGURATION_FILE; - Toolbox::ReadFile(content, p.string()); - LOG(INFO) << "Using the configuration from: " << p.string(); - } - catch (OrthancException&) - { - // No configuration file found, give up with empty configuration - LOG(INFO) << "Using the default Orthanc configuration"; + LOG(WARNING) << "Using the default Orthanc configuration"; return; } #endif @@ -118,12 +98,88 @@ } + static void RegisterUserMetadata() + { + if (configuration_->isMember("UserMetadata")) + { + const Json::Value& parameter = (*configuration_) ["UserMetadata"]; + + Json::Value::Members members = parameter.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString(); + LOG(INFO) << "Registering user-defined metadata: " << info; + + if (!parameter[members[i]].asBool()) + { + LOG(ERROR) << "Not a number in this user-defined metadata: " << info; + throw OrthancException(ErrorCode_BadParameterType); + } + + int metadata = parameter[members[i]].asInt(); + + try + { + RegisterUserMetadata(metadata, members[i]); + } + catch (OrthancException&) + { + LOG(ERROR) << "Cannot register this user-defined metadata: " << info; + throw; + } + } + } + } + + + static void RegisterUserContentType() + { + if (configuration_->isMember("UserContentType")) + { + const Json::Value& parameter = (*configuration_) ["UserContentType"]; + + Json::Value::Members members = parameter.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString(); + LOG(INFO) << "Registering user-defined attachment type: " << info; + + if (!parameter[members[i]].asBool()) + { + LOG(ERROR) << "Not a number in this user-defined attachment type: " << info; + throw OrthancException(ErrorCode_BadParameterType); + } + + int contentType = parameter[members[i]].asInt(); + + try + { + RegisterUserContentType(contentType, members[i]); + } + catch (OrthancException&) + { + LOG(ERROR) << "Cannot register this user-defined attachment type: " << info; + throw; + } + } + } + } + + void OrthancInitialize(const char* configurationFile) { boost::mutex::scoped_lock lock(globalMutex_); + + InitializeServerEnumerations(); defaultDirectory_ = boost::filesystem::current_path(); ReadGlobalConfiguration(configurationFile); - curl_global_init(CURL_GLOBAL_ALL); + + HttpClient::GlobalInitialize(); + + RegisterUserMetadata(); + RegisterUserContentType(); + + DicomServer::InitializeDictionary(); } @@ -131,7 +187,7 @@ void OrthancFinalize() { boost::mutex::scoped_lock lock(globalMutex_); - curl_global_cleanup(); + HttpClient::GlobalFinalize(); configuration_.reset(NULL); } @@ -186,69 +242,182 @@ - void GetDicomModality(const std::string& name, - std::string& aet, - std::string& address, - int& port) + void GetDicomModalityUsingSymbolicName(const std::string& name, + std::string& aet, + std::string& address, + int& port, + ModalityManufacturer& manufacturer) { boost::mutex::scoped_lock lock(globalMutex_); if (!configuration_->isMember("DicomModalities")) { - throw OrthancException(""); + throw OrthancException(ErrorCode_BadFileFormat); } const Json::Value& modalities = (*configuration_) ["DicomModalities"]; if (modalities.type() != Json::objectValue || - !modalities.isMember(name)) + !modalities.isMember(name) || + (modalities[name].size() != 3 && modalities[name].size() != 4)) { - throw OrthancException(""); + throw OrthancException(ErrorCode_BadFileFormat); } try { aet = modalities[name].get(0u, "").asString(); address = modalities[name].get(1u, "").asString(); - port = modalities[name].get(2u, "").asInt(); + + const Json::Value& portValue = modalities[name].get(2u, ""); + try + { + port = portValue.asInt(); + } + catch (std::runtime_error /* error inside JsonCpp */) + { + try + { + port = boost::lexical_cast<int>(portValue.asString()); + } + catch (boost::bad_lexical_cast) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + if (modalities[name].size() == 4) + { + manufacturer = StringToModalityManufacturer(modalities[name].get(3u, "").asString()); + } + else + { + manufacturer = ModalityManufacturer_Generic; + } } - catch (...) + catch (OrthancException& e) { - throw OrthancException("Badly formatted DICOM modality"); + LOG(ERROR) << "Syntax error in the definition of modality \"" << name + << "\". Please check your configuration file."; + throw e; } } - void GetListOfDicomModalities(std::set<std::string>& target) + void GetOrthancPeer(const std::string& name, + std::string& url, + std::string& username, + std::string& password) + { + boost::mutex::scoped_lock lock(globalMutex_); + + if (!configuration_->isMember("OrthancPeers")) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + try + { + const Json::Value& modalities = (*configuration_) ["OrthancPeers"]; + if (modalities.type() != Json::objectValue || + !modalities.isMember(name)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + try + { + url = modalities[name].get(0u, "").asString(); + + if (modalities[name].size() == 1) + { + username = ""; + password = ""; + } + else if (modalities[name].size() == 3) + { + username = modalities[name].get(1u, "").asString(); + password = modalities[name].get(2u, "").asString(); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + catch (...) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + if (url.size() != 0 && url[url.size() - 1] != '/') + { + url += '/'; + } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Syntax error in the definition of peer \"" << name + << "\". Please check your configuration file."; + throw e; + } + } + + + static bool ReadKeys(std::set<std::string>& target, + const char* parameter, + bool onlyAlphanumeric) { boost::mutex::scoped_lock lock(globalMutex_); target.clear(); - if (!configuration_->isMember("DicomModalities")) + if (!configuration_->isMember(parameter)) { - return; + return true; } - const Json::Value& modalities = (*configuration_) ["DicomModalities"]; + const Json::Value& modalities = (*configuration_) [parameter]; if (modalities.type() != Json::objectValue) { - throw OrthancException("Badly formatted list of DICOM modalities"); + throw OrthancException(ErrorCode_BadFileFormat); } Json::Value::Members members = modalities.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { - for (size_t j = 0; j < members[i].size(); j++) + if (onlyAlphanumeric) { - if (!isalnum(members[i][j]) && members[i][j] != '-') + for (size_t j = 0; j < members[i].size(); j++) { - throw OrthancException("Only alphanumeric and dash characters are allowed in the names of the modalities"); + if (!isalnum(members[i][j]) && members[i][j] != '-') + { + return false; + } } } target.insert(members[i]); } + + return true; + } + + + void GetListOfDicomModalities(std::set<std::string>& target) + { + if (!ReadKeys(target, "DicomModalities", true)) + { + throw OrthancException("Only alphanumeric and dash characters are allowed in the names of the modalities"); + } + } + + + void GetListOfOrthancPeers(std::set<std::string>& target) + { + if (!ReadKeys(target, "OrthancPeers", true)) + { + throw OrthancException("Only alphanumeric and dash characters are allowed in the names of Orthanc peers"); + } } @@ -280,10 +449,36 @@ } + std::string InterpretRelativePath(const std::string& baseDirectory, + const std::string& relativePath) + { + boost::filesystem::path base(baseDirectory); + boost::filesystem::path relative(relativePath); + + /** + The following lines should be equivalent to this one: + + return (base / relative).string(); + + However, for some unknown reason, some versions of Boost do not + make the proper path resolution when "baseDirectory" is an + absolute path. So, a hack is used below. + **/ + + if (relative.is_absolute()) + { + return relative.string(); + } + else + { + return (base / relative).string(); + } + } + std::string InterpretStringParameterAsPath(const std::string& parameter) { boost::mutex::scoped_lock lock(globalMutex_); - return (defaultDirectory_ / parameter).string(); + return InterpretRelativePath(defaultDirectory_.string(), parameter); } @@ -311,4 +506,106 @@ target.push_back(lst[i].asString()); } } + + + void ConnectToModalityUsingSymbolicName(DicomUserConnection& connection, + const std::string& name) + { + std::string aet, address; + int port; + ModalityManufacturer manufacturer; + GetDicomModalityUsingSymbolicName(name, aet, address, port, manufacturer); + + LOG(WARNING) << "Connecting to remote DICOM modality: AET=" << aet << ", address=" << address << ", port=" << port; + + connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); + connection.SetDistantApplicationEntityTitle(aet); + connection.SetDistantHost(address); + connection.SetDistantPort(port); + connection.SetDistantManufacturer(manufacturer); + connection.Open(); + } + + + bool IsSameAETitle(const std::string& aet1, + const std::string& aet2) + { + if (GetGlobalBoolParameter("StrictAetComparison", false)) + { + // Case-sensitive matching + return aet1 == aet2; + } + else + { + // Case-insensitive matching (default) + std::string tmp1, tmp2; + Toolbox::ToLowerCase(tmp1, aet1); + Toolbox::ToLowerCase(tmp2, aet2); + return tmp1 == tmp2; + } + } + + + bool LookupDicomModalityUsingAETitle(const std::string& aet, + std::string& symbolicName, + std::string& address, + int& port, + ModalityManufacturer& manufacturer) + { + std::set<std::string> modalities; + GetListOfDicomModalities(modalities); + + for (std::set<std::string>::const_iterator + it = modalities.begin(); it != modalities.end(); ++it) + { + try + { + std::string thisAet; + GetDicomModalityUsingSymbolicName(*it, thisAet, address, port, manufacturer); + + if (IsSameAETitle(aet, thisAet)) + { + return true; + } + } + catch (OrthancException&) + { + } + } + + return false; + } + + + bool IsKnownAETitle(const std::string& aet) + { + std::string symbolicName, address; + int port; + ModalityManufacturer manufacturer; + + return LookupDicomModalityUsingAETitle(aet, symbolicName, address, port, manufacturer); + } + + + void ConnectToModalityUsingAETitle(DicomUserConnection& connection, + const std::string& aet) + { + std::string symbolicName, address; + int port; + ModalityManufacturer manufacturer; + + if (!LookupDicomModalityUsingAETitle(aet, symbolicName, address, port, manufacturer)) + { + throw OrthancException("Unknown modality: " + aet); + } + + LOG(WARNING) << "Connecting to remote DICOM modality: AET=" << aet << ", address=" << address << ", port=" << port; + + connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); + connection.SetDistantApplicationEntityTitle(aet); + connection.SetDistantHost(address); + connection.SetDistantPort(port); + connection.SetDistantManufacturer(manufacturer); + connection.Open(); + } }
--- a/OrthancServer/OrthancInitialization.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/OrthancInitialization.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -37,6 +37,8 @@ #include <json/json.h> #include <stdint.h> #include "../Core/HttpServer/MongooseServer.h" +#include "DicomProtocol/DicomUserConnection.h" +#include "ServerEnumerations.h" namespace Orthanc { @@ -53,17 +55,45 @@ bool GetGlobalBoolParameter(const std::string& parameter, bool defaultValue); - void GetDicomModality(const std::string& name, - std::string& aet, - std::string& address, - int& port); + void GetDicomModalityUsingSymbolicName(const std::string& name, + std::string& aet, + std::string& address, + int& port, + ModalityManufacturer& manufacturer); + + bool LookupDicomModalityUsingAETitle(const std::string& aet, + std::string& symbolicName, + std::string& address, + int& port, + ModalityManufacturer& manufacturer); + + void GetOrthancPeer(const std::string& name, + std::string& url, + std::string& username, + std::string& password); void GetListOfDicomModalities(std::set<std::string>& target); + void GetListOfOrthancPeers(std::set<std::string>& target); + void SetupRegisteredUsers(MongooseServer& httpServer); + std::string InterpretRelativePath(const std::string& baseDirectory, + const std::string& relativePath); + std::string InterpretStringParameterAsPath(const std::string& parameter); void GetGlobalListOfStringsParameter(std::list<std::string>& target, const std::string& key); + + void ConnectToModalityUsingSymbolicName(DicomUserConnection& connection, + const std::string& name); + + void ConnectToModalityUsingAETitle(DicomUserConnection& connection, + const std::string& aet); + + bool IsKnownAETitle(const std::string& aet); + + bool IsSameAETitle(const std::string& aet1, + const std::string& aet2); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,178 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + +#include "OrthancMoveRequestHandler.h" + +#include <glog/logging.h> + +#include "DicomProtocol/DicomUserConnection.h" +#include "OrthancInitialization.h" + +namespace Orthanc +{ + namespace + { + // Anonymous namespace to avoid clashes between compilation modules + + class OrthancMoveRequestIterator : public IMoveRequestIterator + { + private: + ServerContext& context_; + std::vector<std::string> instances_; + DicomUserConnection connection_; + size_t position_; + + public: + OrthancMoveRequestIterator(ServerContext& context, + const std::string& target, + const std::string& publicId) : + context_(context), + position_(0) + { + LOG(INFO) << "Sending resource " << publicId << " to modality \"" << target << "\""; + + std::list<std::string> tmp; + context_.GetIndex().GetChildInstances(tmp, publicId); + + instances_.reserve(tmp.size()); + for (std::list<std::string>::iterator it = tmp.begin(); it != tmp.end(); ++it) + { + instances_.push_back(*it); + } + + ConnectToModalityUsingAETitle(connection_, target); + } + + virtual unsigned int GetSubOperationCount() const + { + return instances_.size(); + } + + virtual Status DoNext() + { + if (position_ >= instances_.size()) + { + return Status_Failure; + } + + const std::string& id = instances_[position_++]; + + std::string dicom; + context_.ReadFile(dicom, id, FileContentType_Dicom); + connection_.Store(dicom); + + return Status_Success; + } + }; + } + + + bool OrthancMoveRequestHandler::LookupResource(std::string& publicId, + DicomTag tag, + const DicomMap& input) + { + if (!input.HasTag(tag)) + { + return false; + } + + std::string value = input.GetValue(tag).AsString(); + + std::list<std::string> ids; + context_.GetIndex().LookupTagValue(ids, tag, value); + + if (ids.size() != 1) + { + return false; + } + else + { + publicId = ids.front(); + return true; + } + } + + + IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& target, + const DicomMap& input) + { + LOG(WARNING) << "Move-SCU request received for AET \"" << target << "\""; + + + /** + * Retrieve the query level. + **/ + + const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); + if (levelTmp == NULL) + { + throw OrthancException(ErrorCode_BadRequest); + } + + ResourceType level = StringToResourceType(levelTmp->AsString().c_str()); + + /** + * Lookup for the resource to be sent. + **/ + + bool ok; + std::string publicId; + + switch (level) + { + case ResourceType_Patient: + ok = LookupResource(publicId, DICOM_TAG_PATIENT_ID, input); + break; + + case ResourceType_Study: + ok = LookupResource(publicId, DICOM_TAG_STUDY_INSTANCE_UID, input); + break; + + case ResourceType_Series: + ok = LookupResource(publicId, DICOM_TAG_SERIES_INSTANCE_UID, input); + break; + + case ResourceType_Instance: + ok = LookupResource(publicId, DICOM_TAG_SOP_INSTANCE_UID, input); + break; + + default: + ok = false; + } + + if (!ok) + { + throw OrthancException(ErrorCode_BadRequest); + } + + return new OrthancMoveRequestIterator(context_, target, publicId); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancMoveRequestHandler.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,57 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + +#pragma once + +#include "DicomProtocol/IMoveRequestHandler.h" +#include "ServerContext.h" + +namespace Orthanc +{ + class OrthancMoveRequestHandler : public IMoveRequestHandler + { + private: + ServerContext& context_; + + bool LookupResource(std::string& publicId, + DicomTag tag, + const DicomMap& input); + + public: + OrthancMoveRequestHandler(ServerContext& context) : + context_(context) + { + } + + virtual IMoveRequestIterator* Handle(const std::string& target, + const DicomMap& input); + }; +}
--- a/OrthancServer/OrthancRestApi.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1542 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 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 <http://www.gnu.org/licenses/>. - **/ - - -#include "OrthancRestApi.h" - -#include "../Core/HttpServer/FilesystemHttpSender.h" -#include "../Core/Uuid.h" -#include "../Core/Compression/HierarchicalZipWriter.h" -#include "DicomProtocol/DicomUserConnection.h" -#include "FromDcmtkBridge.h" -#include "OrthancInitialization.h" -#include "ServerToolbox.h" - -#include <dcmtk/dcmdata/dcistrmb.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <boost/lexical_cast.hpp> -#include <glog/logging.h> - - -#define RETRIEVE_CONTEXT(call) \ - OrthancRestApi& contextApi = \ - dynamic_cast<OrthancRestApi&>(call.GetContext()); \ - ServerContext& context = contextApi.GetContext() - -#define RETRIEVE_MODALITIES(call) \ - const OrthancRestApi::Modalities& modalities = \ - dynamic_cast<OrthancRestApi&>(call.GetContext()).GetModalities(); - - - -namespace Orthanc -{ - // TODO IMPROVE MULTITHREADING - // Every call to "ParsedDicomFile" must lock this mutex!!! - static boost::mutex cacheMutex_; - - - // DICOM SCU ---------------------------------------------------------------- - - static void ConnectToModality(DicomUserConnection& connection, - const std::string& name) - { - std::string aet, address; - int port; - GetDicomModality(name, aet, address, port); - connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); - connection.SetDistantApplicationEntityTitle(aet); - connection.SetDistantHost(address); - connection.SetDistantPort(port); - connection.Open(); - } - - static bool MergeQueryAndTemplate(DicomMap& result, - const std::string& postData) - { - Json::Value query; - Json::Reader reader; - - if (!reader.parse(postData, query) || - query.type() != Json::objectValue) - { - return false; - } - - Json::Value::Members members = query.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - DicomTag t = FromDcmtkBridge::ParseTag(members[i]); - result.SetValue(t, query[members[i]].asString()); - } - - return true; - } - - static void DicomFindPatient(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindPatientTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers answers; - connection.FindPatient(answers, m); - - Json::Value result; - answers.ToJson(result); - call.GetOutput().AnswerJson(result); - } - - static void DicomFindStudy(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindStudyTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) - { - return; - } - - DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers answers; - connection.FindStudy(answers, m); - - Json::Value result; - answers.ToJson(result); - call.GetOutput().AnswerJson(result); - } - - static void DicomFindSeries(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindSeriesTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || - m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2) - { - return; - } - - DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers answers; - connection.FindSeries(answers, m); - - Json::Value result; - answers.ToJson(result); - call.GetOutput().AnswerJson(result); - } - - static void DicomFind(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindPatientTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers patients; - connection.FindPatient(patients, m); - - // Loop over the found patients - Json::Value result = Json::arrayValue; - for (size_t i = 0; i < patients.GetSize(); i++) - { - Json::Value patient(Json::objectValue); - FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i)); - - DicomMap::SetupFindStudyTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); - - DicomFindAnswers studies; - connection.FindStudy(studies, m); - - patient["Studies"] = Json::arrayValue; - - // Loop over the found studies - for (size_t j = 0; j < studies.GetSize(); j++) - { - Json::Value study(Json::objectValue); - FromDcmtkBridge::ToJson(study, studies.GetAnswer(j)); - - DicomMap::SetupFindSeriesTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID); - m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); - - DicomFindAnswers series; - connection.FindSeries(series, m); - - // Loop over the found series - study["Series"] = Json::arrayValue; - for (size_t k = 0; k < series.GetSize(); k++) - { - Json::Value series2(Json::objectValue); - FromDcmtkBridge::ToJson(series2, series.GetAnswer(k)); - study["Series"].append(series2); - } - - patient["Studies"].append(study); - } - - result.append(patient); - } - - call.GetOutput().AnswerJson(result); - } - - - static void DicomStore(RestApi::PostCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string remote = call.GetUriComponent("id", ""); - std::string stripped = Toolbox::StripSpaces(call.GetPostBody()); - - Json::Value request; - if (Toolbox::IsSHA1(stripped)) - { - // This is for compatibility with Orthanc <= 0.5.1. - request = stripped; - } - else if (!call.ParseJsonRequest(request)) - { - // Bad JSON request - return; - } - - std::list<std::string> instances; - if (request.isString()) - { - LOG(INFO) << "Sending resource " << request.asString() << " to modality " << remote; - context.GetIndex().LogExportedResource(request.asString(), remote); - context.GetIndex().GetChildInstances(instances, request.asString()); - } - else if (request.isArray()) - { - for (Json::Value::ArrayIndex i = 0; i < request.size(); i++) - { - if (!request[i].isString()) - { - return; - } - - std::string stripped = Toolbox::StripSpaces(request[i].asString()); - if (!Toolbox::IsSHA1(stripped)) - { - return; - } - - LOG(INFO) << "Sending resource " << stripped << " to modality " << remote; - context.GetIndex().LogExportedResource(stripped, remote); - - std::list<std::string> tmp; - context.GetIndex().GetChildInstances(tmp, stripped); - instances.merge(tmp); - assert(tmp.size() == 0); - } - } - else - { - // Neither a string, nor a list of strings. Bad request. - return; - } - - DicomUserConnection connection; - ConnectToModality(connection, remote); - - for (std::list<std::string>::const_iterator - it = instances.begin(); it != instances.end(); it++) - { - std::string dicom; - context.ReadFile(dicom, *it, FileContentType_Dicom); - connection.Store(dicom); - } - - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - - - - // System information ------------------------------------------------------- - - static void ServeRoot(RestApi::GetCall& call) - { - call.GetOutput().Redirect("app/explorer.html"); - } - - static void GetSystemInformation(RestApi::GetCall& call) - { - Json::Value result = Json::objectValue; - - result["Version"] = ORTHANC_VERSION; - result["Name"] = GetGlobalStringParameter("Name", ""); - - call.GetOutput().AnswerJson(result); - } - - static void GetStatistics(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - Json::Value result = Json::objectValue; - context.GetIndex().ComputeStatistics(result); - call.GetOutput().AnswerJson(result); - } - - static void GenerateUid(RestApi::GetCall& call) - { - std::string level = call.GetArgument("level", ""); - if (level == "patient") - { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient), "text/plain"); - } - else if (level == "study") - { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study), "text/plain"); - } - else if (level == "series") - { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series), "text/plain"); - } - else if (level == "instance") - { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance), "text/plain"); - } - } - - - // List all the patients, studies, series or instances ---------------------- - - template <enum ResourceType resourceType> - static void ListResources(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value result; - context.GetIndex().GetAllUuids(result, resourceType); - call.GetOutput().AnswerJson(result); - } - - template <enum ResourceType resourceType> - static void GetSingleResource(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value result; - if (context.GetIndex().LookupResource(result, call.GetUriComponent("id", ""), resourceType)) - { - call.GetOutput().AnswerJson(result); - } - } - - template <enum ResourceType resourceType> - static void DeleteSingleResource(RestApi::DeleteCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value result; - if (context.GetIndex().DeleteResource(result, call.GetUriComponent("id", ""), resourceType)) - { - call.GetOutput().AnswerJson(result); - } - } - - - // Download of ZIP files ---------------------------------------------------- - - - static std::string GetDirectoryNameInArchive(const Json::Value& resource, - ResourceType resourceType) - { - switch (resourceType) - { - case ResourceType_Patient: - { - std::string p = resource["MainDicomTags"]["PatientID"].asString(); - std::string n = resource["MainDicomTags"]["PatientName"].asString(); - return p + " " + n; - } - - case ResourceType_Study: - { - return resource["MainDicomTags"]["StudyDescription"].asString(); - } - - case ResourceType_Series: - { - std::string d = resource["MainDicomTags"]["SeriesDescription"].asString(); - std::string m = resource["MainDicomTags"]["Modality"].asString(); - return m + " " + d; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - static bool CreateRootDirectoryInArchive(HierarchicalZipWriter& writer, - ServerContext& context, - const Json::Value& resource, - ResourceType resourceType) - { - if (resourceType == ResourceType_Patient) - { - return true; - } - - ResourceType parentType = GetParentResourceType(resourceType); - Json::Value parent; - - switch (resourceType) - { - case ResourceType_Study: - { - if (!context.GetIndex().LookupResource(parent, resource["ParentPatient"].asString(), parentType)) - { - return false; - } - - break; - } - - case ResourceType_Series: - if (!context.GetIndex().LookupResource(parent, resource["ParentStudy"].asString(), parentType) || - !CreateRootDirectoryInArchive(writer, context, parent, parentType)) - { - return false; - } - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - writer.OpenDirectory(GetDirectoryNameInArchive(parent, parentType).c_str()); - return true; - } - - static bool ArchiveInstance(HierarchicalZipWriter& writer, - ServerContext& context, - const std::string& instancePublicId) - { - Json::Value instance; - if (!context.GetIndex().LookupResource(instance, instancePublicId, ResourceType_Instance)) - { - return false; - } - - std::string filename = instance["MainDicomTags"]["SOPInstanceUID"].asString() + ".dcm"; - writer.OpenFile(filename.c_str()); - - std::string dicom; - context.ReadFile(dicom, instancePublicId, FileContentType_Dicom); - writer.Write(dicom); - - return true; - } - - static bool ArchiveInternal(HierarchicalZipWriter& writer, - ServerContext& context, - const std::string& publicId, - ResourceType resourceType, - bool isFirstLevel) - { - Json::Value resource; - if (!context.GetIndex().LookupResource(resource, publicId, resourceType)) - { - return false; - } - - if (isFirstLevel && - !CreateRootDirectoryInArchive(writer, context, resource, resourceType)) - { - return false; - } - - writer.OpenDirectory(GetDirectoryNameInArchive(resource, resourceType).c_str()); - - switch (resourceType) - { - case ResourceType_Patient: - for (Json::Value::ArrayIndex i = 0; i < resource["Studies"].size(); i++) - { - std::string studyId = resource["Studies"][i].asString(); - if (!ArchiveInternal(writer, context, studyId, ResourceType_Study, false)) - { - return false; - } - } - break; - - case ResourceType_Study: - for (Json::Value::ArrayIndex i = 0; i < resource["Series"].size(); i++) - { - std::string seriesId = resource["Series"][i].asString(); - if (!ArchiveInternal(writer, context, seriesId, ResourceType_Series, false)) - { - return false; - } - } - break; - - case ResourceType_Series: - for (Json::Value::ArrayIndex i = 0; i < resource["Instances"].size(); i++) - { - if (!ArchiveInstance(writer, context, resource["Instances"][i].asString())) - { - return false; - } - } - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - writer.CloseDirectory(); - return true; - } - - template <enum ResourceType resourceType> - static void GetArchive(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - // Create a RAII for the temporary file to manage the ZIP file - Toolbox::TemporaryFile tmp; - std::string id = call.GetUriComponent("id", ""); - - { - // Create a ZIP writer - HierarchicalZipWriter writer(tmp.GetPath().c_str()); - - // Store the requested resource into the ZIP - if (!ArchiveInternal(writer, context, id, resourceType, true)) - { - return; - } - } - - // Prepare the sending of the ZIP file - FilesystemHttpSender sender(tmp.GetPath().c_str()); - sender.SetContentType("application/zip"); - sender.SetDownloadFilename(id + ".zip"); - - // Send the ZIP - call.GetOutput().AnswerFile(sender); - - // The temporary file is automatically removed thanks to the RAII - } - - - // Changes API -------------------------------------------------------------- - - static void GetSinceAndLimit(int64_t& since, - unsigned int& limit, - bool& last, - const RestApi::GetCall& call) - { - static const unsigned int MAX_RESULTS = 100; - - if (call.HasArgument("last")) - { - last = true; - return; - } - - last = false; - - try - { - since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0")); - limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", "0")); - } - catch (boost::bad_lexical_cast) - { - return; - } - - if (limit == 0 || limit > MAX_RESULTS) - { - limit = MAX_RESULTS; - } - } - - static void GetChanges(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - //std::string filter = GetArgument(getArguments, "filter", ""); - int64_t since; - unsigned int limit; - bool last; - GetSinceAndLimit(since, limit, last, call); - - Json::Value result; - if ((!last && context.GetIndex().GetChanges(result, since, limit)) || - ( last && context.GetIndex().GetLastChange(result))) - { - call.GetOutput().AnswerJson(result); - } - } - - - static void GetExports(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - int64_t since; - unsigned int limit; - bool last; - GetSinceAndLimit(since, limit, last, call); - - Json::Value result; - if ((!last && context.GetIndex().GetExportedResources(result, since, limit)) || - ( last && context.GetIndex().GetLastExportedResource(result))) - { - call.GetOutput().AnswerJson(result); - } - } - - - // Get information about a single patient ----------------------------------- - - static void IsProtectedPatient(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - std::string publicId = call.GetUriComponent("id", ""); - bool isProtected = context.GetIndex().IsProtectedPatient(publicId); - call.GetOutput().AnswerBuffer(isProtected ? "1" : "0", "text/plain"); - } - - - static void SetPatientProtection(RestApi::PutCall& call) - { - RETRIEVE_CONTEXT(call); - std::string publicId = call.GetUriComponent("id", ""); - std::string s = Toolbox::StripSpaces(call.GetPutBody()); - - if (s == "0") - { - context.GetIndex().SetProtectedPatient(publicId, false); - call.GetOutput().AnswerBuffer("", "text/plain"); - } - else if (s == "1") - { - context.GetIndex().SetProtectedPatient(publicId, true); - call.GetOutput().AnswerBuffer("", "text/plain"); - } - else - { - // Bad request - } - } - - - // Get information about a single instance ---------------------------------- - - static void GetInstanceFile(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string publicId = call.GetUriComponent("id", ""); - context.AnswerFile(call.GetOutput(), publicId, FileContentType_Dicom); - } - - - template <bool simplify> - static void GetInstanceTags(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string publicId = call.GetUriComponent("id", ""); - - Json::Value full; - context.ReadJson(full, publicId); - - if (simplify) - { - Json::Value simplified; - SimplifyTags(simplified, full); - call.GetOutput().AnswerJson(simplified); - } - else - { - call.GetOutput().AnswerJson(full); - } - } - - - static void ListFrames(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value instance; - if (context.GetIndex().LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance)) - { - unsigned int numberOfFrames = 1; - - try - { - Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"]; - numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString()); - } - catch (...) - { - } - - Json::Value result = Json::arrayValue; - for (unsigned int i = 0; i < numberOfFrames; i++) - { - result.append(i); - } - - call.GetOutput().AnswerJson(result); - } - } - - - template <enum ImageExtractionMode mode> - static void GetImage(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string frameId = call.GetUriComponent("frame", "0"); - - unsigned int frame; - try - { - frame = boost::lexical_cast<unsigned int>(frameId); - } - catch (boost::bad_lexical_cast) - { - return; - } - - std::string publicId = call.GetUriComponent("id", ""); - std::string dicomContent, png; - context.ReadFile(dicomContent, publicId, FileContentType_Dicom); - - try - { - FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode); - call.GetOutput().AnswerBuffer(png, "image/png"); - } - catch (OrthancException& e) - { - if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange) - { - // The frame number is out of the range for this DICOM - // instance, the resource is not existent - } - else - { - std::string root = ""; - for (size_t i = 1; i < call.GetFullUri().size(); i++) - { - root += "../"; - } - - call.GetOutput().Redirect(root + "app/images/unsupported.png"); - } - } - } - - - // Upload of DICOM files through HTTP --------------------------------------- - - static void UploadDicomFile(RestApi::PostCall& call) - { - RETRIEVE_CONTEXT(call); - - const std::string& postData = call.GetPostBody(); - if (postData.size() == 0) - { - return; - } - - LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP"; - - std::string publicId; - StoreStatus status = context.Store(publicId, postData); - Json::Value result = Json::objectValue; - - if (status != StoreStatus_Failure) - { - result["ID"] = publicId; - result["Path"] = GetBasePath(ResourceType_Instance, publicId); - } - - result["Status"] = ToString(status); - call.GetOutput().AnswerJson(result); - } - - - - // DICOM bridge ------------------------------------------------------------- - - static bool IsExistingModality(const OrthancRestApi::Modalities& modalities, - const std::string& id) - { - return modalities.find(id) != modalities.end(); - } - - static void ListModalities(RestApi::GetCall& call) - { - RETRIEVE_MODALITIES(call); - - Json::Value result = Json::arrayValue; - for (OrthancRestApi::Modalities::const_iterator - it = modalities.begin(); it != modalities.end(); it++) - { - result.append(*it); - } - - call.GetOutput().AnswerJson(result); - } - - - static void ListModalityOperations(RestApi::GetCall& call) - { - RETRIEVE_MODALITIES(call); - - std::string id = call.GetUriComponent("id", ""); - if (IsExistingModality(modalities, id)) - { - Json::Value result = Json::arrayValue; - result.append("find-patient"); - result.append("find-study"); - result.append("find-series"); - result.append("find"); - result.append("store"); - call.GetOutput().AnswerJson(result); - } - } - - - - // Raw access to the DICOM tags of an instance ------------------------------ - - static void GetRawContent(RestApi::GetCall& call) - { - boost::mutex::scoped_lock lock(cacheMutex_); - - RETRIEVE_CONTEXT(call); - std::string id = call.GetUriComponent("id", ""); - ParsedDicomFile& dicom = context.GetDicomFile(id); - dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri()); - } - - - - // Modification of DICOM instances ------------------------------------------ - - namespace - { - typedef std::set<DicomTag> Removals; - typedef std::map<DicomTag, std::string> Replacements; - typedef std::map< std::pair<DicomRootLevel, std::string>, std::string> UidMap; - } - - static void ReplaceInstanceInternal(ParsedDicomFile& toModify, - const Removals& removals, - const Replacements& replacements, - DicomReplaceMode mode, - bool removePrivateTags) - { - if (removePrivateTags) - { - toModify.RemovePrivateTags(); - } - - for (Removals::const_iterator it = removals.begin(); - it != removals.end(); it++) - { - toModify.Remove(*it); - } - - for (Replacements::const_iterator it = replacements.begin(); - it != replacements.end(); it++) - { - toModify.Replace(it->first, it->second, mode); - } - - // A new SOP instance UID is automatically generated - std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance); - toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, DicomReplaceMode_InsertIfAbsent); - } - - - static void ParseRemovals(Removals& target, - const Json::Value& removals) - { - if (!removals.isArray()) - { - throw OrthancException(ErrorCode_BadRequest); - } - - target.clear(); - - for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++) - { - std::string name = removals[i].asString(); - DicomTag tag = FromDcmtkBridge::ParseTag(name); - target.insert(tag); - - VLOG(1) << "Removal: " << name << " " << tag << std::endl; - } - } - - - static void ParseReplacements(Replacements& target, - const Json::Value& replacements) - { - if (!replacements.isObject()) - { - throw OrthancException(ErrorCode_BadRequest); - } - - target.clear(); - - Json::Value::Members members = replacements.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - const std::string& name = members[i]; - std::string value = replacements[name].asString(); - - DicomTag tag = FromDcmtkBridge::ParseTag(name); - target[tag] = value; - - VLOG(1) << "Replacement: " << name << " " << tag << " == " << value << std::endl; - } - } - - - static std::string GeneratePatientName(ServerContext& context) - { - uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); - return "Anonymized" + boost::lexical_cast<std::string>(seq); - } - - - static void SetupAnonymization(Removals& removals, - Replacements& replacements) - { - // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles - removals.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID - //removals.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set by ReplaceInstanceInternal() - removals.insert(DicomTag(0x0008, 0x0050)); // Accession Number - removals.insert(DicomTag(0x0008, 0x0080)); // Institution Name - removals.insert(DicomTag(0x0008, 0x0081)); // Institution Address - removals.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name - removals.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address - removals.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers - removals.insert(DicomTag(0x0008, 0x1010)); // Station Name - removals.insert(DicomTag(0x0008, 0x1030)); // Study Description - removals.insert(DicomTag(0x0008, 0x103e)); // Series Description - removals.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name - removals.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record - removals.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name - removals.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study - removals.insert(DicomTag(0x0008, 0x1070)); // Operators' Name - removals.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description - removals.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID - removals.insert(DicomTag(0x0008, 0x2111)); // Derivation Description - removals.insert(DicomTag(0x0010, 0x0010)); // Patient's Name - removals.insert(DicomTag(0x0010, 0x0020)); // Patient ID - removals.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date - removals.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time - removals.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex - removals.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids - removals.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names - removals.insert(DicomTag(0x0010, 0x1010)); // Patient's Age - removals.insert(DicomTag(0x0010, 0x1020)); // Patient's Size - removals.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight - removals.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator - removals.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group - removals.insert(DicomTag(0x0010, 0x2180)); // Occupation - removals.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History - removals.insert(DicomTag(0x0010, 0x4000)); // Patient Comments - removals.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number - removals.insert(DicomTag(0x0018, 0x1030)); // Protocol Name - //removals.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => generated below - //removals.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => generated below - removals.insert(DicomTag(0x0020, 0x0010)); // Study ID - removals.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID - removals.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID - removals.insert(DicomTag(0x0020, 0x4000)); // Image Comments - removals.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence - removals.insert(DicomTag(0x0040, 0xa124)); // UID - removals.insert(DicomTag(0x0040, 0xa730)); // Content Sequence - removals.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID - removals.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID - removals.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID - - // Some more removals (from the experience of DICOM files at the CHU of Liege) - removals.insert(DicomTag(0x0010, 0x1040)); // Patient's Address - removals.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician - - // Set the DeidentificationMethod tag - replacements.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1")); - - // Set the PatientIdentityRemoved - replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES")); - - // Generate random study UID if not specified - if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study))); - } - - // Generate random series UID if not specified - if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series))); - } - } - - - static bool ParseModifyRequest(Removals& removals, - Replacements& replacements, - bool& removePrivateTags, - const RestApi::PostCall& call) - { - removePrivateTags = false; - Json::Value request; - if (call.ParseJsonRequest(request) && - request.isObject()) - { - Json::Value removalsPart = Json::arrayValue; - Json::Value replacementsPart = Json::objectValue; - - if (request.isMember("Remove")) - { - removalsPart = request["Remove"]; - } - - if (request.isMember("Replace")) - { - replacementsPart = request["Replace"]; - } - - if (request.isMember("RemovePrivateTags")) - { - removePrivateTags = true; - } - - ParseRemovals(removals, removalsPart); - ParseReplacements(replacements, replacementsPart); - - return true; - } - else - { - return false; - } - } - - - static bool ParseAnonymizationRequest(Removals& removals, - Replacements& replacements, - bool& removePrivateTags, - RestApi::PostCall& call) - { - RETRIEVE_CONTEXT(call); - - removePrivateTags = true; - - Json::Value request; - if (call.ParseJsonRequest(request) && - request.isObject()) - { - Json::Value keepPart = Json::arrayValue; - Json::Value removalsPart = Json::arrayValue; - Json::Value replacementsPart = Json::objectValue; - - if (request.isMember("Keep")) - { - keepPart = request["Keep"]; - } - - if (request.isMember("KeepPrivateTags")) - { - removePrivateTags = false; - } - - if (request.isMember("Replace")) - { - replacementsPart = request["Replace"]; - } - - Removals toKeep; - ParseRemovals(toKeep, keepPart); - - SetupAnonymization(removals, replacements); - - for (Removals::iterator it = toKeep.begin(); it != toKeep.end(); it++) - { - removals.erase(*it); - } - - Removals additionalRemovals; - ParseRemovals(additionalRemovals, removalsPart); - - for (Removals::iterator it = additionalRemovals.begin(); - it != additionalRemovals.end(); it++) - { - removals.insert(*it); - } - - ParseReplacements(replacements, replacementsPart); - - // Generate random Patient's Name if none is specified - if (replacements.find(DicomTag(0x0010, 0x0010)) == replacements.end()) - { - replacements.insert(std::make_pair(DicomTag(0x0010, 0x0010), GeneratePatientName(context))); - } - - // Generate random Patient's ID if none is specified - if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient))); - } - - return true; - } - else - { - return false; - } - } - - - static void AnonymizeOrModifyInstance(Removals& removals, - Replacements& replacements, - bool removePrivateTags, - RestApi::PostCall& call) - { - boost::mutex::scoped_lock lock(cacheMutex_); - RETRIEVE_CONTEXT(call); - - std::string id = call.GetUriComponent("id", ""); - ParsedDicomFile& dicom = context.GetDicomFile(id); - - std::auto_ptr<ParsedDicomFile> modified(dicom.Clone()); - ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); - modified->Answer(call.GetOutput()); - } - - - static bool RetrieveMappedUid(ParsedDicomFile& dicom, - DicomRootLevel level, - Replacements& replacements, - UidMap& uidMap) - { - std::auto_ptr<DicomTag> tag; - if (level == DicomRootLevel_Series) - { - tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); - } - else - { - assert(level == DicomRootLevel_Study); - tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); - } - - std::string original; - if (!dicom.GetTagValue(original, *tag)) - { - throw OrthancException(ErrorCode_InternalError); - } - - std::string mapped; - bool isNew; - - UidMap::const_iterator previous = uidMap.find(std::make_pair(level, original)); - if (previous == uidMap.end()) - { - mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); - uidMap.insert(std::make_pair(std::make_pair(level, original), mapped)); - isNew = true; - } - else - { - mapped = previous->second; - isNew = false; - } - - replacements[*tag] = mapped; - return isNew; - } - - - static void AnonymizeOrModifyResource(Removals& removals, - Replacements& replacements, - bool removePrivateTags, - MetadataType metadataType, - ChangeType changeType, - ResourceType resourceType, - RestApi::PostCall& call) - { - typedef std::list<std::string> Instances; - - bool isFirst = true; - Json::Value result(Json::objectValue); - - boost::mutex::scoped_lock lock(cacheMutex_); - RETRIEVE_CONTEXT(call); - - Instances instances; - std::string id = call.GetUriComponent("id", ""); - context.GetIndex().GetChildInstances(instances, id); - - if (instances.size() == 0) - { - return; - } - - - /** - * Loop over all the instances of the resource. - **/ - - UidMap uidMap; - for (Instances::const_iterator it = instances.begin(); - it != instances.end(); it++) - { - LOG(INFO) << "Modifying instance " << *it; - ParsedDicomFile& original = context.GetDicomFile(*it); - - bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap); - - bool isNewStudy = false; - if (resourceType == ResourceType_Study || - resourceType == ResourceType_Patient) - { - isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap); - } - - /** - * Compute the resulting DICOM instance and store it into the Orthanc store. - **/ - - std::auto_ptr<ParsedDicomFile> modified(original.Clone()); - ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); - - std::string modifiedInstance; - if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success) - { - LOG(ERROR) << "Error while storing a modified instance " << *it; - return; - } - - - /** - * Record metadata information (AnonimizedFrom/ModifiedFrom). - **/ - - DicomInstanceHasher modifiedHasher = modified->GetHasher(); - DicomInstanceHasher originalHasher = original.GetHasher(); - - if (isNewSeries) - { - context.GetIndex().SetMetadata(modifiedHasher.HashSeries(), - metadataType, originalHasher.HashSeries()); - } - - if (isNewStudy) - { - context.GetIndex().SetMetadata(modifiedHasher.HashStudy(), - metadataType, originalHasher.HashStudy()); - } - - assert(*it == originalHasher.HashInstance()); - assert(modifiedInstance == modifiedHasher.HashInstance()); - context.GetIndex().SetMetadata(modifiedInstance, metadataType, *it); - - - /** - * Compute the JSON object that is returned by the REST call. - **/ - - if (isFirst) - { - std::string newId; - - switch (resourceType) - { - case ResourceType_Series: - newId = modifiedHasher.HashSeries(); - break; - - case ResourceType_Study: - newId = modifiedHasher.HashStudy(); - break; - - case ResourceType_Patient: - newId = modifiedHasher.HashPatient(); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - result["Type"] = ToString(resourceType); - result["ID"] = newId; - result["Path"] = GetBasePath(resourceType, newId); - result["PatientID"] = modifiedHasher.HashPatient(); - isFirst = false; - } - } - - call.GetOutput().AnswerJson(result); - } - - - - static void ModifyInstance(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); - } - } - - - static void AnonymizeInstance(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); - } - } - - - static void ModifySeriesInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, - MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, - ResourceType_Series, call); - } - } - - - static void AnonymizeSeriesInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, - MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, - ResourceType_Series, call); - } - } - - - static void ModifyStudyInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, - MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, - ResourceType_Study, call); - } - } - - - static void AnonymizeStudyInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, - MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, - ResourceType_Study, call); - } - } - - - static void ModifyPatientInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, - MetadataType_ModifiedFrom, ChangeType_ModifiedPatient, - ResourceType_Patient, call); - } - } - - - static void AnonymizePatientInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, - MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient, - ResourceType_Patient, call); - } - } - - - - // Registration of the various REST handlers -------------------------------- - - OrthancRestApi::OrthancRestApi(ServerContext& context) : - context_(context) - { - GetListOfDicomModalities(modalities_); - - Register("/", ServeRoot); - Register("/system", GetSystemInformation); - Register("/statistics", GetStatistics); - Register("/changes", GetChanges); - Register("/exports", GetExports); - - Register("/instances", UploadDicomFile); - Register("/instances", ListResources<ResourceType_Instance>); - Register("/patients", ListResources<ResourceType_Patient>); - Register("/series", ListResources<ResourceType_Series>); - Register("/studies", ListResources<ResourceType_Study>); - - Register("/instances/{id}", DeleteSingleResource<ResourceType_Instance>); - Register("/instances/{id}", GetSingleResource<ResourceType_Instance>); - Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>); - Register("/patients/{id}", GetSingleResource<ResourceType_Patient>); - Register("/series/{id}", DeleteSingleResource<ResourceType_Series>); - Register("/series/{id}", GetSingleResource<ResourceType_Series>); - Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>); - Register("/studies/{id}", GetSingleResource<ResourceType_Study>); - - Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>); - Register("/studies/{id}/archive", GetArchive<ResourceType_Study>); - Register("/series/{id}/archive", GetArchive<ResourceType_Series>); - - Register("/patients/{id}/protected", IsProtectedPatient); - Register("/patients/{id}/protected", SetPatientProtection); - Register("/instances/{id}/file", GetInstanceFile); - Register("/instances/{id}/tags", GetInstanceTags<false>); - Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); - Register("/instances/{id}/frames", ListFrames); - Register("/instances/{id}/content/*", GetRawContent); - - Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); - Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); - Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); - Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); - Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); - Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); - - Register("/modalities", ListModalities); - Register("/modalities/{id}", ListModalityOperations); - Register("/modalities/{id}/find-patient", DicomFindPatient); - Register("/modalities/{id}/find-study", DicomFindStudy); - Register("/modalities/{id}/find-series", DicomFindSeries); - Register("/modalities/{id}/find", DicomFind); - Register("/modalities/{id}/store", DicomStore); - - Register("/instances/{id}/modify", ModifyInstance); - Register("/series/{id}/modify", ModifySeriesInplace); - Register("/studies/{id}/modify", ModifyStudyInplace); - Register("/patients/{id}/modify", ModifyPatientInplace); - - Register("/instances/{id}/anonymize", AnonymizeInstance); - Register("/series/{id}/anonymize", AnonymizeSeriesInplace); - Register("/studies/{id}/anonymize", AnonymizeStudyInplace); - Register("/patients/{id}/anonymize", AnonymizePatientInplace); - - Register("/tools/generate-uid", GenerateUid); - } -}
--- a/OrthancServer/OrthancRestApi.h Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 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 <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ServerContext.h" -#include "../Core/RestApi/RestApi.h" - -#include <set> - -namespace Orthanc -{ - class OrthancRestApi : public RestApi - { - public: - typedef std::set<std::string> Modalities; - - private: - ServerContext& context_; - Modalities modalities_; - - public: - OrthancRestApi(ServerContext& context); - - ServerContext& GetContext() - { - return context_; - } - - Modalities& GetModalities() - { - return modalities_; - } - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,692 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + // TODO IMPROVE MULTITHREADING + // Every call to "ParsedDicomFile" must lock this mutex!!! + static boost::mutex cacheMutex_; + + + // Raw access to the DICOM tags of an instance ------------------------------ + + static void GetRawContent(RestApi::GetCall& call) + { + boost::mutex::scoped_lock lock(cacheMutex_); + + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string id = call.GetUriComponent("id", ""); + ParsedDicomFile& dicom = context.GetDicomFile(id); + dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri()); + } + + + + // Modification of DICOM instances ------------------------------------------ + + namespace + { + typedef std::set<DicomTag> Removals; + typedef std::map<DicomTag, std::string> Replacements; + typedef std::map< std::pair<DicomRootLevel, std::string>, std::string> UidMap; + } + + static void ReplaceInstanceInternal(ParsedDicomFile& toModify, + const Removals& removals, + const Replacements& replacements, + DicomReplaceMode mode, + bool removePrivateTags) + { + if (removePrivateTags) + { + toModify.RemovePrivateTags(); + } + + for (Removals::const_iterator it = removals.begin(); + it != removals.end(); ++it) + { + toModify.Remove(*it); + } + + for (Replacements::const_iterator it = replacements.begin(); + it != replacements.end(); ++it) + { + toModify.Replace(it->first, it->second, mode); + } + + // A new SOP instance UID is automatically generated + std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance); + toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, DicomReplaceMode_InsertIfAbsent); + } + + + static void ParseRemovals(Removals& target, + const Json::Value& removals) + { + if (!removals.isArray()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++) + { + std::string name = removals[i].asString(); + DicomTag tag = FromDcmtkBridge::ParseTag(name); + target.insert(tag); + + VLOG(1) << "Removal: " << name << " " << tag << std::endl; + } + } + + + static void ParseReplacements(Replacements& target, + const Json::Value& replacements) + { + if (!replacements.isObject()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + Json::Value::Members members = replacements.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + const std::string& name = members[i]; + std::string value = replacements[name].asString(); + + DicomTag tag = FromDcmtkBridge::ParseTag(name); + target[tag] = value; + + VLOG(1) << "Replacement: " << name << " " << tag << " == " << value << std::endl; + } + } + + + static std::string GeneratePatientName(ServerContext& context) + { + uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); + return "Anonymized" + boost::lexical_cast<std::string>(seq); + } + + + static void SetupAnonymization(Removals& removals, + Replacements& replacements) + { + // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles + removals.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID + //removals.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set by ReplaceInstanceInternal() + removals.insert(DicomTag(0x0008, 0x0050)); // Accession Number + removals.insert(DicomTag(0x0008, 0x0080)); // Institution Name + removals.insert(DicomTag(0x0008, 0x0081)); // Institution Address + removals.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name + removals.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address + removals.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers + removals.insert(DicomTag(0x0008, 0x1010)); // Station Name + removals.insert(DicomTag(0x0008, 0x1030)); // Study Description + removals.insert(DicomTag(0x0008, 0x103e)); // Series Description + removals.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name + removals.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record + removals.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name + removals.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study + removals.insert(DicomTag(0x0008, 0x1070)); // Operators' Name + removals.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description + removals.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID + removals.insert(DicomTag(0x0008, 0x2111)); // Derivation Description + removals.insert(DicomTag(0x0010, 0x0010)); // Patient's Name + //removals.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) + removals.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date + removals.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time + removals.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex + removals.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids + removals.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names + removals.insert(DicomTag(0x0010, 0x1010)); // Patient's Age + removals.insert(DicomTag(0x0010, 0x1020)); // Patient's Size + removals.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight + removals.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator + removals.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group + removals.insert(DicomTag(0x0010, 0x2180)); // Occupation + removals.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History + removals.insert(DicomTag(0x0010, 0x4000)); // Patient Comments + removals.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number + removals.insert(DicomTag(0x0018, 0x1030)); // Protocol Name + //removals.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => cf. below (*) + //removals.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => cf. below (*) + removals.insert(DicomTag(0x0020, 0x0010)); // Study ID + removals.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID + removals.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID + removals.insert(DicomTag(0x0020, 0x4000)); // Image Comments + removals.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + removals.insert(DicomTag(0x0040, 0xa124)); // UID + removals.insert(DicomTag(0x0040, 0xa730)); // Content Sequence + removals.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID + removals.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID + removals.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID + + /** + * (*) Patient ID, Study Instance UID and Series Instance UID + * are modified by "AnonymizeInstance()" if anonymizing a single + * instance, or by "RetrieveMappedUid()" if anonymizing a + * patient/study/series. + **/ + + + // Some more removals (from the experience of DICOM files at the CHU of Liege) + removals.insert(DicomTag(0x0010, 0x1040)); // Patient's Address + removals.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician + removals.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers + removals.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts + + // Set the DeidentificationMethod tag + replacements.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1")); + + // Set the PatientIdentityRemoved tag + replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES")); + } + + + static bool ParseModifyRequest(Removals& removals, + Replacements& replacements, + bool& removePrivateTags, + const RestApi::PostCall& call) + { + removePrivateTags = false; + Json::Value request; + if (call.ParseJsonRequest(request) && + request.isObject()) + { + Json::Value removalsPart = Json::arrayValue; + Json::Value replacementsPart = Json::objectValue; + + if (request.isMember("Remove")) + { + removalsPart = request["Remove"]; + } + + if (request.isMember("Replace")) + { + replacementsPart = request["Replace"]; + } + + if (request.isMember("RemovePrivateTags")) + { + removePrivateTags = true; + } + + ParseRemovals(removals, removalsPart); + ParseReplacements(replacements, replacementsPart); + + return true; + } + else + { + return false; + } + } + + + static bool ParseAnonymizationRequest(Removals& removals, + Replacements& replacements, + bool& removePrivateTags, + bool& keepPatientId, + RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + removePrivateTags = true; + keepPatientId = false; + + Json::Value request; + if (call.ParseJsonRequest(request) && + request.isObject()) + { + Json::Value keepPart = Json::arrayValue; + Json::Value removalsPart = Json::arrayValue; + Json::Value replacementsPart = Json::objectValue; + + if (request.isMember("Keep")) + { + keepPart = request["Keep"]; + } + + if (request.isMember("KeepPrivateTags")) + { + removePrivateTags = false; + } + + if (request.isMember("Replace")) + { + replacementsPart = request["Replace"]; + } + + Removals toKeep; + ParseRemovals(toKeep, keepPart); + + SetupAnonymization(removals, replacements); + + for (Removals::iterator it = toKeep.begin(); it != toKeep.end(); ++it) + { + if (*it == DICOM_TAG_PATIENT_ID) + { + keepPatientId = true; + } + + removals.erase(*it); + } + + Removals additionalRemovals; + ParseRemovals(additionalRemovals, removalsPart); + + for (Removals::iterator it = additionalRemovals.begin(); + it != additionalRemovals.end(); ++it) + { + removals.insert(*it); + } + + ParseReplacements(replacements, replacementsPart); + + // Generate random Patient's Name if none is specified + if (toKeep.find(DICOM_TAG_PATIENT_NAME) == toKeep.end() && + replacements.find(DICOM_TAG_PATIENT_NAME) == replacements.end()) + { + replacements.insert(std::make_pair(DICOM_TAG_PATIENT_NAME, GeneratePatientName(context))); + } + + return true; + } + else + { + return false; + } + } + + + static void AnonymizeOrModifyInstance(Removals& removals, + Replacements& replacements, + bool removePrivateTags, + RestApi::PostCall& call) + { + boost::mutex::scoped_lock lock(cacheMutex_); + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string id = call.GetUriComponent("id", ""); + ParsedDicomFile& dicom = context.GetDicomFile(id); + + std::auto_ptr<ParsedDicomFile> modified(dicom.Clone()); + ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); + modified->Answer(call.GetOutput()); + } + + + static bool RetrieveMappedUid(ParsedDicomFile& dicom, + DicomRootLevel level, + Replacements& replacements, + UidMap& uidMap) + { + std::auto_ptr<DicomTag> tag; + + switch (level) + { + case DicomRootLevel_Series: + tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); + break; + + case DicomRootLevel_Study: + tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); + break; + + case DicomRootLevel_Patient: + tag.reset(new DicomTag(DICOM_TAG_PATIENT_ID)); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + std::string original; + if (!dicom.GetTagValue(original, *tag)) + { + throw OrthancException(ErrorCode_InternalError); + } + + std::string mapped; + bool isNew; + + UidMap::const_iterator previous = uidMap.find(std::make_pair(level, original)); + if (previous == uidMap.end()) + { + mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); + uidMap.insert(std::make_pair(std::make_pair(level, original), mapped)); + isNew = true; + } + else + { + mapped = previous->second; + isNew = false; + } + + replacements[*tag] = mapped; + return isNew; + } + + + static void AnonymizeOrModifyResource(Removals& removals, + Replacements& replacements, + bool removePrivateTags, + bool keepPatientId, + MetadataType metadataType, + ChangeType changeType, + ResourceType resourceType, + RestApi::PostCall& call) + { + typedef std::list<std::string> Instances; + + bool isFirst = true; + Json::Value result(Json::objectValue); + + boost::mutex::scoped_lock lock(cacheMutex_); + ServerContext& context = OrthancRestApi::GetContext(call); + + Instances instances; + std::string id = call.GetUriComponent("id", ""); + context.GetIndex().GetChildInstances(instances, id); + + if (instances.empty()) + { + return; + } + + /** + * Loop over all the instances of the resource. + **/ + + UidMap uidMap; + for (Instances::const_iterator it = instances.begin(); + it != instances.end(); ++it) + { + LOG(INFO) << "Modifying instance " << *it; + ParsedDicomFile& original = context.GetDicomFile(*it); + + DicomInstanceHasher originalHasher = original.GetHasher(); + + if (isFirst && keepPatientId) + { + std::string patientId = originalHasher.GetPatientId(); + uidMap[std::make_pair(DicomRootLevel_Patient, patientId)] = patientId; + } + + bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap); + bool isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap); + bool isNewPatient = RetrieveMappedUid(original, DicomRootLevel_Patient, replacements, uidMap); + + + /** + * Compute the resulting DICOM instance and store it into the Orthanc store. + **/ + + std::auto_ptr<ParsedDicomFile> modified(original.Clone()); + ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); + + std::string modifiedInstance; + if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success) + { + LOG(ERROR) << "Error while storing a modified instance " << *it; + return; + } + + + /** + * Record metadata information (AnonymizedFrom/ModifiedFrom). + **/ + + DicomInstanceHasher modifiedHasher = modified->GetHasher(); + + if (isNewSeries) + { + context.GetIndex().SetMetadata(modifiedHasher.HashSeries(), + metadataType, originalHasher.HashSeries()); + } + + if (isNewStudy) + { + context.GetIndex().SetMetadata(modifiedHasher.HashStudy(), + metadataType, originalHasher.HashStudy()); + } + + if (isNewPatient) + { + context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), + metadataType, originalHasher.HashPatient()); + } + + assert(*it == originalHasher.HashInstance()); + assert(modifiedInstance == modifiedHasher.HashInstance()); + context.GetIndex().SetMetadata(modifiedInstance, metadataType, *it); + + + /** + * Compute the JSON object that is returned by the REST call. + **/ + + if (isFirst) + { + std::string newId; + + switch (resourceType) + { + case ResourceType_Series: + newId = modifiedHasher.HashSeries(); + break; + + case ResourceType_Study: + newId = modifiedHasher.HashStudy(); + break; + + case ResourceType_Patient: + newId = modifiedHasher.HashPatient(); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + result["Type"] = EnumerationToString(resourceType); + result["ID"] = newId; + result["Path"] = GetBasePath(resourceType, newId); + result["PatientID"] = modifiedHasher.HashPatient(); + isFirst = false; + } + } + + call.GetOutput().AnswerJson(result); + } + + + + static void ModifyInstance(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags; + + if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) + { + AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); + } + } + + + static void AnonymizeInstance(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags, keepPatientId; + + if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) + { + // TODO Handle "keepPatientId" + + // Generate random patient ID if not specified + if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end()) + { + replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, + FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient))); + } + + // Generate random study UID if not specified + if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end()) + { + replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, + FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study))); + } + + // Generate random series UID if not specified + if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end()) + { + replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, + FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series))); + } + + AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); + } + } + + + static void ModifySeriesInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags; + + if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) + { + AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/, + MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, + ResourceType_Series, call); + } + } + + + static void AnonymizeSeriesInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags, keepPatientId; + + if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) + { + AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, + MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, + ResourceType_Series, call); + } + } + + + static void ModifyStudyInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags; + + if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) + { + AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/, + MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, + ResourceType_Study, call); + } + } + + + static void AnonymizeStudyInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags, keepPatientId; + + if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) + { + AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, + MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, + ResourceType_Study, call); + } + } + + + /*static void ModifyPatientInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags; + + if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) + { + AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, + MetadataType_ModifiedFrom, ChangeType_ModifiedPatient, + ResourceType_Patient, call); + } + }*/ + + + static void AnonymizePatientInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags, keepPatientId; + + if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) + { + AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, + MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient, + ResourceType_Patient, call); + } + } + + + + void OrthancRestApi::RegisterAnonymizeModify() + { + Register("/instances/{id}/content/*", GetRawContent); + + Register("/instances/{id}/modify", ModifyInstance); + Register("/series/{id}/modify", ModifySeriesInplace); + Register("/studies/{id}/modify", ModifyStudyInplace); + //Register("/patients/{id}/modify", ModifyPatientInplace); + + Register("/instances/{id}/anonymize", AnonymizeInstance); + Register("/series/{id}/anonymize", AnonymizeSeriesInplace); + Register("/studies/{id}/anonymize", AnonymizeStudyInplace); + Register("/patients/{id}/anonymize", AnonymizePatientInplace); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,84 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + // Upload of DICOM files through HTTP --------------------------------------- + + static void UploadDicomFile(RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + const std::string& postData = call.GetPostBody(); + if (postData.size() == 0) + { + return; + } + + LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP"; + + std::string publicId; + StoreStatus status = context.Store(publicId, postData); + Json::Value result = Json::objectValue; + + if (status != StoreStatus_Failure) + { + result["ID"] = publicId; + result["Path"] = GetBasePath(ResourceType_Instance, publicId); + } + + result["Status"] = EnumerationToString(status); + call.GetOutput().AnswerJson(result); + } + + + + // Registration of the various REST handlers -------------------------------- + + OrthancRestApi::OrthancRestApi(ServerContext& context) : + context_(context) + { + RegisterSystem(); + + RegisterChanges(); + RegisterResources(); + RegisterModalities(); + RegisterAnonymizeModify(); + RegisterArchive(); + + Register("/instances", UploadDicomFile); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,76 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../ServerContext.h" +#include "../../Core/RestApi/RestApi.h" + +#include <set> + +namespace Orthanc +{ + class OrthancRestApi : public RestApi + { + public: + typedef std::set<std::string> SetOfStrings; + + private: + ServerContext& context_; + + void RegisterSystem(); + + void RegisterChanges(); + + void RegisterResources(); + + void RegisterModalities(); + + void RegisterAnonymizeModify(); + + void RegisterArchive(); + + public: + OrthancRestApi(ServerContext& context); + + static ServerContext& GetContext(RestApi::Call& call) + { + OrthancRestApi& that = dynamic_cast<OrthancRestApi&>(call.GetContext()); + return that.context_; + } + + static ServerIndex& GetIndex(RestApi::Call& call) + { + return GetContext(call).GetIndex(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,295 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include "../../Core/Compression/HierarchicalZipWriter.h" +#include "../../Core/HttpServer/FilesystemHttpSender.h" +#include "../../Core/Uuid.h" + +#include <glog/logging.h> + +#if defined(_MSC_VER) +#define snprintf _snprintf +#endif + +static const uint64_t MEGA_BYTES = 1024 * 1024; +static const uint64_t GIGA_BYTES = 1024 * 1024 * 1024; + +namespace Orthanc +{ + // Download of ZIP files ---------------------------------------------------- + + static std::string GetDirectoryNameInArchive(const Json::Value& resource, + ResourceType resourceType) + { + switch (resourceType) + { + case ResourceType_Patient: + { + std::string p = resource["MainDicomTags"]["PatientID"].asString(); + std::string n = resource["MainDicomTags"]["PatientName"].asString(); + return p + " " + n; + } + + case ResourceType_Study: + { + return resource["MainDicomTags"]["StudyDescription"].asString(); + } + + case ResourceType_Series: + { + std::string d = resource["MainDicomTags"]["SeriesDescription"].asString(); + std::string m = resource["MainDicomTags"]["Modality"].asString(); + return m + " " + d; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + static bool CreateRootDirectoryInArchive(HierarchicalZipWriter& writer, + ServerContext& context, + const Json::Value& resource, + ResourceType resourceType) + { + if (resourceType == ResourceType_Patient) + { + return true; + } + + ResourceType parentType = GetParentResourceType(resourceType); + Json::Value parent; + + switch (resourceType) + { + case ResourceType_Study: + { + if (!context.GetIndex().LookupResource(parent, resource["ParentPatient"].asString(), parentType)) + { + return false; + } + + break; + } + + case ResourceType_Series: + if (!context.GetIndex().LookupResource(parent, resource["ParentStudy"].asString(), parentType) || + !CreateRootDirectoryInArchive(writer, context, parent, parentType)) + { + return false; + } + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + writer.OpenDirectory(GetDirectoryNameInArchive(parent, parentType).c_str()); + return true; + } + + static bool ArchiveInstance(HierarchicalZipWriter& writer, + ServerContext& context, + const std::string& instancePublicId, + const char* filename) + { + Json::Value instance; + + if (!context.GetIndex().LookupResource(instance, instancePublicId, ResourceType_Instance)) + { + return false; + } + + writer.OpenFile(filename); + + std::string dicom; + context.ReadFile(dicom, instancePublicId, FileContentType_Dicom); + writer.Write(dicom); + + return true; + } + + static bool ArchiveInternal(HierarchicalZipWriter& writer, + ServerContext& context, + const std::string& publicId, + ResourceType resourceType, + bool isFirstLevel) + { + Json::Value resource; + if (!context.GetIndex().LookupResource(resource, publicId, resourceType)) + { + return false; + } + + if (isFirstLevel && + !CreateRootDirectoryInArchive(writer, context, resource, resourceType)) + { + return false; + } + + writer.OpenDirectory(GetDirectoryNameInArchive(resource, resourceType).c_str()); + + switch (resourceType) + { + case ResourceType_Patient: + for (Json::Value::ArrayIndex i = 0; i < resource["Studies"].size(); i++) + { + std::string studyId = resource["Studies"][i].asString(); + if (!ArchiveInternal(writer, context, studyId, ResourceType_Study, false)) + { + return false; + } + } + break; + + case ResourceType_Study: + for (Json::Value::ArrayIndex i = 0; i < resource["Series"].size(); i++) + { + std::string seriesId = resource["Series"][i].asString(); + if (!ArchiveInternal(writer, context, seriesId, ResourceType_Series, false)) + { + return false; + } + } + break; + + case ResourceType_Series: + { + // Create a filename prefix, depending on the modality + char format[16] = "%08d"; + + if (resource["MainDicomTags"].isMember("Modality")) + { + std::string modality = resource["MainDicomTags"]["Modality"].asString(); + + if (modality.size() == 1) + { + snprintf(format, sizeof(format) - 1, "%c%%07d", toupper(modality[0])); + } + else if (modality.size() >= 2) + { + snprintf(format, sizeof(format) - 1, "%c%c%%06d", toupper(modality[0]), toupper(modality[1])); + } + } + + char filename[16]; + + for (Json::Value::ArrayIndex i = 0; i < resource["Instances"].size(); i++) + { + snprintf(filename, sizeof(filename) - 1, format, i); + + std::string publicId = resource["Instances"][i].asString(); + + // This was the implementation up to Orthanc 0.7.0: + // std::string filename = instance["MainDicomTags"]["SOPInstanceUID"].asString() + ".dcm"; + + if (!ArchiveInstance(writer, context, publicId, filename)) + { + return false; + } + } + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + writer.CloseDirectory(); + return true; + } + + template <enum ResourceType resourceType> + static void GetArchive(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string id = call.GetUriComponent("id", ""); + + /** + * Determine whether ZIP64 is required. Original ZIP format can + * store up to 2GB of data (some implementation supporting up to + * 4GB of data), and up to 65535 files. + * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 + **/ + + uint64_t uncompressedSize; + uint64_t compressedSize; + unsigned int countStudies; + unsigned int countSeries; + unsigned int countInstances; + context.GetIndex().GetStatistics(compressedSize, uncompressedSize, + countStudies, countSeries, countInstances, id); + const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES || + countInstances >= 65535); + + LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " + << (uncompressedSize / MEGA_BYTES) << "MB using the " + << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; + + // Create a RAII for the temporary file to manage the ZIP file + Toolbox::TemporaryFile tmp; + + { + // Create a ZIP writer + HierarchicalZipWriter writer(tmp.GetPath().c_str()); + writer.SetZip64(isZip64); + + // Store the requested resource into the ZIP + if (!ArchiveInternal(writer, context, id, resourceType, true)) + { + return; + } + } + + // Prepare the sending of the ZIP file + FilesystemHttpSender sender(tmp.GetPath().c_str()); + sender.SetContentType("application/zip"); + sender.SetDownloadFilename(id + ".zip"); + + // Send the ZIP + call.GetOutput().AnswerFile(sender); + + // The temporary file is automatically removed thanks to the RAII + } + + + void OrthancRestApi::RegisterArchive() + { + Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>); + Register("/studies/{id}/archive", GetArchive<ResourceType_Study>); + Register("/series/{id}/archive", GetArchive<ResourceType_Series>); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,132 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + // Changes API -------------------------------------------------------------- + + static void GetSinceAndLimit(int64_t& since, + unsigned int& limit, + bool& last, + const RestApi::GetCall& call) + { + static const unsigned int MAX_RESULTS = 100; + + if (call.HasArgument("last")) + { + last = true; + return; + } + + last = false; + + try + { + since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0")); + limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", "0")); + } + catch (boost::bad_lexical_cast) + { + return; + } + + if (limit == 0 || limit > MAX_RESULTS) + { + limit = MAX_RESULTS; + } + } + + static void GetChanges(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + //std::string filter = GetArgument(getArguments, "filter", ""); + int64_t since; + unsigned int limit; + bool last; + GetSinceAndLimit(since, limit, last, call); + + Json::Value result; + if ((!last && context.GetIndex().GetChanges(result, since, limit)) || + ( last && context.GetIndex().GetLastChange(result))) + { + call.GetOutput().AnswerJson(result); + } + } + + + static void DeleteChanges(RestApi::DeleteCall& call) + { + OrthancRestApi::GetIndex(call).DeleteChanges(); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + + // Exports API -------------------------------------------------------------- + + static void GetExports(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + int64_t since; + unsigned int limit; + bool last; + GetSinceAndLimit(since, limit, last, call); + + Json::Value result; + if ((!last && context.GetIndex().GetExportedResources(result, since, limit)) || + ( last && context.GetIndex().GetLastExportedResource(result))) + { + call.GetOutput().AnswerJson(result); + } + } + + + static void DeleteExports(RestApi::DeleteCall& call) + { + OrthancRestApi::GetIndex(call).DeleteExportedResources(); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + + void OrthancRestApi::RegisterChanges() + { + Register("/changes", GetChanges); + Register("/changes", DeleteChanges); + Register("/exports", GetExports); + Register("/exports", DeleteExports); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,470 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include "../DicomProtocol/DicomUserConnection.h" +#include "../OrthancInitialization.h" +#include "../../Core/HttpClient.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + // DICOM SCU ---------------------------------------------------------------- + + static bool MergeQueryAndTemplate(DicomMap& result, + const std::string& postData) + { + Json::Value query; + Json::Reader reader; + + if (!reader.parse(postData, query) || + query.type() != Json::objectValue) + { + return false; + } + + Json::Value::Members members = query.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + DicomTag t = FromDcmtkBridge::ParseTag(members[i]); + result.SetValue(t, query[members[i]].asString()); + } + + return true; + } + + static void DicomFindPatient(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindPatientTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindPatient(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + + static void DicomFindStudy(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindStudyTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindStudy(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + + static void DicomFindSeries(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindSeriesTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || + m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindSeries(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + + static void DicomFindInstance(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindInstanceTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || + m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 || + m.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindInstance(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + + static void DicomFind(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindPatientTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers patients; + connection.FindPatient(patients, m); + + // Loop over the found patients + Json::Value result = Json::arrayValue; + for (size_t i = 0; i < patients.GetSize(); i++) + { + Json::Value patient(Json::objectValue); + FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i)); + + DicomMap::SetupFindStudyTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); + + DicomFindAnswers studies; + connection.FindStudy(studies, m); + + patient["Studies"] = Json::arrayValue; + + // Loop over the found studies + for (size_t j = 0; j < studies.GetSize(); j++) + { + Json::Value study(Json::objectValue); + FromDcmtkBridge::ToJson(study, studies.GetAnswer(j)); + + DicomMap::SetupFindSeriesTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID); + m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); + + DicomFindAnswers series; + connection.FindSeries(series, m); + + // Loop over the found series + study["Series"] = Json::arrayValue; + for (size_t k = 0; k < series.GetSize(); k++) + { + Json::Value series2(Json::objectValue); + FromDcmtkBridge::ToJson(series2, series.GetAnswer(k)); + study["Series"].append(series2); + } + + patient["Studies"].append(study); + } + + result.append(patient); + } + + call.GetOutput().AnswerJson(result); + } + + + static bool GetInstancesToExport(std::list<std::string>& instances, + const std::string& remote, + RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string stripped = Toolbox::StripSpaces(call.GetPostBody()); + + Json::Value request; + if (Toolbox::IsSHA1(stripped)) + { + // This is for compatibility with Orthanc <= 0.5.1. + request = stripped; + } + else if (!call.ParseJsonRequest(request)) + { + // Bad JSON request + return false; + } + + if (request.isString()) + { + context.GetIndex().LogExportedResource(request.asString(), remote); + context.GetIndex().GetChildInstances(instances, request.asString()); + } + else if (request.isArray()) + { + for (Json::Value::ArrayIndex i = 0; i < request.size(); i++) + { + if (!request[i].isString()) + { + return false; + } + + std::string stripped = Toolbox::StripSpaces(request[i].asString()); + if (!Toolbox::IsSHA1(stripped)) + { + return false; + } + + context.GetIndex().LogExportedResource(stripped, remote); + + std::list<std::string> tmp; + context.GetIndex().GetChildInstances(tmp, stripped); + + for (std::list<std::string>::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) + { + instances.push_back(*it); + } + } + } + else + { + // Neither a string, nor a list of strings. Bad request. + return false; + } + + return true; + } + + + static void DicomStore(RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string remote = call.GetUriComponent("id", ""); + + std::list<std::string> instances; + if (!GetInstancesToExport(instances, remote, call)) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, remote); + + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + LOG(INFO) << "Sending resource " << *it << " to modality \"" << remote << "\""; + + std::string dicom; + context.ReadFile(dicom, *it, FileContentType_Dicom); + connection.Store(dicom); + } + + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + // Orthanc Peers ------------------------------------------------------------ + + static bool IsExistingPeer(const OrthancRestApi::SetOfStrings& peers, + const std::string& id) + { + return peers.find(id) != peers.end(); + } + + static void ListPeers(RestApi::GetCall& call) + { + OrthancRestApi::SetOfStrings peers; + GetListOfOrthancPeers(peers); + + Json::Value result = Json::arrayValue; + for (OrthancRestApi::SetOfStrings::const_iterator + it = peers.begin(); it != peers.end(); ++it) + { + result.append(*it); + } + + call.GetOutput().AnswerJson(result); + } + + static void ListPeerOperations(RestApi::GetCall& call) + { + OrthancRestApi::SetOfStrings peers; + GetListOfOrthancPeers(peers); + + std::string id = call.GetUriComponent("id", ""); + if (IsExistingPeer(peers, id)) + { + Json::Value result = Json::arrayValue; + result.append("store"); + call.GetOutput().AnswerJson(result); + } + } + + static void PeerStore(RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string remote = call.GetUriComponent("id", ""); + + std::list<std::string> instances; + if (!GetInstancesToExport(instances, remote, call)) + { + return; + } + + std::string url, username, password; + GetOrthancPeer(remote, url, username, password); + + // Configure the HTTP client + HttpClient client; + if (username.size() != 0 && password.size() != 0) + { + client.SetCredentials(username.c_str(), password.c_str()); + } + + client.SetUrl(url + "instances"); + client.SetMethod(HttpMethod_Post); + + // Loop over the instances that are to be sent + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + LOG(INFO) << "Sending resource " << *it << " to peer \"" << remote << "\""; + + context.ReadFile(client.AccessPostData(), *it, FileContentType_Dicom); + + std::string answer; + if (!client.Apply(answer)) + { + LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << remote << "\""; + return; + } + } + + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + // DICOM bridge ------------------------------------------------------------- + + static bool IsExistingModality(const OrthancRestApi::SetOfStrings& modalities, + const std::string& id) + { + return modalities.find(id) != modalities.end(); + } + + static void ListModalities(RestApi::GetCall& call) + { + OrthancRestApi::SetOfStrings modalities; + GetListOfDicomModalities(modalities); + + Json::Value result = Json::arrayValue; + for (OrthancRestApi::SetOfStrings::const_iterator + it = modalities.begin(); it != modalities.end(); ++it) + { + result.append(*it); + } + + call.GetOutput().AnswerJson(result); + } + + + static void ListModalityOperations(RestApi::GetCall& call) + { + OrthancRestApi::SetOfStrings modalities; + GetListOfDicomModalities(modalities); + + std::string id = call.GetUriComponent("id", ""); + if (IsExistingModality(modalities, id)) + { + Json::Value result = Json::arrayValue; + result.append("find-patient"); + result.append("find-study"); + result.append("find-series"); + result.append("find-instance"); + result.append("find"); + result.append("store"); + call.GetOutput().AnswerJson(result); + } + } + + + void OrthancRestApi::RegisterModalities() + { + Register("/modalities", ListModalities); + Register("/modalities/{id}", ListModalityOperations); + Register("/modalities/{id}/find-patient", DicomFindPatient); + Register("/modalities/{id}/find-study", DicomFindStudy); + Register("/modalities/{id}/find-series", DicomFindSeries); + Register("/modalities/{id}/find-instance", DicomFindInstance); + Register("/modalities/{id}/find", DicomFind); + Register("/modalities/{id}/store", DicomStore); + + Register("/peers", ListPeers); + Register("/peers/{id}", ListPeerOperations); + Register("/peers/{id}/store", PeerStore); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,602 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include "../ServerToolbox.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + // List all the patients, studies, series or instances ---------------------- + + template <enum ResourceType resourceType> + static void ListResources(RestApi::GetCall& call) + { + Json::Value result; + OrthancRestApi::GetIndex(call).GetAllUuids(result, resourceType); + call.GetOutput().AnswerJson(result); + } + + template <enum ResourceType resourceType> + static void GetSingleResource(RestApi::GetCall& call) + { + Json::Value result; + if (OrthancRestApi::GetIndex(call).LookupResource(result, call.GetUriComponent("id", ""), resourceType)) + { + call.GetOutput().AnswerJson(result); + } + } + + template <enum ResourceType resourceType> + static void DeleteSingleResource(RestApi::DeleteCall& call) + { + Json::Value result; + if (OrthancRestApi::GetIndex(call).DeleteResource(result, call.GetUriComponent("id", ""), resourceType)) + { + call.GetOutput().AnswerJson(result); + } + } + + + // Get information about a single patient ----------------------------------- + + static void IsProtectedPatient(RestApi::GetCall& call) + { + std::string publicId = call.GetUriComponent("id", ""); + bool isProtected = OrthancRestApi::GetIndex(call).IsProtectedPatient(publicId); + call.GetOutput().AnswerBuffer(isProtected ? "1" : "0", "text/plain"); + } + + + static void SetPatientProtection(RestApi::PutCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string s = Toolbox::StripSpaces(call.GetPutBody()); + + if (s == "0") + { + context.GetIndex().SetProtectedPatient(publicId, false); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + else if (s == "1") + { + context.GetIndex().SetProtectedPatient(publicId, true); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + else + { + // Bad request + } + } + + + // Get information about a single instance ---------------------------------- + + static void GetInstanceFile(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string publicId = call.GetUriComponent("id", ""); + context.AnswerDicomFile(call.GetOutput(), publicId, FileContentType_Dicom); + } + + + static void ExportInstanceFile(RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string publicId = call.GetUriComponent("id", ""); + + std::string dicom; + context.ReadFile(dicom, publicId, FileContentType_Dicom); + + Toolbox::WriteFile(dicom, call.GetPostBody()); + + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + template <bool simplify> + static void GetInstanceTags(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string publicId = call.GetUriComponent("id", ""); + + Json::Value full; + context.ReadJson(full, publicId); + + if (simplify) + { + Json::Value simplified; + SimplifyTags(simplified, full); + call.GetOutput().AnswerJson(simplified); + } + else + { + call.GetOutput().AnswerJson(full); + } + } + + + static void ListFrames(RestApi::GetCall& call) + { + Json::Value instance; + if (OrthancRestApi::GetIndex(call).LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance)) + { + unsigned int numberOfFrames = 1; + + try + { + Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"]; + numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString()); + } + catch (...) + { + } + + Json::Value result = Json::arrayValue; + for (unsigned int i = 0; i < numberOfFrames; i++) + { + result.append(i); + } + + call.GetOutput().AnswerJson(result); + } + } + + + template <enum ImageExtractionMode mode> + static void GetImage(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string frameId = call.GetUriComponent("frame", "0"); + + unsigned int frame; + try + { + frame = boost::lexical_cast<unsigned int>(frameId); + } + catch (boost::bad_lexical_cast) + { + return; + } + + std::string publicId = call.GetUriComponent("id", ""); + std::string dicomContent, png; + context.ReadFile(dicomContent, publicId, FileContentType_Dicom); + + try + { + FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode); + call.GetOutput().AnswerBuffer(png, "image/png"); + } + catch (OrthancException& e) + { + if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange) + { + // The frame number is out of the range for this DICOM + // instance, the resource is not existent + } + else + { + std::string root = ""; + for (size_t i = 1; i < call.GetFullUri().size(); i++) + { + root += "../"; + } + + call.GetOutput().Redirect(root + "app/images/unsupported.png"); + } + } + } + + + + static void GetResourceStatistics(RestApi::GetCall& call) + { + std::string publicId = call.GetUriComponent("id", ""); + Json::Value result; + OrthancRestApi::GetIndex(call).GetStatistics(result, publicId); + call.GetOutput().AnswerJson(result); + } + + + + // Handling of metadata ----------------------------------------------------- + + static void CheckValidResourceType(RestApi::Call& call) + { + std::string resourceType = call.GetUriComponent("resourceType", ""); + StringToResourceType(resourceType.c_str()); + } + + + static void ListMetadata(RestApi::GetCall& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::list<MetadataType> metadata; + + OrthancRestApi::GetIndex(call).ListAvailableMetadata(metadata, publicId); + Json::Value result = Json::arrayValue; + + for (std::list<MetadataType>::const_iterator + it = metadata.begin(); it != metadata.end(); ++it) + { + result.append(EnumerationToString(*it)); + } + + call.GetOutput().AnswerJson(result); + } + + + static void GetMetadata(RestApi::GetCall& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + MetadataType metadata = StringToMetadata(name); + + std::string value; + if (OrthancRestApi::GetIndex(call).LookupMetadata(value, publicId, metadata)) + { + call.GetOutput().AnswerBuffer(value, "text/plain"); + } + } + + + static void DeleteMetadata(RestApi::DeleteCall& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + MetadataType metadata = StringToMetadata(name); + + if (metadata >= MetadataType_StartUser && + metadata <= MetadataType_EndUser) + { + // It is forbidden to modify internal metadata + OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + } + + + static void SetMetadata(RestApi::PutCall& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + MetadataType metadata = StringToMetadata(name); + std::string value = call.GetPutBody(); + + if (metadata >= MetadataType_StartUser && + metadata <= MetadataType_EndUser) + { + // It is forbidden to modify internal metadata + OrthancRestApi::GetIndex(call).SetMetadata(publicId, metadata, value); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + } + + + + + // Handling of attached files ----------------------------------------------- + + static void ListAttachments(RestApi::GetCall& call) + { + std::string resourceType = call.GetUriComponent("resourceType", ""); + std::string publicId = call.GetUriComponent("id", ""); + std::list<FileContentType> attachments; + OrthancRestApi::GetIndex(call).ListAvailableAttachments(attachments, publicId, StringToResourceType(resourceType.c_str())); + + Json::Value result = Json::arrayValue; + + for (std::list<FileContentType>::const_iterator + it = attachments.begin(); it != attachments.end(); ++it) + { + result.append(EnumerationToString(*it)); + } + + call.GetOutput().AnswerJson(result); + } + + + static bool GetAttachmentInfo(FileInfo& info, RestApi::Call& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + FileContentType contentType = StringToContentType(name); + + return OrthancRestApi::GetIndex(call).LookupAttachment(info, publicId, contentType); + } + + + static void GetAttachmentOperations(RestApi::GetCall& call) + { + FileInfo info; + if (GetAttachmentInfo(info, call)) + { + Json::Value operations = Json::arrayValue; + + operations.append("compressed-data"); + + if (info.GetCompressedMD5() != "") + { + operations.append("compressed-md5"); + } + + operations.append("compressed-size"); + operations.append("data"); + + if (info.GetUncompressedMD5() != "") + { + operations.append("md5"); + } + + operations.append("size"); + + if (info.GetCompressedMD5() != "" && + info.GetUncompressedMD5() != "") + { + operations.append("verify-md5"); + } + + call.GetOutput().AnswerJson(operations); + } + } + + + template <int uncompress> + static void GetAttachmentData(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + + std::string content; + context.ReadFile(content, publicId, StringToContentType(name), + (uncompress == 1)); + + call.GetOutput().AnswerBuffer(content, "application/octet-stream"); + } + + + static void GetAttachmentSize(RestApi::GetCall& call) + { + FileInfo info; + if (GetAttachmentInfo(info, call)) + { + call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedSize()), "text/plain"); + } + } + + + static void GetAttachmentCompressedSize(RestApi::GetCall& call) + { + FileInfo info; + if (GetAttachmentInfo(info, call)) + { + call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedSize()), "text/plain"); + } + } + + + static void GetAttachmentMD5(RestApi::GetCall& call) + { + FileInfo info; + if (GetAttachmentInfo(info, call) && + info.GetUncompressedMD5() != "") + { + call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedMD5()), "text/plain"); + } + } + + + static void GetAttachmentCompressedMD5(RestApi::GetCall& call) + { + FileInfo info; + if (GetAttachmentInfo(info, call) && + info.GetCompressedMD5() != "") + { + call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedMD5()), "text/plain"); + } + } + + + static void VerifyAttachment(RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + + FileInfo info; + if (!GetAttachmentInfo(info, call) || + info.GetCompressedMD5() == "" || + info.GetUncompressedMD5() == "") + { + // Inexistent resource, or no MD5 available + return; + } + + bool ok = false; + + // First check whether the compressed data is correctly stored in the disk + std::string data; + context.ReadFile(data, publicId, StringToContentType(name), false); + + std::string actualMD5; + Toolbox::ComputeMD5(actualMD5, data); + + if (actualMD5 == info.GetCompressedMD5()) + { + // The compressed data is OK. If a compression algorithm was + // applied to it, now check the MD5 of the uncompressed data. + if (info.GetCompressionType() == CompressionType_None) + { + ok = true; + } + else + { + context.ReadFile(data, publicId, StringToContentType(name), true); + Toolbox::ComputeMD5(actualMD5, data); + ok = (actualMD5 == info.GetUncompressedMD5()); + } + } + + if (ok) + { + LOG(INFO) << "The attachment " << name << " of resource " << publicId << " has the right MD5"; + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + else + { + LOG(INFO) << "The attachment " << name << " of resource " << publicId << " has bad MD5!"; + } + } + + + static void UploadAttachment(RestApi::PutCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + + const void* data = call.GetPutBody().size() ? &call.GetPutBody()[0] : NULL; + + FileContentType contentType = StringToContentType(name); + if (contentType >= FileContentType_StartUser && // It is forbidden to modify internal attachments + contentType <= FileContentType_EndUser && + context.AddAttachment(publicId, StringToContentType(name), data, call.GetPutBody().size())) + { + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + } + + + static void DeleteAttachment(RestApi::DeleteCall& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + FileContentType contentType = StringToContentType(name); + + if (contentType >= FileContentType_StartUser && + contentType <= FileContentType_EndUser) + { + // It is forbidden to delete internal attachments + OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType); + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + } + + + + + void OrthancRestApi::RegisterResources() + { + Register("/instances", ListResources<ResourceType_Instance>); + Register("/patients", ListResources<ResourceType_Patient>); + Register("/series", ListResources<ResourceType_Series>); + Register("/studies", ListResources<ResourceType_Study>); + + Register("/instances/{id}", DeleteSingleResource<ResourceType_Instance>); + Register("/instances/{id}", GetSingleResource<ResourceType_Instance>); + Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>); + Register("/patients/{id}", GetSingleResource<ResourceType_Patient>); + Register("/series/{id}", DeleteSingleResource<ResourceType_Series>); + Register("/series/{id}", GetSingleResource<ResourceType_Series>); + Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>); + Register("/studies/{id}", GetSingleResource<ResourceType_Study>); + + Register("/instances/{id}/statistics", GetResourceStatistics); + Register("/patients/{id}/statistics", GetResourceStatistics); + Register("/studies/{id}/statistics", GetResourceStatistics); + Register("/series/{id}/statistics", GetResourceStatistics); + + Register("/instances/{id}/file", GetInstanceFile); + Register("/instances/{id}/export", ExportInstanceFile); + Register("/instances/{id}/tags", GetInstanceTags<false>); + Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); + Register("/instances/{id}/frames", ListFrames); + + Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); + Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); + Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); + Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); + Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); + Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); + Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); + Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); + + Register("/patients/{id}/protected", IsProtectedPatient); + Register("/patients/{id}/protected", SetPatientProtection); + + Register("/{resourceType}/{id}/metadata", ListMetadata); + Register("/{resourceType}/{id}/metadata/{name}", DeleteMetadata); + Register("/{resourceType}/{id}/metadata/{name}", GetMetadata); + Register("/{resourceType}/{id}/metadata/{name}", SetMetadata); + + Register("/{resourceType}/{id}/attachments", ListAttachments); + Register("/{resourceType}/{id}/attachments/{name}", DeleteAttachment); + Register("/{resourceType}/{id}/attachments/{name}", GetAttachmentOperations); + Register("/{resourceType}/{id}/attachments/{name}/compressed-data", GetAttachmentData<0>); + Register("/{resourceType}/{id}/attachments/{name}/compressed-md5", GetAttachmentCompressedMD5); + Register("/{resourceType}/{id}/attachments/{name}/compressed-size", GetAttachmentCompressedSize); + Register("/{resourceType}/{id}/attachments/{name}/data", GetAttachmentData<1>); + Register("/{resourceType}/{id}/attachments/{name}/md5", GetAttachmentMD5); + Register("/{resourceType}/{id}/attachments/{name}/size", GetAttachmentSize); + Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment); + Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,109 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include "../OrthancInitialization.h" + +#include <glog/logging.h> + + +namespace Orthanc +{ + // System information ------------------------------------------------------- + + static void ServeRoot(RestApi::GetCall& call) + { + call.GetOutput().Redirect("app/explorer.html"); + } + + static void GetSystemInformation(RestApi::GetCall& call) + { + Json::Value result = Json::objectValue; + + result["Version"] = ORTHANC_VERSION; + result["Name"] = GetGlobalStringParameter("Name", ""); + + call.GetOutput().AnswerJson(result); + } + + static void GetStatistics(RestApi::GetCall& call) + { + Json::Value result = Json::objectValue; + OrthancRestApi::GetIndex(call).ComputeStatistics(result); + call.GetOutput().AnswerJson(result); + } + + static void GenerateUid(RestApi::GetCall& call) + { + std::string level = call.GetArgument("level", ""); + if (level == "patient") + { + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient), "text/plain"); + } + else if (level == "study") + { + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study), "text/plain"); + } + else if (level == "series") + { + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series), "text/plain"); + } + else if (level == "instance") + { + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance), "text/plain"); + } + } + + static void ExecuteScript(RestApi::PostCall& call) + { + std::string result; + ServerContext& context = OrthancRestApi::GetContext(call); + context.GetLuaContext().Execute(result, call.GetPostBody()); + call.GetOutput().AnswerBuffer(result, "text/plain"); + } + + static void GetNowIsoString(RestApi::GetCall& call) + { + call.GetOutput().AnswerBuffer(Toolbox::GetNowIsoString(), "text/plain"); + } + + void OrthancRestApi::RegisterSystem() + { + Register("/", ServeRoot); + Register("/system", GetSystemInformation); + Register("/statistics", GetStatistics); + Register("/tools/generate-uid", GenerateUid); + Register("/tools/execute-script", ExecuteScript); + Register("/tools/now", GetNowIsoString); + } +}
--- a/OrthancServer/PrepareDatabase.sql Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/PrepareDatabase.sql Tue Apr 22 16:47:21 2014 +0200 @@ -32,6 +32,8 @@ compressedSize INTEGER, uncompressedSize INTEGER, compressionType INTEGER, + uncompressedMD5 TEXT, -- New in Orthanc 0.7.3 (database v4) + compressedMD5 TEXT, -- New in Orthanc 0.7.3 (database v4) PRIMARY KEY(id, fileType) ); @@ -75,7 +77,9 @@ AFTER DELETE ON AttachedFiles BEGIN SELECT SignalFileDeleted(old.uuid, old.fileType, old.uncompressedSize, - old.compressionType, old.compressedSize); + old.compressionType, old.compressedSize, + -- These 2 arguments are new in Orthanc 0.7.3 (database v4) + old.uncompressedMD5, old.compressedMD5); END; CREATE TRIGGER ResourceDeleted @@ -103,4 +107,4 @@ -- Set the version of the database schema -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration -INSERT INTO GlobalProperties VALUES (1, "3"); +INSERT INTO GlobalProperties VALUES (1, "4");
--- a/OrthancServer/ServerContext.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/ServerContext.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -61,6 +61,7 @@ storage_(storagePath.string()), index_(*this, indexPath.string()), accessor_(storage_), + compressionEnabled_(false), provider_(*this), dicomCache_(provider_, DICOM_CACHE_SIZE) { @@ -115,7 +116,7 @@ } FileInfo dicomInfo = accessor_.Write(dicomInstance, dicomSize, FileContentType_Dicom); - FileInfo jsonInfo = accessor_.Write(dicomJson.toStyledString(), FileContentType_Json); + FileInfo jsonInfo = accessor_.Write(dicomJson.toStyledString(), FileContentType_DicomAsJson); ServerIndex::Attachments attachments; attachments.push_back(dicomInfo); @@ -152,9 +153,9 @@ } - void ServerContext::AnswerFile(RestApiOutput& output, - const std::string& instancePublicId, - FileContentType content) + void ServerContext::AnswerDicomFile(RestApiOutput& output, + const std::string& instancePublicId, + FileContentType content) { FileInfo attachment; if (!index_.LookupAttachment(attachment, instancePublicId, content)) @@ -175,7 +176,7 @@ const std::string& instancePublicId) { std::string s; - ReadFile(s, instancePublicId, FileContentType_Json); + ReadFile(s, instancePublicId, FileContentType_DicomAsJson); Json::Reader reader; if (!reader.parse(s, result)) @@ -187,7 +188,8 @@ void ServerContext::ReadFile(std::string& result, const std::string& instancePublicId, - FileContentType content) + FileContentType content, + bool uncompressIfNeeded) { FileInfo attachment; if (!index_.LookupAttachment(attachment, instancePublicId, content)) @@ -195,7 +197,15 @@ throw OrthancException(ErrorCode_InternalError); } - accessor_.SetCompressionForNextOperations(attachment.GetCompressionType()); + if (uncompressIfNeeded) + { + accessor_.SetCompressionForNextOperations(attachment.GetCompressionType()); + } + else + { + accessor_.SetCompressionForNextOperations(CompressionType_None); + } + accessor_.Read(result, attachment.GetUuid()); } @@ -228,19 +238,31 @@ DicomMap dicomSummary; FromDcmtkBridge::Convert(dicomSummary, *dicomInstance.getDataset()); - DicomInstanceHasher hasher(dicomSummary); - resultPublicId = hasher.HashInstance(); + try + { + DicomInstanceHasher hasher(dicomSummary); + resultPublicId = hasher.HashInstance(); - Json::Value dicomJson; - FromDcmtkBridge::ToJson(dicomJson, *dicomInstance.getDataset()); + Json::Value dicomJson; + FromDcmtkBridge::ToJson(dicomJson, *dicomInstance.getDataset()); - StoreStatus status = StoreStatus_Failure; - if (dicomSize > 0) + StoreStatus status = StoreStatus_Failure; + if (dicomSize > 0) + { + status = Store(dicomBuffer, dicomSize, dicomSummary, dicomJson, ""); + } + + return status; + } + catch (OrthancException& e) { - status = Store(dicomBuffer, dicomSize, dicomSummary, dicomJson, ""); - } + if (e.GetErrorCode() == ErrorCode_InexistentTag) + { + LogMissingRequiredTag(dicomSummary); + } - return status; + throw e; + } } @@ -268,4 +290,40 @@ return Store(resultPublicId, dicom.GetDicom(), dicomBuffer, dicomSize); } + void ServerContext::SetStoreMD5ForAttachments(bool storeMD5) + { + LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no"); + accessor_.SetStoreMD5(storeMD5); + } + + + bool ServerContext::AddAttachment(const std::string& resourceId, + FileContentType attachmentType, + const void* data, + size_t size) + { + LOG(INFO) << "Adding attachment " << EnumerationToString(attachmentType) << " to resource " << resourceId; + + if (compressionEnabled_) + { + accessor_.SetCompressionForNextOperations(CompressionType_Zlib); + } + else + { + accessor_.SetCompressionForNextOperations(CompressionType_None); + } + + FileInfo info = accessor_.Write(data, size, attachmentType); + StoreStatus status = index_.AddAttachment(info, resourceId); + + if (status != StoreStatus_Success) + { + storage_.Remove(info.GetUuid()); + return false; + } + else + { + return true; + } + } }
--- a/OrthancServer/ServerContext.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/ServerContext.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -91,6 +91,11 @@ void RemoveFile(const std::string& fileUuid); + bool AddAttachment(const std::string& resourceId, + FileContentType attachmentType, + const void* data, + size_t size); + StoreStatus Store(const char* dicomInstance, size_t dicomSize, const DicomMap& dicomSummary, @@ -118,9 +123,9 @@ return Store(resultPublicId, &dicomContent[0], dicomContent.size()); } - void AnswerFile(RestApiOutput& output, - const std::string& instancePublicId, - FileContentType content); + void AnswerDicomFile(RestApiOutput& output, + const std::string& instancePublicId, + FileContentType content); void ReadJson(Json::Value& result, const std::string& instancePublicId); @@ -128,7 +133,8 @@ // TODO CACHING MECHANISM AT THIS POINT void ReadFile(std::string& result, const std::string& instancePublicId, - FileContentType content); + FileContentType content, + bool uncompressIfNeeded = true); // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD ParsedDicomFile& GetDicomFile(const std::string& instancePublicId); @@ -137,5 +143,12 @@ { return lua_; } + + void SetStoreMD5ForAttachments(bool storeMD5); + + bool IsStoreMD5ForAttachments() const + { + return accessor_.IsStoreMD5(); + } }; }
--- a/OrthancServer/ServerEnumerations.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/ServerEnumerations.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,28 +32,87 @@ #include "ServerEnumerations.h" #include "../Core/OrthancException.h" +#include "../Core/EnumerationDictionary.h" +#include "../Core/Toolbox.h" + +#include <boost/thread.hpp> namespace Orthanc { - const char* ToString(ResourceType type) + static boost::mutex enumerationsMutex_; + static Toolbox::EnumerationDictionary<MetadataType> dictMetadataType_; + static Toolbox::EnumerationDictionary<FileContentType> dictContentType_; + + void InitializeServerEnumerations() { - switch (type) + boost::mutex::scoped_lock lock(enumerationsMutex_); + + dictMetadataType_.Add(MetadataType_Instance_IndexInSeries, "IndexInSeries"); + dictMetadataType_.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate"); + dictMetadataType_.Add(MetadataType_Instance_RemoteAet, "RemoteAET"); + dictMetadataType_.Add(MetadataType_Series_ExpectedNumberOfInstances, "ExpectedNumberOfInstances"); + dictMetadataType_.Add(MetadataType_ModifiedFrom, "ModifiedFrom"); + dictMetadataType_.Add(MetadataType_AnonymizedFrom, "AnonymizedFrom"); + dictMetadataType_.Add(MetadataType_LastUpdate, "LastUpdate"); + + dictContentType_.Add(FileContentType_Dicom, "dicom"); + dictContentType_.Add(FileContentType_DicomAsJson, "dicom-as-json"); + } + + void RegisterUserMetadata(int metadata, + const std::string& name) + { + boost::mutex::scoped_lock lock(enumerationsMutex_); + + if (metadata < static_cast<int>(MetadataType_StartUser) || + metadata > static_cast<int>(MetadataType_EndUser)) { - case ResourceType_Patient: - return "Patient"; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } - case ResourceType_Study: - return "Study"; + dictMetadataType_.Add(static_cast<MetadataType>(metadata), name); + } - case ResourceType_Series: - return "Series"; + std::string EnumerationToString(MetadataType type) + { + // This function MUST return a "std::string" and not "const + // char*", as the result is not a static string + boost::mutex::scoped_lock lock(enumerationsMutex_); + return dictMetadataType_.Translate(type); + } + + MetadataType StringToMetadata(const std::string& str) + { + boost::mutex::scoped_lock lock(enumerationsMutex_); + return dictMetadataType_.Translate(str); + } + + void RegisterUserContentType(int contentType, + const std::string& name) + { + boost::mutex::scoped_lock lock(enumerationsMutex_); - case ResourceType_Instance: - return "Instance"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + if (contentType < static_cast<int>(FileContentType_StartUser) || + contentType > static_cast<int>(FileContentType_EndUser)) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); } + + dictContentType_.Add(static_cast<FileContentType>(contentType), name); + } + + std::string EnumerationToString(FileContentType type) + { + // This function MUST return a "std::string" and not "const + // char*", as the result is not a static string + boost::mutex::scoped_lock lock(enumerationsMutex_); + return dictContentType_.Translate(type); + } + + FileContentType StringToContentType(const std::string& str) + { + boost::mutex::scoped_lock lock(enumerationsMutex_); + return dictContentType_.Translate(str); } std::string GetBasePath(ResourceType type, @@ -78,7 +137,7 @@ } } - const char* ToString(SeriesStatus status) + const char* EnumerationToString(SeriesStatus status) { switch (status) { @@ -99,7 +158,7 @@ } } - const char* ToString(StoreStatus status) + const char* EnumerationToString(StoreStatus status) { switch (status) { @@ -121,7 +180,7 @@ } - const char* ToString(ChangeType type) + const char* EnumerationToString(ChangeType type) { switch (type) { @@ -158,6 +217,15 @@ case ChangeType_ModifiedPatient: return "ModifiedPatient"; + case ChangeType_StablePatient: + return "StablePatient"; + + case ChangeType_StableStudy: + return "StableStudy"; + + case ChangeType_StableSeries: + return "StableSeries"; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -200,4 +268,83 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } + + + const char* EnumerationToString(ModalityManufacturer manufacturer) + { + switch (manufacturer) + { + case ModalityManufacturer_Generic: + return "Generic"; + + case ModalityManufacturer_ClearCanvas: + return "ClearCanvas"; + + case ModalityManufacturer_MedInria: + return "MedInria"; + + case ModalityManufacturer_Dcm4Chee: + return "Dcm4Chee"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(DicomRequestType type) + { + switch (type) + { + case DicomRequestType_Echo: + return "Echo"; + break; + + case DicomRequestType_Find: + return "Find"; + break; + + case DicomRequestType_Get: + return "Get"; + break; + + case DicomRequestType_Move: + return "Move"; + break; + + case DicomRequestType_Store: + return "Store"; + break; + + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + + ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer) + { + if (manufacturer == "Generic") + { + return ModalityManufacturer_Generic; + } + else if (manufacturer == "ClearCanvas") + { + return ModalityManufacturer_ClearCanvas; + } + else if (manufacturer == "MedInria") + { + return ModalityManufacturer_MedInria; + } + else if (manufacturer == "Dcm4Chee") + { + return ModalityManufacturer_Dcm4Chee; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } }
--- a/OrthancServer/ServerEnumerations.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/ServerEnumerations.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -33,6 +33,8 @@ #include <string> +#include "../Core/Enumerations.h" + namespace Orthanc { enum SeriesStatus @@ -51,6 +53,23 @@ StoreStatus_FilteredOut // Removed by NewInstanceFilter }; + enum ModalityManufacturer + { + ModalityManufacturer_Generic, + ModalityManufacturer_ClearCanvas, + ModalityManufacturer_MedInria, + ModalityManufacturer_Dcm4Chee + }; + + enum DicomRequestType + { + DicomRequestType_Echo, + DicomRequestType_Find, + DicomRequestType_Get, + DicomRequestType_Move, + DicomRequestType_Store + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -65,14 +84,6 @@ GlobalProperty_AnonymizationSequence = 3 }; - enum ResourceType - { - ResourceType_Patient = 1, - ResourceType_Study = 2, - ResourceType_Series = 3, - ResourceType_Instance = 4 - }; - enum MetadataType { MetadataType_Instance_IndexInSeries = 1, @@ -80,7 +91,12 @@ MetadataType_Instance_RemoteAet = 3, MetadataType_Series_ExpectedNumberOfInstances = 4, MetadataType_ModifiedFrom = 5, - MetadataType_AnonymizedFrom = 6 + MetadataType_AnonymizedFrom = 6, + MetadataType_LastUpdate = 7, + + // Make sure that the value "65535" can be stored into this enumeration + MetadataType_StartUser = 1024, + MetadataType_EndUser = 65535 }; enum ChangeType @@ -95,19 +111,42 @@ ChangeType_ModifiedStudy = 8, ChangeType_ModifiedSeries = 9, ChangeType_AnonymizedPatient = 10, - ChangeType_ModifiedPatient = 11 + ChangeType_ModifiedPatient = 11, + ChangeType_StablePatient = 12, + ChangeType_StableStudy = 13, + ChangeType_StableSeries = 14 }; + void InitializeServerEnumerations(); + + void RegisterUserMetadata(int metadata, + const std::string& name); + + MetadataType StringToMetadata(const std::string& str); + + std::string EnumerationToString(MetadataType type); + + void RegisterUserContentType(int contentType, + const std::string& name); + + FileContentType StringToContentType(const std::string& str); + + std::string EnumerationToString(FileContentType type); + std::string GetBasePath(ResourceType type, const std::string& publicId); - const char* ToString(ResourceType type); + const char* EnumerationToString(SeriesStatus status); - const char* ToString(SeriesStatus status); + const char* EnumerationToString(StoreStatus status); + + const char* EnumerationToString(ChangeType type); - const char* ToString(StoreStatus status); + const char* EnumerationToString(ModalityManufacturer manufacturer); - const char* ToString(ChangeType type); + const char* EnumerationToString(DicomRequestType type); + + ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); ResourceType GetParentResourceType(ResourceType type);
--- a/OrthancServer/ServerIndex.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/ServerIndex.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -37,6 +37,7 @@ #endif #include "EmbeddedResources.h" +#include "OrthancInitialization.h" #include "../Core/Toolbox.h" #include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomArray.h" @@ -48,6 +49,8 @@ #include <stdio.h> #include <glog/logging.h> +static const uint64_t MEGA_BYTES = 1024 * 1024; + namespace Orthanc { namespace Internals @@ -88,7 +91,7 @@ { for (std::list<std::string>::iterator it = pendingFilesToRemove_.begin(); - it != pendingFilesToRemove_.end(); it++) + it != pendingFilesToRemove_.end(); ++it) { context_.RemoveFile(*it); } @@ -185,6 +188,27 @@ }; + struct ServerIndex::UnstableResourcePayload + { + Orthanc::ResourceType type_; + boost::posix_time::ptime time_; + + UnstableResourcePayload() : type_(Orthanc::ResourceType_Instance) + { + } + + UnstableResourcePayload(Orthanc::ResourceType type) : type_(type) + { + time_ = boost::posix_time::second_clock::local_time(); + } + + unsigned int GetAge() const + { + return (boost::posix_time::second_clock::local_time() - time_).total_seconds(); + } + }; + + bool ServerIndex::DeleteResource(Json::Value& target, const std::string& uuid, ResourceType expectedType) @@ -211,7 +235,7 @@ target["RemainingAncestor"] = Json::Value(Json::objectValue); target["RemainingAncestor"]["Path"] = GetBasePath(type, uuid); - target["RemainingAncestor"]["Type"] = ToString(type); + target["RemainingAncestor"]["Type"] = EnumerationToString(type); target["RemainingAncestor"]["ID"] = uuid; } else @@ -225,23 +249,87 @@ } - static void FlushThread(DatabaseWrapper* db, - boost::mutex* mutex, - unsigned int sleep) + void ServerIndex::FlushThread(ServerIndex* that) { + unsigned int sleep; + + try + { + boost::mutex::scoped_lock lock(that->mutex_); + std::string sleepString = that->db_->GetGlobalProperty(GlobalProperty_FlushSleep); + sleep = boost::lexical_cast<unsigned int>(sleepString); + } + catch (boost::bad_lexical_cast&) + { + // By default, wait for 10 seconds before flushing + sleep = 10; + } + LOG(INFO) << "Starting the database flushing thread (sleep = " << sleep << ")"; - while (1) + unsigned int count = 0; + + while (!that->done_) + { + boost::this_thread::sleep(boost::posix_time::seconds(1)); + count++; + if (count < sleep) + { + continue; + } + + boost::mutex::scoped_lock lock(that->mutex_); + that->db_->FlushToDisk(); + count = 0; + } + + LOG(INFO) << "Stopping the database flushing thread"; + } + + + static void ComputeExpectedNumberOfInstances(DatabaseWrapper& db, + int64_t series, + const DicomMap& dicomSummary) + { + try { - boost::this_thread::sleep(boost::posix_time::seconds(sleep)); - boost::mutex::scoped_lock lock(*mutex); - db->FlushToDisk(); + const DicomValue* value; + const DicomValue* value2; + + if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL && + (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS)) != NULL) + { + // Patch for series with temporal positions thanks to Will Ryder + int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->AsString()); + int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->AsString()); + std::string expected = boost::lexical_cast<std::string>(imagesInAcquisition * countTemporalPositions); + db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected); + } + + else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL && + (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TIME_SLICES)) != NULL) + { + // Support of Cardio-PET images + int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->AsString()); + int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->AsString()); + std::string expected = boost::lexical_cast<std::string>(numberOfSlices * numberOfTimeSlices); + db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected); + } + + else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL) + { + db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->AsString()); + } + } + catch (boost::bad_lexical_cast) + { } } ServerIndex::ServerIndex(ServerContext& context, const std::string& dbPath) : + done_(false), maximumStorageSize_(0), maximumPatients_(0) { @@ -272,27 +360,24 @@ // execution of Orthanc StandaloneRecycling(); - unsigned int sleep; - try - { - std::string sleepString = db_->GetGlobalProperty(GlobalProperty_FlushSleep); - sleep = boost::lexical_cast<unsigned int>(sleepString); - } - catch (boost::bad_lexical_cast&) - { - // By default, wait for 10 seconds before flushing - sleep = 10; - } - - flushThread_ = boost::thread(FlushThread, db_.get(), &mutex_, sleep); + flushThread_ = boost::thread(FlushThread, this); + unstableResourcesMonitorThread_ = boost::thread(UnstableResourcesMonitorThread, this); } ServerIndex::~ServerIndex() { - LOG(INFO) << "Stopping the database flushing thread"; - /*flushThread_.terminate(); - flushThread_.join();*/ + done_ = true; + + if (flushThread_.joinable()) + { + flushThread_.join(); + } + + if (unstableResourcesMonitorThread_.joinable()) + { + unstableResourcesMonitorThread_.join(); + } } @@ -309,21 +394,21 @@ { Transaction t(*this); - int64_t patient, study, series, instance; - ResourceType type; - bool isNewSeries = false; - // Do nothing if the instance already exists - if (db_->LookupResource(hasher.HashInstance(), patient, type)) { - assert(type == ResourceType_Instance); - return StoreStatus_AlreadyStored; + ResourceType type; + int64_t tmp; + if (db_->LookupResource(hasher.HashInstance(), tmp, type)) + { + assert(type == ResourceType_Instance); + return StoreStatus_AlreadyStored; + } } // Ensure there is enough room in the storage for the new instance uint64_t instanceSize = 0; for (Attachments::const_iterator it = attachments.begin(); - it != attachments.end(); it++) + it != attachments.end(); ++it) { instanceSize += it->GetCompressedSize(); } @@ -331,65 +416,114 @@ Recycle(instanceSize, hasher.HashPatient()); // Create the instance - instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance); + int64_t instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance); DicomMap dicom; dicomSummary.ExtractInstanceInformation(dicom); db_->SetMainDicomTags(instance, dicom); - // Create the patient/study/series/instance hierarchy - if (!db_->LookupResource(hasher.HashSeries(), series, type)) + // Detect up to which level the patient/study/series/instance + // hierarchy must be created + int64_t patient = -1, study = -1, series = -1; + bool isNewPatient = false; + bool isNewStudy = false; + bool isNewSeries = false; + { - // This is a new series - isNewSeries = true; + ResourceType dummy; + + if (db_->LookupResource(hasher.HashSeries(), series, dummy)) + { + assert(dummy == ResourceType_Series); + // The patient, the study and the series already exist + + bool ok = (db_->LookupResource(hasher.HashPatient(), patient, dummy) && + db_->LookupResource(hasher.HashStudy(), study, dummy)); + assert(ok); + } + else if (db_->LookupResource(hasher.HashStudy(), study, dummy)) + { + assert(dummy == ResourceType_Study); + + // New series: The patient and the study already exist + isNewSeries = true; + + bool ok = db_->LookupResource(hasher.HashPatient(), patient, dummy); + assert(ok); + } + else if (db_->LookupResource(hasher.HashPatient(), patient, dummy)) + { + assert(dummy == ResourceType_Patient); + + // New study and series: The patient already exist + isNewStudy = true; + isNewSeries = true; + } + else + { + // New patient, study and series: Nothing exists + isNewPatient = true; + isNewStudy = true; + isNewSeries = true; + } + } + + // Create the series if needed + if (isNewSeries) + { series = db_->CreateResource(hasher.HashSeries(), ResourceType_Series); dicomSummary.ExtractSeriesInformation(dicom); db_->SetMainDicomTags(series, dicom); - db_->AttachChild(series, instance); + } - if (!db_->LookupResource(hasher.HashStudy(), study, type)) - { - // This is a new study - study = db_->CreateResource(hasher.HashStudy(), ResourceType_Study); - dicomSummary.ExtractStudyInformation(dicom); - db_->SetMainDicomTags(study, dicom); - db_->AttachChild(study, series); + // Create the study if needed + if (isNewStudy) + { + study = db_->CreateResource(hasher.HashStudy(), ResourceType_Study); + dicomSummary.ExtractStudyInformation(dicom); + db_->SetMainDicomTags(study, dicom); + } + + // Create the patient if needed + if (isNewPatient) + { + patient = db_->CreateResource(hasher.HashPatient(), ResourceType_Patient); + dicomSummary.ExtractPatientInformation(dicom); + db_->SetMainDicomTags(patient, dicom); + } - if (!db_->LookupResource(hasher.HashPatient(), patient, type)) - { - // This is a new patient - patient = db_->CreateResource(hasher.HashPatient(), ResourceType_Patient); - dicomSummary.ExtractPatientInformation(dicom); - db_->SetMainDicomTags(patient, dicom); - db_->AttachChild(patient, study); - } - else - { - assert(type == ResourceType_Patient); - db_->AttachChild(patient, study); - } - } - else - { - assert(type == ResourceType_Study); - db_->AttachChild(study, series); - } + // Create the parent-to-child links + db_->AttachChild(series, instance); + + if (isNewSeries) + { + db_->AttachChild(study, series); } - else + + if (isNewStudy) { - assert(type == ResourceType_Series); - db_->AttachChild(series, instance); + db_->AttachChild(patient, study); } + // Sanity checks + assert(patient != -1); + assert(study != -1); + assert(series != -1); + assert(instance != -1); + // Attach the files to the newly created instance for (Attachments::const_iterator it = attachments.begin(); - it != attachments.end(); it++) + it != attachments.end(); ++it) { db_->AddAttachment(instance, *it); } // Attach the metadata - db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, Toolbox::GetNowIsoString()); + std::string now = Toolbox::GetNowIsoString(); + db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, now); + db_->SetMetadata(series, MetadataType_LastUpdate, now); + db_->SetMetadata(study, MetadataType_LastUpdate, now); + db_->SetMetadata(patient, MetadataType_LastUpdate, now); db_->SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet); const DicomValue* value; @@ -401,12 +535,7 @@ if (isNewSeries) { - if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL || - (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL || - (value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL) - { - db_->SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->AsString()); - } + ComputeExpectedNumberOfInstances(*db_, series, dicomSummary); } // Check whether the series of this new instance is now completed @@ -416,6 +545,11 @@ db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series); } + // Mark the parent resources of this instance as unstable + MarkAsUnstable(patient, ResourceType_Patient); + MarkAsUnstable(study, ResourceType_Study); + MarkAsUnstable(series, ResourceType_Series); + t.Commit(instanceSize); return StoreStatus_Success; @@ -432,18 +566,16 @@ void ServerIndex::ComputeStatistics(Json::Value& target) { - static const uint64_t MB = 1024 * 1024; - boost::mutex::scoped_lock lock(mutex_); target = Json::objectValue; uint64_t cs = currentStorageSize_; assert(cs == db_->GetTotalCompressedSize()); uint64_t us = db_->GetTotalUncompressedSize(); - target["TotalDiskSpace"] = boost::lexical_cast<std::string>(cs); + target["TotalDiskSize"] = boost::lexical_cast<std::string>(cs); target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us); - target["TotalDiskSpaceMB"] = boost::lexical_cast<unsigned int>(cs / MB); - target["TotalUncompressedSizeMB"] = boost::lexical_cast<unsigned int>(us / MB); + target["TotalDiskSizeMB"] = boost::lexical_cast<unsigned int>(cs / MEGA_BYTES); + target["TotalUncompressedSizeMB"] = boost::lexical_cast<unsigned int>(us / MEGA_BYTES); target["CountPatients"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Patient)); target["CountStudies"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Study)); @@ -453,7 +585,7 @@ - SeriesStatus ServerIndex::GetSeriesStatus(int id) + SeriesStatus ServerIndex::GetSeriesStatus(int64_t id) { // Get the expected number of instances in this series (from the metadata) std::string s = db_->GetMetadata(id, MetadataType_Series_ExpectedNumberOfInstances); @@ -462,10 +594,6 @@ try { expected = boost::lexical_cast<size_t>(s); - if (expected < 0) - { - return SeriesStatus_Unknown; - } } catch (boost::bad_lexical_cast&) { @@ -478,7 +606,7 @@ std::set<size_t> instances; for (std::list<int64_t>::const_iterator - it = children.begin(); it != children.end(); it++) + it = children.begin(); it != children.end(); ++it) { // Get the index of this instance in the series s = db_->GetMetadata(*it, MetadataType_Instance_IndexInSeries); @@ -492,7 +620,7 @@ return SeriesStatus_Unknown; } - if (index <= 0 || index > expected) + if (!(index > 0 && index <= expected)) { // Out-of-range instance index return SeriesStatus_Inconsistent; @@ -584,7 +712,7 @@ Json::Value c = Json::arrayValue; for (std::list<std::string>::const_iterator - it = children.begin(); it != children.end(); it++) + it = children.begin(); it != children.end(); ++it) { c.append(*it); } @@ -622,7 +750,7 @@ case ResourceType_Series: { result["Type"] = "Series"; - result["Status"] = ToString(GetSeriesStatus(id)); + result["Status"] = EnumerationToString(GetSeriesStatus(id)); int i; if (db_->GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances)) @@ -673,6 +801,13 @@ if (tmp.size() != 0) result["ModifiedFrom"] = tmp; + if (type == ResourceType_Patient || + type == ResourceType_Study || + type == ResourceType_Series) + { + result["IsStable"] = !unstableResources_.Contains(id); + } + return true; } @@ -685,8 +820,7 @@ int64_t id; ResourceType type; - if (!db_->LookupResource(instanceUuid, id, type) || - type != ResourceType_Instance) + if (!db_->LookupResource(instanceUuid, id, type)) { throw OrthancException(ErrorCode_InternalError); } @@ -918,7 +1052,7 @@ } else { - LOG(WARNING) << "At most " << (size / (1024 * 1024)) << "MB will be used for the storage area"; + LOG(WARNING) << "At most " << (size / MEGA_BYTES) << "MB will be used for the storage area"; } StandaloneRecycling(); @@ -974,6 +1108,37 @@ } + void ServerIndex::GetChildren(std::list<std::string>& result, + const std::string& publicId) + { + result.clear(); + + boost::mutex::scoped_lock lock(mutex_); + + ResourceType type; + int64_t resource; + if (!db_->LookupResource(publicId, resource, type)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + if (type == ResourceType_Instance) + { + // An instance cannot have a child + throw OrthancException(ErrorCode_BadParameterType); + } + + std::list<int64_t> tmp; + db_->GetChildrenInternalId(tmp, resource); + + for (std::list<int64_t>::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) + { + result.push_back(db_->GetPublicId(*it)); + } + } + + void ServerIndex::GetChildInstances(std::list<std::string>& result, const std::string& publicId) { @@ -1015,7 +1180,7 @@ // Tag all the children of this resource as to be explored db_->GetChildrenInternalId(tmp, resource); for (std::list<int64_t>::const_iterator - it = tmp.begin(); it != tmp.end(); it++) + it = tmp.begin(); it != tmp.end(); ++it) { toExplore.push(*it); } @@ -1040,6 +1205,23 @@ db_->SetMetadata(id, type, value); } + + void ServerIndex::DeleteMetadata(const std::string& publicId, + MetadataType type) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType rtype; + int64_t id; + if (!db_->LookupResource(publicId, id, rtype)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + db_->DeleteMetadata(id, type); + } + + bool ServerIndex::LookupMetadata(std::string& target, const std::string& publicId, MetadataType type) @@ -1057,6 +1239,40 @@ } + void ServerIndex::ListAvailableMetadata(std::list<MetadataType>& target, + const std::string& publicId) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType rtype; + int64_t id; + if (!db_->LookupResource(publicId, id, rtype)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + db_->ListAvailableMetadata(target, id); + } + + + void ServerIndex::ListAvailableAttachments(std::list<FileContentType>& target, + const std::string& publicId, + ResourceType expectedType) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType type; + int64_t id; + if (!db_->LookupResource(publicId, id, type) || + expectedType != type) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + db_->ListAvailableAttachments(target, id); + } + + bool ServerIndex::LookupParent(std::string& target, const std::string& publicId) { @@ -1115,4 +1331,366 @@ transaction->Commit(); } + + + void ServerIndex::DeleteChanges() + { + boost::mutex::scoped_lock lock(mutex_); + db_->ClearTable("Changes"); + } + + void ServerIndex::DeleteExportedResources() + { + boost::mutex::scoped_lock lock(mutex_); + db_->ClearTable("ExportedResources"); + } + + + void ServerIndex::GetStatisticsInternal(/* out */ uint64_t& compressedSize, + /* out */ uint64_t& uncompressedSize, + /* out */ unsigned int& countStudies, + /* out */ unsigned int& countSeries, + /* out */ unsigned int& countInstances, + /* in */ int64_t id, + /* in */ ResourceType type) + { + std::stack<int64_t> toExplore; + toExplore.push(id); + + countInstances = 0; + countSeries = 0; + countStudies = 0; + compressedSize = 0; + uncompressedSize = 0; + + while (!toExplore.empty()) + { + // Get the internal ID of the current resource + int64_t resource = toExplore.top(); + toExplore.pop(); + + ResourceType thisType = db_->GetResourceType(resource); + + std::list<FileContentType> f; + db_->ListAvailableAttachments(f, resource); + + for (std::list<FileContentType>::const_iterator + it = f.begin(); it != f.end(); ++it) + { + FileInfo attachment; + if (db_->LookupAttachment(attachment, resource, *it)) + { + compressedSize += attachment.GetCompressedSize(); + uncompressedSize += attachment.GetUncompressedSize(); + } + } + + if (thisType == ResourceType_Instance) + { + countInstances++; + } + else + { + switch (thisType) + { + case ResourceType_Study: + countStudies++; + break; + + case ResourceType_Series: + countSeries++; + break; + + default: + break; + } + + // Tag all the children of this resource as to be explored + std::list<int64_t> tmp; + db_->GetChildrenInternalId(tmp, resource); + for (std::list<int64_t>::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) + { + toExplore.push(*it); + } + } + } + + if (countStudies == 0) + { + countStudies = 1; + } + + if (countSeries == 0) + { + countSeries = 1; + } + } + + + + void ServerIndex::GetStatistics(Json::Value& target, + const std::string& publicId) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType type; + int64_t top; + if (!db_->LookupResource(publicId, top, type)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + uint64_t uncompressedSize; + uint64_t compressedSize; + unsigned int countStudies; + unsigned int countSeries; + unsigned int countInstances; + GetStatisticsInternal(compressedSize, uncompressedSize, countStudies, + countSeries, countInstances, top, type); + + target = Json::objectValue; + target["DiskSize"] = boost::lexical_cast<std::string>(compressedSize); + target["DiskSizeMB"] = boost::lexical_cast<unsigned int>(compressedSize / MEGA_BYTES); + target["UncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize); + target["UncompressedSizeMB"] = boost::lexical_cast<unsigned int>(uncompressedSize / MEGA_BYTES); + + switch (type) + { + // Do NOT add "break" below this point! + case ResourceType_Patient: + target["CountStudies"] = countStudies; + + case ResourceType_Study: + target["CountSeries"] = countSeries; + + case ResourceType_Series: + target["CountInstances"] = countInstances; + + case ResourceType_Instance: + default: + break; + } + } + + + void ServerIndex::GetStatistics(/* out */ uint64_t& compressedSize, + /* out */ uint64_t& uncompressedSize, + /* out */ unsigned int& countStudies, + /* out */ unsigned int& countSeries, + /* out */ unsigned int& countInstances, + const std::string& publicId) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType type; + int64_t top; + if (!db_->LookupResource(publicId, top, type)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + GetStatisticsInternal(compressedSize, uncompressedSize, countStudies, + countSeries, countInstances, top, type); + } + + + void ServerIndex::UnstableResourcesMonitorThread(ServerIndex* that) + { + int stableAge = GetGlobalIntegerParameter("StableAge", 60); + if (stableAge <= 0) + { + stableAge = 60; + } + + LOG(INFO) << "Starting the monitor for stable resources (stable age = " << stableAge << ")"; + + while (!that->done_) + { + // Check for stable resources each second + boost::this_thread::sleep(boost::posix_time::seconds(1)); + + boost::mutex::scoped_lock lock(that->mutex_); + + while (!that->unstableResources_.IsEmpty() && + that->unstableResources_.GetOldestPayload().GetAge() > static_cast<unsigned int>(stableAge)) + { + // This DICOM resource has not received any new instance for + // some time. It can be considered as stable. + + UnstableResourcePayload payload; + int64_t id = that->unstableResources_.RemoveOldest(payload); + + // Ensure that the resource is still existing before logging the change + if (that->db_->IsExistingResource(id)) + { + switch (payload.type_) + { + case Orthanc::ResourceType_Patient: + that->db_->LogChange(ChangeType_StablePatient, id, ResourceType_Patient); + break; + + case Orthanc::ResourceType_Study: + that->db_->LogChange(ChangeType_StableStudy, id, ResourceType_Study); + break; + + case Orthanc::ResourceType_Series: + that->db_->LogChange(ChangeType_StableSeries, id, ResourceType_Series); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + //LOG(INFO) << "Stable resource: " << EnumerationToString(payload.type_) << " " << id; + } + } + } + + LOG(INFO) << "Closing the monitor thread for stable resources"; + } + + + void ServerIndex::MarkAsUnstable(int64_t id, + Orthanc::ResourceType type) + { + // WARNING: Before calling this method, "mutex_" must be locked. + + assert(type == Orthanc::ResourceType_Patient || + type == Orthanc::ResourceType_Study || + type == Orthanc::ResourceType_Series); + + unstableResources_.AddOrMakeMostRecent(id, type); + //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id; + } + + + + void ServerIndex::LookupTagValue(std::list<std::string>& result, + DicomTag tag, + const std::string& value, + ResourceType type) + { + result.clear(); + + boost::mutex::scoped_lock lock(mutex_); + + std::list<int64_t> id; + db_->LookupTagValue(id, tag, value); + + for (std::list<int64_t>::const_iterator + it = id.begin(); it != id.end(); ++it) + { + if (db_->GetResourceType(*it) == type) + { + result.push_back(db_->GetPublicId(*it)); + } + } + } + + + void ServerIndex::LookupTagValue(std::list<std::string>& result, + DicomTag tag, + const std::string& value) + { + result.clear(); + + boost::mutex::scoped_lock lock(mutex_); + + std::list<int64_t> id; + db_->LookupTagValue(id, tag, value); + + for (std::list<int64_t>::const_iterator + it = id.begin(); it != id.end(); ++it) + { + result.push_back(db_->GetPublicId(*it)); + } + } + + + void ServerIndex::LookupTagValue(std::list<std::string>& result, + const std::string& value) + { + result.clear(); + + boost::mutex::scoped_lock lock(mutex_); + + std::list<int64_t> id; + db_->LookupTagValue(id, value); + + for (std::list<int64_t>::const_iterator + it = id.begin(); it != id.end(); ++it) + { + result.push_back(db_->GetPublicId(*it)); + } + } + + + StoreStatus ServerIndex::AddAttachment(const FileInfo& attachment, + const std::string& publicId) + { + boost::mutex::scoped_lock lock(mutex_); + + Transaction t(*this); + + ResourceType resourceType; + int64_t resourceId; + if (!db_->LookupResource(publicId, resourceId, resourceType)) + { + return StoreStatus_Failure; // Inexistent resource + } + + // Remove possible previous attachment + db_->DeleteAttachment(resourceId, attachment.GetContentType()); + + // Locate the patient of the target resource + int64_t patientId = resourceId; + for (;;) + { + int64_t parent; + if (db_->LookupParent(parent, patientId)) + { + // We have not reached the patient level yet + patientId = parent; + } + else + { + // We have reached the patient level + break; + } + } + + // Possibly apply the recycling mechanism while preserving this patient + assert(db_->GetResourceType(patientId) == ResourceType_Patient); + Recycle(attachment.GetCompressedSize(), db_->GetPublicId(patientId)); + + db_->AddAttachment(resourceId, attachment); + + t.Commit(attachment.GetCompressedSize()); + + return StoreStatus_Success; + } + + + void ServerIndex::DeleteAttachment(const std::string& publicId, + FileContentType type) + { + boost::mutex::scoped_lock lock(mutex_); + listener_->Reset(); + + Transaction t(*this); + + ResourceType rtype; + int64_t id; + if (!db_->LookupResource(publicId, id, rtype)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + db_->DeleteAttachment(id, type); + + t.Commit(0); + } + + }
--- a/OrthancServer/ServerIndex.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/ServerIndex.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -34,6 +34,7 @@ #include <boost/thread.hpp> #include <boost/noncopyable.hpp> +#include "../Core/Cache/LeastRecentlyUsedIndex.h" #include "../Core/SQLite/Connection.h" #include "../Core/DicomFormat/DicomMap.h" #include "../Core/DicomFormat/DicomInstanceHasher.h" @@ -55,21 +56,29 @@ { private: class Transaction; + struct UnstableResourcePayload; + bool done_; boost::mutex mutex_; boost::thread flushThread_; + boost::thread unstableResourcesMonitorThread_; std::auto_ptr<Internals::ServerIndexListener> listener_; std::auto_ptr<DatabaseWrapper> db_; + LeastRecentlyUsedIndex<int64_t, UnstableResourcePayload> unstableResources_; uint64_t currentStorageSize_; uint64_t maximumStorageSize_; unsigned int maximumPatients_; + static void FlushThread(ServerIndex* that); + + static void UnstableResourcesMonitorThread(ServerIndex* that); + void MainDicomTagsToJson(Json::Value& result, int64_t resourceId); - SeriesStatus GetSeriesStatus(int id); + SeriesStatus GetSeriesStatus(int64_t id); bool IsRecyclingNeeded(uint64_t instanceSize); @@ -78,6 +87,17 @@ void StandaloneRecycling(); + void MarkAsUnstable(int64_t id, + Orthanc::ResourceType type); + + void GetStatisticsInternal(/* out */ uint64_t& compressedSize, + /* out */ uint64_t& uncompressedSize, + /* out */ unsigned int& countStudies, + /* out */ unsigned int& countSeries, + /* out */ unsigned int& countInstances, + /* in */ int64_t id, + /* in */ ResourceType type); + public: typedef std::list<FileInfo> Attachments; @@ -143,6 +163,9 @@ void SetProtectedPatient(const std::string& publicId, bool isProtected); + void GetChildren(std::list<std::string>& result, + const std::string& publicId); + void GetChildInstances(std::list<std::string>& result, const std::string& publicId); @@ -150,10 +173,20 @@ MetadataType type, const std::string& value); + void DeleteMetadata(const std::string& publicId, + MetadataType type); + bool LookupMetadata(std::string& target, const std::string& publicId, MetadataType type); + void ListAvailableMetadata(std::list<MetadataType>& target, + const std::string& publicId); + + void ListAvailableAttachments(std::list<FileContentType>& target, + const std::string& publicId, + ResourceType expectedType); + bool LookupParent(std::string& target, const std::string& publicId); @@ -161,5 +194,37 @@ void LogChange(ChangeType changeType, const std::string& publicId); + + void DeleteChanges(); + + void DeleteExportedResources(); + + void GetStatistics(Json::Value& target, + const std::string& publicId); + + void GetStatistics(/* out */ uint64_t& compressedSize, + /* out */ uint64_t& uncompressedSize, + /* out */ unsigned int& countStudies, + /* out */ unsigned int& countSeries, + /* out */ unsigned int& countInstances, + const std::string& publicId); + + void LookupTagValue(std::list<std::string>& result, + DicomTag tag, + const std::string& value, + ResourceType type); + + void LookupTagValue(std::list<std::string>& result, + DicomTag tag, + const std::string& value); + + void LookupTagValue(std::list<std::string>& result, + const std::string& value); + + StoreStatus AddAttachment(const FileInfo& attachment, + const std::string& publicId); + + void DeleteAttachment(const std::string& publicId, + FileContentType type); }; }
--- a/OrthancServer/ServerToolbox.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/ServerToolbox.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -35,6 +35,7 @@ #include "../Core/OrthancException.h" #include <cassert> +#include <glog/logging.h> namespace Orthanc { @@ -82,4 +83,71 @@ } } } + + + void LogMissingRequiredTag(const DicomMap& summary) + { + std::string s, t; + + if (summary.HasTag(DICOM_TAG_PATIENT_ID)) + { + if (t.size() > 0) + t += ", "; + t += "PatientID=" + summary.GetValue(DICOM_TAG_PATIENT_ID).AsString(); + } + else + { + if (s.size() > 0) + s += ", "; + s += "PatientID"; + } + + if (summary.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "StudyInstanceUID=" + summary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString(); + } + else + { + if (s.size() > 0) + s += ", "; + s += "StudyInstanceUID"; + } + + if (summary.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "SeriesInstanceUID=" + summary.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString(); + } + else + { + if (s.size() > 0) + s += ", "; + s += "SeriesInstanceUID"; + } + + if (summary.HasTag(DICOM_TAG_SOP_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "SOPInstanceUID=" + summary.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString(); + } + else + { + if (s.size() > 0) + s += ", "; + s += "SOPInstanceUID"; + } + + if (t.size() == 0) + { + LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)"; + } + else + { + LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t; + } + } }
--- a/OrthancServer/ServerToolbox.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/ServerToolbox.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,10 +32,14 @@ #pragma once +#include "../Core/DicomFormat/DicomMap.h" + #include <json/json.h> namespace Orthanc { void SimplifyTags(Json::Value& target, const Json::Value& source); + + void LogMissingRequiredTag(const DicomMap& summary); }
--- a/OrthancServer/ToDcmtkBridge.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/ToDcmtkBridge.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -49,7 +49,7 @@ std::auto_ptr<DcmDataset> result(new DcmDataset); for (DicomMap::Map::const_iterator - it = map.map_.begin(); it != map.map_.end(); it++) + it = map.map_.begin(); it != map.map_.end(); ++it) { std::string s = it->second->AsString(); DU_putStringDOElement(result.get(), Convert(it->first), s.c_str());
--- a/OrthancServer/ToDcmtkBridge.h Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/ToDcmtkBridge.h Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Upgrade3To4.sql Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,24 @@ +-- This SQLite script updates the version of the Orthanc database from 3 to 4. + +-- Add 2 new columns at "AttachedFiles" + +ALTER TABLE AttachedFiles ADD COLUMN uncompressedMD5 TEXT; +ALTER TABLE AttachedFiles ADD COLUMN compressedMD5 TEXT; + +-- Update the "AttachedFileDeleted" trigger + +DROP TRIGGER AttachedFileDeleted; + +CREATE TRIGGER AttachedFileDeleted +AFTER DELETE ON AttachedFiles +BEGIN + SELECT SignalFileDeleted(old.uuid, old.fileType, old.uncompressedSize, + old.compressionType, old.compressedSize, + -- These 2 arguments are new in Orthanc 0.7.3 (database v4) + old.uncompressedMD5, old.compressedMD5); +END; + +-- Change the database version +-- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration + +UPDATE GlobalProperties SET value="4" WHERE property=1;
--- a/OrthancServer/main.cpp Fri May 03 12:23:02 2013 +0200 +++ b/OrthancServer/main.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -30,7 +30,7 @@ **/ -#include "OrthancRestApi.h" +#include "OrthancRestApi/OrthancRestApi.h" #include <fstream> #include <glog/logging.h> @@ -41,20 +41,24 @@ #include "../Core/Lua/LuaFunctionCall.h" #include "../Core/DicomFormat/DicomArray.h" #include "DicomProtocol/DicomServer.h" +#include "DicomProtocol/DicomUserConnection.h" #include "OrthancInitialization.h" #include "ServerContext.h" +#include "OrthancFindRequestHandler.h" +#include "OrthancMoveRequestHandler.h" +#include "ServerToolbox.h" using namespace Orthanc; -class MyStoreRequestHandler : public IStoreRequestHandler +class OrthancStoreRequestHandler : public IStoreRequestHandler { private: ServerContext& server_; public: - MyStoreRequestHandler(ServerContext& context) : + OrthancStoreRequestHandler(ServerContext& context) : server_(context) { } @@ -72,47 +76,6 @@ }; -class MyFindRequestHandler : public IFindRequestHandler -{ -private: - ServerContext& context_; - -public: - MyFindRequestHandler(ServerContext& context) : - context_(context) - { - } - - virtual void Handle(const DicomMap& input, - DicomFindAnswers& answers) - { - LOG(WARNING) << "Find-SCU request received"; - DicomArray a(input); - a.Print(stdout); - } -}; - - -class MyMoveRequestHandler : public IMoveRequestHandler -{ -private: - ServerContext& context_; - -public: - MyMoveRequestHandler(ServerContext& context) : - context_(context) - { - } - -public: - virtual IMoveRequestIterator* Handle(const std::string& target, - const DicomMap& input) - { - LOG(WARNING) << "Move-SCU request received"; - return NULL; - } -}; - class MyDicomServerFactory : public IStoreRequestHandlerFactory, @@ -129,17 +92,17 @@ virtual IStoreRequestHandler* ConstructStoreRequestHandler() { - return new MyStoreRequestHandler(context_); + return new OrthancStoreRequestHandler(context_); } virtual IFindRequestHandler* ConstructFindRequestHandler() { - return new MyFindRequestHandler(context_); + return new OrthancFindRequestHandler(context_); } virtual IMoveRequestHandler* ConstructMoveRequestHandler() { - return new MyMoveRequestHandler(context_); + return new OrthancMoveRequestHandler(context_); } void Done() @@ -148,6 +111,38 @@ }; +class OrthancApplicationEntityFilter : public IApplicationEntityFilter +{ +public: + virtual bool IsAllowedConnection(const std::string& /*callingIp*/, + const std::string& /*callingAet*/) + { + return true; + } + + virtual bool IsAllowedRequest(const std::string& /*callingIp*/, + const std::string& callingAet, + DicomRequestType type) + { + if (type == DicomRequestType_Store) + { + // Incoming store requests are always accepted, even from unknown AET + return true; + } + + if (!IsKnownAETitle(callingAet)) + { + LOG(ERROR) << "Unknown remote DICOM modality AET: \"" << callingAet << "\""; + return false; + } + else + { + return true; + } + } +}; + + class MyIncomingHttpRequestFilter : public IIncomingHttpRequestFilter { private: @@ -158,7 +153,7 @@ { } - virtual bool IsAllowed(Orthanc_HttpMethod method, + virtual bool IsAllowed(HttpMethod method, const char* uri, const char* ip, const char* username) const @@ -172,19 +167,19 @@ switch (method) { - case Orthanc_HttpMethod_Get: + case HttpMethod_Get: call.PushString("GET"); break; - case Orthanc_HttpMethod_Put: + case HttpMethod_Put: call.PushString("PUT"); break; - case Orthanc_HttpMethod_Post: + case HttpMethod_Post: call.PushString("POST"); break; - case Orthanc_HttpMethod_Delete: + case HttpMethod_Delete: call.PushString("DELETE"); break; @@ -239,7 +234,7 @@ { std::cout << path << " " << ORTHANC_VERSION << std::endl - << "Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege (Belgium) " << std::endl + << "Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege (Belgium) " << std::endl << "Licensing GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>, with OpenSSL exception." << std::endl << "This is free software: you are free to change and redistribute it." << std::endl << "There is NO WARRANTY, to the extent permitted by law." << std::endl @@ -291,6 +286,11 @@ std::string configurationSample; GetFileResource(configurationSample, EmbeddedResources::CONFIGURATION_SAMPLE); +#if defined(_WIN32) + // Replace UNIX newlines with DOS newlines + boost::replace_all(configurationSample, "\n", "\r\n"); +#endif + std::string target = std::string(argv[i]).substr(9); std::ofstream f(target.c_str()); f << configurationSample; @@ -324,21 +324,22 @@ OrthancInitialize(); } - boost::filesystem::path storageDirectory = - InterpretStringParameterAsPath(GetGlobalStringParameter("StorageDirectory", "OrthancStorage")); + std::string storageDirectoryStr = GetGlobalStringParameter("StorageDirectory", "OrthancStorage"); + boost::filesystem::path storageDirectory = InterpretStringParameterAsPath(storageDirectoryStr); boost::filesystem::path indexDirectory = - InterpretStringParameterAsPath(GetGlobalStringParameter("IndexDirectory", storageDirectory.string())); + InterpretStringParameterAsPath(GetGlobalStringParameter("IndexDirectory", storageDirectoryStr)); ServerContext context(storageDirectory, indexDirectory); LOG(WARNING) << "Storage directory: " << storageDirectory; LOG(WARNING) << "Index directory: " << indexDirectory; context.SetCompressionEnabled(GetGlobalBoolParameter("StorageCompression", false)); + context.SetStoreMD5ForAttachments(GetGlobalBoolParameter("StoreMD5ForAttachments", true)); std::list<std::string> luaScripts; GetGlobalListOfStringsParameter(luaScripts, "LuaScripts"); for (std::list<std::string>::const_iterator - it = luaScripts.begin(); it != luaScripts.end(); it++) + it = luaScripts.begin(); it != luaScripts.end(); ++it) { std::string path = InterpretStringParameterAsPath(*it); LOG(WARNING) << "Installing the Lua scripts from: " << path; @@ -369,16 +370,17 @@ MyDicomServerFactory serverFactory(context); - { // DICOM server DicomServer dicomServer; + OrthancApplicationEntityFilter dicomFilter; dicomServer.SetCalledApplicationEntityTitleCheck(GetGlobalBoolParameter("DicomCheckCalledAet", false)); dicomServer.SetStoreRequestHandlerFactory(serverFactory); - //dicomServer.SetMoveRequestHandlerFactory(serverFactory); - //dicomServer.SetFindRequestHandlerFactory(serverFactory); + dicomServer.SetMoveRequestHandlerFactory(serverFactory); + dicomServer.SetFindRequestHandlerFactory(serverFactory); dicomServer.SetPortNumber(GetGlobalIntegerParameter("DicomPort", 4242)); dicomServer.SetApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); + dicomServer.SetApplicationEntityFilter(dicomFilter); // HTTP server MyIncomingHttpRequestFilter httpFilter(context); @@ -402,9 +404,6 @@ httpServer.SetSslEnabled(false); } - LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber(); - LOG(WARNING) << "HTTP server listening on port: " << httpServer.GetPortNumber(); - #if ORTHANC_STANDALONE == 1 httpServer.RegisterHandler(new EmbeddedResourceHttpHandler("/app", EmbeddedResources::ORTHANC_EXPLORER)); #else @@ -413,14 +412,31 @@ httpServer.RegisterHandler(new OrthancRestApi(context)); - // GO !!! - httpServer.Start(); - dicomServer.Start(); + // GO !!! Start the requested servers + if (GetGlobalBoolParameter("HttpServerEnabled", true)) + { + httpServer.Start(); + LOG(WARNING) << "HTTP server listening on port: " << httpServer.GetPortNumber(); + } + else + { + LOG(WARNING) << "The HTTP server is disabled"; + } + + if (GetGlobalBoolParameter("DicomServerEnabled", true)) + { + dicomServer.Start(); + LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber(); + } + else + { + LOG(WARNING) << "The DICOM server is disabled"; + } LOG(WARNING) << "Orthanc has started"; Toolbox::ServerBarrier(); - // Stop + // We're done LOG(WARNING) << "Orthanc is stopping"; } @@ -439,5 +455,7 @@ OrthancFinalize(); + LOG(WARNING) << "Orthanc has stopped"; + return status; }
--- a/README Fri May 03 12:23:02 2013 +0200 +++ b/README Tue Apr 22 16:47:21 2014 +0200 @@ -5,9 +5,9 @@ General Information ------------------- -General information about this software can be found on our Google -Code hosting page: -http://code.google.com/p/orthanc/ +General information about this software can be found on its official +Website: +http://www.orthanc-server.com/ The instructions for building Orthanc can be found in the "INSTALL" file. @@ -41,12 +41,23 @@ exception: http://people.gnome.org/~markmc/openssl-and-the-gpl.html -Because Orthanc uses the Software-as-a-Service paradigm, commercial -products are allowed to access the Orthanc REST services and to bundle -Orthanc in an commercial aggregate. +We also kindly require scientific works and clinical studies that make +use of Orthanc to cite Orthanc in their associated +publications. Similarly, we require open-source and closed-source +products that make use of Orthanc to warn us about this use. You can +cite our work using the following BibTeX entry: -We also kindly require scientific works and clinical studies that make -use of Orthanc to cite Orthanc in their associated publications. +@inproceedings{Jodogne:ISBI2013, + author = {Jodogne, S. and Bernard, C. and Devillers, M. and Lenaerts, E. and Coucke, P.}, + title = {Orthanc -- {A} Lightweight, {REST}ful {DICOM} Server for Healthcare and Medical Research}, + booktitle={Biomedical Imaging ({ISBI}), {IEEE} 10th International Symposium on}, + year={2013}, + pages={190-193}, + ISSN={1945-7928}, + month=apr, + url={http://ieeexplore.ieee.org/xpl/articleDetails.jsp?tp=&arnumber=6556444}, + address={San Francisco, {CA}, {USA}} +} Licensing of special directories @@ -54,10 +65,6 @@ The following directories have separate licensing terms: -* The files of the "OrthancCppClient/" directory are licensed under - the MIT license, which allows commercial products to statically link - against the C++ client library. - * The file of the "Core/SQLite/" directory are licensed under the 3-clause BSD license, as they are derived from the Chromium project. @@ -68,7 +75,7 @@ This archive contains the following directories: * Core/ - The core C++ classes (independent of DCMTK) -* OrthancCppClient/ - Code of the C++ client (under MIT license) +* OrthancCppClient/ - Code of the C++ client * OrthancExplorer/ - Code of the Web application (HTML5/Javascript) * OrthancServer/ - Code of the Orthanc server (depends on DCMTK) * Resources/ - Scripts, resources for building third-party code
--- a/Resources/Archives/MessageWithDestination.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,171 +0,0 @@ -#include "../Core/IDynamicObject.h" - -#include "../Core/OrthancException.h" - -#include <stdint.h> -#include <memory> -#include <map> -#include <gtest/gtest.h> -#include <string> -#include <boost/thread.hpp> -#include <boost/date_time/posix_time/posix_time_types.hpp> - -namespace Orthanc -{ - class SharedMessageQueue - { - private: - typedef std::list<IDynamicObject*> Queue; - - unsigned int maxSize_; - Queue queue_; - boost::mutex mutex_; - boost::condition_variable elementAvailable_; - - public: - SharedMessageQueue(unsigned int maxSize = 0) - { - maxSize_ = maxSize; - } - - ~SharedMessageQueue() - { - for (Queue::iterator it = queue_.begin(); it != queue_.end(); it++) - { - delete *it; - } - } - - void Enqueue(IDynamicObject* message) - { - boost::mutex::scoped_lock lock(mutex_); - - if (maxSize_ != 0 && queue_.size() > maxSize_) - { - // Too many elements in the queue: First remove the oldest - delete queue_.front(); - queue_.pop_front(); - } - - queue_.push_back(message); - elementAvailable_.notify_one(); - } - - IDynamicObject* Dequeue(int32_t timeout) - { - boost::mutex::scoped_lock lock(mutex_); - - // Wait for a message to arrive in the FIFO queue - while (queue_.empty()) - { - if (timeout == 0) - { - elementAvailable_.wait(lock); - } - else - { - bool success = elementAvailable_.timed_wait - (lock, boost::posix_time::milliseconds(timeout)); - if (!success) - { - throw OrthancException(ErrorCode_Timeout); - } - } - } - - std::auto_ptr<IDynamicObject> message(queue_.front()); - queue_.pop_front(); - - return message.release(); - } - - IDynamicObject* Dequeue() - { - return Dequeue(0); - } - }; - - - /** - * This class represents a message that is to be sent to some destination. - **/ - class MessageToDispatch : public boost::noncopyable - { - private: - IDynamicObject* message_; - std::string destination_; - - public: - /** - * Create a new message with a destination. - * \param message The content of the message (takes the ownership) - * \param destination The destination of the message - **/ - MessageToDispatch(IDynamicObject* message, - const char* destination) - { - message_ = message; - destination_ = destination; - } - - ~MessageToDispatch() - { - if (message_) - { - delete message_; - } - } - }; - - - class IDestinationContext : public IDynamicObject - { - public: - virtual void Handle(const IDynamicObject& message) = 0; - }; - - - class IDestinationContextFactory : public IDynamicObject - { - public: - virtual IDestinationContext* Construct(const char* destination) = 0; - }; - - - class MessageDispatcher - { - private: - typedef std::map<std::string, IDestinationContext*> ActiveContexts; - - std::auto_ptr<IDestinationContextFactory> factory_; - ActiveContexts activeContexts_; - SharedMessageQueue queue_; - - public: - MessageDispatcher(IDestinationContextFactory* factory) // takes the ownership - { - factory_.reset(factory); - } - - ~MessageDispatcher() - { - for (ActiveContexts::iterator it = activeContexts_.begin(); - it != activeContexts_.end(); it++) - { - delete it->second; - } - } - }; -} - - - -#include "../Core/DicomFormat/DicomString.h" - -using namespace Orthanc; - -TEST(MessageToDispatch, A) -{ - MessageToDispatch a(new DicomString("coucou"), "pukkaj"); -} -
--- a/Resources/Archives/PrepareDatabase-v1.sql Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ -CREATE TABLE GlobalProperties( - name TEXT PRIMARY KEY, - value TEXT - ); - -CREATE TABLE Resources( - uuid TEXT PRIMARY KEY, - resourceType INTEGER - ); - -CREATE TABLE Patients( - uuid TEXT PRIMARY KEY, - dicomPatientId TEXT - ); - -CREATE TABLE Studies( - uuid TEXT PRIMARY KEY, - parentPatient TEXT REFERENCES Patients(uuid) ON DELETE CASCADE, - dicomStudy TEXT - ); - -CREATE TABLE Series( - uuid TEXT PRIMARY KEY, - parentStudy TEXT REFERENCES Studies(uuid) ON DELETE CASCADE, - dicomSeries TEXT, - expectedNumberOfInstances INTEGER - ); - -CREATE TABLE Instances( - uuid TEXT PRIMARY KEY, - parentSeries TEXT REFERENCES Series(uuid) ON DELETE CASCADE, - dicomInstance TEXT, - fileUuid TEXT, - fileSize INTEGER, - jsonUuid TEXT, - distantAet TEXT, - indexInSeries INTEGER - ); - -CREATE TABLE MainDicomTags( - uuid TEXT, - tagGroup INTEGER, - tagElement INTEGER, - value TEXT, - PRIMARY KEY(uuid, tagGroup, tagElement) - ); - -CREATE TABLE Changes( - seq INTEGER PRIMARY KEY AUTOINCREMENT, - basePath TEXT, - uuid TEXT - ); - - -CREATE INDEX PatientToStudies ON Studies(parentPatient); -CREATE INDEX StudyToSeries ON Series(parentStudy); -CREATE INDEX SeriesToInstances ON Instances(parentSeries); - -CREATE INDEX DicomPatientIndex ON Patients(dicomPatientId); -CREATE INDEX DicomStudyIndex ON Studies(dicomStudy); -CREATE INDEX DicomSeriesIndex ON Series(dicomSeries); -CREATE INDEX DicomInstanceIndex ON Instances(dicomInstance); - -CREATE INDEX MainDicomTagsIndex ON MainDicomTags(uuid); -CREATE INDEX MainDicomTagsGroupElement ON MainDicomTags(tagGroup, tagElement); -CREATE INDEX MainDicomTagsValues ON MainDicomTags(value COLLATE BINARY); - -CREATE INDEX ChangesIndex ON Changes(uuid); - -CREATE TRIGGER InstanceRemoved -AFTER DELETE ON Instances -FOR EACH ROW BEGIN - DELETE FROM Resources WHERE uuid = old.uuid; - DELETE FROM MainDicomTags WHERE uuid = old.uuid; - DELETE FROM Changes WHERE uuid = old.uuid; - SELECT DeleteFromFileStorage(old.fileUuid); - SELECT DeleteFromFileStorage(old.jsonUuid); - SELECT SignalDeletedLevel(3, old.parentSeries); -END; - -CREATE TRIGGER SeriesRemoved -AFTER DELETE ON Series -FOR EACH ROW BEGIN - DELETE FROM Resources WHERE uuid = old.uuid; - DELETE FROM MainDicomTags WHERE uuid = old.uuid; - DELETE FROM Changes WHERE uuid = old.uuid; - SELECT SignalDeletedLevel(2, old.parentStudy); -END; - -CREATE TRIGGER StudyRemoved -AFTER DELETE ON Studies -FOR EACH ROW BEGIN - DELETE FROM Resources WHERE uuid = old.uuid; - DELETE FROM MainDicomTags WHERE uuid = old.uuid; - DELETE FROM Changes WHERE uuid = old.uuid; - SELECT SignalDeletedLevel(1, old.parentPatient); -END; - -CREATE TRIGGER PatientRemoved -AFTER DELETE ON Patients -FOR EACH ROW BEGIN - DELETE FROM Resources WHERE uuid = old.uuid; - DELETE FROM MainDicomTags WHERE uuid = old.uuid; - DELETE FROM Changes WHERE uuid = old.uuid; - SELECT SignalDeletedLevel(0, ""); -END; - - - - -CREATE TRIGGER InstanceRemovedUpwardCleaning -AFTER DELETE ON Instances -FOR EACH ROW - WHEN (SELECT COUNT(*) FROM Instances WHERE parentSeries = old.parentSeries) = 0 - BEGIN - SELECT DeleteFromFileStorage("deleting parent series"); -- TODO REMOVE THIS - DELETE FROM Series WHERE uuid = old.parentSeries; - END; - -CREATE TRIGGER SeriesRemovedUpwardCleaning -AFTER DELETE ON Series -FOR EACH ROW - WHEN (SELECT COUNT(*) FROM Series WHERE parentStudy = old.parentStudy) = 0 - BEGIN - SELECT DeleteFromFileStorage("deleting parent study"); -- TODO REMOVE THIS - DELETE FROM Studies WHERE uuid = old.parentStudy; - END; - -CREATE TRIGGER StudyRemovedUpwardCleaning -AFTER DELETE ON Studies -FOR EACH ROW - WHEN (SELECT COUNT(*) FROM Studies WHERE parentPatient = old.parentPatient) = 0 - BEGIN - SELECT DeleteFromFileStorage("deleting parent patient"); -- TODO REMOVE THIS - DELETE FROM Patients WHERE uuid = old.parentPatient; - END;
--- a/Resources/CMake/BoostConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/BoostConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,113 +1,125 @@ -if (${STATIC_BUILD}) - SET(BOOST_STATIC 1) -else() - include(FindBoost) - - SET(BOOST_STATIC 0) - #set(Boost_DEBUG 1) - #set(Boost_USE_STATIC_LIBS ON) - - find_package(Boost - COMPONENTS filesystem thread system date_time) - - if (NOT Boost_FOUND) - message(FATAL_ERROR "Unable to locate Boost on this system") - endif() - - # Boost releases 1.44 through 1.47 supply both V2 and V3 filesystem - # http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/index.htm - if (${Boost_VERSION} LESS 104400) - add_definitions( - -DBOOST_HAS_FILESYSTEM_V3=0 - ) - else() - add_definitions( - -DBOOST_HAS_FILESYSTEM_V3=1 - -DBOOST_FILESYSTEM_VERSION=3 - ) - endif() - - #if (${Boost_VERSION} LESS 104800) - # boost::locale is only available from 1.48.00 - #message("Too old version of Boost (${Boost_LIB_VERSION}): Building the static version") - # SET(BOOST_STATIC 1) - #endif() - - include_directories(${Boost_INCLUDE_DIRS}) - link_libraries(${Boost_LIBRARIES}) -endif() - - -if (BOOST_STATIC) - SET(BOOST_NAME boost_1_49_0) - SET(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME}) - DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}.tar.gz" "${BOOST_SOURCES_DIR}" "${BOOST_PRELOADED}" "${BOOST_NAME}/boost ${BOOST_NAME}/libs/thread/src ${BOOST_NAME}/libs/system/src ${BOOST_NAME}/libs/filesystem/v3/src ${BOOST_NAME}/libs/locale/src ${BOOST_NAME}/libs/date_time/src") - - set(BOOST_SOURCES) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - list(APPEND BOOST_SOURCES - ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp - ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp - ) - add_definitions( - -DBOOST_LOCALE_WITH_ICONV=1 - ) - - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - add_definitions(-DBOOST_HAS_SCHED_YIELD=1) - endif() - - elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - list(APPEND BOOST_SOURCES - ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp - ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp - ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp - ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/windows_file_codecvt.cpp - ) - add_definitions( - -DBOOST_LOCALE_WITH_WCONV=1 - ) - else() - message(FATAL_ERROR "Support your platform here") - endif() - - list(APPEND BOOST_SOURCES - ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp - ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/codecvt_error_category.cpp - ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/operations.cpp - ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/path.cpp - ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/path_traits.cpp - ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp - ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp - ) - - list(APPEND THIRD_PARTY_SOURCES ${BOOST_SOURCES}) - - add_definitions( - # Static build of Boost - -DBOOST_ALL_NO_LIB - -DBOOST_ALL_NOLIB - -DBOOST_DATE_TIME_NO_LIB - -DBOOST_THREAD_BUILD_LIB - -DBOOST_PROGRAM_OPTIONS_NO_LIB - -DBOOST_REGEX_NO_LIB - -DBOOST_SYSTEM_NO_LIB - -DBOOST_LOCALE_NO_LIB - -DBOOST_HAS_LOCALE=1 - -DBOOST_HAS_FILESYSTEM_V3=1 - ) - - if (${CMAKE_COMPILER_IS_GNUCXX}) - add_definitions(-isystem ${BOOST_SOURCES_DIR}) - endif() - - include_directories( - ${BOOST_SOURCES_DIR} - ) - - source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*) -else() - add_definitions( - -DBOOST_HAS_LOCALE=0 - ) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST) + set(BOOST_STATIC 1) +else() + include(FindBoost) + + set(BOOST_STATIC 0) + #set(Boost_DEBUG 1) + #set(Boost_USE_STATIC_LIBS ON) + + find_package(Boost + COMPONENTS filesystem thread system date_time regex) + + if (NOT Boost_FOUND) + message(FATAL_ERROR "Unable to locate Boost on this system") + endif() + + # Boost releases 1.44 through 1.47 supply both V2 and V3 filesystem + # http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/index.htm + if (${Boost_VERSION} LESS 104400) + add_definitions( + -DBOOST_HAS_FILESYSTEM_V3=0 + ) + else() + add_definitions( + -DBOOST_HAS_FILESYSTEM_V3=1 + -DBOOST_FILESYSTEM_VERSION=3 + ) + endif() + + #if (${Boost_VERSION} LESS 104800) + # boost::locale is only available from 1.48.00 + #message("Too old version of Boost (${Boost_LIB_VERSION}): Building the static version") + # set(BOOST_STATIC 1) + #endif() + + include_directories(${Boost_INCLUDE_DIRS}) + link_libraries(${Boost_LIBRARIES}) +endif() + + +if (BOOST_STATIC) + # Parameters for Boost 1.55.0 + set(BOOST_NAME boost_1_55_0) + set(BOOST_BCP_SUFFIX bcpdigest-0.7.4) + set(BOOST_MD5 "409f7a0e4fb1f5659d07114f3133b67b") + set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") + + set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME}) + DownloadPackage( + "${BOOST_MD5}" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz" + "${BOOST_SOURCES_DIR}" + ) + + set(BOOST_SOURCES) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp + ) + add_definitions( + -DBOOST_LOCALE_WITH_ICONV=1 + ) + + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + add_definitions(-DBOOST_HAS_SCHED_YIELD=1) + endif() + + elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp + ) + add_definitions( + -DBOOST_LOCALE_WITH_WCONV=1 + ) + else() + message(FATAL_ERROR "Support your platform here") + endif() + + aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES) + + list(APPEND BOOST_SOURCES + ${BOOST_REGEX_SOURCES} + ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp + ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp + ) + + list(APPEND THIRD_PARTY_SOURCES ${BOOST_SOURCES}) + + add_definitions( + # Static build of Boost + -DBOOST_ALL_NO_LIB + -DBOOST_ALL_NOLIB + -DBOOST_DATE_TIME_NO_LIB + -DBOOST_THREAD_BUILD_LIB + -DBOOST_PROGRAM_OPTIONS_NO_LIB + -DBOOST_REGEX_NO_LIB + -DBOOST_SYSTEM_NO_LIB + -DBOOST_LOCALE_NO_LIB + -DBOOST_HAS_LOCALE=1 + -DBOOST_HAS_FILESYSTEM_V3=1 + ) + + if (${CMAKE_COMPILER_IS_GNUCXX}) + add_definitions(-isystem ${BOOST_SOURCES_DIR}) + endif() + + include_directories( + ${BOOST_SOURCES_DIR} + ) + + source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*) +else() + add_definitions( + -DBOOST_HAS_LOCALE=0 + ) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/BoostConfiguration.sh Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,33 @@ +#!/bin/bash + +set -e +set -u + +## Starting with version 0.6.2, Orthanc is shipped with a subset of the +## Boost libraries that is generated with the BCP tool: +## +## http://www.boost.org/doc/libs/1_54_0/tools/bcp/doc/html/index.html +## +## This script generates this subset. +## +## History: +## - Orthanc between 0.6.2 and 0.7.3: Boost 1.54.0 +## - Orthanc above 0.7.4: Boost 1.55.0 + +rm -rf /tmp/boost_1_55_0 +rm -rf /tmp/bcp/boost_1_55_0 + +cd /tmp +echo "Uncompressing the source of Boost 1.55.0..." +tar xfz boost_1_55_0.tar.gz + +echo "Generating the subset..." +mkdir -p /tmp/bcp/boost_1_55_0 +bcp --boost=/tmp/boost_1_55_0 thread system locale date_time filesystem math/special_functions algorithm uuid /tmp/bcp/boost_1_55_0 +cd /tmp/bcp + +echo "Compressing the subset..." +tar cfz boost_1_55_0_bcpdigest-0.7.4.tar.gz boost_1_55_0 +ls -l boost_1_55_0_bcpdigest-0.7.4.tar.gz +md5sum boost_1_55_0_bcpdigest-0.7.4.tar.gz +readlink -f boost_1_55_0_bcpdigest-0.7.4.tar.gz
--- a/Resources/CMake/Compiler.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/Compiler.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -5,7 +5,15 @@ # --std=c99 makes libcurl not to compile # -pedantic gives a lot of warnings on OpenSSL set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wno-long-long -Wno-variadic-macros") + + if (CMAKE_CROSSCOMPILING) + # http://stackoverflow.com/a/3543845/881731 + set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff -I<CMAKE_CURRENT_SOURCE_DIR> <SOURCE> <OBJECT>") + endif() + elseif (${MSVC}) + # Use static runtime under Visual Studio + # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace # http://stackoverflow.com/a/6510446 foreach(flag_var CMAKE_C_FLAGS_DEBUG @@ -19,6 +27,7 @@ string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") string(REGEX REPLACE "/MDd" "/MTd" ${flag_var} "${${flag_var}}") endforeach(flag_var) + add_definitions( -D_CRT_SECURE_NO_WARNINGS=1 -D_CRT_SECURE_NO_DEPRECATE=1 @@ -29,39 +38,19 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - if (DEBIAN_FORCE_HARDENING) - execute_process( - COMMAND dpkg-buildflags --get CPPFLAGS - OUTPUT_VARIABLE DEBIAN_CPP_FLAGS - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process( - COMMAND dpkg-buildflags --get CFLAGS - OUTPUT_VARIABLE DEBIAN_C_FLAGS - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process( - COMMAND dpkg-buildflags --get CXXFLAGS - OUTPUT_VARIABLE DEBIAN_CXX_FLAGS - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process( - COMMAND dpkg-buildflags --get LDFLAGS - OUTPUT_VARIABLE DEBIAN_LD_FLAGS - OUTPUT_STRIP_TRAILING_WHITESPACE) - - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DEBIAN_C_FLAGS} ${DEBIAN_CPP_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEBIAN_CXX_FLAGS} ${DEBIAN_CPP_FLAGS}") - endif() - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH}") endif() add_definitions( -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64 ) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed ${DEBIAN_LD_FLAGS}") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined ${DEBIAN_LD_FLAGS}") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined ${DEBIAN_LD_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") # Remove the "-rdynamic" option # http://www.mail-archive.com/cmake@cmake.org/msg08837.html @@ -74,6 +63,12 @@ -D_CRT_SECURE_NO_WARNINGS=1 ) link_libraries(rpcrt4 ws2_32) + + if (${CMAKE_COMPILER_IS_GNUCXX}) + # This is a patch for MinGW64 + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++") + endif() + endif()
--- a/Resources/CMake/DcmtkConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/DcmtkConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,124 +1,151 @@ -if (${STATIC_BUILD}) - SET(DCMTK_VERSION_NUMBER 360) - SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) - DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip" "${DCMTK_SOURCES_DIR}" "" "") - - IF(CMAKE_CROSSCOMPILING) - SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.") - ENDIF() - SET(DCMTK_SOURCE_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) - include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake) - include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake) - - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - set(HAVE_SSTREAM 1) - set(HAVE_PROTOTYPE_BZERO 1) - set(HAVE_PROTOTYPE_GETHOSTNAME 1) - set(HAVE_PROTOTYPE_GETSOCKOPT 1) - set(HAVE_PROTOTYPE_SETSOCKOPT 1) - set(HAVE_PROTOTYPE_CONNECT 1) - set(HAVE_PROTOTYPE_BIND 1) - set(HAVE_PROTOTYPE_ACCEPT 1) - set(HAVE_PROTOTYPE_SETSOCKNAME 1) - set(HAVE_PROTOTYPE_GETSOCKNAME 1) - endif() - - CONFIGURE_FILE( - ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in - ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h) - - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmnet/libsrc DCMTK_SOURCES) - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmdata/libsrc DCMTK_SOURCES) - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/ofstd/libsrc DCMTK_SOURCES) - - # Source for the logging facility of DCMTK - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc - ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc - ) - elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc - ) - endif() - - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc - ) - - # This fixes crashes related to the destruction of the DCMTK OFLogger - # http://support.dcmtk.org/docs-snapshot/file_macros.html - add_definitions( - -DLOG4CPLUS_DISABLE_FATAL=1 - -DDCMTK_VERSION_NUMBER=360 - ) - - include_directories( - #${DCMTK_SOURCES_DIR} - ${DCMTK_SOURCES_DIR}/config/include - ${DCMTK_SOURCES_DIR}/dcmnet/include - ${DCMTK_SOURCES_DIR}/ofstd/include - ${DCMTK_SOURCES_DIR}/oflog/include - ${DCMTK_SOURCES_DIR}/dcmdata/include - ) - - source_group(ThirdParty\\Dcmtk REGULAR_EXPRESSION ${DCMTK_SOURCES_DIR}/.*) - - set(DCMTK_BUNDLES_LOG4CPLUS 1) - - if (STANDALONE_BUILD) - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=1) - else() - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) - endif() - - set(DCMTK_DICTIONARIES - DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic - DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic - DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic - ) - -else() - # The following line allows to manually add libraries at the - # command-line, which is necessary for Ubuntu/Debian packages - set(tmp "${DCMTK_LIBRARIES}") - include(FindDCMTK) - list(APPEND DCMTK_LIBRARIES "${tmp}") - - include_directories(${DCMTK_INCLUDE_DIR}) - link_libraries(${DCMTK_LIBRARIES}) - - add_definitions( - -DHAVE_CONFIG_H=1 - ) - - if (EXISTS "${DCMTK_DIR}/config/cfunix.h") - set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/cfunix.h") - elseif (EXISTS "${DCMTK_DIR}/config/osconfig.h") # This is for Arch Linux - set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/osconfig.h") - else() - message(FATAL_ERROR "Please install libdcmtk1-dev") - endif() - - # Autodetection of the version of DCMTK - file(STRINGS - "${DCMTK_CONFIGURATION_FILE}" - DCMTK_VERSION_NUMBER1 REGEX - ".*PACKAGE_VERSION .*") - - string(REGEX REPLACE - ".*PACKAGE_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$" - "\\1\\2\\3" - DCMTK_VERSION_NUMBER - ${DCMTK_VERSION_NUMBER1}) - - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) - -endif() - -add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER}) -message("DCMTK version: ${DCMTK_VERSION_NUMBER}") +# Lookup for DICOM dictionaries, if none is specified by the user +if (DCMTK_DICTIONARY_DIR STREQUAL "") + find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic + /usr/share/dcmtk + /usr/share/libdcmtk2 + ) + + message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}") + add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR_AUTO}") +else() + add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}") +endif() + + + +if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK) + SET(DCMTK_VERSION_NUMBER 360) + SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) + DownloadPackage( + "219ad631b82031806147e4abbfba4fa4" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip" + "${DCMTK_SOURCES_DIR}") + + IF(CMAKE_CROSSCOMPILING) + SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.") + ENDIF() + SET(DCMTK_SOURCE_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) + include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake) + include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake) + + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + set(HAVE_SSTREAM 1) + set(HAVE_PROTOTYPE_BZERO 1) + set(HAVE_PROTOTYPE_GETHOSTNAME 1) + set(HAVE_PROTOTYPE_GETSOCKOPT 1) + set(HAVE_PROTOTYPE_SETSOCKOPT 1) + set(HAVE_PROTOTYPE_CONNECT 1) + set(HAVE_PROTOTYPE_BIND 1) + set(HAVE_PROTOTYPE_ACCEPT 1) + set(HAVE_PROTOTYPE_SETSOCKNAME 1) + set(HAVE_PROTOTYPE_GETSOCKNAME 1) + endif() + + CONFIGURE_FILE( + ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in + ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h) + + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmnet/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmdata/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/ofstd/libsrc DCMTK_SOURCES) + + # Source for the logging facility of DCMTK + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc + ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc + ) + elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc + ) + + if (${CMAKE_COMPILER_IS_GNUCXX}) + # This is a patch for MinGW64 + execute_process( + COMMAND patch -p0 -i ${CMAKE_SOURCE_DIR}/Resources/Patches/dcmtk-mingw64.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + endif() + + endif() + + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc + ) + + # This fixes crashes related to the destruction of the DCMTK OFLogger + # http://support.dcmtk.org/docs-snapshot/file_macros.html + add_definitions( + -DLOG4CPLUS_DISABLE_FATAL=1 + -DDCMTK_VERSION_NUMBER=360 + ) + + include_directories( + #${DCMTK_SOURCES_DIR} + ${DCMTK_SOURCES_DIR}/config/include + ${DCMTK_SOURCES_DIR}/dcmnet/include + ${DCMTK_SOURCES_DIR}/ofstd/include + ${DCMTK_SOURCES_DIR}/oflog/include + ${DCMTK_SOURCES_DIR}/dcmdata/include + ) + + source_group(ThirdParty\\Dcmtk REGULAR_EXPRESSION ${DCMTK_SOURCES_DIR}/.*) + + set(DCMTK_BUNDLES_LOG4CPLUS 1) + + if (STANDALONE_BUILD) + add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=1) + else() + add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) + endif() + + set(DCMTK_DICTIONARIES + DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic + DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic + DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic + ) + +else() + # The following line allows to manually add libraries at the + # command-line, which is necessary for Ubuntu/Debian packages + set(tmp "${DCMTK_LIBRARIES}") + include(FindDCMTK) + list(APPEND DCMTK_LIBRARIES "${tmp}") + + include_directories(${DCMTK_INCLUDE_DIR}) + link_libraries(${DCMTK_LIBRARIES}) + + add_definitions( + -DHAVE_CONFIG_H=1 + ) + + if (EXISTS "${DCMTK_DIR}/config/cfunix.h") + set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/cfunix.h") + elseif (EXISTS "${DCMTK_DIR}/config/osconfig.h") # This is for Arch Linux + set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/osconfig.h") + else() + message(FATAL_ERROR "Please install libdcmtk1-dev") + endif() + + # Autodetection of the version of DCMTK + file(STRINGS + "${DCMTK_CONFIGURATION_FILE}" + DCMTK_VERSION_NUMBER1 REGEX + ".*PACKAGE_VERSION .*") + + string(REGEX REPLACE + ".*PACKAGE_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$" + "\\1\\2\\3" + DCMTK_VERSION_NUMBER + ${DCMTK_VERSION_NUMBER1}) + + add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) + +endif() + +add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER}) +message("DCMTK version: ${DCMTK_VERSION_NUMBER}")
--- a/Resources/CMake/DownloadPackage.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/DownloadPackage.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,127 +1,133 @@ -macro(GetUrlFilename TargetVariable Url) - string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${Url}") -endmacro() - - -macro(GetUrlExtension TargetVariable Url) - #string(REGEX REPLACE "^.*/[^.]*\\." "" TMP "${Url}") - string(REGEX REPLACE "^.*\\." "" TMP "${Url}") - string(TOLOWER "${TMP}" "${TargetVariable}") -endmacro() - - -macro(DownloadPackage Url TargetDirectory PreloadedVariable UncompressArguments) - if (NOT IS_DIRECTORY "${TargetDirectory}") - GetUrlFilename(TMP_FILENAME "${Url}") - if ("${PreloadedVariable}" STREQUAL "") - set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}") - if (NOT EXISTS "${TMP_PATH}") - message("Downloading ${Url}") - file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS) - else() - message("Using local copy of ${Url}") - endif() - else() - message("Using preloaded archive ${PreloadedVariable} for ${Url}") - set(TMP_PATH "${PreloadedVariable}") - endif() - - GetUrlExtension(TMP_EXTENSION "${Url}") - #message(${TMP_EXTENSION}) - message("Uncompressing ${TMP_FILENAME}") - - if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") - # How to silently extract files using 7-zip - # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly - - FIND_PROGRAM(ZIP_EXECUTABLE 7z PATHS "$ENV{ProgramFiles}/7-Zip") - - if (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) - execute_process( - COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - OUTPUT_QUIET - ) - - if (Failure) - message(FATAL_ERROR "Error while running the uncompression tool") - endif() - - set(ARGS ${UncompressArguments}) - SEPARATE_ARGUMENTS(ARGS) - list(LENGTH ARGS TMP_LENGTH) - - if ("${TMP_EXTENSION}" STREQUAL "tgz") - string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}") - else() - string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}") - endif() - - if (TMP_LENGTH EQUAL 0) - execute_process( - COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - OUTPUT_QUIET - ) - else() - foreach(SUBDIR ${ARGS}) - execute_process( - COMMAND ${ZIP_EXECUTABLE} x -y "-i!${SUBDIR}" "${TMP_FILENAME2}" - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - OUTPUT_QUIET - ) - - if (Failure) - message(FATAL_ERROR "Error while running the uncompression tool") - endif() - endforeach() - endif() - elseif ("${TMP_EXTENSION}" STREQUAL "zip") - execute_process( - COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - OUTPUT_QUIET - ) - else() - message(FATAL_ERROR "Support your platform here") - endif() - - else() - if ("${TMP_EXTENSION}" STREQUAL "zip") - execute_process( - COMMAND sh -c "unzip -q ${TMP_PATH} ${UncompressArguments}" - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - ) - elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) - #message("tar xvfz ${TMP_PATH} ${UncompressArguments}") - execute_process( - COMMAND sh -c "tar xfz ${TMP_PATH} ${UncompressArguments}" - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - ) - elseif ("${TMP_EXTENSION}" STREQUAL "bz2") - execute_process( - COMMAND sh -c "tar xfj ${TMP_PATH} ${UncompressArguments}" - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - ) - else() - message(FATAL_ERROR "Unknown package format.") - endif() - endif() - - if (Failure) - message(FATAL_ERROR "Error while running the uncompression tool") - endif() - - if (NOT IS_DIRECTORY "${TargetDirectory}") - message(FATAL_ERROR "The package was not uncompressed at the proper location. Check the CMake instructions.") - endif() - endif() -endmacro() - +macro(GetUrlFilename TargetVariable Url) + string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${Url}") +endmacro() + + +macro(GetUrlExtension TargetVariable Url) + #string(REGEX REPLACE "^.*/[^.]*\\." "" TMP "${Url}") + string(REGEX REPLACE "^.*\\." "" TMP "${Url}") + string(TOLOWER "${TMP}" "${TargetVariable}") +endmacro() + + +## +## Check the existence of the required decompression tools +## + +if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + find_program(ZIP_EXECUTABLE 7z PATHS "$ENV{ProgramFiles}/7-Zip") + if (${ZIP_EXECUTABLE} MATCHES "ZIP_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)") + endif() + +else() + find_program(UNZIP_EXECUTABLE unzip) + if (${UNZIP_EXECUTABLE} MATCHES "UNZIP_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the 'unzip' package") + endif() + + find_program(TAR_EXECUTABLE tar) + if (${TAR_EXECUTABLE} MATCHES "TAR_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the 'tar' package") + endif() +endif() + + +macro(DownloadPackage MD5 Url TargetDirectory) + if (NOT IS_DIRECTORY "${TargetDirectory}") + GetUrlFilename(TMP_FILENAME "${Url}") + + set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}") + if (NOT EXISTS "${TMP_PATH}") + message("Downloading ${Url}") + + # This fixes issue 6: "I think cmake shouldn't download the + # packages which are not in the system, it should stop and let + # user know." + # https://code.google.com/p/orthanc/issues/detail?id=6 + if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS) + message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON") + endif() + + file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS EXPECTED_MD5 "${MD5}") + else() + message("Using local copy of ${Url}") + endif() + + GetUrlExtension(TMP_EXTENSION "${Url}") + #message(${TMP_EXTENSION}) + message("Uncompressing ${TMP_FILENAME}") + + if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + # How to silently extract files using 7-zip + # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly + + if (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) + execute_process( + COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + + if (Failure) + message(FATAL_ERROR "Error while running the uncompression tool") + endif() + + if ("${TMP_EXTENSION}" STREQUAL "tgz") + string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}") + else() + string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}") + endif() + + execute_process( + COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + elseif ("${TMP_EXTENSION}" STREQUAL "zip") + execute_process( + COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + else() + message(FATAL_ERROR "Support your platform here") + endif() + + else() + if ("${TMP_EXTENSION}" STREQUAL "zip") + execute_process( + COMMAND sh -c "${UNZIP_EXECUTABLE} -q ${TMP_PATH}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) + #message("tar xvfz ${TMP_PATH}") + execute_process( + COMMAND sh -c "${TAR_EXECUTABLE} xfz ${TMP_PATH}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + elseif ("${TMP_EXTENSION}" STREQUAL "bz2") + execute_process( + COMMAND sh -c "${TAR_EXECUTABLE} xfj ${TMP_PATH}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + else() + message(FATAL_ERROR "Unknown package format.") + endif() + endif() + + if (Failure) + message(FATAL_ERROR "Error while running the uncompression tool") + endif() + + if (NOT IS_DIRECTORY "${TargetDirectory}") + message(FATAL_ERROR "The package was not uncompressed at the proper location. Check the CMake instructions.") + endif() + endif() +endmacro()
--- a/Resources/CMake/GoogleLogConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/GoogleLogConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,21 @@ -if (STATIC_BUILD OR NOT USE_DYNAMIC_GOOGLE_LOG) +if (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_LOG) SET(GOOGLE_LOG_SOURCES_DIR ${CMAKE_BINARY_DIR}/glog-0.3.2) - DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/glog-0.3.2.tar.gz" "${GOOGLE_LOG_SOURCES_DIR}" "" "") + DownloadPackage( + "897fbff90d91ea2b6d6e78c8cea641cc" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/glog-0.3.2.tar.gz" + "${GOOGLE_LOG_SOURCES_DIR}") + + + # Glog 0.3.3 fails to build with old versions of MinGW, such as the + # one installed on our Continuous Integration Server that runs + # Debian Squeeze. We thus stick to Glog 0.3.2 for the time being. + + #SET(GOOGLE_LOG_SOURCES_DIR ${CMAKE_BINARY_DIR}/glog-0.3.3) + #DownloadPackage( + # "a6fd2c22f8996846e34c763422717c18" + # "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/glog-0.3.3.tar.gz" + # "${GOOGLE_LOG_SOURCES_DIR}") + set(GOOGLE_LOG_HEADERS ${GOOGLE_LOG_SOURCES_DIR}/src/glog/logging.h @@ -46,10 +61,18 @@ ) if (CMAKE_COMPILER_IS_GNUCXX) - execute_process( - COMMAND patch utilities.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-utilities.diff - WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src - ) + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + execute_process( + COMMAND patch utilities.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-utilities-lsb.diff + WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src + ) + else() + execute_process( + COMMAND patch utilities.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-utilities.diff + WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src + ) + endif() + execute_process( COMMAND patch port.h ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-port-h.diff WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows @@ -61,10 +84,18 @@ endif() if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - configure_file( - ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.h - ${GOOGLE_LOG_SOURCES_DIR}/src/config.h - COPYONLY) + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + # Install the specific configuration for LSB SDK + configure_file( + ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationLSB.h + ${GOOGLE_LOG_SOURCES_DIR}/src/config.h + COPYONLY) + else() + configure_file( + ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.h + ${GOOGLE_LOG_SOURCES_DIR}/src/config.h + COPYONLY) + endif() set(GOOGLE_LOG_SOURCES ${GOOGLE_LOG_SOURCES_DIR}/src/demangle.cc @@ -94,8 +125,16 @@ -DNO_FRAME_POINTER=1 -DGOOGLE_GLOG_DLL_DECL= ) + + if (${CMAKE_COMPILER_IS_GNUCXX}) + # This is a patch for MinGW64 + add_definitions(-D_TIME_H__S=1) + endif() + endif() + + add_library(GoogleLog STATIC ${GOOGLE_LOG_SOURCES}) link_libraries(GoogleLog)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/GoogleLogConfigurationLSB.h Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,175 @@ +/* src/config.h. Generated from config.h.in by configure. */ +/* src/config.h.in. Generated from configure.ac by autoheader. */ + +/* Namespace for Google classes */ +#define GOOGLE_NAMESPACE google + +/* Define if you have the `dladdr' function */ +/* #undef HAVE_DLADDR */ + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the <execinfo.h> header file. */ +#define HAVE_EXECINFO_H 1 + +/* Define if you have the `fcntl' function */ +#define HAVE_FCNTL 1 + +/* Define to 1 if you have the <glob.h> header file. */ +#define HAVE_GLOB_H 1 + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#define HAVE_LIBPTHREAD 1 + +/* Define to 1 if you have the <libunwind.h> header file. */ +/* #undef HAVE_LIBUNWIND_H */ + +/* define if you have google gflags library */ +/* #undef HAVE_LIB_GFLAGS */ + +/* define if you have google gmock library */ +/* #undef HAVE_LIB_GMOCK */ + +/* define if you have google gtest library */ +/* #undef HAVE_LIB_GTEST */ + +/* define if you have libunwind */ +/* #undef HAVE_LIB_UNWIND */ + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* define if the compiler implements namespaces */ +#define HAVE_NAMESPACES 1 + +/* Define if you have POSIX threads libraries and header files. */ +#define HAVE_PTHREAD 1 + +/* Define to 1 if you have the <pwd.h> header file. */ +#define HAVE_PWD_H 1 + +/* define if the compiler implements pthread_rwlock_* */ +#define HAVE_RWLOCK 1 + +/* Define if you have the `sigaltstack' function */ +#define HAVE_SIGALTSTACK 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the <syscall.h> header file. */ +/* #undef HAVE_SYSCALL_H */ + +/* Define to 1 if you have the <syslog.h> header file. */ +#define HAVE_SYSLOG_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/syscall.h> header file. */ +/* #undef HAVE_SYS_SYSCALL_H */ + +/* Define to 1 if you have the <sys/time.h> header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <sys/ucontext.h> header file. */ +/* #define HAVE_SYS_UCONTEXT_H 1 */ + +/* Define to 1 if you have the <sys/utsname.h> header file. */ +#define HAVE_SYS_UTSNAME_H 1 + +/* Define to 1 if you have the <ucontext.h> header file. */ +/* #define HAVE_UCONTEXT_H 1 */ + +/* Define to 1 if you have the <unistd.h> header file. */ +#define HAVE_UNISTD_H 1 + +/* define if the compiler supports using expression for operator */ +#define HAVE_USING_OPERATOR 1 + +/* define if your compiler has __attribute__ */ +#define HAVE___ATTRIBUTE__ 1 + +/* define if your compiler has __builtin_expect */ +#define HAVE___BUILTIN_EXPECT 1 + +/* define if your compiler has __sync_val_compare_and_swap */ +#define HAVE___SYNC_VAL_COMPARE_AND_SWAP 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "glog" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "opensource@google.com" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "glog" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "glog 0.3.2" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "glog" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "0.3.2" + +/* How to access the PC from a struct ucontext */ +/*#include <ucontext.h> +#include <sys/ucontext.h> +#ifdef REG_RIP +#define PC_FROM_UCONTEXT uc_mcontext.gregs[REG_RIP] +#else +#undef PC_FROM_UCONTEXT +#endif*/ + +// This is required for older versions of Linux +#undef PC_FROM_UCONTEXT + +/* Define to necessary symbol if this constant uses a non-standard name on + your system. */ +/* #undef PTHREAD_CREATE_JOINABLE */ + +/* The size of `void *', as computed by sizeof. */ +#define SIZEOF_VOID_P 8 + +/* Define to 1 if you have the ANSI C header files. */ +/* #undef STDC_HEADERS */ + +/* the namespace where STL code like vector<> is defined */ +#define STL_NAMESPACE std + +/* location of source code */ +#define TEST_SRC_DIR "." + +/* Version number of package */ +#define VERSION "0.3.2" + +/* Stops putting the code inside the Google namespace */ +#define _END_GOOGLE_NAMESPACE_ } + +/* Puts following code inside the Google namespace */ +#define _START_GOOGLE_NAMESPACE_ namespace google {
--- a/Resources/CMake/GoogleTestConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/GoogleTestConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,4 +1,4 @@ -if (DEBIAN_USE_GTEST_SOURCE_PACKAGE) +if (USE_GTEST_DEBIAN_SOURCE_PACKAGE) set(GTEST_SOURCES /usr/src/gtest/src/gtest-all.cc) include_directories(/usr/src/gtest) @@ -7,9 +7,12 @@ message(FATAL_ERROR "Please install the libgtest-dev package") endif() -elseif (STATIC_BUILD OR NOT USE_DYNAMIC_GOOGLE_TEST) +elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST) SET(GTEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.6.0) - DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gtest-1.6.0.zip" "${GTEST_SOURCES_DIR}" "" "") + DownloadPackage( + "4577b49f2973c90bf9ba69aa8166b786" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gtest-1.6.0.zip" + "${GTEST_SOURCES_DIR}") include_directories( ${GTEST_SOURCES_DIR}/include
--- a/Resources/CMake/JsonCppConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/JsonCppConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,26 +1,29 @@ -if (USE_DYNAMIC_JSONCPP) - CHECK_INCLUDE_FILE_CXX(jsoncpp/json/reader.h HAVE_JSONCPP_H) - if (NOT HAVE_JSONCPP_H) - message(FATAL_ERROR "Please install the libjsoncpp-dev package") - endif() - - include_directories(/usr/include/jsoncpp) - link_libraries(jsoncpp) - -else() - SET(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.5.0) - DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-src-0.5.0.tar.gz" "${JSONCPP_SOURCES_DIR}" "" "") - - list(APPEND THIRD_PARTY_SOURCES - ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp - ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp - ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp - ) - - include_directories( - ${JSONCPP_SOURCES_DIR}/include - ) - - source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*) -endif() - +if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP) + set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.6.0-rc2) + DownloadPackage( + "363e2f4cbd3aeb63bf4e571f377400fb" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-src-0.6.0-rc2.tar.gz" + "${JSONCPP_SOURCES_DIR}") + + list(APPEND THIRD_PARTY_SOURCES + ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp + ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp + ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp + ) + + include_directories( + ${JSONCPP_SOURCES_DIR}/include + ) + + source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(jsoncpp/json/reader.h HAVE_JSONCPP_H) + if (NOT HAVE_JSONCPP_H) + message(FATAL_ERROR "Please install the libjsoncpp-dev package") + endif() + + include_directories(/usr/include/jsoncpp) + link_libraries(jsoncpp) + +endif()
--- a/Resources/CMake/LibCurlConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/LibCurlConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,98 +1,101 @@ -if (${STATIC_BUILD}) - SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.26.0) - DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/curl-7.26.0.tar.gz" "${CURL_SOURCES_DIR}" "" "") - - include_directories(${CURL_SOURCES_DIR}/include) - AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES) - source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*) - - add_library(Curl STATIC ${CURL_SOURCES}) - link_libraries(Curl) - - add_definitions( - -DCURL_STATICLIB=1 - -DBUILDING_LIBCURL=1 - -DCURL_DISABLE_LDAPS=1 - -DCURL_DISABLE_LDAP=1 - -D_WIN32_WINNT=0x0501 - - -DCURL_DISABLE_DICT=1 - -DCURL_DISABLE_FILE=1 - -DCURL_DISABLE_FTP=1 - -DCURL_DISABLE_GOPHER=1 - -DCURL_DISABLE_LDAP=1 - -DCURL_DISABLE_LDAPS=1 - -DCURL_DISABLE_POP3=1 - -DCURL_DISABLE_PROXY=1 - -DCURL_DISABLE_RTSP=1 - -DCURL_DISABLE_TELNET=1 - -DCURL_DISABLE_TFTP=1 - ) - - if (${ENABLE_SSL}) - add_definitions( - #-DHAVE_LIBSSL=1 - -DUSE_OPENSSL=1 - -DUSE_SSLEAY=1 - ) - endif() - - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - SET(TMP_OS "x86_64") - else() - SET(TMP_OS "x86") - endif() - - set_property( - SOURCE ${CURL_SOURCES} - PROPERTY COMPILE_DEFINITIONS "HAVE_TIME_H;HAVE_STRUCT_TIMEVAL;HAVE_SYS_STAT_H;HAVE_SOCKET;HAVE_STRUCT_SOCKADDR_STORAGE;HAVE_SYS_SOCKET_H;HAVE_SOCKET;HAVE_SYS_SOCKET_H;HAVE_NETINET_IN_H;HAVE_NETDB_H;HAVE_FCNTL_O_NONBLOCK;HAVE_FCNTL_H;HAVE_SELECT;HAVE_ERRNO_H;HAVE_SEND;HAVE_RECV;OS=\"${TMP_OS}\"") - - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - add_definitions( - -DRECV_TYPE_ARG1=int - -DRECV_TYPE_ARG2=void* - -DRECV_TYPE_ARG3=size_t - -DRECV_TYPE_ARG4=int - -DRECV_TYPE_RETV=ssize_t - -DSEND_TYPE_ARG1=int - -DSEND_TYPE_ARG2=void* - -DSEND_QUAL_ARG2=const - -DSEND_TYPE_ARG3=size_t - -DSEND_TYPE_ARG4=int - -DSEND_TYPE_RETV=ssize_t - -DSIZEOF_SHORT=2 - -DSIZEOF_INT=4 - -DSIZEOF_SIZE_T=8 - ) - elseif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "4") - add_definitions( - -DRECV_TYPE_ARG1=int - -DRECV_TYPE_ARG2=void* - -DRECV_TYPE_ARG3=size_t - -DRECV_TYPE_ARG4=int - -DRECV_TYPE_RETV=int - -DSEND_TYPE_ARG1=int - -DSEND_TYPE_ARG2=void* - -DSEND_QUAL_ARG2=const - -DSEND_TYPE_ARG3=size_t - -DSEND_TYPE_ARG4=int - -DSEND_TYPE_RETV=int - -DSIZEOF_SHORT=2 - -DSIZEOF_INT=4 - -DSIZEOF_SIZE_T=4 - ) - else() - message(FATAL_ERROR "Support your platform here") - endif() - endif() - -else() - include(FindCURL) - include_directories(${CURL_INCLUDE_DIRS}) - link_libraries(${CURL_LIBRARIES}) - - if (NOT ${CURL_FOUND}) - message(FATAL_ERROR "Unable to find LibCurl") - endif() -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_CURL) + SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.26.0) + DownloadPackage( + "3fa4d5236f2a36ca5c3af6715e837691" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/curl-7.26.0.tar.gz" + "${CURL_SOURCES_DIR}") + + include_directories(${CURL_SOURCES_DIR}/include) + AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES) + source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*) + + #add_library(Curl STATIC ${CURL_SOURCES}) + #link_libraries(Curl) + + add_definitions( + -DCURL_STATICLIB=1 + -DBUILDING_LIBCURL=1 + -DCURL_DISABLE_LDAPS=1 + -DCURL_DISABLE_LDAP=1 + -D_WIN32_WINNT=0x0501 + + -DCURL_DISABLE_DICT=1 + -DCURL_DISABLE_FILE=1 + -DCURL_DISABLE_FTP=1 + -DCURL_DISABLE_GOPHER=1 + -DCURL_DISABLE_LDAP=1 + -DCURL_DISABLE_LDAPS=1 + -DCURL_DISABLE_POP3=1 + -DCURL_DISABLE_PROXY=1 + -DCURL_DISABLE_RTSP=1 + -DCURL_DISABLE_TELNET=1 + -DCURL_DISABLE_TFTP=1 + ) + + if (${ENABLE_SSL}) + add_definitions( + #-DHAVE_LIBSSL=1 + -DUSE_OPENSSL=1 + -DUSE_SSLEAY=1 + ) + endif() + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + SET(TMP_OS "x86_64") + else() + SET(TMP_OS "x86") + endif() + + set_property( + SOURCE ${CURL_SOURCES} + PROPERTY COMPILE_DEFINITIONS "HAVE_TIME_H;HAVE_STRUCT_TIMEVAL;HAVE_SYS_STAT_H;HAVE_SOCKET;HAVE_STRUCT_SOCKADDR_STORAGE;HAVE_SYS_SOCKET_H;HAVE_SOCKET;HAVE_SYS_SOCKET_H;HAVE_NETINET_IN_H;HAVE_NETDB_H;HAVE_FCNTL_O_NONBLOCK;HAVE_FCNTL_H;HAVE_SELECT;HAVE_ERRNO_H;HAVE_SEND;HAVE_RECV;OS=\"${TMP_OS}\"") + + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + add_definitions( + -DRECV_TYPE_ARG1=int + -DRECV_TYPE_ARG2=void* + -DRECV_TYPE_ARG3=size_t + -DRECV_TYPE_ARG4=int + -DRECV_TYPE_RETV=ssize_t + -DSEND_TYPE_ARG1=int + -DSEND_TYPE_ARG2=void* + -DSEND_QUAL_ARG2=const + -DSEND_TYPE_ARG3=size_t + -DSEND_TYPE_ARG4=int + -DSEND_TYPE_RETV=ssize_t + -DSIZEOF_SHORT=2 + -DSIZEOF_INT=4 + -DSIZEOF_SIZE_T=8 + ) + elseif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "4") + add_definitions( + -DRECV_TYPE_ARG1=int + -DRECV_TYPE_ARG2=void* + -DRECV_TYPE_ARG3=size_t + -DRECV_TYPE_ARG4=int + -DRECV_TYPE_RETV=int + -DSEND_TYPE_ARG1=int + -DSEND_TYPE_ARG2=void* + -DSEND_QUAL_ARG2=const + -DSEND_TYPE_ARG3=size_t + -DSEND_TYPE_ARG4=int + -DSEND_TYPE_RETV=int + -DSIZEOF_SHORT=2 + -DSIZEOF_INT=4 + -DSIZEOF_SIZE_T=4 + ) + else() + message(FATAL_ERROR "Support your platform here") + endif() + endif() + +else() + include(FindCURL) + include_directories(${CURL_INCLUDE_DIRS}) + link_libraries(${CURL_LIBRARIES}) + + if (NOT ${CURL_FOUND}) + message(FATAL_ERROR "Unable to find LibCurl") + endif() +endif()
--- a/Resources/CMake/LibPngConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/LibPngConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,57 +1,60 @@ -if (${STATIC_BUILD}) - SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12) - DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz" "${LIBPNG_SOURCES_DIR}" "${LIBPNG_PRELOADED}" "") - - include_directories( - ${LIBPNG_SOURCES_DIR} - ) - - configure_file( - ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt - ${LIBPNG_SOURCES_DIR}/pnglibconf.h - COPY_ONLY) - - set(LIBPNG_SOURCES - #${LIBPNG_SOURCES_DIR}/example.c - ${LIBPNG_SOURCES_DIR}/png.c - ${LIBPNG_SOURCES_DIR}/pngerror.c - ${LIBPNG_SOURCES_DIR}/pngget.c - ${LIBPNG_SOURCES_DIR}/pngmem.c - ${LIBPNG_SOURCES_DIR}/pngpread.c - ${LIBPNG_SOURCES_DIR}/pngread.c - ${LIBPNG_SOURCES_DIR}/pngrio.c - ${LIBPNG_SOURCES_DIR}/pngrtran.c - ${LIBPNG_SOURCES_DIR}/pngrutil.c - ${LIBPNG_SOURCES_DIR}/pngset.c - #${LIBPNG_SOURCES_DIR}/pngtest.c - ${LIBPNG_SOURCES_DIR}/pngtrans.c - ${LIBPNG_SOURCES_DIR}/pngwio.c - ${LIBPNG_SOURCES_DIR}/pngwrite.c - ${LIBPNG_SOURCES_DIR}/pngwtran.c - ${LIBPNG_SOURCES_DIR}/pngwutil.c - ) - - #set_property( - # SOURCE ${LIBPNG_SOURCES} - # PROPERTY COMPILE_FLAGS -UHAVE_CONFIG_H) - - list(APPEND THIRD_PARTY_SOURCES ${LIBPNG_SOURCES}) - - add_definitions( - -DPNG_NO_CONSOLE_IO=1 - -DPNG_NO_STDIO=1 - ) - - source_group(ThirdParty\\Libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*) - -else() - include(FindPNG) - - if (NOT ${PNG_FOUND}) - message(FATAL_ERROR "Unable to find LibPNG") - endif() - - include_directories(${PNG_INCLUDE_DIRS}) - link_libraries(${PNG_LIBRARIES}) - add_definitions(${PNG_DEFINITIONS}) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG) + SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12) + DownloadPackage( + "8ea7f60347a306c5faf70b977fa80e28" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz" + "${LIBPNG_SOURCES_DIR}") + + include_directories( + ${LIBPNG_SOURCES_DIR} + ) + + configure_file( + ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt + ${LIBPNG_SOURCES_DIR}/pnglibconf.h + COPY_ONLY) + + set(LIBPNG_SOURCES + #${LIBPNG_SOURCES_DIR}/example.c + ${LIBPNG_SOURCES_DIR}/png.c + ${LIBPNG_SOURCES_DIR}/pngerror.c + ${LIBPNG_SOURCES_DIR}/pngget.c + ${LIBPNG_SOURCES_DIR}/pngmem.c + ${LIBPNG_SOURCES_DIR}/pngpread.c + ${LIBPNG_SOURCES_DIR}/pngread.c + ${LIBPNG_SOURCES_DIR}/pngrio.c + ${LIBPNG_SOURCES_DIR}/pngrtran.c + ${LIBPNG_SOURCES_DIR}/pngrutil.c + ${LIBPNG_SOURCES_DIR}/pngset.c + #${LIBPNG_SOURCES_DIR}/pngtest.c + ${LIBPNG_SOURCES_DIR}/pngtrans.c + ${LIBPNG_SOURCES_DIR}/pngwio.c + ${LIBPNG_SOURCES_DIR}/pngwrite.c + ${LIBPNG_SOURCES_DIR}/pngwtran.c + ${LIBPNG_SOURCES_DIR}/pngwutil.c + ) + + #set_property( + # SOURCE ${LIBPNG_SOURCES} + # PROPERTY COMPILE_FLAGS -UHAVE_CONFIG_H) + + list(APPEND THIRD_PARTY_SOURCES ${LIBPNG_SOURCES}) + + add_definitions( + -DPNG_NO_CONSOLE_IO=1 + -DPNG_NO_STDIO=1 + ) + + source_group(ThirdParty\\Libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*) + +else() + include(FindPNG) + + if (NOT ${PNG_FOUND}) + message(FATAL_ERROR "Unable to find LibPNG") + endif() + + include_directories(${PNG_INCLUDE_DIRS}) + link_libraries(${PNG_LIBRARIES}) + add_definitions(${PNG_DEFINITIONS}) +endif()
--- a/Resources/CMake/LuaConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/LuaConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,67 +1,67 @@ -if (STATIC_BUILD OR NOT USE_DYNAMIC_LUA) - SET(LUA_SOURCES_DIR ${CMAKE_BINARY_DIR}/lua-5.1.5) - DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/lua-5.1.5.tar.gz" "${LUA_SOURCES_DIR}" "" "") - - add_definitions( - #-DLUA_LIB=1 - #-Dluaall_c=1 - #-DLUA_COMPAT_ALL=1 # Compile a generic version of Lua - ) - - include_directories( - ${LUA_SOURCES_DIR}/src - ) - - set(LUA_SOURCES - # Core Lua - ${LUA_SOURCES_DIR}/src/lapi.c - ${LUA_SOURCES_DIR}/src/lcode.c - ${LUA_SOURCES_DIR}/src/ldebug.c - ${LUA_SOURCES_DIR}/src/ldo.c - ${LUA_SOURCES_DIR}/src/ldump.c - ${LUA_SOURCES_DIR}/src/lfunc.c - ${LUA_SOURCES_DIR}/src/lgc.c - ${LUA_SOURCES_DIR}/src/llex.c - ${LUA_SOURCES_DIR}/src/lmem.c - ${LUA_SOURCES_DIR}/src/lobject.c - ${LUA_SOURCES_DIR}/src/lopcodes.c - ${LUA_SOURCES_DIR}/src/lparser.c - ${LUA_SOURCES_DIR}/src/lstate.c - ${LUA_SOURCES_DIR}/src/lstring.c - ${LUA_SOURCES_DIR}/src/ltable.c - ${LUA_SOURCES_DIR}/src/ltm.c - ${LUA_SOURCES_DIR}/src/lundump.c - ${LUA_SOURCES_DIR}/src/lvm.c - ${LUA_SOURCES_DIR}/src/lzio.c - - # Base Lua modules - ${LUA_SOURCES_DIR}/src/lauxlib.c - ${LUA_SOURCES_DIR}/src/lbaselib.c - ${LUA_SOURCES_DIR}/src/ldblib.c - ${LUA_SOURCES_DIR}/src/liolib.c - ${LUA_SOURCES_DIR}/src/lmathlib.c - ${LUA_SOURCES_DIR}/src/loslib.c - ${LUA_SOURCES_DIR}/src/ltablib.c - ${LUA_SOURCES_DIR}/src/lstrlib.c - ${LUA_SOURCES_DIR}/src/loadlib.c - ${LUA_SOURCES_DIR}/src/linit.c - ) - - add_library(Lua STATIC ${LUA_SOURCES}) - link_libraries(Lua) - - source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*) - -else() - CHECK_INCLUDE_FILE_CXX(lua.h HAVE_LUA_H) - if (NOT HAVE_LUA_H) - message(FATAL_ERROR "Please install the liblua-dev package") - endif() - - CHECK_LIBRARY_EXISTS(lua "lua_pcall" HAVE_LUA_LIB) - if (NOT HAVE_LUA_LIB) - message(FATAL_ERROR "Please install the liblua-dev package") - endif() - - link_libraries(lua) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_LUA) + SET(LUA_SOURCES_DIR ${CMAKE_BINARY_DIR}/lua-5.1.5) + DownloadPackage( + "2e115fe26e435e33b0d5c022e4490567" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/lua-5.1.5.tar.gz" + "${LUA_SOURCES_DIR}") + + add_definitions( + #-DLUA_LIB=1 + #-Dluaall_c=1 + #-DLUA_COMPAT_ALL=1 # Compile a generic version of Lua + ) + + include_directories( + ${LUA_SOURCES_DIR}/src + ) + + set(LUA_SOURCES + # Core Lua + ${LUA_SOURCES_DIR}/src/lapi.c + ${LUA_SOURCES_DIR}/src/lcode.c + ${LUA_SOURCES_DIR}/src/ldebug.c + ${LUA_SOURCES_DIR}/src/ldo.c + ${LUA_SOURCES_DIR}/src/ldump.c + ${LUA_SOURCES_DIR}/src/lfunc.c + ${LUA_SOURCES_DIR}/src/lgc.c + ${LUA_SOURCES_DIR}/src/llex.c + ${LUA_SOURCES_DIR}/src/lmem.c + ${LUA_SOURCES_DIR}/src/lobject.c + ${LUA_SOURCES_DIR}/src/lopcodes.c + ${LUA_SOURCES_DIR}/src/lparser.c + ${LUA_SOURCES_DIR}/src/lstate.c + ${LUA_SOURCES_DIR}/src/lstring.c + ${LUA_SOURCES_DIR}/src/ltable.c + ${LUA_SOURCES_DIR}/src/ltm.c + ${LUA_SOURCES_DIR}/src/lundump.c + ${LUA_SOURCES_DIR}/src/lvm.c + ${LUA_SOURCES_DIR}/src/lzio.c + + # Base Lua modules + ${LUA_SOURCES_DIR}/src/lauxlib.c + ${LUA_SOURCES_DIR}/src/lbaselib.c + ${LUA_SOURCES_DIR}/src/ldblib.c + ${LUA_SOURCES_DIR}/src/liolib.c + ${LUA_SOURCES_DIR}/src/lmathlib.c + ${LUA_SOURCES_DIR}/src/loslib.c + ${LUA_SOURCES_DIR}/src/ltablib.c + ${LUA_SOURCES_DIR}/src/lstrlib.c + ${LUA_SOURCES_DIR}/src/loadlib.c + ${LUA_SOURCES_DIR}/src/linit.c + ) + + add_library(Lua STATIC ${LUA_SOURCES}) + link_libraries(Lua) + + source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*) + +else() + include(FindLua51) + + if (NOT LUA51_FOUND) + message(FATAL_ERROR "Please install the liblua-dev package") + endif() + + include_directories(${LUA_INCLUDE_DIR}) + link_libraries(${LUA_LIBRARIES}) +endif()
--- a/Resources/CMake/MongooseConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/MongooseConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,48 +1,59 @@ -if (STATIC_BUILD OR NOT USE_DYNAMIC_MONGOOSE) - SET(MONGOOSE_SOURCES_DIR ${CMAKE_BINARY_DIR}/mongoose) - DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/mongoose-3.1.tgz" "${MONGOOSE_SOURCES_DIR}" "" "") - - # Patch mongoose - execute_process( - COMMAND patch mongoose.c ${CMAKE_SOURCE_DIR}/Resources/Patches/mongoose-patch.diff - WORKING_DIRECTORY ${MONGOOSE_SOURCES_DIR} - ) - - include_directories( - ${MONGOOSE_SOURCES_DIR} - ) - - list(APPEND THIRD_PARTY_SOURCES - ${MONGOOSE_SOURCES_DIR}/mongoose.c - ) - - - if (${ENABLE_SSL}) - add_definitions( - -DNO_SSL_DL=1 - ) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - link_libraries(dl) - endif() - - else() - add_definitions( - -DNO_SSL=1 # Remove SSL support from mongoose - ) - endif() - - source_group(ThirdParty\\Mongoose REGULAR_EXPRESSION ${MONGOOSE_SOURCES_DIR}/.*) - -else() - CHECK_INCLUDE_FILE_CXX(mongoose.h HAVE_MONGOOSE_H) - if (NOT HAVE_MONGOOSE_H) - message(FATAL_ERROR "Please install the mongoose-devel package") - endif() - - CHECK_LIBRARY_EXISTS(mongoose mg_start "" HAVE_MONGOOSE_LIB) - if (NOT HAVE_MONGOOSE_LIB) - message(FATAL_ERROR "Please install the mongoose-devel package") - endif() - - link_libraries(mongoose) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_MONGOOSE) + SET(MONGOOSE_SOURCES_DIR ${CMAKE_BINARY_DIR}/mongoose) + DownloadPackage( + "e718fc287b4eb1bd523be3fa00942bb0" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/mongoose-3.1.tgz" + "${MONGOOSE_SOURCES_DIR}") + + # Patch mongoose + execute_process( + COMMAND patch mongoose.c ${CMAKE_SOURCE_DIR}/Resources/Patches/mongoose-patch.diff + WORKING_DIRECTORY ${MONGOOSE_SOURCES_DIR} + ) + + include_directories( + ${MONGOOSE_SOURCES_DIR} + ) + + list(APPEND THIRD_PARTY_SOURCES + ${MONGOOSE_SOURCES_DIR}/mongoose.c + ) + + + if (${ENABLE_SSL}) + add_definitions( + -DNO_SSL_DL=1 + ) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + link_libraries(dl) + endif() + + else() + add_definitions( + -DNO_SSL=1 # Remove SSL support from mongoose + ) + endif() + + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + if (${CMAKE_COMPILER_IS_GNUCXX}) + # This is a patch for MinGW64 + add_definitions(-D_TIMESPEC_DEFINED=1) + endif() + endif() + + source_group(ThirdParty\\Mongoose REGULAR_EXPRESSION ${MONGOOSE_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(mongoose.h HAVE_MONGOOSE_H) + if (NOT HAVE_MONGOOSE_H) + message(FATAL_ERROR "Please install the mongoose-devel package") + endif() + + CHECK_LIBRARY_EXISTS(mongoose mg_start "" HAVE_MONGOOSE_LIB) + if (NOT HAVE_MONGOOSE_LIB) + message(FATAL_ERROR "Please install the mongoose-devel package") + endif() + + link_libraries(mongoose) +endif()
--- a/Resources/CMake/OpenSslConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/OpenSslConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,186 +1,209 @@ -if (${STATIC_BUILD}) - SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.1c) - DownloadPackage("www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/openssl-1.0.1c.tar.gz" "${OPENSSL_SOURCES_DIR}" "" "") - - if (NOT EXISTS "${OPENSSL_SOURCES_DIR}/include/PATCHED") - if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") - message("Patching the symbolic links") - # Patch the symbolic links by copying the files - file(GLOB headers "${OPENSSL_SOURCES_DIR}/include/openssl/*.h") - foreach(header ${headers}) - message(${header}) - file(READ "${header}" symbolicLink) - message(${symbolicLink}) - configure_file("${OPENSSL_SOURCES_DIR}/include/openssl/${symbolicLink}" "${header}" COPYONLY) - endforeach() - file(WRITE "${OPENSSL_SOURCES_DIR}/include/PATCHED") - endif() - endif() - - add_definitions( - -DOPENSSL_THREADS - -DOPENSSL_IA32_SSE2 - -DOPENSSL_NO_ASM - -DOPENSSL_NO_DYNAMIC_ENGINE - -DNO_WINDOWS_BRAINDEATH - - -DOPENSSL_NO_BF - -DOPENSSL_NO_CAMELLIA - -DOPENSSL_NO_CAST - -DOPENSSL_NO_EC - -DOPENSSL_NO_ECDH - -DOPENSSL_NO_ECDSA - -DOPENSSL_NO_EC_NISTP_64_GCC_128 - -DOPENSSL_NO_GMP - -DOPENSSL_NO_GOST - -DOPENSSL_NO_HW - -DOPENSSL_NO_JPAKE - -DOPENSSL_NO_IDEA - -DOPENSSL_NO_KRB5 - -DOPENSSL_NO_MD2 - -DOPENSSL_NO_MDC2 - -DOPENSSL_NO_MD4 - -DOPENSSL_NO_RC2 - -DOPENSSL_NO_RC4 - -DOPENSSL_NO_RC5 - -DOPENSSL_NO_RFC3779 - -DOPENSSL_NO_SCTP - -DOPENSSL_NO_STORE - -DOPENSSL_NO_SEED - -DOPENSSL_NO_WHIRLPOOL - -DOPENSSL_NO_RIPEMD - ) - - include_directories( - ${OPENSSL_SOURCES_DIR} - ${OPENSSL_SOURCES_DIR}/crypto - ${OPENSSL_SOURCES_DIR}/crypto/asn1 - ${OPENSSL_SOURCES_DIR}/crypto/modes - ${OPENSSL_SOURCES_DIR}/crypto/evp - ${OPENSSL_SOURCES_DIR}/include - ) - - set(OPENSSL_SOURCES_SUBDIRS - ${OPENSSL_SOURCES_DIR}/crypto - ${OPENSSL_SOURCES_DIR}/crypto/aes - ${OPENSSL_SOURCES_DIR}/crypto/asn1 - ${OPENSSL_SOURCES_DIR}/crypto/bio - ${OPENSSL_SOURCES_DIR}/crypto/bn - ${OPENSSL_SOURCES_DIR}/crypto/buffer - ${OPENSSL_SOURCES_DIR}/crypto/cmac - ${OPENSSL_SOURCES_DIR}/crypto/cms - ${OPENSSL_SOURCES_DIR}/crypto/comp - ${OPENSSL_SOURCES_DIR}/crypto/conf - ${OPENSSL_SOURCES_DIR}/crypto/des - ${OPENSSL_SOURCES_DIR}/crypto/dh - ${OPENSSL_SOURCES_DIR}/crypto/dsa - ${OPENSSL_SOURCES_DIR}/crypto/dso - ${OPENSSL_SOURCES_DIR}/crypto/engine - ${OPENSSL_SOURCES_DIR}/crypto/err - ${OPENSSL_SOURCES_DIR}/crypto/evp - ${OPENSSL_SOURCES_DIR}/crypto/hmac - ${OPENSSL_SOURCES_DIR}/crypto/lhash - ${OPENSSL_SOURCES_DIR}/crypto/md5 - ${OPENSSL_SOURCES_DIR}/crypto/modes - ${OPENSSL_SOURCES_DIR}/crypto/objects - ${OPENSSL_SOURCES_DIR}/crypto/ocsp - ${OPENSSL_SOURCES_DIR}/crypto/pem - ${OPENSSL_SOURCES_DIR}/crypto/pkcs12 - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7 - ${OPENSSL_SOURCES_DIR}/crypto/pqueue - ${OPENSSL_SOURCES_DIR}/crypto/rand - ${OPENSSL_SOURCES_DIR}/crypto/rsa - ${OPENSSL_SOURCES_DIR}/crypto/sha - ${OPENSSL_SOURCES_DIR}/crypto/srp - ${OPENSSL_SOURCES_DIR}/crypto/stack - ${OPENSSL_SOURCES_DIR}/crypto/ts - ${OPENSSL_SOURCES_DIR}/crypto/txt_db - ${OPENSSL_SOURCES_DIR}/crypto/ui - ${OPENSSL_SOURCES_DIR}/crypto/x509 - ${OPENSSL_SOURCES_DIR}/crypto/x509v3 - ${OPENSSL_SOURCES_DIR}/ssl - ) - - foreach(d ${OPENSSL_SOURCES_SUBDIRS}) - AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES) - endforeach() - - list(REMOVE_ITEM OPENSSL_SOURCES - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c - ${OPENSSL_SOURCES_DIR}/crypto/armcap.c - ${OPENSSL_SOURCES_DIR}/crypto/bf/bfs.cpp - ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_rtcp.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/exp.c - ${OPENSSL_SOURCES_DIR}/crypto/conf/cnf_save.c - ${OPENSSL_SOURCES_DIR}/crypto/conf/test.c - ${OPENSSL_SOURCES_DIR}/crypto/des/des.c - ${OPENSSL_SOURCES_DIR}/crypto/des/des3s.cpp - ${OPENSSL_SOURCES_DIR}/crypto/des/des_opts.c - ${OPENSSL_SOURCES_DIR}/crypto/des/dess.cpp - ${OPENSSL_SOURCES_DIR}/crypto/des/read_pwd.c - ${OPENSSL_SOURCES_DIR}/crypto/des/speed.c - ${OPENSSL_SOURCES_DIR}/crypto/evp/e_dsa.c - ${OPENSSL_SOURCES_DIR}/crypto/evp/m_ripemd.c - ${OPENSSL_SOURCES_DIR}/crypto/lhash/lh_test.c - ${OPENSSL_SOURCES_DIR}/crypto/md5/md5s.cpp - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/bio_ber.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pk7_enc.c - ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c - ${OPENSSL_SOURCES_DIR}/crypto/rand/randtest.c - ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c - ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c - ${OPENSSL_SOURCES_DIR}/crypto/x509v3/tabtest.c - ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3conf.c - ${OPENSSL_SOURCES_DIR}/ssl/ssl_task.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c - ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c - ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/bntest.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/expspeed.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/exptest.c - ${OPENSSL_SOURCES_DIR}/crypto/engine/enginetest.c - ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_test.c - ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmactest.c - ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.c - ${OPENSSL_SOURCES_DIR}/crypto/md5/md5test.c - ${OPENSSL_SOURCES_DIR}/crypto/o_dir_test.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/dec.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/enc.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/sign.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/verify.c - ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa_test.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1t.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1test.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha256t.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha512t.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/shatest.c - ${OPENSSL_SOURCES_DIR}/crypto/srp/srptest.c - ) - - #if (${MSVC}) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - set_source_files_properties( - ${OPENSSL_SOURCES} - PROPERTIES COMPILE_DEFINITIONS - "OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN") - endif() - - add_library(OpenSSL STATIC ${OPENSSL_SOURCES}) - link_libraries(OpenSSL) - -else() - include(FindOpenSSL) - - if (NOT ${OPENSSL_FOUND}) - message(FATAL_ERROR "Unable to find OpenSSL") - endif() - - include_directories(${OPENSSL_INCLUDE_DIR}) - link_libraries(${OPENSSL_LIBRARIES}) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL) + SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.1g) + DownloadPackage( + "de62b43dfcd858e66a74bee1c834e959" + "www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/openssl-1.0.1g.tar.gz" + "${OPENSSL_SOURCES_DIR}") + + if (NOT EXISTS "${OPENSSL_SOURCES_DIR}/include/PATCHED") + if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + message("Patching the symbolic links") + # Patch the symbolic links by copying the files + file(GLOB headers "${OPENSSL_SOURCES_DIR}/include/openssl/*.h") + foreach(header ${headers}) + message(${header}) + file(READ "${header}" symbolicLink) + message(${symbolicLink}) + configure_file("${OPENSSL_SOURCES_DIR}/include/openssl/${symbolicLink}" "${header}" COPYONLY) + endforeach() + file(WRITE "${OPENSSL_SOURCES_DIR}/include/PATCHED") + endif() + endif() + + add_definitions( + -DOPENSSL_THREADS + -DOPENSSL_IA32_SSE2 + -DOPENSSL_NO_ASM + -DOPENSSL_NO_DYNAMIC_ENGINE + -DNO_WINDOWS_BRAINDEATH + + -DOPENSSL_NO_BF + -DOPENSSL_NO_CAMELLIA + -DOPENSSL_NO_CAST + -DOPENSSL_NO_EC + -DOPENSSL_NO_ECDH + -DOPENSSL_NO_ECDSA + -DOPENSSL_NO_EC_NISTP_64_GCC_128 + -DOPENSSL_NO_GMP + -DOPENSSL_NO_GOST + -DOPENSSL_NO_HW + -DOPENSSL_NO_JPAKE + -DOPENSSL_NO_IDEA + -DOPENSSL_NO_KRB5 + -DOPENSSL_NO_MD2 + -DOPENSSL_NO_MDC2 + -DOPENSSL_NO_MD4 + -DOPENSSL_NO_RC2 + -DOPENSSL_NO_RC4 + -DOPENSSL_NO_RC5 + -DOPENSSL_NO_RFC3779 + -DOPENSSL_NO_SCTP + -DOPENSSL_NO_STORE + -DOPENSSL_NO_SEED + -DOPENSSL_NO_WHIRLPOOL + -DOPENSSL_NO_RIPEMD + ) + + include_directories( + ${OPENSSL_SOURCES_DIR} + ${OPENSSL_SOURCES_DIR}/crypto + ${OPENSSL_SOURCES_DIR}/crypto/asn1 + ${OPENSSL_SOURCES_DIR}/crypto/modes + ${OPENSSL_SOURCES_DIR}/crypto/evp + ${OPENSSL_SOURCES_DIR}/include + ) + + set(OPENSSL_SOURCES_SUBDIRS + ${OPENSSL_SOURCES_DIR}/crypto + ${OPENSSL_SOURCES_DIR}/crypto/aes + ${OPENSSL_SOURCES_DIR}/crypto/asn1 + ${OPENSSL_SOURCES_DIR}/crypto/bio + ${OPENSSL_SOURCES_DIR}/crypto/bn + ${OPENSSL_SOURCES_DIR}/crypto/buffer + ${OPENSSL_SOURCES_DIR}/crypto/cmac + ${OPENSSL_SOURCES_DIR}/crypto/cms + ${OPENSSL_SOURCES_DIR}/crypto/comp + ${OPENSSL_SOURCES_DIR}/crypto/conf + ${OPENSSL_SOURCES_DIR}/crypto/des + ${OPENSSL_SOURCES_DIR}/crypto/dh + ${OPENSSL_SOURCES_DIR}/crypto/dsa + ${OPENSSL_SOURCES_DIR}/crypto/dso + ${OPENSSL_SOURCES_DIR}/crypto/engine + ${OPENSSL_SOURCES_DIR}/crypto/err + ${OPENSSL_SOURCES_DIR}/crypto/evp + ${OPENSSL_SOURCES_DIR}/crypto/hmac + ${OPENSSL_SOURCES_DIR}/crypto/lhash + ${OPENSSL_SOURCES_DIR}/crypto/md5 + ${OPENSSL_SOURCES_DIR}/crypto/modes + ${OPENSSL_SOURCES_DIR}/crypto/objects + ${OPENSSL_SOURCES_DIR}/crypto/ocsp + ${OPENSSL_SOURCES_DIR}/crypto/pem + ${OPENSSL_SOURCES_DIR}/crypto/pkcs12 + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7 + ${OPENSSL_SOURCES_DIR}/crypto/pqueue + ${OPENSSL_SOURCES_DIR}/crypto/rand + ${OPENSSL_SOURCES_DIR}/crypto/rsa + ${OPENSSL_SOURCES_DIR}/crypto/sha + ${OPENSSL_SOURCES_DIR}/crypto/srp + ${OPENSSL_SOURCES_DIR}/crypto/stack + ${OPENSSL_SOURCES_DIR}/crypto/ts + ${OPENSSL_SOURCES_DIR}/crypto/txt_db + ${OPENSSL_SOURCES_DIR}/crypto/ui + ${OPENSSL_SOURCES_DIR}/crypto/x509 + ${OPENSSL_SOURCES_DIR}/crypto/x509v3 + ${OPENSSL_SOURCES_DIR}/ssl + ) + + foreach(d ${OPENSSL_SOURCES_SUBDIRS}) + AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES) + endforeach() + + list(REMOVE_ITEM OPENSSL_SOURCES + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c + ${OPENSSL_SOURCES_DIR}/crypto/armcap.c + ${OPENSSL_SOURCES_DIR}/crypto/bf/bfs.cpp + ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_rtcp.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/exp.c + ${OPENSSL_SOURCES_DIR}/crypto/conf/cnf_save.c + ${OPENSSL_SOURCES_DIR}/crypto/conf/test.c + ${OPENSSL_SOURCES_DIR}/crypto/des/des.c + ${OPENSSL_SOURCES_DIR}/crypto/des/des3s.cpp + ${OPENSSL_SOURCES_DIR}/crypto/des/des_opts.c + ${OPENSSL_SOURCES_DIR}/crypto/des/dess.cpp + ${OPENSSL_SOURCES_DIR}/crypto/des/read_pwd.c + ${OPENSSL_SOURCES_DIR}/crypto/des/speed.c + ${OPENSSL_SOURCES_DIR}/crypto/evp/e_dsa.c + ${OPENSSL_SOURCES_DIR}/crypto/evp/m_ripemd.c + ${OPENSSL_SOURCES_DIR}/crypto/lhash/lh_test.c + ${OPENSSL_SOURCES_DIR}/crypto/md5/md5s.cpp + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/bio_ber.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pk7_enc.c + ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c + ${OPENSSL_SOURCES_DIR}/crypto/rand/randtest.c + ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c + ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c + ${OPENSSL_SOURCES_DIR}/crypto/x509v3/tabtest.c + ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3conf.c + ${OPENSSL_SOURCES_DIR}/ssl/ssl_task.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c + ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c + ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/bntest.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/expspeed.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/exptest.c + ${OPENSSL_SOURCES_DIR}/crypto/engine/enginetest.c + ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_test.c + ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmactest.c + ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.c + ${OPENSSL_SOURCES_DIR}/crypto/md5/md5test.c + ${OPENSSL_SOURCES_DIR}/crypto/o_dir_test.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/dec.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/enc.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/sign.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/verify.c + ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa_test.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1t.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1test.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha256t.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha512t.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/shatest.c + ${OPENSSL_SOURCES_DIR}/crypto/srp/srptest.c + + ${OPENSSL_SOURCES_DIR}/crypto/bn/divtest.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/bnspeed.c + ${OPENSSL_SOURCES_DIR}/crypto/des/destest.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/p192.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/p512.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/p1024.c + ${OPENSSL_SOURCES_DIR}/crypto/des/rpw.c + ${OPENSSL_SOURCES_DIR}/ssl/ssltest.c + ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsagen.c + ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsatest.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/dhtest.c + ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pq_test.c + ${OPENSSL_SOURCES_DIR}/crypto/des/ncbc_enc.c + ) + + if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") + set_source_files_properties( + ${OPENSSL_SOURCES} + PROPERTIES COMPILE_DEFINITIONS + "OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN") + + elseif ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + execute_process( + COMMAND patch ui_openssl.c ${CMAKE_SOURCE_DIR}/Resources/Patches/openssl-lsb.diff + WORKING_DIRECTORY ${OPENSSL_SOURCES_DIR}/crypto/ui + ) + + endif() + + #add_library(OpenSSL STATIC ${OPENSSL_SOURCES}) + #link_libraries(OpenSSL) + +else() + include(FindOpenSSL) + + if (NOT ${OPENSSL_FOUND}) + message(FATAL_ERROR "Unable to find OpenSSL") + endif() + + include_directories(${OPENSSL_INCLUDE_DIR}) + link_libraries(${OPENSSL_LIBRARIES}) +endif()
--- a/Resources/CMake/SQLiteConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/SQLiteConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,39 +1,43 @@ -if (STATIC_BUILD OR NOT USE_DYNAMIC_SQLITE) - SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3071300) - DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/sqlite-amalgamation-3071300.zip" "${SQLITE_SOURCES_DIR}" "" "") - - list(APPEND THIRD_PARTY_SOURCES - ${SQLITE_SOURCES_DIR}/sqlite3.c - ) - - add_definitions( - # For SQLite to run in the "Serialized" thread-safe mode - # http://www.sqlite.org/threadsafe.html - -DSQLITE_THREADSAFE=1 - -DSQLITE_OMIT_LOAD_EXTENSION # Disable SQLite plugins - ) - - include_directories( - ${SQLITE_SOURCES_DIR} - ) - - source_group(ThirdParty\\SQLite REGULAR_EXPRESSION ${SQLITE_SOURCES_DIR}/.*) -else() - CHECK_INCLUDE_FILE_CXX(sqlite3.h HAVE_SQLITE_H) - if (NOT HAVE_SQLITE_H) - message(FATAL_ERROR "Please install the libsqlite3-dev package") - endif() - - # Autodetection of the version of SQLite - file(STRINGS "/usr/include/sqlite3.h" SQLITE_VERSION_NUMBER1 REGEX "#define SQLITE_VERSION_NUMBER.*$") - string(REGEX REPLACE "#define SQLITE_VERSION_NUMBER(.*)$" "\\1" SQLITE_VERSION_NUMBER ${SQLITE_VERSION_NUMBER1}) - - message("Detected version of SQLite: ${SQLITE_VERSION_NUMBER}") - - IF (${SQLITE_VERSION_NUMBER} LESS 3007000) - # "sqlite3_create_function_v2" is not defined in SQLite < 3.7.0 - message(FATAL_ERROR "SQLite version must be above 3.7.0. Please set the CMake variable USE_DYNAMIC_SQLITE to OFF.") - ENDIF() - - link_libraries(sqlite3) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_SQLITE) + SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3071300) + DownloadPackage( + "5fbeff9645ab035a1f580e90b279a16d" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/sqlite-amalgamation-3071300.zip" + "${SQLITE_SOURCES_DIR}") + + list(APPEND THIRD_PARTY_SOURCES + ${SQLITE_SOURCES_DIR}/sqlite3.c + ) + + add_definitions( + # For SQLite to run in the "Serialized" thread-safe mode + # http://www.sqlite.org/threadsafe.html + -DSQLITE_THREADSAFE=1 + -DSQLITE_OMIT_LOAD_EXTENSION # Disable SQLite plugins + ) + + include_directories( + ${SQLITE_SOURCES_DIR} + ) + + source_group(ThirdParty\\SQLite REGULAR_EXPRESSION ${SQLITE_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(sqlite3.h HAVE_SQLITE_H) + if (NOT HAVE_SQLITE_H) + message(FATAL_ERROR "Please install the libsqlite3-dev package") + endif() + + # Autodetection of the version of SQLite + file(STRINGS "/usr/include/sqlite3.h" SQLITE_VERSION_NUMBER1 REGEX "#define SQLITE_VERSION_NUMBER.*$") + string(REGEX REPLACE "#define SQLITE_VERSION_NUMBER(.*)$" "\\1" SQLITE_VERSION_NUMBER ${SQLITE_VERSION_NUMBER1}) + + message("Detected version of SQLite: ${SQLITE_VERSION_NUMBER}") + + IF (${SQLITE_VERSION_NUMBER} LESS 3007000) + # "sqlite3_create_function_v2" is not defined in SQLite < 3.7.0 + message(FATAL_ERROR "SQLite version must be above 3.7.0. Please set the CMake variable USE_SYSTEM_SQLITE to OFF.") + ENDIF() + + link_libraries(sqlite3) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/Uninstall.cmake.in Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,25 @@ +# Code taken from the CMake FAQ +# http://www.cmake.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F + +if (NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +list(REVERSE files) +foreach (file ${files}) + message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + if (EXISTS "$ENV{DESTDIR}${file}") + execute_process( + COMMAND @CMAKE_COMMAND@ -E remove "$ENV{DESTDIR}${file}" + OUTPUT_VARIABLE rm_out + RESULT_VARIABLE rm_retval + ) + if(NOT ${rm_retval} EQUAL 0) + message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + endif (NOT ${rm_retval} EQUAL 0) + else (EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + endif (EXISTS "$ENV{DESTDIR}${file}") +endforeach(file)
--- a/Resources/CMake/ZlibConfiguration.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/CMake/ZlibConfiguration.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,39 +1,42 @@ -# This is the minizip distribution to create ZIP files -list(APPEND THIRD_PARTY_SOURCES - ${CMAKE_SOURCE_DIR}/Resources/minizip/ioapi.c - ${CMAKE_SOURCE_DIR}/Resources/minizip/zip.c - ) - -if (${STATIC_BUILD}) - SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7) - DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz" "${ZLIB_SOURCES_DIR}" "${ZLIB_PRELOADED}" "") - - include_directories( - ${ZLIB_SOURCES_DIR} - ) - - list(APPEND THIRD_PARTY_SOURCES - ${ZLIB_SOURCES_DIR}/adler32.c - ${ZLIB_SOURCES_DIR}/compress.c - ${ZLIB_SOURCES_DIR}/crc32.c - ${ZLIB_SOURCES_DIR}/deflate.c - ${ZLIB_SOURCES_DIR}/gzclose.c - ${ZLIB_SOURCES_DIR}/gzlib.c - ${ZLIB_SOURCES_DIR}/gzread.c - ${ZLIB_SOURCES_DIR}/gzwrite.c - ${ZLIB_SOURCES_DIR}/infback.c - ${ZLIB_SOURCES_DIR}/inffast.c - ${ZLIB_SOURCES_DIR}/inflate.c - ${ZLIB_SOURCES_DIR}/inftrees.c - ${ZLIB_SOURCES_DIR}/trees.c - ${ZLIB_SOURCES_DIR}/uncompr.c - ${ZLIB_SOURCES_DIR}/zutil.c - ) - -else() - include(FindZLIB) - include_directories(${ZLIB_INCLUDE_DIRS}) - link_libraries(${ZLIB_LIBRARIES}) -endif() - -source_group(ThirdParty\\ZLib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*) +# This is the minizip distribution to create ZIP files +list(APPEND THIRD_PARTY_SOURCES + ${ORTHANC_ROOT}/Resources/minizip/ioapi.c + ${ORTHANC_ROOT}/Resources/minizip/zip.c + ) + +if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB) + SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7) + DownloadPackage( + "60df6a37c56e7c1366cca812414f7b85" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz" + "${ZLIB_SOURCES_DIR}") + + include_directories( + ${ZLIB_SOURCES_DIR} + ) + + list(APPEND THIRD_PARTY_SOURCES + ${ZLIB_SOURCES_DIR}/adler32.c + ${ZLIB_SOURCES_DIR}/compress.c + ${ZLIB_SOURCES_DIR}/crc32.c + ${ZLIB_SOURCES_DIR}/deflate.c + ${ZLIB_SOURCES_DIR}/gzclose.c + ${ZLIB_SOURCES_DIR}/gzlib.c + ${ZLIB_SOURCES_DIR}/gzread.c + ${ZLIB_SOURCES_DIR}/gzwrite.c + ${ZLIB_SOURCES_DIR}/infback.c + ${ZLIB_SOURCES_DIR}/inffast.c + ${ZLIB_SOURCES_DIR}/inflate.c + ${ZLIB_SOURCES_DIR}/inftrees.c + ${ZLIB_SOURCES_DIR}/trees.c + ${ZLIB_SOURCES_DIR}/uncompr.c + ${ZLIB_SOURCES_DIR}/zutil.c + ) + +else() + include(FindZLIB) + include_directories(${ZLIB_INCLUDE_DIRS}) + link_libraries(${ZLIB_LIBRARIES}) +endif() + +source_group(ThirdParty\\ZLib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
--- a/Resources/Configuration.json Fri May 03 12:23:02 2013 +0200 +++ b/Resources/Configuration.json Tue Apr 22 16:47:21 2014 +0200 @@ -1,104 +1,164 @@ -{ - /** - * General configuration of Orthanc - **/ - - // The logical name of this instance of Orthanc. This one is - // displayed in Orthanc Explorer and at the URI "/system". - "Name" : "MyOrthanc", - - // Path to the directory that holds the heavyweight files - // (i.e. the raw DICOM instances) - "StorageDirectory" : "OrthancStorage", - - // Path to the directory that holds the SQLite index (if unset, - // the value of StorageDirectory is used). This index could be - // stored on a RAM-drive or a SSD device for performance reasons. - "IndexDirectory" : "OrthancStorage", - - // Enable the transparent compression of the DICOM instances - "StorageCompression" : false, - - // Maximum size of the storage in MB (a value of "0" indicates no - // limit on the storage size) - "MaximumStorageSize" : 0, - - // Maximum number of patients that can be stored at a given time - // in the storage (a value of "0" indicates no limit on the number - // of patients) - "MaximumPatientCount" : 0, - - // List of paths to the custom Lua scripts to load into this - // instance of Orthanc - "LuaScripts" : [ - ], - - - - /** - * Configuration of the HTTP server - **/ - - // HTTP port for the REST services and for the GUI - "HttpPort" : 8042, - - - - /** - * Configuration of the DICOM server - **/ - - // The DICOM Application Entity Title - "DicomAet" : "ORTHANC", - - // Check whether the called AET corresponds during a DICOM request - "DicomCheckCalledAet" : false, - - // The DICOM port - "DicomPort" : 4242, - - - - /** - * Security-related options for the HTTP server - **/ - - // Whether remote hosts can connect to the HTTP server - "RemoteAccessAllowed" : false, - - // Whether or not SSL is enabled - "SslEnabled" : false, - - // Path to the SSL certificate (meaningful only if SSL is enabled) - "SslCertificate" : "certificate.pem", - - // Whether or not the password protection is enabled - "AuthenticationEnabled" : false, - - // The list of the registered users. Because Orthanc uses HTTP - // Basic Authentication, the passwords are stored as plain text. - "RegisteredUsers" : { - "alice" : "alicePassword" - }, - - - - /** - * Network topology - **/ - - // The list of the known DICOM modalities - "DicomModalities" : { - /** - * Uncommenting the following line would enable Orthanc to - * connect to an instance of the "storescp" open-source DICOM - * store (shipped in the DCMTK distribution) started by the - * command line "storescp 2000". - **/ - // "sample" : [ "STORESCP", "localhost", 2000 ] - }, - - // The list of the known Orthanc peers (currently unused) - "OrthancPeers" : { - } -} +{ + /** + * General configuration of Orthanc + **/ + + // The logical name of this instance of Orthanc. This one is + // displayed in Orthanc Explorer and at the URI "/system". + "Name" : "MyOrthanc", + + // Path to the directory that holds the heavyweight files + // (i.e. the raw DICOM instances) + "StorageDirectory" : "OrthancStorage", + + // Path to the directory that holds the SQLite index (if unset, + // the value of StorageDirectory is used). This index could be + // stored on a RAM-drive or a SSD device for performance reasons. + "IndexDirectory" : "OrthancStorage", + + // Enable the transparent compression of the DICOM instances + "StorageCompression" : false, + + // Maximum size of the storage in MB (a value of "0" indicates no + // limit on the storage size) + "MaximumStorageSize" : 0, + + // Maximum number of patients that can be stored at a given time + // in the storage (a value of "0" indicates no limit on the number + // of patients) + "MaximumPatientCount" : 0, + + // List of paths to the custom Lua scripts to load into this + // instance of Orthanc + "LuaScripts" : [ + ], + + + + /** + * Configuration of the HTTP server + **/ + + // HTTP port for the REST services and for the GUI + "HttpPort" : 8042, + + + + /** + * Configuration of the DICOM server + **/ + + // The DICOM Application Entity Title + "DicomAet" : "ORTHANC", + + // Check whether the called AET corresponds during a DICOM request + "DicomCheckCalledAet" : false, + + // The DICOM port + "DicomPort" : 4242, + + + + /** + * Security-related options for the HTTP server + **/ + + // Whether remote hosts can connect to the HTTP server + "RemoteAccessAllowed" : false, + + // Whether or not SSL is enabled + "SslEnabled" : false, + + // Path to the SSL certificate (meaningful only if SSL is enabled) + "SslCertificate" : "certificate.pem", + + // Whether or not the password protection is enabled + "AuthenticationEnabled" : false, + + // The list of the registered users. Because Orthanc uses HTTP + // Basic Authentication, the passwords are stored as plain text. + "RegisteredUsers" : { + // "alice" : "alicePassword" + }, + + + + /** + * Network topology + **/ + + // The list of the known DICOM modalities + "DicomModalities" : { + /** + * Uncommenting the following line would enable Orthanc to + * connect to an instance of the "storescp" open-source DICOM + * store (shipped in the DCMTK distribution) started by the + * command line "storescp 2000". + **/ + // "sample" : [ "STORESCP", "localhost", 2000 ] + + /** + * A fourth parameter is available to enable patches for a + * specific PACS manufacturer. The allowed values are currently + * "Generic" (default value), "ClearCanvas", "MedInria" and + * "Dcm4Chee". This parameter is case-sensitive. + **/ + // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ] + }, + + // The list of the known Orthanc peers + "OrthancPeers" : { + /** + * Each line gives the base URL of an Orthanc peer, possibly + * followed by the username/password pair (if the password + * protection is enabled on the peer). + **/ + // "peer" : [ "http://localhost:8043/", "alice", "alicePassword" ] + // "peer2" : [ "http://localhost:8044/" ] + }, + + + + /** + * Advanced options + **/ + + // Dictionary of symbolic names for the user-defined metadata. Each + // entry must map a number between 1024 and 65535 to an unique + // string. + "UserMetadata" : { + // "Sample" : 1024 + }, + + // Dictionary of symbolic names for the user-defined types of + // attached files. Each entry must map a number between 1024 and + // 65535 to an unique string. + "UserContentType" : { + // "sample" : 1024 + }, + + // Number of seconds without receiving any instance before a + // patient, a study or a series is considered as stable. + "StableAge" : 60, + + // Enable the HTTP server. If this parameter is set to "false", + // Orthanc acts as a pure DICOM server. The REST API and Orthanc + // Explorer will not be available. + "HttpServerEnabled" : true, + + // Enable the DICOM server. If this parameter is set to "false", + // Orthanc acts as a pure REST server. It will not be possible to + // receive files or to do query/retrieve through the DICOM protocol. + "DicomServerEnabled" : true, + + // By default, Orthanc compares AET (Application Entity Titles) in a + // case-insensitive way. Setting this option to "true" will enable + // case-sensitive matching. + "StrictAetComparison" : false, + + // When the following option is "true", the MD5 of the DICOM files + // will be computed and stored in the Orthanc database. This + // information can be used to detect disk corruption, at the price + // of a small performance overhead. + "StoreMD5ForAttachments" : true +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/EclipseCodingStyle.xml Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<profiles version="1"> +<profile kind="CodeFormatterProfile" name="Orthanc" version="1"> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_in_empty_block" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.lineSplit" value="80"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_member_access" value="0"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_base_types" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.keep_else_statement_on_same_line" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_constructor_initializer_list" value="0"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_exception_specification" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_base_types" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_access_specifier" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_exception_specification" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_arguments" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_case" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.comment.min_distance_between_code_and_line_comment" value="1"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_expressions_in_array_initializer" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_declarator_list" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_bracket" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.tabulation.size" value="2"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_enumerator_list" value="49"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_declarator_list" value="16"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_empty_lines" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.put_empty_statement_on_new_line" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_method_declaration" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_arguments" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_base_clause" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_breaks_compare_to_cases" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.join_wrapped_lines" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_declarator_list" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_arguments_in_method_invocation" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.comment.never_indent_line_comments_on_first_column" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_brackets" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_bracket" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_parameters_in_method_declaration" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_type_declaration" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_assignment_operator" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_arguments" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_expression_list" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_parameters" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.continuation_indentation" value="2"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_expression_list" value="0"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_parameters" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_binary_operator" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression" value="34"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.format_guardian_clause_on_one_line" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_extra_spaces" value="0"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_compare_to_type_header" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_namespace_header" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_compact_if" value="16"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_assignment_operator" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_assignment" value="16"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression_chain" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_parameters" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_expression_list" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_exception_specification" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_binary_operator" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_identifier_in_function_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_base_clause_in_type_declaration" value="80"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_exception_specification" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_declaration_compare_to_template_header" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_body" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_binary_expression" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_block" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_arguments" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_parameters" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.tabulation.char" value="space"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_parameters" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_colon_in_constructor_initializer_list" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block_in_case" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.compact_else_if" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_template_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_base_clause" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.keep_then_statement_on_same_line" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_switch" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_overloaded_left_shift_chain" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.keep_imple_if_on_one_line" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indentation.size" value="2"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_namespace_declaration" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_arguments" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_array_initializer" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_namespace_declaration" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_bracket" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_while_in_do_statement" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_parameters" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_arguments" value="do not insert"/> +</profile> +</profiles>
--- a/Resources/EmbedResources.py Fri May 03 12:23:02 2013 +0200 +++ b/Resources/EmbedResources.py Tue Apr 22 16:47:21 2014 +0200 @@ -1,5 +1,5 @@ # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, +# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, # Belgium # # This program is free software: you can redistribute it and/or
--- a/Resources/LinuxStandardBaseToolchain.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/LinuxStandardBaseToolchain.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -11,14 +11,15 @@ IF (EXISTS ${LSB_PATH}/lib64) SET(LSB_TARGET_PROCESSOR "x86_64") + SET(LSB_LIBPATH ${LSB_PATH}/lib64-${LSB_TARGET_VERSION}) ELSEIF (EXISTS ${LSB_PATH}/lib) SET(LSB_TARGET_PROCESSOR "x86") + SET(LSB_LIBPATH ${LSB_PATH}/lib-${LSB_TARGET_VERSION}) ELSE() MESSAGE(FATAL_ERROR "Unable to detect the target processor architecture. Check the LSB_PATH environment variable.") ENDIF() SET(LSB_CPPPATH ${LSB_PATH}/include) -SET(LSB_LIBPATH ${LSB_PATH}/lib-${LSB_TARGET_VERSION}) SET(PKG_CONFIG_PATH ${LSB_LIBPATH}/pkgconfig/) # the name of the target operating system
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/MinGW64Toolchain.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,32 @@ +# http://sourceforge.net/apps/trac/mingw-w64/wiki/GeneralUsageInstructions + +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Windows) + +# Detect the prefix of the mingw-w64 compiler +execute_process( + COMMAND uname -p + OUTPUT_VARIABLE MINGW64_ARCHITECTURE + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + +if (${MINGW64_ARCHITECTURE} STREQUAL "x86_64") + set(MINGW64_PREFIX "x86_64") +else() + set(MINGW64_PREFIX "i686") +endif() + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER ${MINGW64_PREFIX}-w64-mingw32-gcc) +set(CMAKE_CXX_COMPILER ${MINGW64_PREFIX}-w64-mingw32-g++) +set(CMAKE_RC_COMPILER ${MINGW64_PREFIX}-w64-mingw32-windres) + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, search +# programs in the host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- a/Resources/MinGWToolchain.cmake Fri May 03 12:23:02 2013 +0200 +++ b/Resources/MinGWToolchain.cmake Tue Apr 22 16:47:21 2014 +0200 @@ -1,15 +1,15 @@ # http://www.vtk.org/Wiki/CmakeMingw # the name of the target operating system -SET(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_NAME Windows) # which compilers to use for C and C++ -SET(CMAKE_C_COMPILER i586-mingw32msvc-gcc) -SET(CMAKE_CXX_COMPILER i586-mingw32msvc-g++) -SET(CMAKE_RC_COMPILER i586-mingw32msvc-windres) +set(CMAKE_C_COMPILER i586-mingw32msvc-gcc) +set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++) +set(CMAKE_RC_COMPILER i586-mingw32msvc-windres) # here is the target environment located -SET(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc) +set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search
--- a/Resources/Orthanc.doxygen Fri May 03 12:23:02 2013 +0200 +++ b/Resources/Orthanc.doxygen Tue Apr 22 16:47:21 2014 +0200 @@ -1,4 +1,4 @@ -# Doxyfile 1.7.4 +# Doxyfile 1.8.1.2 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -22,8 +22,9 @@ DOXYFILE_ENCODING = UTF-8 -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded -# by quotes) that should identify the project. +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. PROJECT_NAME = Orthanc @@ -37,21 +38,21 @@ # for a project that appears at the top of each page and should give viewer # a quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = "Internal documentation of Orthanc" # With the PROJECT_LOGO tag one can specify an logo or icon that is # included in the documentation. The maximum height of the logo should not # exceed 55 pixels and the maximum width should not exceed 200 pixels. # Doxygen will copy the logo to the output directory. -PROJECT_LOGO = +PROJECT_LOGO = @CMAKE_SOURCE_DIR@/Resources/OrthancLogoDocumentation.png # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. -OUTPUT_DIRECTORY = +OUTPUT_DIRECTORY = OrthancInternalDocumentation # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output @@ -118,7 +119,7 @@ # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. -FULL_PATH_NAMES = YES +FULL_PATH_NAMES = NO # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is @@ -194,6 +195,13 @@ ALIASES = +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list @@ -232,6 +240,15 @@ EXTENSION_MAPPING = +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and @@ -283,6 +300,15 @@ INLINE_GROUPED_CLASSES = NO +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct @@ -305,10 +331,21 @@ # a logarithmic scale so increasing the size by one will roughly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols +# corresponding to a cache size of 2^16 = 65536 symbols. SYMBOL_CACHE_SIZE = 0 +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -325,6 +362,10 @@ EXTRACT_PRIVATE = NO +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. @@ -512,12 +553,6 @@ SHOW_USED_FILES = YES -# If the sources in your project are distributed over multiple directories -# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy -# in the documentation. The default is NO. - -SHOW_DIRECTORIES = NO - # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. @@ -543,13 +578,23 @@ # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. The create the layout file +# output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. # You can optionally specify a file name after the option, if omitted # DoxygenLayout.xml will be used as the name of the layout file. LAYOUT_FILE = +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- @@ -610,7 +655,9 @@ # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = @CMAKE_SOURCE_DIR@/Core @CMAKE_SOURCE_DIR@/OrthancServer @CMAKE_SOURCE_DIR@/OrthancCppClient +INPUT = @CMAKE_SOURCE_DIR@/Core \ + @CMAKE_SOURCE_DIR@/OrthancServer \ + @CMAKE_SOURCE_DIR@/OrthancCppClient # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is @@ -636,13 +683,15 @@ RECURSIVE = YES -# The EXCLUDE tag can be used to specify files and/or directories that should +# The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. EXCLUDE = -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. @@ -744,7 +793,7 @@ # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code -# fragments. Normal C and C++ comments will always remain visible. +# fragments. Normal C, C++ and Fortran comments will always remain visible. STRIP_CODE_COMMENTS = YES @@ -829,12 +878,13 @@ # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. Note that when using a custom header you are responsible -# for the proper inclusion of any scripts and style sheets that doxygen +# for the proper inclusion of any scripts and style sheets that doxygen # needs, which is dependent on the configuration options used. -# It is adviced to generate a default header using "doxygen -w html +# It is advised to generate a default header using "doxygen -w html # header.html footer.html stylesheet.css YourConfigFile" and then modify # that header. Note that the header is subject to change so you typically -# have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW! +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! HTML_HEADER = @@ -849,7 +899,7 @@ # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own -# stylesheet in the HTML output directory as well, or it will be erased! +# style sheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = @@ -863,7 +913,7 @@ HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. -# Doxygen will adjust the colors in the stylesheet and background images +# Doxygen will adjust the colors in the style sheet and background images # according to this color. Hue is specified as an angle on a colorwheel, # see http://en.wikipedia.org/wiki/Hue for more information. # For instance the value 0 represents red, 60 is yellow, 120 is green, @@ -893,20 +943,23 @@ HTML_TIMESTAMP = YES -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. - -HTML_ALIGN_MEMBERS = YES - # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the -# page has loaded. For this to work a browser that supports -# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox -# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). +# page has loaded. HTML_DYNAMIC_SECTIONS = NO +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). @@ -1058,19 +1111,14 @@ ECLIPSE_DOC_ID = org.doxygen.Project -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. DISABLE_INDEX = NO -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values -# (range [0,1..20]) that doxygen will group on one line in the generated HTML -# documentation. Note that a value of 0 will completely suppress the enum -# values from appearing in the overview section. - -ENUM_VALUES_PER_LINE = 4 - # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to YES, a side panel will be generated @@ -1078,13 +1126,17 @@ # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). # Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. GENERATE_TREEVIEW = NO -# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, -# and Class Hierarchy pages using a tree view instead of an ordered list. +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. -USE_INLINE_TREES = NO +ENUM_VALUES_PER_LINE = 1 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree @@ -1117,7 +1169,7 @@ # (see http://www.mathjax.org) which uses client side Javascript for the # rendering instead of using prerendered bitmaps. Use this if you do not # have LaTeX installed or if you want to formulas look prettier in the HTML -# output. When enabled you also need to install MathJax separately and +# output. When enabled you may also need to install MathJax separately and # configure the path to it using the MATHJAX_RELPATH option. USE_MATHJAX = NO @@ -1126,13 +1178,19 @@ # HTML output directory using the MATHJAX_RELPATH option. The destination # directory should contain the MathJax.js script. For instance, if the mathjax # directory is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the -# mathjax.org site, so you can quickly see the result without installing -# MathJax, but it is strongly recommended to install a local copy of MathJax -# before deployment. +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. MATHJAX_RELPATH = http://www.mathjax.org/mathjax +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + # When the SEARCHENGINE tag is enabled doxygen will generate a search box # for the HTML output. The underlying search engine uses javascript # and DHTML and should work on any modern browser. Note that when using @@ -1246,6 +1304,12 @@ LATEX_SOURCE_CODE = NO +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- @@ -1277,7 +1341,7 @@ RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's +# Load style sheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. @@ -1468,22 +1532,18 @@ # Configuration::additions related to external references #--------------------------------------------------------------------------- -# The TAGFILES option can be used to specify one or more tagfiles. -# Optionally an initial location of the external documentation -# can be added for each tagfile. The format of a tag file without -# this location is as follows: +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: # # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # # TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths or -# URLs. If a location is present for each tag, the installdox tool -# does not have to be run to correct the links. -# Note that each tag file must have a unique name -# (where the name does NOT include the path) -# If a tag file is not located in the directory in which doxygen -# is run, you must also specify the path to the tagfile here. +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. TAGFILES = @@ -1551,13 +1611,12 @@ DOT_NUM_THREADS = 0 -# By default doxygen will write a font called Helvetica to the output -# directory and reference it in all dot files that doxygen generates. -# When you want a differently looking font you can specify the font name -# using DOT_FONTNAME. You need to make sure dot is able to find the font, -# which can be done by putting it in a standard location or by setting the -# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory -# containing the font. +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. DOT_FONTNAME = Helvetica @@ -1566,17 +1625,16 @@ DOT_FONTSIZE = 10 -# By default doxygen will tell dot to use the output directory to look for the -# FreeSans.ttf font (which doxygen will put there itself). If you specify a -# different font using DOT_FONTNAME you can set the path where dot -# can find it using this tag. +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the -# the CLASS_DIAGRAMS tag to NO. +# CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES @@ -1598,6 +1656,15 @@ UML_LOOK = NO +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. @@ -1638,7 +1705,7 @@ GRAPHICAL_HIERARCHY = YES -# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. @@ -1647,10 +1714,21 @@ # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are svg, png, jpg, or gif. -# If left blank png will be used. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). DOT_IMAGE_FORMAT = png +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/OrthancClient.doxygen Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,1792 @@ +# Doxyfile 1.8.1.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = "Orthanc Client" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "Documentation of the client library of Orthanc" + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = @CMAKE_SOURCE_DIR@/Resources/OrthancLogoDocumentation.png + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = OrthancClientDocumentation + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = NO + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = YES + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command <command> <input-file>, where <command> is the value of +# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = @CMAKE_SOURCE_DIR@/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = OrthancClient::Internals + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command <filter> <input-file>, where <filter> +# is the value of the INPUT_FILTER tag, and <input-file> is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = doc + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# style sheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters"> +# Qt Help Project / Custom Filters</a>. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes"> +# Qt Help Project / Filter Attributes</a>. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 1 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-mingw64.patch Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,22 @@ +diff -urEb dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h +--- dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h 2010-12-17 11:50:30.000000000 +0100 ++++ dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h 2013-07-19 15:56:25.688996134 +0200 +@@ -196,7 +196,7 @@ + OFBool popen(const char *command, const char *modes) + { + if (file_) fclose(); +-#ifdef _WIN32 ++#if 0 + file_ = _popen(command, modes); + #else + file_ = :: popen(command, modes); +@@ -258,7 +258,7 @@ + { + if (popened_) + { +-#ifdef _WIN32 ++#if 0 + result = _pclose(file_); + #else + result = :: pclose(file_); +Only in dcmtk-3.6.0/ofstd/include/dcmtk/ofstd: offile.h~
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-mingw64.txt Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,1 @@ +diff -urEb dcmtk-3.6.0.orig/ dcmtk-3.6.0 > ../Resources/Patches/dcmtk-mingw64.patch
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/glog-utilities-lsb.diff Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,52 @@ +--- utilities.cc.orig 2012-01-12 09:40:21.000000000 +0100 ++++ utilities.cc 2013-09-23 17:37:35.033275313 +0200 +@@ -233,40 +233,7 @@ + } + + pid_t GetTID() { +- // On Linux and FreeBSD, we try to use gettid(). +-#if defined OS_LINUX || defined OS_FREEBSD || defined OS_MACOSX +-#ifndef __NR_gettid +-#ifdef OS_MACOSX +-#define __NR_gettid SYS_gettid +-#elif ! defined __i386__ +-#error "Must define __NR_gettid for non-x86 platforms" +-#else +-#define __NR_gettid 224 +-#endif +-#endif +- static bool lacks_gettid = false; +- if (!lacks_gettid) { +- pid_t tid = syscall(__NR_gettid); +- if (tid != -1) { +- return tid; +- } +- // Technically, this variable has to be volatile, but there is a small +- // performance penalty in accessing volatile variables and there should +- // not be any serious adverse effect if a thread does not immediately see +- // the value change to "true". +- lacks_gettid = true; +- } +-#endif // OS_LINUX || OS_FREEBSD +- +- // If gettid() could not be used, we use one of the following. +-#if defined OS_LINUX +- return getpid(); // Linux: getpid returns thread ID when gettid is absent +-#elif defined OS_WINDOWS || defined OS_CYGWIN +- return GetCurrentThreadId(); +-#else +- // If none of the techniques above worked, we use pthread_self(). + return (pid_t)(uintptr_t)pthread_self(); +-#endif + } + + const char* const_basename(const char* filepath) { +@@ -295,7 +262,7 @@ + g_my_user_name = "invalid-user"; + } + } +-REGISTER_MODULE_INITIALIZER(utilities, MyUserNameInitializer()); ++REGISTER_MODULE_INITIALIZER(utilities, MyUserNameInitializer()) + + #ifdef HAVE_STACKTRACE + void DumpStackTraceToString(string* stacktrace) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/openssl-lsb.diff Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,118 @@ +--- ui_openssl.c.orig 2013-09-24 15:06:54.264420779 +0200 ++++ ui_openssl.c 2013-09-24 14:22:43.512312998 +0200 +@@ -291,7 +291,7 @@ + static unsigned short channel = 0; + #else + #if !defined(OPENSSL_SYS_MSDOS) || defined(__DJGPP__) +-static TTY_STRUCT tty_orig,tty_new; ++//static TTY_STRUCT tty_orig,tty_new; + #endif + #endif + static FILE *tty_in, *tty_out; +@@ -475,106 +475,21 @@ + /* Internal functions to open, handle and close a channel to the console. */ + static int open_console(UI *ui) + { +- CRYPTO_w_lock(CRYPTO_LOCK_UI); +- is_a_tty = 1; +- +-#if defined(OPENSSL_SYS_MACINTOSH_CLASSIC) || defined(OPENSSL_SYS_VXWORKS) || defined(OPENSSL_SYS_NETWARE) || defined(OPENSSL_SYS_BEOS) +- tty_in=stdin; +- tty_out=stderr; +-#else +-# ifdef OPENSSL_SYS_MSDOS +-# define DEV_TTY "con" +-# else +-# define DEV_TTY "/dev/tty" +-# endif +- if ((tty_in=fopen(DEV_TTY,"r")) == NULL) +- tty_in=stdin; +- if ((tty_out=fopen(DEV_TTY,"w")) == NULL) +- tty_out=stderr; +-#endif +- +-#if defined(TTY_get) && !defined(OPENSSL_SYS_VMS) +- if (TTY_get(fileno(tty_in),&tty_orig) == -1) +- { +-#ifdef ENOTTY +- if (errno == ENOTTY) +- is_a_tty=0; +- else +-#endif +-#ifdef EINVAL +- /* Ariel Glenn ariel@columbia.edu reports that solaris +- * can return EINVAL instead. This should be ok */ +- if (errno == EINVAL) +- is_a_tty=0; +- else +-#endif +- return 0; +- } +-#endif +-#ifdef OPENSSL_SYS_VMS +- status = sys$assign(&terminal,&channel,0,0); +- if (status != SS$_NORMAL) +- return 0; +- status=sys$qiow(0,channel,IO$_SENSEMODE,&iosb,0,0,tty_orig,12,0,0,0,0); +- if ((status != SS$_NORMAL) || (iosb.iosb$w_value != SS$_NORMAL)) +- return 0; +-#endif + return 1; + } + + static int noecho_console(UI *ui) + { +-#ifdef TTY_FLAGS +- memcpy(&(tty_new),&(tty_orig),sizeof(tty_orig)); +- tty_new.TTY_FLAGS &= ~ECHO; +-#endif +- +-#if defined(TTY_set) && !defined(OPENSSL_SYS_VMS) +- if (is_a_tty && (TTY_set(fileno(tty_in),&tty_new) == -1)) +- return 0; +-#endif +-#ifdef OPENSSL_SYS_VMS +- tty_new[0] = tty_orig[0]; +- tty_new[1] = tty_orig[1] | TT$M_NOECHO; +- tty_new[2] = tty_orig[2]; +- status = sys$qiow(0,channel,IO$_SETMODE,&iosb,0,0,tty_new,12,0,0,0,0); +- if ((status != SS$_NORMAL) || (iosb.iosb$w_value != SS$_NORMAL)) +- return 0; +-#endif + return 1; + } + + static int echo_console(UI *ui) + { +-#if defined(TTY_set) && !defined(OPENSSL_SYS_VMS) +- memcpy(&(tty_new),&(tty_orig),sizeof(tty_orig)); +- tty_new.TTY_FLAGS |= ECHO; +-#endif +- +-#if defined(TTY_set) && !defined(OPENSSL_SYS_VMS) +- if (is_a_tty && (TTY_set(fileno(tty_in),&tty_new) == -1)) +- return 0; +-#endif +-#ifdef OPENSSL_SYS_VMS +- tty_new[0] = tty_orig[0]; +- tty_new[1] = tty_orig[1] & ~TT$M_NOECHO; +- tty_new[2] = tty_orig[2]; +- status = sys$qiow(0,channel,IO$_SETMODE,&iosb,0,0,tty_new,12,0,0,0,0); +- if ((status != SS$_NORMAL) || (iosb.iosb$w_value != SS$_NORMAL)) +- return 0; +-#endif + return 1; + } + + static int close_console(UI *ui) + { +- if (tty_in != stdin) fclose(tty_in); +- if (tty_out != stderr) fclose(tty_out); +-#ifdef OPENSSL_SYS_VMS +- status = sys$dassgn(channel); +-#endif +- CRYPTO_w_unlock(CRYPTO_LOCK_UI); +- + return 1; + } +
--- a/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py Fri May 03 12:23:02 2013 +0200 +++ b/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py Tue Apr 22 16:47:21 2014 +0200 @@ -1,5 +1,23 @@ #!/usr/bin/python +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + import os import sys import os.path @@ -21,16 +39,19 @@ URL = 'http://%s:%d/instances' % (sys.argv[1], int(sys.argv[2])) -success = 0 +success_count = 0 +total_file_count = 0 # This function will upload a single file to Orthanc through the REST API def UploadFile(path): - global success + global success_count + global total_file_count f = open(path, "rb") content = f.read() f.close() + total_file_count += 1 try: sys.stdout.write("Importing %s" % path) @@ -51,14 +72,14 @@ # not always work) # http://en.wikipedia.org/wiki/Basic_access_authentication headers['authorization'] = 'Basic ' + base64.b64encode(username + ':' + password) - + resp, content = h.request(URL, 'POST', body = content, headers = headers) if resp.status == 200: sys.stdout.write(" => success\n") - success += 1 + success_count += 1 else: sys.stdout.write(" => failure (Is it a DICOM file?)\n") @@ -74,6 +95,8 @@ for root, dirs, files in os.walk(sys.argv[3]): for f in files: UploadFile(os.path.join(root, f)) - -print("\nSummary: %d DICOM file(s) have been imported" % success) +if success_count == total_file_count: + print("\nSummary: all %d DICOM file(s) have been imported successfully" % success_count) +else: + print("\nSummary: %d out of %d files have been imported successfully as DICOM instances" % (success_count, total_file_count))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancClient/Basic/CMakeLists.txt Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 2.8) + +project(Basic) + +add_executable(Test main.cpp) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + # Linking with "pthread" is necessary, otherwise the software crashes + # http://sourceware.org/bugzilla/show_bug.cgi?id=10652#c17 + target_link_libraries(Test pthread dl) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancClient/Basic/main.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,78 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ + + +#include <iostream> +#include <orthanc/OrthancCppClient.h> + +int main() +{ + try + { + // The following explicit initialization is not required, except + // if you wish to specify the full path to the shared library + OrthancClient::Initialize(); + + // Display the content of the local Orthanc instance + OrthancClient::OrthancConnection orthanc("http://localhost:8042"); + + for (unsigned int i = 0; i < orthanc.GetPatientCount(); i++) + { + OrthancClient::Patient patient(orthanc.GetPatient(i)); + std::cout << "Patient: " << patient.GetId() << std::endl; + + for (unsigned int j = 0; j < patient.GetStudyCount(); j++) + { + OrthancClient::Study study(patient.GetStudy(j)); + std::cout << " Study: " << study.GetId() << std::endl; + + for (unsigned int k = 0; k < study.GetSeriesCount(); k++) + { + OrthancClient::Series series(study.GetSeries(k)); + std::cout << " Series: " << series.GetId() << std::endl; + + for (unsigned int l = 0; l < series.GetInstanceCount(); l++) + { + std::cout << " Instance: " << series.GetInstance(l).GetId() << std::endl; + + // Load and display some raw DICOM tag + series.GetInstance(l).LoadTagContent("0020-000d"); + std::cout << " SOP instance UID: " << series.GetInstance(l).GetLoadedTagContent() << std::endl; + } + } + } + } + + OrthancClient::Finalize(); + + return 0; + } + catch (OrthancClient::OrthancClientException& e) + { + std::cerr << "EXCEPTION: [" << e.What() << "]" << std::endl; + return -1; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancClient/Vtk/CMakeLists.txt Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 2.8) + +project(Vtk) + +find_package(VTK REQUIRED) +include(${VTK_USE_FILE}) + +add_executable(Test + main.cpp + ) + +# Linking with "pthread" is necessary, otherwise the software crashes +# http://sourceware.org/bugzilla/show_bug.cgi?id=10652#c17 +target_link_libraries(Test pthread dl) + +if(VTK_LIBRARIES) + target_link_libraries(Test ${VTK_LIBRARIES}) +else() + target_link_libraries(Test vtkHybrid vtkVolumeRendering) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancClient/Vtk/main.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,181 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ + + +#include <iostream> + +#include <vtkRenderWindow.h> +#include <vtkImageData.h> +#include <vtkPiecewiseFunction.h> +#include <vtkFixedPointVolumeRayCastMapper.h> +#include <vtkColorTransferFunction.h> +#include <vtkVolumeProperty.h> +#include <vtkRenderWindowInteractor.h> +#include <vtkRenderer.h> +#include <vtkSmartPointer.h> +#include <vtkOpenGLRenderer.h> +#include <vtkInteractorStyleTrackballCamera.h> + +#include <orthanc/OrthancCppClient.h> + + +void Display(OrthancClient::Series& series) +{ + /** + * Load the 3D image from Orthanc into VTK. + **/ + + vtkSmartPointer<vtkImageData> image = vtkSmartPointer<vtkImageData>::New(); + image->SetDimensions(series.GetWidth(), series.GetHeight(), series.GetInstanceCount()); + image->SetScalarType(VTK_SHORT); + image->AllocateScalars(); + + if (series.GetWidth() != 0 && + series.GetHeight() != 0 && + series.GetInstanceCount() != 0) + { + series.Load3DImage(image->GetScalarPointer(0, 0, 0), Orthanc::PixelFormat_SignedGrayscale16, + 2 * series.GetWidth(), 2 * series.GetHeight() * series.GetWidth()); + } + + image->SetSpacing(series.GetVoxelSizeX(), + series.GetVoxelSizeY(), + series.GetVoxelSizeZ()); + + + /** + * The following code is based on the VTK sample for MIP + * http://www.vtk.org/Wiki/VTK/Examples/Cxx/VolumeRendering/MinIntensityRendering + **/ + + // Create a transfer function mapping scalar value to opacity + double range[2]; + image->GetScalarRange(range); + + vtkSmartPointer<vtkPiecewiseFunction> opacityTransfer = + vtkSmartPointer<vtkPiecewiseFunction>::New(); + opacityTransfer->AddSegment(range[0], 0.0, range[1], 1.0); + + vtkSmartPointer<vtkColorTransferFunction> colorTransfer = + vtkSmartPointer<vtkColorTransferFunction>::New(); + colorTransfer->AddRGBPoint(0, 1.0, 1.0, 1.0); + colorTransfer->AddRGBPoint(range[1], 1.0, 1.0, 1.0); + + vtkSmartPointer<vtkVolumeProperty> property = + vtkSmartPointer<vtkVolumeProperty>::New(); + property->SetScalarOpacity(opacityTransfer); + property->SetColor(colorTransfer); + property->SetInterpolationTypeToLinear(); + + // Create a Maximum Intensity Projection rendering + vtkSmartPointer<vtkFixedPointVolumeRayCastMapper> mapper = + vtkSmartPointer<vtkFixedPointVolumeRayCastMapper>::New(); + mapper->SetBlendModeToMaximumIntensity(); + mapper->SetInput(image); + + vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New(); + volume->SetMapper(mapper); + volume->SetProperty(property); + + vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkOpenGLRenderer>::New(); + renderer->AddViewProp(volume); + renderer->SetBackground(0.1, 0.2, 0.3); // Background color dark blue + + vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = + vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New(); + + vtkSmartPointer<vtkRenderWindow> window = vtkSmartPointer<vtkRenderWindow>::New(); + window->AddRenderer(renderer); + + vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); + interactor->SetRenderWindow(window); + interactor->SetInteractorStyle(style); + interactor->Start(); +} + + +int main() +{ + try + { + // The following explicit initialization is not required, except + // if you wish to specify the full path to the shared library + OrthancClient::Initialize(); + + // Use the commented code below if you know the identifier of a + // series that corresponds to a 3D image. + + /* + { + OrthancClient::OrthancConnection orthanc("http://localhost:8042"); + OrthancClient::Series series(orthanc, "dc5ec3d9-6e1a7b2c-73a829f0-64c609f6-ef976a97"); + Display(series); + return 0; + } + */ + + + // Try and find a 3D image inside the local store + OrthancClient::OrthancConnection orthanc("http://localhost:8042"); + + for (unsigned int i = 0; i < orthanc.GetPatientCount(); i++) + { + OrthancClient::Patient patient(orthanc.GetPatient(i)); + std::cout << "Patient: " << patient.GetId() << std::endl; + + for (unsigned int j = 0; j < patient.GetStudyCount(); j++) + { + OrthancClient::Study study(patient.GetStudy(j)); + std::cout << " Study: " << study.GetId() << std::endl; + + for (unsigned int k = 0; k < study.GetSeriesCount(); k++) + { + OrthancClient::Series series(study.GetSeries(k)); + std::cout << " Series: " << series.GetId() << std::endl; + + if (series.Is3DImage()) + { + Display(series); + return 0; + } + else + { + std::cout << " => Not a 3D image..." << std::endl; + } + } + } + } + + std::cout << "Unable to find a 3D image in the local Orthanc store" << std::endl; + + return 0; + } + catch (OrthancClient::OrthancClientException& e) + { + std::cerr << "EXCEPTION: [" << e.What() << "]" << std::endl; + return -1; + } +}
--- a/Resources/Samples/Python/AnonymizeAllPatients.py Fri May 03 12:23:02 2013 +0200 +++ b/Resources/Samples/Python/AnonymizeAllPatients.py Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,24 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + URL = 'http://localhost:8042'
--- a/Resources/Samples/Python/ChangesLoop.py Fri May 03 12:23:02 2013 +0200 +++ b/Resources/Samples/Python/ChangesLoop.py Tue Apr 22 16:47:21 2014 +0200 @@ -1,5 +1,24 @@ #!/usr/bin/python +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + + import time import sys import RestToolbox
--- a/Resources/Samples/Python/DownloadAnonymized.py Fri May 03 12:23:02 2013 +0200 +++ b/Resources/Samples/Python/DownloadAnonymized.py Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,24 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + URL = 'http://localhost:8042'
--- a/Resources/Samples/Python/HighPerformanceAutoRouting.py Fri May 03 12:23:02 2013 +0200 +++ b/Resources/Samples/Python/HighPerformanceAutoRouting.py Tue Apr 22 16:47:21 2014 +0200 @@ -1,6 +1,24 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + URL = 'http://localhost:8042' TARGET = 'sample' @@ -99,6 +117,10 @@ for instance in instances: RestToolbox.DoDelete('%s/instances/%s' % (URL, instance)) + # Clear the log of the exported instances (to prevent the + # SQLite database from growing indefinitely) + RestToolbox.DoDelete('%s/exports' % URL) + end = time.time() print 'The packet of %d instances has been sent in %d seconds' % (len(instances), end - start)
--- a/Resources/Samples/Python/RestToolbox.py Fri May 03 12:23:02 2013 +0200 +++ b/Resources/Samples/Python/RestToolbox.py Tue Apr 22 16:47:21 2014 +0200 @@ -1,3 +1,21 @@ +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + import httplib2 import json from urllib import urlencode @@ -13,7 +31,7 @@ if _credentials != None: h.add_credentials(_credentials[0], _credentials[1]) -def DoGet(uri, data = {}): +def DoGet(uri, data = {}, interpretAsJson = True): d = '' if len(data.keys()) > 0: d = '?' + urlencode(data) @@ -23,6 +41,8 @@ resp, content = h.request(uri + d, 'GET') if not (resp.status in [ 200 ]): raise Exception(resp.status) + elif not interpretAsJson: + return content else: try: return json.loads(content)
--- a/Resources/Samples/RestApi/CMakeLists.txt Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -project(RestApiSample) - -include(ExternalProject) - -# Send the toolchain information to the Orthanc -if (CMAKE_TOOLCHAIN_FILE) - set(TOOLCHAIN "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}") -endif() - -ExternalProject_Add( - ORTHANC_CORE - - # We use the Orthanc-0.3.1 branch for this sample - DOWNLOAD_COMMAND hg clone https://code.google.com/p/orthanc/ -r Orthanc-0.3.1 - - # Optional step, to reuse the third-party downloads - PATCH_COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/../../../ThirdPartyDownloads ThirdPartyDownloads - - PREFIX ${CMAKE_BINARY_DIR}/Orthanc/ - UPDATE_COMMAND "" - SOURCE_DIR ${CMAKE_BINARY_DIR}/Orthanc/src/orthanc/ - CMAKE_COMMAND ${CMAKE_COMMAND} - CMAKE_ARGS -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DUSE_DYNAMIC_GOOGLE_LOG=OFF -DUSE_DYNAMIC_SQLITE=OFF -DONLY_CORE_LIBRARY=ON -DENABLE_SSL=OFF ${TOOLCHAIN} - BUILD_COMMAND $(MAKE) - INSTALL_COMMAND "" - BUILD_IN_SOURCE 0 - ) - -ExternalProject_Get_Property(ORTHANC_CORE source_dir) -include_directories(${source_dir}) - -ExternalProject_Get_Property(ORTHANC_CORE binary_dir) -link_directories(${binary_dir}) -include_directories(${binary_dir}/jsoncpp-src-0.5.0/include) -include_directories(${binary_dir}/glog-0.3.2/src) -include_directories(${binary_dir}/boost_1_49_0) - - -add_executable(RestApiSample - Sample.cpp - ) - -add_dependencies(RestApiSample ORTHANC_CORE) - -target_link_libraries(RestApiSample - # From Orthanc - CoreLibrary - GoogleLog - - # These two libraries are not necessary - #OpenSSL - #Curl - ) - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - target_link_libraries(RestApiSample pthread) -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - add_definitions(-DGOOGLE_GLOG_DLL_DECL=) - target_link_libraries(RestApiSample wsock32) -endif()
--- a/Resources/Samples/RestApi/Sample.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 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 <http://www.gnu.org/licenses/>. - **/ - - -#include <Core/HttpServer/MongooseServer.h> -#include <Core/RestApi/RestApi.h> -#include <Core/Toolbox.h> -#include <glog/logging.h> -#include <stdio.h> - - -/** - * This is a demo program that shows how to setup a REST server with - * the Orthanc Core API. Once the server is running, here are some - * sample command lines to interact with it: - * - * # curl http://localhost:8042 - * # curl 'http://localhost:8042?name=Hide' - * # curl http://localhost:8042 -X DELETE - * # curl http://localhost:8042 -X PUT -d "PutBody" - * # curl http://localhost:8042 -X POST -d "PostBody" - **/ - -static void GetRoot(Orthanc::RestApi::GetCall& call) -{ - std::string answer = "Hello world\n"; - answer += "Glad to meet you, Mr. " + call.GetArgument("name", "Nobody") + "\n"; - call.GetOutput().AnswerBuffer(answer, "text/plain"); -} - -static void DeleteRoot(Orthanc::RestApi::DeleteCall& call) -{ - call.GetOutput().AnswerBuffer("Hey, you have just deleted the server!\n", - "text/plain"); -} - -static void PostRoot(Orthanc::RestApi::PostCall& call) -{ - call.GetOutput().AnswerBuffer("I have received a POST with body: [" + - call.GetPostBody() + "]\n", "text/plain"); -} - -static void PutRoot(Orthanc::RestApi::PutCall& call) -{ - call.GetOutput().AnswerBuffer("I have received a PUT with body: [" + - call.GetPutBody() + "]\n", "text/plain"); -} - -int main() -{ - // Initialize the logging mechanism - google::InitGoogleLogging("Orthanc"); - FLAGS_logtostderr = true; - FLAGS_minloglevel = 0; // Use the verbose mode - FLAGS_v = 0; - - // Define the callbacks of the REST API - std::auto_ptr<Orthanc::RestApi> rest(new Orthanc::RestApi); - rest->Register("/", GetRoot); - rest->Register("/", PostRoot); - rest->Register("/", PutRoot); - rest->Register("/", DeleteRoot); - - // Setup the embedded HTTP server - Orthanc::MongooseServer httpServer; - httpServer.SetPortNumber(8042); // Use TCP port 8042 - httpServer.SetRemoteAccessAllowed(true); // Do not block remote requests - httpServer.RegisterHandler(rest.release()); // The REST API is the handler - - // Start the server and wait for the user to hit "Ctrl-C" - httpServer.Start(); - LOG(WARNING) << "REST server has started"; - Orthanc::Toolbox::ServerBarrier(); - LOG(WARNING) << "REST server has stopped"; - - return 0; -}
--- a/Resources/Samples/RestApiLinuxDynamic/AutoGeneratedCode.cmake Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -set(AUTOGENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/AUTOGENERATED") -set(AUTOGENERATED_SOURCES) - -file(MAKE_DIRECTORY ${AUTOGENERATED_DIR}) -include_directories(${AUTOGENERATED_DIR}) - -macro(EmbedResources) - # Convert a semicolon separated list to a whitespace separated string - set(SCRIPT_ARGUMENTS) - set(DEPENDENCIES) - set(IS_PATH_NAME false) - foreach(arg ${ARGN}) - if (${IS_PATH_NAME}) - list(APPEND SCRIPT_ARGUMENTS "${arg}") - list(APPEND DEPENDENCIES "${arg}") - set(IS_PATH_NAME false) - else() - list(APPEND SCRIPT_ARGUMENTS "${arg}") - set(IS_PATH_NAME true) - endif() - endforeach() - - set(TARGET_BASE "${AUTOGENERATED_DIR}/EmbeddedResources") - add_custom_command( - OUTPUT - "${TARGET_BASE}.h" - "${TARGET_BASE}.cpp" - COMMAND - python - "${ORTHANC_DIR}/Resources/EmbedResources.py" - "${AUTOGENERATED_DIR}/EmbeddedResources" - ${SCRIPT_ARGUMENTS} - DEPENDS - "${ORTHANC_DIR}/Resources/EmbedResources.py" - ${DEPENDENCIES} - ) - - list(APPEND AUTOGENERATED_SOURCES - "${AUTOGENERATED_DIR}/EmbeddedResources.cpp" - ) -endmacro()
--- a/Resources/Samples/RestApiLinuxDynamic/CMakeLists.txt Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -project(RestApiSample) - -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -Wall -std=c++0x") - -file(DOWNLOAD - http://mongoose.googlecode.com/files/mongoose-3.1.tgz - ${CMAKE_BINARY_DIR}/mongoose-3.1.tar.gz - EXPECTED_MD5 "e718fc287b4eb1bd523be3fa00942bb0" - SHOW_PROGRESS - ) - -file(DOWNLOAD - http://downloads.sourceforge.net/project/jsoncpp/jsoncpp/0.5.0/jsoncpp-src-0.5.0.tar.gz - ${CMAKE_BINARY_DIR}/jsoncpp-src-0.5.0.tar.gz - EXPECTED_MD5 "24482b67c1cb17aac1ed1814288a3a8f" - SHOW_PROGRESS - ) - -execute_process( - COMMAND hg clone -v -r Orthanc-0.4.0 https://code.google.com/p/orthanc/ Orthanc-0.4.0 - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - -execute_process( - COMMAND ${CMAKE_COMMAND} -E tar xvfz ${CMAKE_BINARY_DIR}/mongoose-3.1.tar.gz - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - -# Apply a patch to improve Mongoose shutdown -execute_process( - COMMAND patch mongoose.c ${ORTHANC_DIR}/Resources/Patches/mongoose-patch.diff - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/mongoose - ) - -execute_process( - COMMAND ${CMAKE_COMMAND} -E tar xvfz ${CMAKE_BINARY_DIR}/jsoncpp-src-0.5.0.tar.gz - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - -include(AutoGeneratedCode.cmake) - -add_definitions( - -DBOOST_HAS_FILESYSTEM_V3=1 - -DBOOST_HAS_SCHED_YIELD=1 - -DORTHANC_SSL_ENABLED=1 - -DORTHANC_STANDALONE=1 - -DORTHANC_STATIC=0 - ) - -set(ORTHANC_DIR ${CMAKE_BINARY_DIR}/Orthanc-0.4.0) -set(MONGOOSE_DIR ${CMAKE_BINARY_DIR}/mongoose) -set(JSONCPP_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.5.0) - -include_directories( - ${ORTHANC_DIR} - ${MONGOOSE_DIR} - ${JSONCPP_DIR}/include - ) - -link_libraries( - boost_date_time - boost_filesystem - boost_system - boost_thread - curl - dl - glog - png - pthread - sqlite3 - uuid - z - ) - -set(THIRD_PARTY_SOURCES - ${MONGOOSE_DIR}/mongoose.c - ${JSONCPP_DIR}/src/lib_json/json_reader.cpp - ${JSONCPP_DIR}/src/lib_json/json_value.cpp - ${JSONCPP_DIR}/src/lib_json/json_writer.cpp - ) - -file(GLOB ORTHANC_SOURCES - ${ORTHANC_DIR}/Core/*.cpp - ${ORTHANC_DIR}/Core/*/*.cpp - ${ORTHANC_DIR}/OrthancCppClient/*.cpp - ${ORTHANC_DIR}/Resources/base64/base64.cpp - ${ORTHANC_DIR}/Resources/md5/md5.c - ${ORTHANC_DIR}/Resources/sha1/sha1.cpp - ${ORTHANC_DIR}/Resources/minizip/zip.c - ${ORTHANC_DIR}/Resources/minizip/ioapi.c - ) - -list(REMOVE_ITEM ORTHANC_SOURCES ${ORTHANC_DIR}/OrthancCppClient/main.cpp) - -EmbedResources( - #ORTHANC_EXPLORER ${ORTHANC_DIR}/OrthancExplorer - ) - -add_executable(RestApiSample - Sample.cpp - ${ORTHANC_SOURCES} - ${AUTOGENERATED_SOURCES} - ${THIRD_PARTY_SOURCES} - )
--- a/Resources/Samples/RestApiLinuxDynamic/README.txt Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -This folder shows how it is possible to link against the "Core" and -"OrthancCppClient" libraries from the Orthanc distribution. It is -shown how a sample REST API can be created. - -This is the same sample than in folder "../RestApi", but with a -different build script and the use of C++ lambda functions. - -The build script of this folder does not rely on the default CMake -script from Orthanc. It dynamically links against the standard system -Linux libraries. This results in a simpler, standalone build -script. However, it will only work on Linux-based systems.
--- a/Resources/Samples/RestApiLinuxDynamic/Sample.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 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 <http://www.gnu.org/licenses/>. - **/ - - -#include <Core/HttpServer/MongooseServer.h> -#include <Core/RestApi/RestApi.h> -#include <Core/Toolbox.h> -#include <glog/logging.h> -#include <stdio.h> - - -/** - * This is a demo program that shows how to setup a REST server with - * the Orthanc Core API. Once the server is running, here are some - * sample command lines to interact with it: - * - * # curl http://localhost:8042 - * # curl 'http://localhost:8042?name=Hide' - * # curl http://localhost:8042 -X DELETE - * # curl http://localhost:8042 -X PUT -d "PutBody" - * # curl http://localhost:8042 -X POST -d "PostBody" - **/ - -int main() -{ - // Initialize the logging mechanism - google::InitGoogleLogging("Orthanc"); - FLAGS_logtostderr = true; - FLAGS_minloglevel = 0; // Use the verbose mode - FLAGS_v = 0; - - // Define the callbacks of the REST API using C++11 lambda functions - std::auto_ptr<Orthanc::RestApi> rest(new Orthanc::RestApi); - - rest->Register("/", [] (Orthanc::RestApi::GetCall& call) { - std::string answer = "Hello world\n"; - answer += "Glad to meet you, Mr. " + call.GetArgument("name", "Nobody") + "\n"; - call.GetOutput().AnswerBuffer(answer, "text/plain"); - }); - - rest->Register("/", [] (Orthanc::RestApi::DeleteCall& call) { - call.GetOutput().AnswerBuffer("Hey, you have just deleted the server!\n", - "text/plain"); - }); - - rest->Register("/", [] (Orthanc::RestApi::PostCall& call) { - call.GetOutput().AnswerBuffer("I have received a POST with body: [" + - call.GetPostBody() + "]\n", "text/plain"); - }); - - rest->Register("/", [] (Orthanc::RestApi::PutCall& call) { - call.GetOutput().AnswerBuffer("I have received a PUT with body: [" + - call.GetPutBody() + "]\n", "text/plain"); - }); - - // Setup the embedded HTTP server - Orthanc::MongooseServer httpServer; - httpServer.SetPortNumber(8042); // Use TCP port 8042 - httpServer.SetRemoteAccessAllowed(true); // Do not block remote requests - httpServer.RegisterHandler(rest.release()); // The REST API is the handler - - // Start the server and wait for the user to hit "Ctrl-C" - httpServer.Start(); - LOG(WARNING) << "REST server has started"; - Orthanc::Toolbox::ServerBarrier(); - LOG(WARNING) << "REST server has stopped"; - - return 0; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Tools/CMakeLists.txt Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 2.8) + +project(OrthancTools) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + # Linking with "pthread" is necessary, otherwise the software crashes + # http://sourceware.org/bugzilla/show_bug.cgi?id=10652#c17 + link_libraries(pthread dl) +endif() + +set(STATIC_BUILD ON) +set(ALLOW_DOWNLOADS ON) + +set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../..) + +include(CheckIncludeFiles) +include(CheckIncludeFileCXX) +include(CheckLibraryExists) +include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake) + +add_library(CommonLibraries + ${BOOST_SOURCES} + ${THIRD_PARTY_SOURCES} + ${ORTHANC_ROOT}/Core/OrthancException.cpp + ${ORTHANC_ROOT}/Core/Toolbox.cpp + ${ORTHANC_ROOT}/Core/Uuid.cpp + ${ORTHANC_ROOT}/Resources/md5/md5.c + ${ORTHANC_ROOT}/Resources/base64/base64.cpp + ) + +add_executable(RecoverCompressedFile + RecoverCompressedFile.cpp + ${ORTHANC_ROOT}/Core/Compression/BufferCompressor.cpp + ${ORTHANC_ROOT}/Core/Compression/ZlibCompressor.cpp + ) + +target_link_libraries(RecoverCompressedFile CommonLibraries)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Tools/RecoverCompressedFile.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,76 @@ +/** + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../../Core/Compression/ZlibCompressor.h" +#include "../../../Core/Toolbox.h" +#include "../../../Core/OrthancException.h" + +#include <stdio.h> + +int main(int argc, const char* argv[]) +{ + if (argc != 2 && argc != 3) + { + fprintf(stderr, "Maintenance tool to recover a DICOM file that was compressed by Orthanc.\n\n"); + fprintf(stderr, "Usage: %s <input> [output]\n", argv[0]); + fprintf(stderr, "If \"output\" is not given, the data will be output to stdout\n"); + return -1; + } + + try + { + fprintf(stderr, "Reading the file into memory...\n"); + fflush(stderr); + + std::string content; + Orthanc::Toolbox::ReadFile(content, argv[1]); + + fprintf(stderr, "Decompressing the content of the file...\n"); + fflush(stderr); + + Orthanc::ZlibCompressor compressor; + std::string uncompressed; + compressor.Uncompress(uncompressed, content); + + fprintf(stderr, "Writing the uncompressed data...\n"); + fflush(stderr); + + if (argc == 3) + { + Orthanc::Toolbox::WriteFile(uncompressed, argv[2]); + } + else + { + if (uncompressed.size() > 0) + { + fwrite(&uncompressed[0], uncompressed.size(), 1, stdout); + } + } + + fprintf(stderr, "Done!\n"); + } + catch (Orthanc::OrthancException& e) + { + fprintf(stderr, "Error: %s\n", e.What()); + return -1; + } + + return 0; +}
--- a/Resources/Toolbox.lua Fri May 03 12:23:02 2013 +0200 +++ b/Resources/Toolbox.lua Tue Apr 22 16:47:21 2014 +0200 @@ -17,3 +17,5 @@ end return l end + +print('Lua toolbox installed')
--- a/Resources/sha1/Makefile Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -# -# Makefile -# -# Copyright (C) 1998, 2009 -# Paul E. Jones <paulej@packetizer.com> -# All Rights Reserved. -# -############################################################################# -# $Id: Makefile 12 2009-06-22 19:34:25Z paulej $ -############################################################################# -# -# Description: -# This is a makefile for UNIX to build the programs sha, shacmp, and -# shatest -# -# - -CC = g++ - -CFLAGS = -c -O2 -Wall -D_FILE_OFFSET_BITS=64 - -LIBS = - -OBJS = sha1.o - -all: sha shacmp shatest - -sha: sha.o $(OBJS) - $(CC) -o $@ sha.o $(OBJS) $(LIBS) - -shacmp: shacmp.o $(OBJS) - $(CC) -o $@ shacmp.o $(OBJS) $(LIBS) - -shatest: shatest.o $(OBJS) - $(CC) -o $@ shatest.o $(OBJS) $(LIBS) - -%.o: %.cpp - $(CC) $(CFLAGS) -o $@ $< - -clean: - $(RM) *.o sha shacmp shatest
--- a/Resources/sha1/Makefile.nt Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -# -# Makefile.nt -# -# Copyright (C) 1998, 2009 -# Paul E. Jones <paulej@packetizer.com> -# All Rights Reserved. -# -############################################################################# -# $Id: Makefile.nt 13 2009-06-22 20:20:32Z paulej $ -############################################################################# -# -# Description: -# This is a makefile for Win32 to build the programs sha, shacmp, and -# shatest -# -# Portability Issues: -# Designed to work with Visual C++ -# -# - -.silent: - -!include <win32.mak> - -RM = del /q - -LIBS = $(conlibs) setargv.obj - -CFLAGS = -D _CRT_SECURE_NO_WARNINGS /EHsc /O2 /W3 - -OBJS = sha1.obj - -all: sha.exe shacmp.exe shatest.exe - -sha.exe: sha.obj $(OBJS) - $(link) $(conflags) -out:$@ sha.obj $(OBJS) $(LIBS) - -shacmp.exe: shacmp.obj $(OBJS) - $(link) $(conflags) -out:$@ shacmp.obj $(OBJS) $(LIBS) - -shatest.exe: shatest.obj $(OBJS) - $(link) $(conflags) -out:$@ shatest.obj $(OBJS) $(LIBS) - -.cpp.obj: - $(cc) $(CFLAGS) $(cflags) $(cvars) $< - -clean: - $(RM) *.obj sha.exe shacmp.exe shatest.exe
--- a/Resources/sha1/license.txt Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -Copyright (C) 1998, 2009 -Paul E. Jones <paulej@packetizer.com> - -Freeware Public License (FPL) - -This software is licensed as "freeware." Permission to distribute -this software in source and binary forms, including incorporation -into other products, is hereby granted without a fee. THIS SOFTWARE -IS PROVIDED 'AS IS' AND WITHOUT ANY EXPRESSED OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHOR SHALL NOT BE HELD -LIABLE FOR ANY DAMAGES RESULTING FROM THE USE OF THIS SOFTWARE, EITHER -DIRECTLY OR INDIRECTLY, INCLUDING, BUT NOT LIMITED TO, LOSS OF DATA -OR DATA BEING RENDERED INACCURATE.
--- a/Resources/sha1/sha.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,176 +0,0 @@ -/* - * sha.cpp - * - * Copyright (C) 1998, 2009 - * Paul E. Jones <paulej@packetizer.com> - * All Rights Reserved - * - ***************************************************************************** - * $Id: sha.cpp 13 2009-06-22 20:20:32Z paulej $ - ***************************************************************************** - * - * Description: - * This utility will display the message digest (fingerprint) for - * the specified file(s). - * - * Portability Issues: - * None. - */ - -#include <stdio.h> -#include <string.h> -#ifdef WIN32 -#include <io.h> -#endif -#include <fcntl.h> -#include "sha1.h" - -/* - * Function prototype - */ -void usage(); - - -/* - * main - * - * Description: - * This is the entry point for the program - * - * Parameters: - * argc: [in] - * This is the count of arguments in the argv array - * argv: [in] - * This is an array of filenames for which to compute message digests - * - * Returns: - * Nothing. - * - * Comments: - * - */ -int main(int argc, char *argv[]) -{ - SHA1 sha; // SHA-1 class - FILE *fp; // File pointer for reading files - char c; // Character read from file - unsigned message_digest[5]; // Message digest from "sha" - int i; // Counter - bool reading_stdin; // Are we reading standard in? - bool read_stdin = false; // Have we read stdin? - - /* - * Check the program arguments and print usage information if -? - * or --help is passed as the first argument. - */ - if (argc > 1 && (!strcmp(argv[1],"-?") || !strcmp(argv[1],"--help"))) - { - usage(); - return 1; - } - - /* - * For each filename passed in on the command line, calculate the - * SHA-1 value and display it. - */ - for(i = 0; i < argc; i++) - { - /* - * We start the counter at 0 to guarantee entry into the for loop. - * So if 'i' is zero, we will increment it now. If there is no - * argv[1], we will use STDIN below. - */ - if (i == 0) - { - i++; - } - - if (argc == 1 || !strcmp(argv[i],"-")) - { -#ifdef WIN32 - _setmode(_fileno(stdin), _O_BINARY); -#endif - fp = stdin; - reading_stdin = true; - } - else - { - if (!(fp = fopen(argv[i],"rb"))) - { - fprintf(stderr, "sha: unable to open file %s\n", argv[i]); - return 2; - } - reading_stdin = false; - } - - /* - * We do not want to read STDIN multiple times - */ - if (reading_stdin) - { - if (read_stdin) - { - continue; - } - - read_stdin = true; - } - - /* - * Reset the SHA1 object and process input - */ - sha.Reset(); - - c = fgetc(fp); - while(!feof(fp)) - { - sha.Input(c); - c = fgetc(fp); - } - - if (!reading_stdin) - { - fclose(fp); - } - - if (!sha.Result(message_digest)) - { - fprintf(stderr,"sha: could not compute message digest for %s\n", - reading_stdin?"STDIN":argv[i]); - } - else - { - printf( "%08X %08X %08X %08X %08X - %s\n", - message_digest[0], - message_digest[1], - message_digest[2], - message_digest[3], - message_digest[4], - reading_stdin?"STDIN":argv[i]); - } - } - - return 0; -} - -/* - * usage - * - * Description: - * This function will display program usage information to the user. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void usage() -{ - printf("usage: sha <file> [<file> ...]\n"); - printf("\tThis program will display the message digest (fingerprint)\n"); - printf("\tfor files using the Secure Hashing Algorithm (SHA-1).\n"); -}
--- a/Resources/sha1/sha1.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,589 +0,0 @@ -/* - * sha1.cpp - * - * Copyright (C) 1998, 2009 - * Paul E. Jones <paulej@packetizer.com> - * All Rights Reserved. - * - ***************************************************************************** - * $Id: sha1.cpp 12 2009-06-22 19:34:25Z paulej $ - ***************************************************************************** - * - * Description: - * This class implements the Secure Hashing Standard as defined - * in FIPS PUB 180-1 published April 17, 1995. - * - * The Secure Hashing Standard, which uses the Secure Hashing - * Algorithm (SHA), produces a 160-bit message digest for a - * given data stream. In theory, it is highly improbable that - * two messages will produce the same message digest. Therefore, - * this algorithm can serve as a means of providing a "fingerprint" - * for a message. - * - * Portability Issues: - * SHA-1 is defined in terms of 32-bit "words". This code was - * written with the expectation that the processor has at least - * a 32-bit machine word size. If the machine word size is larger, - * the code should still function properly. One caveat to that - * is that the input functions taking characters and character arrays - * assume that only 8 bits of information are stored in each character. - * - * Caveats: - * SHA-1 is designed to work with messages less than 2^64 bits long. - * Although SHA-1 allows a message digest to be generated for - * messages of any number of bits less than 2^64, this implementation - * only works with messages with a length that is a multiple of 8 - * bits. - * - */ - - -#include "sha1.h" - -/* - * SHA1 - * - * Description: - * This is the constructor for the sha1 class. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -SHA1::SHA1() -{ - Reset(); -} - -/* - * ~SHA1 - * - * Description: - * This is the destructor for the sha1 class - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -SHA1::~SHA1() -{ - // The destructor does nothing -} - -/* - * Reset - * - * Description: - * This function will initialize the sha1 class member variables - * in preparation for computing a new message digest. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::Reset() -{ - Length_Low = 0; - Length_High = 0; - Message_Block_Index = 0; - - H[0] = 0x67452301; - H[1] = 0xEFCDAB89; - H[2] = 0x98BADCFE; - H[3] = 0x10325476; - H[4] = 0xC3D2E1F0; - - Computed = false; - Corrupted = false; -} - -/* - * Result - * - * Description: - * This function will return the 160-bit message digest into the - * array provided. - * - * Parameters: - * message_digest_array: [out] - * This is an array of five unsigned integers which will be filled - * with the message digest that has been computed. - * - * Returns: - * True if successful, false if it failed. - * - * Comments: - * - */ -bool SHA1::Result(unsigned *message_digest_array) -{ - int i; // Counter - - if (Corrupted) - { - return false; - } - - if (!Computed) - { - PadMessage(); - Computed = true; - } - - for(i = 0; i < 5; i++) - { - message_digest_array[i] = H[i]; - } - - return true; -} - -/* - * Input - * - * Description: - * This function accepts an array of octets as the next portion of - * the message. - * - * Parameters: - * message_array: [in] - * An array of characters representing the next portion of the - * message. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::Input( const unsigned char *message_array, - unsigned length) -{ - if (!length) - { - return; - } - - if (Computed || Corrupted) - { - Corrupted = true; - return; - } - - while(length-- && !Corrupted) - { - Message_Block[Message_Block_Index++] = (*message_array & 0xFF); - - Length_Low += 8; - Length_Low &= 0xFFFFFFFF; // Force it to 32 bits - if (Length_Low == 0) - { - Length_High++; - Length_High &= 0xFFFFFFFF; // Force it to 32 bits - if (Length_High == 0) - { - Corrupted = true; // Message is too long - } - } - - if (Message_Block_Index == 64) - { - ProcessMessageBlock(); - } - - message_array++; - } -} - -/* - * Input - * - * Description: - * This function accepts an array of octets as the next portion of - * the message. - * - * Parameters: - * message_array: [in] - * An array of characters representing the next portion of the - * message. - * length: [in] - * The length of the message_array - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::Input( const char *message_array, - unsigned length) -{ - Input((unsigned char *) message_array, length); -} - -/* - * Input - * - * Description: - * This function accepts a single octets as the next message element. - * - * Parameters: - * message_element: [in] - * The next octet in the message. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::Input(unsigned char message_element) -{ - Input(&message_element, 1); -} - -/* - * Input - * - * Description: - * This function accepts a single octet as the next message element. - * - * Parameters: - * message_element: [in] - * The next octet in the message. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::Input(char message_element) -{ - Input((unsigned char *) &message_element, 1); -} - -/* - * operator<< - * - * Description: - * This operator makes it convenient to provide character strings to - * the SHA1 object for processing. - * - * Parameters: - * message_array: [in] - * The character array to take as input. - * - * Returns: - * A reference to the SHA1 object. - * - * Comments: - * Each character is assumed to hold 8 bits of information. - * - */ -SHA1& SHA1::operator<<(const char *message_array) -{ - const char *p = message_array; - - while(*p) - { - Input(*p); - p++; - } - - return *this; -} - -/* - * operator<< - * - * Description: - * This operator makes it convenient to provide character strings to - * the SHA1 object for processing. - * - * Parameters: - * message_array: [in] - * The character array to take as input. - * - * Returns: - * A reference to the SHA1 object. - * - * Comments: - * Each character is assumed to hold 8 bits of information. - * - */ -SHA1& SHA1::operator<<(const unsigned char *message_array) -{ - const unsigned char *p = message_array; - - while(*p) - { - Input(*p); - p++; - } - - return *this; -} - -/* - * operator<< - * - * Description: - * This function provides the next octet in the message. - * - * Parameters: - * message_element: [in] - * The next octet in the message - * - * Returns: - * A reference to the SHA1 object. - * - * Comments: - * The character is assumed to hold 8 bits of information. - * - */ -SHA1& SHA1::operator<<(const char message_element) -{ - Input((unsigned char *) &message_element, 1); - - return *this; -} - -/* - * operator<< - * - * Description: - * This function provides the next octet in the message. - * - * Parameters: - * message_element: [in] - * The next octet in the message - * - * Returns: - * A reference to the SHA1 object. - * - * Comments: - * The character is assumed to hold 8 bits of information. - * - */ -SHA1& SHA1::operator<<(const unsigned char message_element) -{ - Input(&message_element, 1); - - return *this; -} - -/* - * ProcessMessageBlock - * - * Description: - * This function will process the next 512 bits of the message - * stored in the Message_Block array. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * Many of the variable names in this function, especially the single - * character names, were used because those were the names used - * in the publication. - * - */ -void SHA1::ProcessMessageBlock() -{ - const unsigned K[] = { // Constants defined for SHA-1 - 0x5A827999, - 0x6ED9EBA1, - 0x8F1BBCDC, - 0xCA62C1D6 - }; - int t; // Loop counter - unsigned temp; // Temporary word value - unsigned W[80]; // Word sequence - unsigned A, B, C, D, E; // Word buffers - - /* - * Initialize the first 16 words in the array W - */ - for(t = 0; t < 16; t++) - { - W[t] = ((unsigned) Message_Block[t * 4]) << 24; - W[t] |= ((unsigned) Message_Block[t * 4 + 1]) << 16; - W[t] |= ((unsigned) Message_Block[t * 4 + 2]) << 8; - W[t] |= ((unsigned) Message_Block[t * 4 + 3]); - } - - for(t = 16; t < 80; t++) - { - W[t] = CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); - } - - A = H[0]; - B = H[1]; - C = H[2]; - D = H[3]; - E = H[4]; - - for(t = 0; t < 20; t++) - { - temp = CircularShift(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - for(t = 20; t < 40; t++) - { - temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - for(t = 40; t < 60; t++) - { - temp = CircularShift(5,A) + - ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - for(t = 60; t < 80; t++) - { - temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - H[0] = (H[0] + A) & 0xFFFFFFFF; - H[1] = (H[1] + B) & 0xFFFFFFFF; - H[2] = (H[2] + C) & 0xFFFFFFFF; - H[3] = (H[3] + D) & 0xFFFFFFFF; - H[4] = (H[4] + E) & 0xFFFFFFFF; - - Message_Block_Index = 0; -} - -/* - * PadMessage - * - * Description: - * According to the standard, the message must be padded to an even - * 512 bits. The first padding bit must be a '1'. The last 64 bits - * represent the length of the original message. All bits in between - * should be 0. This function will pad the message according to those - * rules by filling the message_block array accordingly. It will also - * call ProcessMessageBlock() appropriately. When it returns, it - * can be assumed that the message digest has been computed. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::PadMessage() -{ - /* - * Check to see if the current message block is too small to hold - * the initial padding bits and length. If so, we will pad the - * block, process it, and then continue padding into a second block. - */ - if (Message_Block_Index > 55) - { - Message_Block[Message_Block_Index++] = 0x80; - while(Message_Block_Index < 64) - { - Message_Block[Message_Block_Index++] = 0; - } - - ProcessMessageBlock(); - - while(Message_Block_Index < 56) - { - Message_Block[Message_Block_Index++] = 0; - } - } - else - { - Message_Block[Message_Block_Index++] = 0x80; - while(Message_Block_Index < 56) - { - Message_Block[Message_Block_Index++] = 0; - } - - } - - /* - * Store the message length as the last 8 octets - */ - Message_Block[56] = (Length_High >> 24) & 0xFF; - Message_Block[57] = (Length_High >> 16) & 0xFF; - Message_Block[58] = (Length_High >> 8) & 0xFF; - Message_Block[59] = (Length_High) & 0xFF; - Message_Block[60] = (Length_Low >> 24) & 0xFF; - Message_Block[61] = (Length_Low >> 16) & 0xFF; - Message_Block[62] = (Length_Low >> 8) & 0xFF; - Message_Block[63] = (Length_Low) & 0xFF; - - ProcessMessageBlock(); -} - - -/* - * CircularShift - * - * Description: - * This member function will perform a circular shifting operation. - * - * Parameters: - * bits: [in] - * The number of bits to shift (1-31) - * word: [in] - * The value to shift (assumes a 32-bit integer) - * - * Returns: - * The shifted value. - * - * Comments: - * - */ -unsigned SHA1::CircularShift(int bits, unsigned word) -{ - return ((word << bits) & 0xFFFFFFFF) | ((word & 0xFFFFFFFF) >> (32-bits)); -}
--- a/Resources/sha1/sha1.h Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/* - * sha1.h - * - * Copyright (C) 1998, 2009 - * Paul E. Jones <paulej@packetizer.com> - * All Rights Reserved. - * - ***************************************************************************** - * $Id: sha1.h 12 2009-06-22 19:34:25Z paulej $ - ***************************************************************************** - * - * Description: - * This class implements the Secure Hashing Standard as defined - * in FIPS PUB 180-1 published April 17, 1995. - * - * Many of the variable names in this class, especially the single - * character names, were used because those were the names used - * in the publication. - * - * Please read the file sha1.cpp for more information. - * - */ - -#ifndef _SHA1_H_ -#define _SHA1_H_ - -class SHA1 -{ - - public: - - SHA1(); - virtual ~SHA1(); - - /* - * Re-initialize the class - */ - void Reset(); - - /* - * Returns the message digest - */ - bool Result(unsigned *message_digest_array); - - /* - * Provide input to SHA1 - */ - void Input( const unsigned char *message_array, - unsigned length); - void Input( const char *message_array, - unsigned length); - void Input(unsigned char message_element); - void Input(char message_element); - SHA1& operator<<(const char *message_array); - SHA1& operator<<(const unsigned char *message_array); - SHA1& operator<<(const char message_element); - SHA1& operator<<(const unsigned char message_element); - - private: - - /* - * Process the next 512 bits of the message - */ - void ProcessMessageBlock(); - - /* - * Pads the current message block to 512 bits - */ - void PadMessage(); - - /* - * Performs a circular left shift operation - */ - inline unsigned CircularShift(int bits, unsigned word); - - unsigned H[5]; // Message digest buffers - - unsigned Length_Low; // Message length in bits - unsigned Length_High; // Message length in bits - - unsigned char Message_Block[64]; // 512-bit message blocks - int Message_Block_Index; // Index into message block array - - bool Computed; // Is the digest computed? - bool Corrupted; // Is the message digest corruped? - -}; - -#endif
--- a/Resources/sha1/shacmp.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +0,0 @@ -/* - * shacmp.cpp - * - * Copyright (C) 1998, 2009 - * Paul E. Jones <paulej@packetizer.com> - * All Rights Reserved - * - ***************************************************************************** - * $Id: shacmp.cpp 12 2009-06-22 19:34:25Z paulej $ - ***************************************************************************** - * - * Description: - * This utility will compare two files by producing a message digest - * for each file using the Secure Hashing Algorithm and comparing - * the message digests. This function will return 0 if they - * compare or 1 if they do not or if there is an error. - * Errors result in a return code higher than 1. - * - * Portability Issues: - * none. - * - */ - -#include <stdio.h> -#include <string.h> -#include "sha1.h" - -/* - * Return codes - */ -#define SHA1_COMPARE 0 -#define SHA1_NO_COMPARE 1 -#define SHA1_USAGE_ERROR 2 -#define SHA1_FILE_ERROR 3 - -/* - * Function prototype - */ -void usage(); - -/* - * main - * - * Description: - * This is the entry point for the program - * - * Parameters: - * argc: [in] - * This is the count of arguments in the argv array - * argv: [in] - * This is an array of filenames for which to compute message digests - * - * Returns: - * Nothing. - * - * Comments: - * - */ -int main(int argc, char *argv[]) -{ - SHA1 sha; // SHA-1 class - FILE *fp; // File pointer for reading files - char c; // Character read from file - unsigned message_digest[2][5]; // Message digest for files - int i; // Counter - bool message_match; // Message digest match flag - int returncode; - - /* - * If we have two arguments, we will assume they are filenames. If - * we do not have to arguments, call usage() and exit. - */ - if (argc != 3) - { - usage(); - return SHA1_USAGE_ERROR; - } - - /* - * Get the message digests for each file - */ - for(i = 1; i <= 2; i++) - { - sha.Reset(); - - if (!(fp = fopen(argv[i],"rb"))) - { - fprintf(stderr, "sha: unable to open file %s\n", argv[i]); - return SHA1_FILE_ERROR; - } - - c = fgetc(fp); - while(!feof(fp)) - { - sha.Input(c); - c = fgetc(fp); - } - - fclose(fp); - - if (!sha.Result(message_digest[i-1])) - { - fprintf(stderr,"shacmp: could not compute message digest for %s\n", - argv[i]); - return SHA1_FILE_ERROR; - } - } - - /* - * Compare the message digest values - */ - message_match = true; - for(i = 0; i < 5; i++) - { - if (message_digest[0][i] != message_digest[1][i]) - { - message_match = false; - break; - } - } - - if (message_match) - { - printf("Fingerprints match:\n"); - returncode = SHA1_COMPARE; - } - else - { - printf("Fingerprints do not match:\n"); - returncode = SHA1_NO_COMPARE; - } - - printf( "\t%08X %08X %08X %08X %08X\n", - message_digest[0][0], - message_digest[0][1], - message_digest[0][2], - message_digest[0][3], - message_digest[0][4]); - printf( "\t%08X %08X %08X %08X %08X\n", - message_digest[1][0], - message_digest[1][1], - message_digest[1][2], - message_digest[1][3], - message_digest[1][4]); - - return returncode; -} - -/* - * usage - * - * Description: - * This function will display program usage information to the user. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void usage() -{ - printf("usage: shacmp <file> <file>\n"); - printf("\tThis program will compare the message digests (fingerprints)\n"); - printf("\tfor two files using the Secure Hashing Algorithm (SHA-1).\n"); -}
--- a/Resources/sha1/shatest.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ -/* - * shatest.cpp - * - * Copyright (C) 1998, 2009 - * Paul E. Jones <paulej@packetizer.com> - * All Rights Reserved - * - ***************************************************************************** - * $Id: shatest.cpp 12 2009-06-22 19:34:25Z paulej $ - ***************************************************************************** - * - * Description: - * This file will exercise the SHA1 class and perform the three - * tests documented in FIPS PUB 180-1. - * - * Portability Issues: - * None. - * - */ - -#include <iostream> -#include "sha1.h" - -using namespace std; - -/* - * Define patterns for testing - */ -#define TESTA "abc" -#define TESTB "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" - -/* - * Function prototype - */ -void DisplayMessageDigest(unsigned *message_digest); - -/* - * main - * - * Description: - * This is the entry point for the program - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -int main() -{ - SHA1 sha; - unsigned message_digest[5]; - - /* - * Perform test A - */ - cout << endl << "Test A: 'abc'" << endl; - - sha.Reset(); - sha << TESTA; - - if (!sha.Result(message_digest)) - { - cerr << "ERROR-- could not compute message digest" << endl; - } - else - { - DisplayMessageDigest(message_digest); - cout << "Should match:" << endl; - cout << '\t' << "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D" << endl; - } - - /* - * Perform test B - */ - cout << endl << "Test B: " << TESTB << endl; - - sha.Reset(); - sha << TESTB; - - if (!sha.Result(message_digest)) - { - cerr << "ERROR-- could not compute message digest" << endl; - } - else - { - DisplayMessageDigest(message_digest); - cout << "Should match:" << endl; - cout << '\t' << "84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1" << endl; - } - - /* - * Perform test C - */ - cout << endl << "Test C: One million 'a' characters" << endl; - - sha.Reset(); - for(int i = 1; i <= 1000000; i++) sha.Input('a'); - - if (!sha.Result(message_digest)) - { - cerr << "ERROR-- could not compute message digest" << endl; - } - else - { - DisplayMessageDigest(message_digest); - cout << "Should match:" << endl; - cout << '\t' << "34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F" << endl; - } - - return 0; -} - -/* - * DisplayMessageDigest - * - * Description: - * Display Message Digest array - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void DisplayMessageDigest(unsigned *message_digest) -{ - ios::fmtflags flags; - - cout << '\t'; - - flags = cout.setf(ios::hex|ios::uppercase,ios::basefield); - cout.setf(ios::uppercase); - - for(int i = 0; i < 5 ; i++) - { - cout << message_digest[i] << ' '; - } - - cout << endl; - - cout.setf(flags); -}
--- a/THANKS Fri May 03 12:23:02 2013 +0200 +++ b/THANKS Tue Apr 22 16:47:21 2014 +0200 @@ -11,8 +11,29 @@ Code contributors ----------------- -* Cyril Paulus (cyril.paulus@student.ulg.ac.be), for the build process +* Cyril Paulus <cyril.paulus@student.ulg.ac.be>, for the build process and suggestions about the REST API. +* Will Ryder <will.ryder@sydney.edu.au>, for improvements with the + handling of series with temporal positions (fMRI and dynamic PET). +* Ryan Walklin <ryanwalklin@gmail.com>, for Mac OS X build. +* Peter Somlo <peter.somlo@gmail.com>, for ClearCanvas and JPEG support. +* 12maksqwe@gmail.com, for fixing issue #8. +* Julien Nabet, for various suggestions to improve the source code. +* Karsten Hilbert <Karsten.Hilbert@gmx.net>, for in-depth testing. + + +Debian/Ubuntu +------------- + +* Mathieu Malaterre <mathieu.malaterre@gmail.com>, for sponsoring Orthanc. +* Andreas Tille <andreas@an3as.eu>, for help about packaging. +* Adam Conrad <adconrad@debian.org>, to improve support of big endianness. + + +Fedora and Red Hat +------------------ + +* Mario Ceresa <mrceresa@gmail.com>, for help about packaging. Artwork @@ -20,14 +41,7 @@ https://code.google.com/p/orthanc/wiki/Logos -* Benjamin Golinvaux (golinvauxb@gmail.com), for creating the official logo. -* Jean-François Degbomont (jfdegbo@gmail.com), for submitting a logo. -* Martin Jolly (martin.jolly@gmail.com), for submitting a logo. -* Philippe Sepers (sepers.philippe@gmail.com), for submitting a logo. - - -Debian ------- - -* Mathieu Malaterre (mathieu.malaterre@gmail.com), for sponsoring Orthanc. -* Andreas Tille (andreas@an3as.eu), for help about Debian packaging. +* Benjamin Golinvaux <golinvauxb@gmail.com>, for creating the official logo. +* Jean-François Degbomont <jfdegbo@gmail.com>, for submitting a logo. +* Martin Jolly <martin.jolly@gmail.com>, for submitting a logo. +* Philippe Sepers <sepers.philippe@gmail.com>, for submitting a logo.
--- a/UnitTests/FileStorage.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ -#include "gtest/gtest.h" - -#include <ctype.h> -#include <glog/logging.h> - -#include "../Core/FileStorage/FileStorage.h" -#include "../OrthancServer/ServerIndex.h" -#include "../Core/Toolbox.h" -#include "../Core/OrthancException.h" -#include "../Core/Uuid.h" -#include "../Core/HttpServer/FilesystemHttpSender.h" -#include "../Core/HttpServer/BufferHttpSender.h" -#include "../Core/FileStorage/FileStorageAccessor.h" -#include "../Core/FileStorage/CompressedFileStorageAccessor.h" - -using namespace Orthanc; - -TEST(FileStorage, Basic) -{ - FileStorage s("FileStorageUnitTests"); - - std::string data = Toolbox::GenerateUuid(); - std::string uid = s.Create(data); - std::string d; - s.ReadFile(d, uid); - ASSERT_EQ(d.size(), data.size()); - ASSERT_FALSE(memcmp(&d[0], &data[0], data.size())); -} - -TEST(FileStorage, EndToEnd) -{ - FileStorage s("FileStorageUnitTests"); - s.Clear(); - - std::list<std::string> u; - for (unsigned int i = 0; i < 10; i++) - { - u.push_back(s.Create(Toolbox::GenerateUuid())); - } - - std::set<std::string> ss; - s.ListAllFiles(ss); - ASSERT_EQ(10u, ss.size()); - - unsigned int c = 0; - for (std::list<std::string>::iterator - i = u.begin(); i != u.end(); i++, c++) - { - ASSERT_TRUE(ss.find(*i) != ss.end()); - if (c < 5) - s.Remove(*i); - } - - s.ListAllFiles(ss); - ASSERT_EQ(5u, ss.size()); - - s.Clear(); - s.ListAllFiles(ss); - ASSERT_EQ(0u, ss.size()); -} - - -TEST(FileStorageAccessor, Simple) -{ - FileStorage s("FileStorageUnitTests"); - FileStorageAccessor accessor(s); - - std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid()); - - ASSERT_EQ(data, r); - ASSERT_EQ(CompressionType_None, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(11u, info.GetCompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, NoCompression) -{ - FileStorage s("FileStorageUnitTests"); - CompressedFileStorageAccessor accessor(s); - - accessor.SetCompressionForNextOperations(CompressionType_None); - std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid()); - - ASSERT_EQ(data, r); - ASSERT_EQ(CompressionType_None, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(11u, info.GetCompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, Compression) -{ - FileStorage s("FileStorageUnitTests"); - CompressedFileStorageAccessor accessor(s); - - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid()); - - ASSERT_EQ(data, r); - ASSERT_EQ(CompressionType_Zlib, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, Mix) -{ - FileStorage s("FileStorageUnitTests"); - CompressedFileStorageAccessor accessor(s); - - std::string r; - std::string compressedData = "Hello"; - std::string uncompressedData = "HelloWorld"; - - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom); - - accessor.SetCompressionForNextOperations(CompressionType_None); - FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom); - - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - accessor.Read(r, compressedInfo.GetUuid()); - ASSERT_EQ(compressedData, r); - - accessor.SetCompressionForNextOperations(CompressionType_None); - accessor.Read(r, compressedInfo.GetUuid()); - ASSERT_NE(compressedData, r); - - /* - // This test is too slow on Windows - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid()), OrthancException); - */ -}
--- a/UnitTests/Lua.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -#include "gtest/gtest.h" - -#include "../Core/Lua/LuaFunctionCall.h" - - -TEST(Lua, Simple) -{ - Orthanc::LuaContext lua; - lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); - lua.Execute("a={}"); - lua.Execute("a['x'] = 10"); - lua.Execute("a['y'] = {}"); - lua.Execute("a['y'][1] = 20"); - lua.Execute("a['y'][2] = 20"); - lua.Execute("PrintRecursive(a)"); - - lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end"); - - Json::Value v, vv, o; - //v["a"] = "b"; - v.append("hello"); - v.append("world"); - v.append("42"); - vv.append("sub"); - vv.append("set"); - v.append(vv); - o = Json::objectValue; - o["x"] = 10; - o["y"] = 20; - o["z"] = 20.5f; - v.append(o); - - { - Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushJSON(v); - f.Execute(); - } - - { - Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); - ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException); - } - - o["bool"] = false; - - { - Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); - ASSERT_FALSE(f.ExecutePredicate()); - } - - o["bool"] = true; - - { - Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); - ASSERT_TRUE(f.ExecutePredicate()); - } -} - - -TEST(Lua, Existing) -{ - Orthanc::LuaContext lua; - lua.Execute("a={}"); - lua.Execute("function f() end"); - - ASSERT_TRUE(lua.IsExistingFunction("f")); - ASSERT_FALSE(lua.IsExistingFunction("a")); - ASSERT_FALSE(lua.IsExistingFunction("Dummy")); -}
--- a/UnitTests/MemoryCache.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -#include "gtest/gtest.h" - -#include <glog/logging.h> -#include <memory> -#include <boost/thread.hpp> -#include <boost/lexical_cast.hpp> -#include "../Core/IDynamicObject.h" -#include "../Core/Cache/MemoryCache.h" - - -TEST(CacheIndex, Basic) -{ - Orthanc::CacheIndex<std::string> r; - - r.Add("d"); - r.Add("a"); - r.Add("c"); - r.Add("b"); - - r.TagAsMostRecent("a"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("b"); - r.TagAsMostRecent("c"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("c"); - - ASSERT_EQ("a", r.RemoveOldest()); - ASSERT_EQ("b", r.RemoveOldest()); - ASSERT_EQ("d", r.RemoveOldest()); - ASSERT_EQ("c", r.RemoveOldest()); - - ASSERT_TRUE(r.IsEmpty()); -} - - -TEST(CacheIndex, Payload) -{ - Orthanc::CacheIndex<std::string, int> r; - - r.Add("a", 420); - r.Add("b", 421); - r.Add("c", 422); - r.Add("d", 423); - - r.TagAsMostRecent("a"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("b"); - r.TagAsMostRecent("c"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("c"); - - ASSERT_TRUE(r.Contains("b")); - ASSERT_EQ(421, r.Invalidate("b")); - ASSERT_FALSE(r.Contains("b")); - - int p; - ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p); - ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p); - ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p); - - ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p); - ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p); - ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p); - - ASSERT_TRUE(r.IsEmpty()); -} - - - - -namespace -{ - class Integer : public Orthanc::IDynamicObject - { - private: - std::string& log_; - int value_; - - public: - Integer(std::string& log, int v) : log_(log), value_(v) - { - } - - virtual ~Integer() - { - LOG(INFO) << "Removing cache entry for " << value_; - log_ += boost::lexical_cast<std::string>(value_) + " "; - } - - int GetValue() const - { - return value_; - } - }; - - class IntegerProvider : public Orthanc::ICachePageProvider - { - public: - std::string log_; - - Orthanc::IDynamicObject* Provide(const std::string& s) - { - LOG(INFO) << "Providing " << s; - return new Integer(log_, boost::lexical_cast<int>(s)); - } - }; -} - - -TEST(MemoryCache, Basic) -{ - IntegerProvider provider; - - { - Orthanc::MemoryCache cache(provider, 3); - cache.Access("42"); // 42 -> exit - cache.Access("43"); // 43, 42 -> exit - cache.Access("45"); // 45, 43, 42 -> exit - cache.Access("42"); // 42, 45, 43 -> exit - cache.Access("43"); // 43, 42, 45 -> exit - cache.Access("47"); // 45 is removed; 47, 43, 42 -> exit - cache.Access("44"); // 42 is removed; 44, 47, 43 -> exit - cache.Access("42"); // 43 is removed; 42, 44, 47 -> exit - // Closing the cache: 47, 44, 42 are successively removed - } - - ASSERT_EQ("45 42 43 47 44 42 ", provider.log_); -}
--- a/UnitTests/PngWriter.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -#include "gtest/gtest.h" - -#include <stdint.h> -#include "../Core/PngWriter.h" - -TEST(PngWriter, ColorPattern) -{ - Orthanc::PngWriter w; - int width = 17; - int height = 61; - int pitch = width * 3; - - std::vector<uint8_t> image(height * pitch); - for (int y = 0; y < height; y++) - { - uint8_t *p = &image[0] + y * pitch; - for (int x = 0; x < width; x++, p += 3) - { - p[0] = (y % 3 == 0) ? 255 : 0; - p[1] = (y % 3 == 1) ? 255 : 0; - p[2] = (y % 3 == 2) ? 255 : 0; - } - } - - w.WriteToFile("ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); -} - -TEST(PngWriter, Gray8Pattern) -{ - Orthanc::PngWriter w; - int width = 17; - int height = 256; - int pitch = width; - - std::vector<uint8_t> image(height * pitch); - for (int y = 0; y < height; y++) - { - uint8_t *p = &image[0] + y * pitch; - for (int x = 0; x < width; x++, p++) - { - *p = y; - } - } - - w.WriteToFile("Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); -} - -TEST(PngWriter, Gray16Pattern) -{ - Orthanc::PngWriter w; - int width = 256; - int height = 256; - int pitch = width * 2 + 16; - - std::vector<uint8_t> image(height * pitch); - - int v = 0; - for (int y = 0; y < height; y++) - { - uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); - for (int x = 0; x < width; x++, p++, v++) - { - *p = v; - } - } - - w.WriteToFile("Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); -}
--- a/UnitTests/RestApi.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -#include "gtest/gtest.h" - -#include <ctype.h> -#include <glog/logging.h> - -#include "../Core/RestApi/RestApi.h" -#include "../Core/Uuid.h" -#include "../Core/OrthancException.h" -#include "../Core/Compression/ZlibCompressor.h" - -using namespace Orthanc; - -TEST(RestApi, ParseCookies) -{ - HttpHandler::Arguments headers; - HttpHandler::Arguments cookies; - - headers["cookie"] = "a=b;c=d;;;e=f;;g=h;"; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(4u, cookies.size()); - ASSERT_EQ("b", cookies["a"]); - ASSERT_EQ("d", cookies["c"]); - ASSERT_EQ("f", cookies["e"]); - ASSERT_EQ("h", cookies["g"]); - - headers["cookie"] = " name = value ; name2=value2"; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(2u, cookies.size()); - ASSERT_EQ("value", cookies["name"]); - ASSERT_EQ("value2", cookies["name2"]); - - headers["cookie"] = " ;;; "; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(0u, cookies.size()); - - headers["cookie"] = " ; n=v ;; "; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(1u, cookies.size()); - ASSERT_EQ("v", cookies["n"]); -} - -TEST(RestApi, RestApiPath) -{ - RestApiPath::Components args; - UriComponents trail; - - { - RestApiPath uri("/coucou/{abc}/d/*"); - ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); - ASSERT_EQ(1u, args.size()); - ASSERT_EQ(3u, trail.size()); - ASSERT_EQ("moi", args["abc"]); - ASSERT_EQ("e", trail[0]); - ASSERT_EQ("f", trail[1]); - ASSERT_EQ("g", trail[2]); - - ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f")); - ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/")); - ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d")); - ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi")); - } - - { - RestApiPath uri("/coucou/{abc}/d"); - ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); - ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d")); - ASSERT_EQ(1u, args.size()); - ASSERT_EQ(0u, trail.size()); - ASSERT_EQ("moi", args["abc"]); - } - - { - RestApiPath uri("/*"); - ASSERT_TRUE(uri.Match(args, trail, "/a/b/c")); - ASSERT_EQ(0u, args.size()); - ASSERT_EQ(3u, trail.size()); - ASSERT_EQ("a", trail[0]); - ASSERT_EQ("b", trail[1]); - ASSERT_EQ("c", trail[2]); - } -}
--- a/UnitTests/SQLite.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,238 +0,0 @@ -#include "gtest/gtest.h" - -#include "../Core/Toolbox.h" -#include "../Core/SQLite/Connection.h" -#include "../Core/SQLite/Statement.h" -#include "../Core/SQLite/Transaction.h" - -#include <sqlite3.h> - -using namespace Orthanc; - - -TEST(SQLite, Configuration) -{ - ASSERT_EQ(1, sqlite3_threadsafe()); -} - - -TEST(SQLite, Connection) -{ - Toolbox::RemoveFile("coucou"); - SQLite::Connection c; - c.Open("coucou"); - c.Execute("CREATE TABLE c(k INTEGER PRIMARY KEY AUTOINCREMENT, v INTEGER)"); - c.Execute("INSERT INTO c VALUES(NULL, 42);"); -} - - -TEST(SQLite, StatementReferenceBasic) -{ - sqlite3* db; - sqlite3_open(":memory:", &db); - - { - SQLite::StatementReference r(db, "SELECT * FROM sqlite_master"); - ASSERT_EQ(0u, r.GetReferenceCount()); - - { - SQLite::StatementReference r1(r); - ASSERT_EQ(1u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - - { - SQLite::StatementReference r2(r); - ASSERT_EQ(2u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - ASSERT_EQ(0u, r2.GetReferenceCount()); - - SQLite::StatementReference r3(r2); - ASSERT_EQ(3u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - ASSERT_EQ(0u, r2.GetReferenceCount()); - ASSERT_EQ(0u, r3.GetReferenceCount()); - } - - ASSERT_EQ(1u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - - { - SQLite::StatementReference r2(r); - ASSERT_EQ(2u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - ASSERT_EQ(0u, r2.GetReferenceCount()); - } - - ASSERT_EQ(1u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - } - - ASSERT_EQ(0u, r.GetReferenceCount()); - } - - sqlite3_close(db); -} - -TEST(SQLite, StatementBasic) -{ - SQLite::Connection c; - c.OpenInMemory(); - - SQLite::Statement s(c, "SELECT * from sqlite_master"); - s.Run(); - - for (unsigned int i = 0; i < 5; i++) - { - SQLite::Statement cs(c, SQLITE_FROM_HERE, "SELECT * from sqlite_master"); - cs.Step(); - } -} - - -namespace -{ - static bool destroyed; - - class MyFunc : public SQLite::IScalarFunction - { - public: - MyFunc() - { - destroyed = false; - } - - virtual ~MyFunc() - { - destroyed = true; - } - - virtual const char* GetName() const - { - return "MYFUNC"; - } - - virtual unsigned int GetCardinality() const - { - return 2; - } - - virtual void Compute(SQLite::FunctionContext& context) - { - context.SetIntResult(1000 + context.GetIntValue(0) * context.GetIntValue(1)); - } - }; - - class MyDelete : public SQLite::IScalarFunction - { - public: - std::set<int> deleted_; - - virtual const char* GetName() const - { - return "MYDELETE"; - } - - virtual unsigned int GetCardinality() const - { - return 1; - } - - virtual void Compute(SQLite::FunctionContext& context) - { - deleted_.insert(context.GetIntValue(0)); - context.SetNullResult(); - } - }; -} - -TEST(SQLite, ScalarFunction) -{ - { - SQLite::Connection c; - c.OpenInMemory(); - c.Register(new MyFunc()); - c.Execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v1 INTEGER, v2 INTEGER);"); - c.Execute("INSERT INTO t VALUES(NULL, 2, 3);"); - c.Execute("INSERT INTO t VALUES(NULL, 4, 4);"); - c.Execute("INSERT INTO t VALUES(NULL, 6, 5);"); - SQLite::Statement t(c, "SELECT MYFUNC(v1, v2), v1, v2 FROM t"); - int i = 0; - while (t.Step()) - { - ASSERT_EQ(t.ColumnInt(0), 1000 + t.ColumnInt(1) * t.ColumnInt(2)); - i++; - } - ASSERT_EQ(3, i); - ASSERT_FALSE(destroyed); - } - ASSERT_TRUE(destroyed); -} - -TEST(SQLite, CascadedDeleteCallback) -{ - SQLite::Connection c; - c.OpenInMemory(); - MyDelete *func = new MyDelete(); - c.Register(func); - c.Execute("CREATE TABLE parent(id INTEGER PRIMARY KEY, dummy INTEGER);"); - c.Execute("CREATE TABLE child(" - " id INTEGER PRIMARY KEY, " - " parent INTEGER REFERENCES parent(id) ON DELETE CASCADE, " - " value INTEGER);"); - c.Execute("CREATE TRIGGER childRemoved " - "AFTER DELETE ON child " - "FOR EACH ROW BEGIN " - " SELECT MYDELETE(old.value); " - "END;"); - - c.Execute("INSERT INTO parent VALUES(42, 100);"); - c.Execute("INSERT INTO parent VALUES(43, 101);"); - - c.Execute("INSERT INTO child VALUES(NULL, 42, 4200);"); - c.Execute("INSERT INTO child VALUES(NULL, 42, 4201);"); - - c.Execute("INSERT INTO child VALUES(NULL, 43, 4300);"); - c.Execute("INSERT INTO child VALUES(NULL, 43, 4301);"); - - // The following command deletes "parent(43, 101)", then in turns - // "child(NULL, 43, 4300/4301)", then calls the MyDelete on 4300 and - // 4301 - c.Execute("DELETE FROM parent WHERE dummy=101"); - - ASSERT_EQ(2u, func->deleted_.size()); - ASSERT_TRUE(func->deleted_.find(4300) != func->deleted_.end()); - ASSERT_TRUE(func->deleted_.find(4301) != func->deleted_.end()); -} - - -TEST(SQLite, EmptyTransactions) -{ - try - { - SQLite::Connection c; - c.OpenInMemory(); - - c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY);"); - c.Execute("INSERT INTO a VALUES(NULL)"); - - { - SQLite::Transaction t(c); - t.Begin(); - { - SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); - s.Step(); - } - //t.Commit(); - } - - { - SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); - s.Step(); - } - } - catch (OrthancException& e) - { - fprintf(stderr, "Exception: [%s]\n", e.What()); - throw e; - } -}
--- a/UnitTests/SQLiteChromium.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,344 +0,0 @@ -#include "gtest/gtest.h" - -#include "../Core/Toolbox.h" -#include "../Core/SQLite/Connection.h" -#include "../Core/SQLite/Statement.h" -#include "../Core/SQLite/Transaction.h" - -#include <sqlite3.h> - - -using namespace Orthanc; -using namespace Orthanc::SQLite; - - -/******************************************************************** - ** Tests from - ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc - ********************************************************************/ - -class SQLConnectionTest : public testing::Test -{ -public: - SQLConnectionTest() - { - } - - virtual ~SQLConnectionTest() - { - } - - virtual void SetUp() - { - db_.OpenInMemory(); - } - - virtual void TearDown() - { - db_.Close(); - } - - Connection& db() - { - return db_; - } - -private: - Connection db_; -}; - - - -TEST_F(SQLConnectionTest, Execute) -{ - // Valid statement should return true. - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - EXPECT_EQ(SQLITE_OK, db().GetErrorCode()); - - // Invalid statement should fail. - ASSERT_EQ(SQLITE_ERROR, - db().ExecuteAndReturnErrorCode("CREATE TAB foo (a, b")); - EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode()); -} - -TEST_F(SQLConnectionTest, ExecuteWithErrorCode) { - ASSERT_EQ(SQLITE_OK, - db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)")); - ASSERT_EQ(SQLITE_ERROR, - db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE")); - ASSERT_EQ(SQLITE_ERROR, - db().ExecuteAndReturnErrorCode( - "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)")); -} - -TEST_F(SQLConnectionTest, CachedStatement) { - StatementId id1("foo", 12); - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)")); - - // Create a new cached statement. - { - Statement s(db(), id1, "SELECT a FROM foo"); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - } - - // The statement should be cached still. - EXPECT_TRUE(db().HasCachedStatement(id1)); - - { - // Get the same statement using different SQL. This should ignore our - // SQL and use the cached one (so it will be valid). - Statement s(db(), id1, "something invalid("); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - } - - // Make sure other statements aren't marked as cached. - EXPECT_FALSE(db().HasCachedStatement(SQLITE_FROM_HERE)); -} - -TEST_F(SQLConnectionTest, IsSQLValidTest) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo")); - ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo")); -} - - - -TEST_F(SQLConnectionTest, DoesStuffExist) { - // Test DoesTableExist. - EXPECT_FALSE(db().DoesTableExist("foo")); - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - EXPECT_TRUE(db().DoesTableExist("foo")); - - // Should be case sensitive. - EXPECT_FALSE(db().DoesTableExist("FOO")); - - // Test DoesColumnExist. - EXPECT_FALSE(db().DoesColumnExist("foo", "bar")); - EXPECT_TRUE(db().DoesColumnExist("foo", "a")); - - // Testing for a column on a nonexistent table. - EXPECT_FALSE(db().DoesColumnExist("bar", "b")); -} - -TEST_F(SQLConnectionTest, GetLastInsertRowId) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)")); - - ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)")); - - // Last insert row ID should be valid. - int64_t row = db().GetLastInsertRowId(); - EXPECT_LT(0, row); - - // It should be the primary key of the row we just inserted. - Statement s(db(), "SELECT value FROM foo WHERE id=?"); - s.BindInt64(0, row); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); -} - -TEST_F(SQLConnectionTest, Rollback) { - ASSERT_TRUE(db().BeginTransaction()); - ASSERT_TRUE(db().BeginTransaction()); - EXPECT_EQ(2, db().GetTransactionNesting()); - db().RollbackTransaction(); - EXPECT_FALSE(db().CommitTransaction()); - EXPECT_TRUE(db().BeginTransaction()); -} - - - - -/******************************************************************** - ** Tests from - ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/statement_unittest.cc - ********************************************************************/ - -namespace Orthanc -{ - namespace SQLite - { - class SQLStatementTest : public SQLConnectionTest - { - }; - - TEST_F(SQLStatementTest, Run) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); - - Statement s(db(), "SELECT b FROM foo WHERE a=?"); - // Stepping it won't work since we haven't bound the value. - EXPECT_FALSE(s.Step()); - - // Run should fail since this produces output, and we should use Step(). This - // gets a bit wonky since sqlite says this is OK so succeeded is set. - s.Reset(true); - s.BindInt(0, 3); - EXPECT_FALSE(s.Run()); - EXPECT_EQ(SQLITE_ROW, db().GetErrorCode()); - - // Resetting it should put it back to the previous state (not runnable). - s.Reset(true); - - // Binding and stepping should produce one row. - s.BindInt(0, 3); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - EXPECT_FALSE(s.Step()); - } - - TEST_F(SQLStatementTest, BasicErrorCallback) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); - // Insert in the foo table the primary key. It is an error to insert - // something other than an number. This error causes the error callback - // handler to be called with SQLITE_MISMATCH as error code. - Statement s(db(), "INSERT INTO foo (a) VALUES (?)"); - s.BindCString(0, "bad bad"); - EXPECT_THROW(s.Run(), OrthancException); - } - - TEST_F(SQLStatementTest, Reset) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)")); - - Statement s(db(), "SELECT b FROM foo WHERE a = ? "); - s.BindInt(0, 3); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - ASSERT_FALSE(s.Step()); - - s.Reset(false); - // Verify that we can get all rows again. - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - EXPECT_FALSE(s.Step()); - - s.Reset(true); - ASSERT_FALSE(s.Step()); - } - } -} - - - - - - -/******************************************************************** - ** Tests from - ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc - ********************************************************************/ - -class SQLTransactionTest : public SQLConnectionTest -{ -public: - virtual void SetUp() - { - SQLConnectionTest::SetUp(); - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - } - - // Returns the number of rows in table "foo". - int CountFoo() - { - Statement count(db(), "SELECT count(*) FROM foo"); - count.Step(); - return count.ColumnInt(0); - } -}; - - -TEST_F(SQLTransactionTest, Commit) { - { - Transaction t(db()); - EXPECT_FALSE(t.IsOpen()); - t.Begin(); - EXPECT_TRUE(t.IsOpen()); - - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - - t.Commit(); - EXPECT_FALSE(t.IsOpen()); - } - - EXPECT_EQ(1, CountFoo()); -} - -TEST_F(SQLTransactionTest, Rollback) { - // Test some basic initialization, and that rollback runs when you exit the - // scope. - { - Transaction t(db()); - EXPECT_FALSE(t.IsOpen()); - t.Begin(); - EXPECT_TRUE(t.IsOpen()); - - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - } - - // Nothing should have been committed since it was implicitly rolled back. - EXPECT_EQ(0, CountFoo()); - - // Test explicit rollback. - Transaction t2(db()); - EXPECT_FALSE(t2.IsOpen()); - t2.Begin(); - - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - t2.Rollback(); - EXPECT_FALSE(t2.IsOpen()); - - // Nothing should have been committed since it was explicitly rolled back. - EXPECT_EQ(0, CountFoo()); -} - -// Rolling back any part of a transaction should roll back all of them. -TEST_F(SQLTransactionTest, NestedRollback) { - EXPECT_EQ(0, db().GetTransactionNesting()); - - // Outermost transaction. - { - Transaction outer(db()); - outer.Begin(); - EXPECT_EQ(1, db().GetTransactionNesting()); - - // The first inner one gets committed. - { - Transaction inner1(db()); - inner1.Begin(); - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - EXPECT_EQ(2, db().GetTransactionNesting()); - - inner1.Commit(); - EXPECT_EQ(1, db().GetTransactionNesting()); - } - - // One row should have gotten inserted. - EXPECT_EQ(1, CountFoo()); - - // The second inner one gets rolled back. - { - Transaction inner2(db()); - inner2.Begin(); - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - EXPECT_EQ(2, db().GetTransactionNesting()); - - inner2.Rollback(); - EXPECT_EQ(1, db().GetTransactionNesting()); - } - - // A third inner one will fail in Begin since one has already been rolled - // back. - EXPECT_EQ(1, db().GetTransactionNesting()); - { - Transaction inner3(db()); - EXPECT_THROW(inner3.Begin(), OrthancException); - EXPECT_EQ(1, db().GetTransactionNesting()); - } - } - EXPECT_EQ(0, db().GetTransactionNesting()); - EXPECT_EQ(0, CountFoo()); -}
--- a/UnitTests/ServerIndex.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,419 +0,0 @@ -#include "gtest/gtest.h" - -#include "../OrthancServer/DatabaseWrapper.h" -#include "../Core/Uuid.h" - -#include <ctype.h> -#include <glog/logging.h> - -using namespace Orthanc; - -namespace -{ - class ServerIndexListener : public IServerIndexListener - { - public: - std::vector<std::string> deletedFiles_; - std::string ancestorId_; - ResourceType ancestorType_; - - void Reset() - { - ancestorId_ = ""; - deletedFiles_.clear(); - } - - virtual void SignalRemainingAncestor(ResourceType type, - const std::string& publicId) - { - ancestorId_ = publicId; - ancestorType_ = type; - } - - virtual void SignalFileDeleted(const FileInfo& info) - { - const std::string fileUuid = info.GetUuid(); - deletedFiles_.push_back(fileUuid); - LOG(INFO) << "A file must be removed: " << fileUuid; - } - }; -} - - -TEST(DatabaseWrapper, Simple) -{ - ServerIndexListener listener; - DatabaseWrapper index(listener); - - int64_t a[] = { - index.CreateResource("a", ResourceType_Patient), // 0 - index.CreateResource("b", ResourceType_Study), // 1 - index.CreateResource("c", ResourceType_Series), // 2 - index.CreateResource("d", ResourceType_Instance), // 3 - index.CreateResource("e", ResourceType_Instance), // 4 - index.CreateResource("f", ResourceType_Instance), // 5 - index.CreateResource("g", ResourceType_Study) // 6 - }; - - ASSERT_EQ("a", index.GetPublicId(a[0])); - ASSERT_EQ("b", index.GetPublicId(a[1])); - ASSERT_EQ("c", index.GetPublicId(a[2])); - ASSERT_EQ("d", index.GetPublicId(a[3])); - ASSERT_EQ("e", index.GetPublicId(a[4])); - ASSERT_EQ("f", index.GetPublicId(a[5])); - ASSERT_EQ("g", index.GetPublicId(a[6])); - - ASSERT_EQ(ResourceType_Patient, index.GetResourceType(a[0])); - ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[1])); - ASSERT_EQ(ResourceType_Series, index.GetResourceType(a[2])); - ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[3])); - ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[4])); - ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[5])); - ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[6])); - - { - Json::Value t; - index.GetAllPublicIds(t, ResourceType_Patient); - - ASSERT_EQ(1u, t.size()); - ASSERT_EQ("a", t[0u].asString()); - - index.GetAllPublicIds(t, ResourceType_Series); - ASSERT_EQ(1u, t.size()); - ASSERT_EQ("c", t[0u].asString()); - - index.GetAllPublicIds(t, ResourceType_Study); - ASSERT_EQ(2u, t.size()); - - index.GetAllPublicIds(t, ResourceType_Instance); - ASSERT_EQ(3u, t.size()); - } - - index.SetGlobalProperty(GlobalProperty_FlushSleep, "World"); - - index.AttachChild(a[0], a[1]); - index.AttachChild(a[1], a[2]); - index.AttachChild(a[2], a[3]); - index.AttachChild(a[2], a[4]); - index.AttachChild(a[6], a[5]); - - int64_t parent; - ASSERT_FALSE(index.LookupParent(parent, a[0])); - ASSERT_TRUE(index.LookupParent(parent, a[1])); ASSERT_EQ(a[0], parent); - ASSERT_TRUE(index.LookupParent(parent, a[2])); ASSERT_EQ(a[1], parent); - ASSERT_TRUE(index.LookupParent(parent, a[3])); ASSERT_EQ(a[2], parent); - ASSERT_TRUE(index.LookupParent(parent, a[4])); ASSERT_EQ(a[2], parent); - ASSERT_TRUE(index.LookupParent(parent, a[5])); ASSERT_EQ(a[6], parent); - ASSERT_FALSE(index.LookupParent(parent, a[6])); - - std::string s; - - ASSERT_FALSE(index.GetParentPublicId(s, a[0])); - ASSERT_FALSE(index.GetParentPublicId(s, a[6])); - ASSERT_TRUE(index.GetParentPublicId(s, a[1])); ASSERT_EQ("a", s); - ASSERT_TRUE(index.GetParentPublicId(s, a[2])); ASSERT_EQ("b", s); - ASSERT_TRUE(index.GetParentPublicId(s, a[3])); ASSERT_EQ("c", s); - ASSERT_TRUE(index.GetParentPublicId(s, a[4])); ASSERT_EQ("c", s); - ASSERT_TRUE(index.GetParentPublicId(s, a[5])); ASSERT_EQ("g", s); - - std::list<std::string> l; - index.GetChildrenPublicId(l, a[0]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("b", l.front()); - index.GetChildrenPublicId(l, a[1]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("c", l.front()); - index.GetChildrenPublicId(l, a[3]); ASSERT_EQ(0u, l.size()); - index.GetChildrenPublicId(l, a[4]); ASSERT_EQ(0u, l.size()); - index.GetChildrenPublicId(l, a[5]); ASSERT_EQ(0u, l.size()); - index.GetChildrenPublicId(l, a[6]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("f", l.front()); - - index.GetChildrenPublicId(l, a[2]); ASSERT_EQ(2u, l.size()); - if (l.front() == "d") - { - ASSERT_EQ("e", l.back()); - } - else - { - ASSERT_EQ("d", l.back()); - ASSERT_EQ("e", l.front()); - } - - index.AddAttachment(a[4], FileInfo("my json file", FileContentType_Json, 42, CompressionType_Zlib, 21)); - index.AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42)); - index.AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44)); - index.SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE"); - - ASSERT_EQ(21u + 42u + 44u, index.GetTotalCompressedSize()); - ASSERT_EQ(42u + 42u + 44u, index.GetTotalUncompressedSize()); - - DicomMap m; - m.SetValue(0x0010, 0x0010, "PatientName"); - index.SetMainDicomTags(a[3], m); - - int64_t b; - ResourceType t; - ASSERT_TRUE(index.LookupResource("g", b, t)); - ASSERT_EQ(7, b); - ASSERT_EQ(ResourceType_Study, t); - - ASSERT_TRUE(index.LookupMetadata(s, a[4], MetadataType_Instance_RemoteAet)); - ASSERT_FALSE(index.LookupMetadata(s, a[4], MetadataType_Instance_IndexInSeries)); - ASSERT_EQ("PINNACLE", s); - ASSERT_EQ("PINNACLE", index.GetMetadata(a[4], MetadataType_Instance_RemoteAet)); - ASSERT_EQ("None", index.GetMetadata(a[4], MetadataType_Instance_IndexInSeries, "None")); - - ASSERT_TRUE(index.LookupGlobalProperty(s, GlobalProperty_FlushSleep)); - ASSERT_FALSE(index.LookupGlobalProperty(s, static_cast<GlobalProperty>(42))); - ASSERT_EQ("World", s); - ASSERT_EQ("World", index.GetGlobalProperty(GlobalProperty_FlushSleep)); - ASSERT_EQ("None", index.GetGlobalProperty(static_cast<GlobalProperty>(42), "None")); - - FileInfo att; - ASSERT_TRUE(index.LookupAttachment(att, a[4], FileContentType_Json)); - ASSERT_EQ("my json file", att.GetUuid()); - ASSERT_EQ(21u, att.GetCompressedSize()); - ASSERT_EQ(42u, att.GetUncompressedSize()); - ASSERT_EQ(CompressionType_Zlib, att.GetCompressionType()); - - ASSERT_EQ(0u, listener.deletedFiles_.size()); - ASSERT_EQ(7u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(3u, index.GetTableRecordCount("AttachedFiles")); - ASSERT_EQ(1u, index.GetTableRecordCount("Metadata")); - ASSERT_EQ(1u, index.GetTableRecordCount("MainDicomTags")); - index.DeleteResource(a[0]); - - ASSERT_EQ(2u, listener.deletedFiles_.size()); - ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), - listener.deletedFiles_.end(), - "my json file") == listener.deletedFiles_.end()); - ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), - listener.deletedFiles_.end(), - "my dicom file") == listener.deletedFiles_.end()); - - ASSERT_EQ(2u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, index.GetTableRecordCount("Metadata")); - ASSERT_EQ(1u, index.GetTableRecordCount("AttachedFiles")); - ASSERT_EQ(0u, index.GetTableRecordCount("MainDicomTags")); - index.DeleteResource(a[5]); - ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, index.GetTableRecordCount("AttachedFiles")); - ASSERT_EQ(2u, index.GetTableRecordCount("GlobalProperties")); - - ASSERT_EQ(3u, listener.deletedFiles_.size()); - ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), - listener.deletedFiles_.end(), - "world") == listener.deletedFiles_.end()); -} - - - - -TEST(DatabaseWrapper, Upward) -{ - ServerIndexListener listener; - DatabaseWrapper index(listener); - - int64_t a[] = { - index.CreateResource("a", ResourceType_Patient), // 0 - index.CreateResource("b", ResourceType_Study), // 1 - index.CreateResource("c", ResourceType_Series), // 2 - index.CreateResource("d", ResourceType_Instance), // 3 - index.CreateResource("e", ResourceType_Instance), // 4 - index.CreateResource("f", ResourceType_Study), // 5 - index.CreateResource("g", ResourceType_Series), // 6 - index.CreateResource("h", ResourceType_Series) // 7 - }; - - index.AttachChild(a[0], a[1]); - index.AttachChild(a[1], a[2]); - index.AttachChild(a[2], a[3]); - index.AttachChild(a[2], a[4]); - index.AttachChild(a[1], a[6]); - index.AttachChild(a[0], a[5]); - index.AttachChild(a[5], a[7]); - - { - Json::Value j; - index.GetChildren(j, a[0]); - ASSERT_EQ(2u, j.size()); - ASSERT_TRUE((j[0u] == "b" && j[1u] == "f") || - (j[1u] == "b" && j[0u] == "f")); - - index.GetChildren(j, a[1]); - ASSERT_EQ(2u, j.size()); - ASSERT_TRUE((j[0u] == "c" && j[1u] == "g") || - (j[1u] == "c" && j[0u] == "g")); - - index.GetChildren(j, a[2]); - ASSERT_EQ(2u, j.size()); - ASSERT_TRUE((j[0u] == "d" && j[1u] == "e") || - (j[1u] == "d" && j[0u] == "e")); - - index.GetChildren(j, a[3]); ASSERT_EQ(0u, j.size()); - index.GetChildren(j, a[4]); ASSERT_EQ(0u, j.size()); - index.GetChildren(j, a[5]); ASSERT_EQ(1u, j.size()); ASSERT_EQ("h", j[0u].asString()); - index.GetChildren(j, a[6]); ASSERT_EQ(0u, j.size()); - index.GetChildren(j, a[7]); ASSERT_EQ(0u, j.size()); - } - - listener.Reset(); - index.DeleteResource(a[3]); - ASSERT_EQ("c", listener.ancestorId_); - ASSERT_EQ(ResourceType_Series, listener.ancestorType_); - - listener.Reset(); - index.DeleteResource(a[4]); - ASSERT_EQ("b", listener.ancestorId_); - ASSERT_EQ(ResourceType_Study, listener.ancestorType_); - - listener.Reset(); - index.DeleteResource(a[7]); - ASSERT_EQ("a", listener.ancestorId_); - ASSERT_EQ(ResourceType_Patient, listener.ancestorType_); - - listener.Reset(); - index.DeleteResource(a[6]); - ASSERT_EQ("", listener.ancestorId_); // No more ancestor -} - - -TEST(DatabaseWrapper, PatientRecycling) -{ - ServerIndexListener listener; - DatabaseWrapper index(listener); - - std::vector<int64_t> patients; - for (int i = 0; i < 10; i++) - { - std::string p = "Patient " + boost::lexical_cast<std::string>(i); - patients.push_back(index.CreateResource(p, ResourceType_Patient)); - index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10)); - ASSERT_FALSE(index.IsProtectedPatient(patients[i])); - } - - ASSERT_EQ(10u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(10u, index.GetTableRecordCount("PatientRecyclingOrder")); - - listener.Reset(); - - index.DeleteResource(patients[5]); - index.DeleteResource(patients[0]); - ASSERT_EQ(8u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(8u, index.GetTableRecordCount("PatientRecyclingOrder")); - - ASSERT_EQ(2u, listener.deletedFiles_.size()); - ASSERT_EQ("Patient 5", listener.deletedFiles_[0]); - ASSERT_EQ("Patient 0", listener.deletedFiles_[1]); - - int64_t p; - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]); - index.DeleteResource(p); - index.DeleteResource(patients[8]); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]); - index.DeleteResource(p); - ASSERT_FALSE(index.SelectPatientToRecycle(p)); - - ASSERT_EQ(10u, listener.deletedFiles_.size()); - ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); -} - - -TEST(DatabaseWrapper, PatientProtection) -{ - ServerIndexListener listener; - DatabaseWrapper index(listener); - - std::vector<int64_t> patients; - for (int i = 0; i < 5; i++) - { - std::string p = "Patient " + boost::lexical_cast<std::string>(i); - patients.push_back(index.CreateResource(p, ResourceType_Patient)); - index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10)); - ASSERT_FALSE(index.IsProtectedPatient(patients[i])); - } - - ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); - - ASSERT_FALSE(index.IsProtectedPatient(patients[2])); - index.SetProtectedPatient(patients[2], true); - ASSERT_TRUE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); - ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); - - index.SetProtectedPatient(patients[2], true); - ASSERT_TRUE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); - index.SetProtectedPatient(patients[2], false); - ASSERT_FALSE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); - index.SetProtectedPatient(patients[2], false); - ASSERT_FALSE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); - - ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); - index.SetProtectedPatient(patients[2], true); - ASSERT_TRUE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); - index.SetProtectedPatient(patients[2], false); - ASSERT_FALSE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); - index.SetProtectedPatient(patients[3], true); - ASSERT_TRUE(index.IsProtectedPatient(patients[3])); - ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); - - ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, listener.deletedFiles_.size()); - - // Unprotecting a patient puts it at the last position in the recycling queue - int64_t p; - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[1])); ASSERT_EQ(p, patients[4]); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); - index.DeleteResource(p); - ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[2])); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); - index.DeleteResource(p); - // "patients[3]" is still protected - ASSERT_FALSE(index.SelectPatientToRecycle(p)); - - ASSERT_EQ(4u, listener.deletedFiles_.size()); - ASSERT_EQ(1u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); - - index.SetProtectedPatient(patients[3], false); - ASSERT_EQ(1u, index.GetTableRecordCount("PatientRecyclingOrder")); - ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[3])); - ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[2])); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); - index.DeleteResource(p); - - ASSERT_EQ(5u, listener.deletedFiles_.size()); - ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); -} - - - -TEST(DatabaseWrapper, Sequence) -{ - ServerIndexListener listener; - DatabaseWrapper index(listener); - - ASSERT_EQ(1u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); - ASSERT_EQ(2u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); - ASSERT_EQ(3u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); - ASSERT_EQ(4u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); -}
--- a/UnitTests/Versions.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -#include "gtest/gtest.h" - -#include <stdint.h> -#include <math.h> -#include <png.h> -#include <ctype.h> -#include <zlib.h> -#include <curl/curl.h> -#include <boost/version.hpp> -#include <sqlite3.h> -#include <lua.h> - - -TEST(Versions, Zlib) -{ - ASSERT_STREQ(zlibVersion(), ZLIB_VERSION); -} - -TEST(Versions, Curl) -{ - curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); - ASSERT_STREQ(LIBCURL_VERSION, v->version); -} - -TEST(Versions, Png) -{ - ASSERT_EQ(PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE, - png_access_version_number()); -} - -TEST(Versions, SQLite) -{ - // http://www.sqlite.org/capi3ref.html#sqlite3_libversion - assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER ); - assert(strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) == 0); - assert(strcmp(sqlite3_libversion(), SQLITE_VERSION) == 0); - - // Ensure that the SQLite version is above 3.7.0. - // "sqlite3_create_function_v2" is not defined in previous versions. - ASSERT_GE(SQLITE_VERSION_NUMBER, 3007000); -} - - -TEST(Versions, Lua) -{ - // Ensure that the Lua version is above 5.1.0. This version has - // introduced some API changes. - ASSERT_GE(LUA_VERSION_NUM, 501); -} - - -#if ORTHANC_STATIC == 1 -TEST(Versions, ZlibStatic) -{ - ASSERT_STREQ("1.2.7", zlibVersion()); -} - -TEST(Versions, BoostStatic) -{ - ASSERT_STREQ("1_49", BOOST_LIB_VERSION); -} - -TEST(Versions, CurlStatic) -{ - curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); - ASSERT_STREQ("7.26.0", v->version); -} - -TEST(Versions, PngStatic) -{ - ASSERT_EQ(10512, png_access_version_number()); - ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING); -} - -TEST(Versions, CurlSslStatic) -{ - curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW); - - // Check that SSL support is enabled when required - bool curlSupportsSsl = vinfo->features & CURL_VERSION_SSL; - -#if ORTHANC_SSL_ENABLED == 0 - ASSERT_FALSE(curlSupportsSsl); -#else - ASSERT_TRUE(curlSupportsSsl); -#endif -} - -TEST(Version, LuaStatic) -{ - ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE); -} -#endif -
--- a/UnitTests/Zip.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +0,0 @@ -#include "gtest/gtest.h" - -#include "../Core/OrthancException.h" -#include "../Core/Compression/ZipWriter.h" -#include "../Core/Compression/HierarchicalZipWriter.h" -#include "../Core/Toolbox.h" - - -using namespace Orthanc; - -TEST(ZipWriter, Basic) -{ - Orthanc::ZipWriter w; - w.SetOutputPath("hello.zip"); - w.Open(); - w.OpenFile("world/hello"); - w.Write("Hello world"); -} - - -TEST(ZipWriter, Exceptions) -{ - Orthanc::ZipWriter w; - ASSERT_THROW(w.Open(), Orthanc::OrthancException); - w.SetOutputPath("hello.zip"); - w.Open(); - ASSERT_THROW(w.Write("hello world"), Orthanc::OrthancException); -} - - - - - -namespace Orthanc -{ - // The namespace is necessary - // http://code.google.com/p/googletest/wiki/AdvancedGuide#Private_Class_Members - - TEST(HierarchicalZipWriter, Index) - { - HierarchicalZipWriter::Index i; - ASSERT_EQ("hello", i.OpenFile("hello")); - ASSERT_EQ("hello-2", i.OpenFile("hello")); - ASSERT_EQ("coucou", i.OpenFile("coucou")); - ASSERT_EQ("hello-3", i.OpenFile("hello")); - - i.OpenDirectory("coucou"); - - ASSERT_EQ("coucou-2/world", i.OpenFile("world")); - ASSERT_EQ("coucou-2/world-2", i.OpenFile("world")); - - i.OpenDirectory("world"); - - ASSERT_EQ("coucou-2/world-3/hello", i.OpenFile("hello")); - ASSERT_EQ("coucou-2/world-3/hello-2", i.OpenFile("hello")); - - i.CloseDirectory(); - - ASSERT_EQ("coucou-2/world-4", i.OpenFile("world")); - - i.CloseDirectory(); - - ASSERT_EQ("coucou-3", i.OpenFile("coucou")); - - ASSERT_THROW(i.CloseDirectory(), OrthancException); - } - - - TEST(HierarchicalZipWriter, Filenames) - { - ASSERT_EQ("trE hell", HierarchicalZipWriter::Index::KeepAlphanumeric(" ÊtrE hellô ")); - - // The "^" character is considered as a space in DICOM - ASSERT_EQ("Hel lo world", HierarchicalZipWriter::Index::KeepAlphanumeric(" Hel^^ ^\r\n\t^^lo \t <world> ")); - } -} - - -TEST(HierarchicalZipWriter, Basic) -{ - static const std::string SPACES = " "; - - HierarchicalZipWriter w("hello2.zip"); - - w.SetCompressionLevel(0); - - // Inside "/" - w.OpenFile("hello"); - w.Write(SPACES + "hello\n"); - w.OpenFile("hello"); - w.Write(SPACES + "hello-2\n"); - w.OpenDirectory("hello"); - - // Inside "/hello-3" - w.OpenFile("hello"); - w.Write(SPACES + "hello\n"); - w.OpenDirectory("hello"); - - w.SetCompressionLevel(9); - - // Inside "/hello-3/hello-2" - w.OpenFile("hello"); - w.Write(SPACES + "hello\n"); - w.OpenFile("hello"); - w.Write(SPACES + "hello-2\n"); - w.CloseDirectory(); - - // Inside "/hello-3" - w.OpenFile("hello"); - w.Write(SPACES + "hello-3\n"); - - /** - - TO CHECK THE CONTENT OF THE "hello2.zip" FILE: - - # unzip -v hello2.zip - - => There must be 6 files. The first 3 files must have a negative - compression ratio. - - **/ -}
--- a/UnitTests/main.cpp Fri May 03 12:23:02 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,354 +0,0 @@ -#include "gtest/gtest.h" - -#include <ctype.h> - -#include "../Core/Compression/ZlibCompressor.h" -#include "../Core/DicomFormat/DicomTag.h" -#include "../OrthancCppClient/HttpClient.h" -#include "../Core/HttpServer/HttpHandler.h" -#include "../Core/OrthancException.h" -#include "../Core/Toolbox.h" -#include "../Core/Uuid.h" -#include "../OrthancServer/FromDcmtkBridge.h" -#include "../OrthancServer/OrthancInitialization.h" - -using namespace Orthanc; - - -TEST(Uuid, Generation) -{ - for (int i = 0; i < 10; i++) - { - std::string s = Toolbox::GenerateUuid(); - ASSERT_TRUE(Toolbox::IsUuid(s)); - } -} - -TEST(Uuid, Test) -{ - ASSERT_FALSE(Toolbox::IsUuid("")); - ASSERT_FALSE(Toolbox::IsUuid("012345678901234567890123456789012345")); - ASSERT_TRUE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-446655440000")); - ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-44665544000")); - ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000")); - ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000 ok")); - ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000ok")); -} - -TEST(Toolbox, IsSHA1) -{ - ASSERT_FALSE(Toolbox::IsSHA1("")); - ASSERT_FALSE(Toolbox::IsSHA1("01234567890123456789012345678901234567890123")); - ASSERT_FALSE(Toolbox::IsSHA1("012345678901234567890123456789012345678901234")); - ASSERT_TRUE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b9")); - - std::string s; - Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog"); - ASSERT_TRUE(Toolbox::IsSHA1(s)); - ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s); -} - -TEST(Zlib, Basic) -{ - std::string s = Toolbox::GenerateUuid(); - s = s + s + s + s; - - std::string compressed; - ZlibCompressor c; - c.Compress(compressed, s); - - std::string uncompressed; - c.Uncompress(uncompressed, compressed); - - ASSERT_EQ(s.size(), uncompressed.size()); - ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size())); -} - -TEST(Zlib, Empty) -{ - std::string s = ""; - - std::string compressed; - ZlibCompressor c; - c.Compress(compressed, s); - - std::string uncompressed; - c.Uncompress(uncompressed, compressed); - - ASSERT_EQ(0u, uncompressed.size()); -} - -TEST(ParseGetQuery, Basic) -{ - HttpHandler::Arguments a; - HttpHandler::ParseGetQuery(a, "aaa=baaa&bb=a&aa=c"); - ASSERT_EQ(3u, a.size()); - ASSERT_EQ(a["aaa"], "baaa"); - ASSERT_EQ(a["bb"], "a"); - ASSERT_EQ(a["aa"], "c"); -} - -TEST(ParseGetQuery, BasicEmpty) -{ - HttpHandler::Arguments a; - HttpHandler::ParseGetQuery(a, "aaa&bb=aa&aa"); - ASSERT_EQ(3u, a.size()); - ASSERT_EQ(a["aaa"], ""); - ASSERT_EQ(a["bb"], "aa"); - ASSERT_EQ(a["aa"], ""); -} - -TEST(ParseGetQuery, Single) -{ - HttpHandler::Arguments a; - HttpHandler::ParseGetQuery(a, "aaa=baaa"); - ASSERT_EQ(1u, a.size()); - ASSERT_EQ(a["aaa"], "baaa"); -} - -TEST(ParseGetQuery, SingleEmpty) -{ - HttpHandler::Arguments a; - HttpHandler::ParseGetQuery(a, "aaa"); - ASSERT_EQ(1u, a.size()); - ASSERT_EQ(a["aaa"], ""); -} - -TEST(DicomFormat, Tag) -{ - ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010))); - - DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription"); - ASSERT_EQ(0x0008, t.GetGroup()); - ASSERT_EQ(0x103E, t.GetElement()); - - t = FromDcmtkBridge::ParseTag("0020-e040"); - ASSERT_EQ(0x0020, t.GetGroup()); - ASSERT_EQ(0xe040, t.GetElement()); -} - - -TEST(Uri, SplitUriComponents) -{ - UriComponents c; - Toolbox::SplitUriComponents(c, "/cou/hello/world"); - ASSERT_EQ(3u, c.size()); - ASSERT_EQ("cou", c[0]); - ASSERT_EQ("hello", c[1]); - ASSERT_EQ("world", c[2]); - - Toolbox::SplitUriComponents(c, "/cou/hello/world/"); - ASSERT_EQ(3u, c.size()); - ASSERT_EQ("cou", c[0]); - ASSERT_EQ("hello", c[1]); - ASSERT_EQ("world", c[2]); - - Toolbox::SplitUriComponents(c, "/cou/hello/world/a"); - ASSERT_EQ(4u, c.size()); - ASSERT_EQ("cou", c[0]); - ASSERT_EQ("hello", c[1]); - ASSERT_EQ("world", c[2]); - ASSERT_EQ("a", c[3]); - - Toolbox::SplitUriComponents(c, "/"); - ASSERT_EQ(0u, c.size()); - - Toolbox::SplitUriComponents(c, "/hello"); - ASSERT_EQ(1u, c.size()); - ASSERT_EQ("hello", c[0]); - - Toolbox::SplitUriComponents(c, "/hello/"); - ASSERT_EQ(1u, c.size()); - ASSERT_EQ("hello", c[0]); - - ASSERT_THROW(Toolbox::SplitUriComponents(c, ""), OrthancException); - ASSERT_THROW(Toolbox::SplitUriComponents(c, "a"), OrthancException); - ASSERT_THROW(Toolbox::SplitUriComponents(c, "/coucou//coucou"), OrthancException); -} - - -TEST(Uri, Child) -{ - UriComponents c1; Toolbox::SplitUriComponents(c1, "/hello/world"); - UriComponents c2; Toolbox::SplitUriComponents(c2, "/hello/hello"); - UriComponents c3; Toolbox::SplitUriComponents(c3, "/hello"); - UriComponents c4; Toolbox::SplitUriComponents(c4, "/world"); - UriComponents c5; Toolbox::SplitUriComponents(c5, "/"); - - ASSERT_TRUE(Toolbox::IsChildUri(c1, c1)); - ASSERT_FALSE(Toolbox::IsChildUri(c1, c2)); - ASSERT_FALSE(Toolbox::IsChildUri(c1, c3)); - ASSERT_FALSE(Toolbox::IsChildUri(c1, c4)); - ASSERT_FALSE(Toolbox::IsChildUri(c1, c5)); - - ASSERT_FALSE(Toolbox::IsChildUri(c2, c1)); - ASSERT_TRUE(Toolbox::IsChildUri(c2, c2)); - ASSERT_FALSE(Toolbox::IsChildUri(c2, c3)); - ASSERT_FALSE(Toolbox::IsChildUri(c2, c4)); - ASSERT_FALSE(Toolbox::IsChildUri(c2, c5)); - - ASSERT_TRUE(Toolbox::IsChildUri(c3, c1)); - ASSERT_TRUE(Toolbox::IsChildUri(c3, c2)); - ASSERT_TRUE(Toolbox::IsChildUri(c3, c3)); - ASSERT_FALSE(Toolbox::IsChildUri(c3, c4)); - ASSERT_FALSE(Toolbox::IsChildUri(c3, c5)); - - ASSERT_FALSE(Toolbox::IsChildUri(c4, c1)); - ASSERT_FALSE(Toolbox::IsChildUri(c4, c2)); - ASSERT_FALSE(Toolbox::IsChildUri(c4, c3)); - ASSERT_TRUE(Toolbox::IsChildUri(c4, c4)); - ASSERT_FALSE(Toolbox::IsChildUri(c4, c5)); - - ASSERT_TRUE(Toolbox::IsChildUri(c5, c1)); - ASSERT_TRUE(Toolbox::IsChildUri(c5, c2)); - ASSERT_TRUE(Toolbox::IsChildUri(c5, c3)); - ASSERT_TRUE(Toolbox::IsChildUri(c5, c4)); - ASSERT_TRUE(Toolbox::IsChildUri(c5, c5)); -} - -TEST(Uri, AutodetectMimeType) -{ - ASSERT_EQ("", Toolbox::AutodetectMimeType("../NOTES")); - ASSERT_EQ("", Toolbox::AutodetectMimeType("")); - ASSERT_EQ("", Toolbox::AutodetectMimeType("/")); - ASSERT_EQ("", Toolbox::AutodetectMimeType("a/a")); - - ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../NOTES.txt")); - ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../coucou.xml/NOTES.txt")); - ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("../.xml")); - - ASSERT_EQ("application/javascript", Toolbox::AutodetectMimeType("NOTES.js")); - ASSERT_EQ("application/json", Toolbox::AutodetectMimeType("NOTES.json")); - ASSERT_EQ("application/pdf", Toolbox::AutodetectMimeType("NOTES.pdf")); - ASSERT_EQ("text/css", Toolbox::AutodetectMimeType("NOTES.css")); - ASSERT_EQ("text/html", Toolbox::AutodetectMimeType("NOTES.html")); - ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("NOTES.txt")); - ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("NOTES.xml")); - ASSERT_EQ("image/gif", Toolbox::AutodetectMimeType("NOTES.gif")); - ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpg")); - ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpeg")); - ASSERT_EQ("image/png", Toolbox::AutodetectMimeType("NOTES.png")); -} - -TEST(Toolbox, ComputeMD5) -{ - std::string s; - - // # echo -n "Hello" | md5sum - - Toolbox::ComputeMD5(s, "Hello"); - ASSERT_EQ("8b1a9953c4611296a827abf8c47804d7", s); - Toolbox::ComputeMD5(s, ""); - ASSERT_EQ("d41d8cd98f00b204e9800998ecf8427e", s); -} - -TEST(Toolbox, ComputeSHA1) -{ - std::string s; - - Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog"); - ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s); - Toolbox::ComputeSHA1(s, ""); - ASSERT_EQ("da39a3ee-5e6b4b0d-3255bfef-95601890-afd80709", s); -} - - -TEST(Toolbox, Base64) -{ - ASSERT_EQ("", Toolbox::EncodeBase64("")); - ASSERT_EQ("YQ==", Toolbox::EncodeBase64("a")); - ASSERT_EQ("SGVsbG8gd29ybGQ=", Toolbox::EncodeBase64("Hello world")); -} - -TEST(Toolbox, PathToExecutable) -{ - printf("[%s]\n", Toolbox::GetPathToExecutable().c_str()); - printf("[%s]\n", Toolbox::GetDirectoryOfExecutable().c_str()); -} - -TEST(Toolbox, StripSpaces) -{ - ASSERT_EQ("", Toolbox::StripSpaces(" \t \r \n ")); - ASSERT_EQ("coucou", Toolbox::StripSpaces(" coucou \t \r \n ")); - ASSERT_EQ("cou cou", Toolbox::StripSpaces(" cou cou \n ")); - ASSERT_EQ("c", Toolbox::StripSpaces(" \n\t c\r \n ")); -} - - -#include <glog/logging.h> - -TEST(Logger, Basic) -{ - LOG(INFO) << "I say hello"; -} - -TEST(Toolbox, ConvertFromLatin1) -{ - // This is a Latin-1 test string - const unsigned char data[10] = { 0xe0, 0xe9, 0xea, 0xe7, 0x26, 0xc6, 0x61, 0x62, 0x63, 0x00 }; - - /*FILE* f = fopen("/tmp/tutu", "w"); - fwrite(&data[0], 9, 1, f); - fclose(f);*/ - - std::string s((char*) &data[0], 10); - ASSERT_EQ("&abc", Toolbox::ConvertToAscii(s)); - - // Open in Emacs, then save with UTF-8 encoding, then "hexdump -C" - std::string utf8 = Toolbox::ConvertToUtf8(s, "ISO-8859-1"); - ASSERT_EQ(15u, utf8.size()); - ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[0])); - ASSERT_EQ(0xa0, static_cast<unsigned char>(utf8[1])); - ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[2])); - ASSERT_EQ(0xa9, static_cast<unsigned char>(utf8[3])); - ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[4])); - ASSERT_EQ(0xaa, static_cast<unsigned char>(utf8[5])); - ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[6])); - ASSERT_EQ(0xa7, static_cast<unsigned char>(utf8[7])); - ASSERT_EQ(0x26, static_cast<unsigned char>(utf8[8])); - ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[9])); - ASSERT_EQ(0x86, static_cast<unsigned char>(utf8[10])); - ASSERT_EQ(0x61, static_cast<unsigned char>(utf8[11])); - ASSERT_EQ(0x62, static_cast<unsigned char>(utf8[12])); - ASSERT_EQ(0x63, static_cast<unsigned char>(utf8[13])); - ASSERT_EQ(0x00, static_cast<unsigned char>(utf8[14])); // Null-terminated string -} - -TEST(Toolbox, UrlDecode) -{ - std::string s; - - s = "Hello%20World"; - Toolbox::UrlDecode(s); - ASSERT_EQ("Hello World", s); - - s = "%21%23%24%26%27%28%29%2A%2B%2c%2f%3A%3b%3d%3f%40%5B%5D%90%ff"; - Toolbox::UrlDecode(s); - std::string ss = "!#$&'()*+,/:;=?@[]"; - ss.push_back((char) 144); - ss.push_back((char) 255); - ASSERT_EQ(ss, s); - - s = "(2000%2C00A4)+Other"; - Toolbox::UrlDecode(s); - ASSERT_EQ("(2000,00A4) Other", s); -} - - -int main(int argc, char **argv) -{ - // Initialize Google's logging library. - FLAGS_logtostderr = true; - FLAGS_minloglevel = 0; - - // Go to trace-level verbosity - //FLAGS_v = 1; - - google::InitGoogleLogging("Orthanc"); - - OrthancInitialize(); - ::testing::InitGoogleTest(&argc, argv); - int result = RUN_ALL_TESTS(); - OrthancFinalize(); - return result; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/DicomMap.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,97 @@ +#include "gtest/gtest.h" + +#include "../Core/Uuid.h" +#include "../Core/OrthancException.h" +#include "../Core/DicomFormat/DicomMap.h" +#include "../Core/DicomFormat/DicomNullValue.h" + +#include <memory> + +using namespace Orthanc; + +TEST(DicomMap, MainTags) +{ + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Patient)); + ASSERT_FALSE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Study)); + + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SOP_INSTANCE_UID)); + + std::set<DicomTag> s; + DicomMap::GetMainDicomTags(s); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Patient); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Study); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Series); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Instance); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); +} + + +TEST(DicomMap, Tags) +{ + DicomMap m; + ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_NAME)); + ASSERT_FALSE(m.HasTag(0x0010, 0x0010)); + m.SetValue(0x0010, 0x0010, "PatientName"); + ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(m.HasTag(0x0010, 0x0010)); + + ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_ID)); + m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID"); + ASSERT_TRUE(m.HasTag(0x0010, 0x0020)); + m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2"); + ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).AsString()); + + m.Remove(DICOM_TAG_PATIENT_ID); + ASSERT_THROW(m.GetValue(0x0010, 0x0020), OrthancException); + + std::auto_ptr<DicomMap> mm(m.Clone()); + ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).AsString()); + + m.SetValue(DICOM_TAG_PATIENT_ID, "Hello"); + ASSERT_THROW(mm->GetValue(DICOM_TAG_PATIENT_ID), OrthancException); + mm->CopyTagIfExists(m, DICOM_TAG_PATIENT_ID); + ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).AsString()); + + DicomNullValue v; + ASSERT_TRUE(v.IsNull()); +} + + +TEST(DicomMap, FindTemplates) +{ + DicomMap m; + + DicomMap::SetupFindPatientTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID)); + + DicomMap::SetupFindStudyTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(m.HasTag(DICOM_TAG_ACCESSION_NUMBER)); + + DicomMap::SetupFindSeriesTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)); + + DicomMap::SetupFindInstanceTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/FileStorage.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,195 @@ +#include "gtest/gtest.h" + +#include <ctype.h> +#include <glog/logging.h> + +#include "../Core/FileStorage/FileStorage.h" +#include "../OrthancServer/ServerIndex.h" +#include "../Core/Toolbox.h" +#include "../Core/OrthancException.h" +#include "../Core/Uuid.h" +#include "../Core/HttpServer/FilesystemHttpSender.h" +#include "../Core/HttpServer/BufferHttpSender.h" +#include "../Core/FileStorage/FileStorageAccessor.h" +#include "../Core/FileStorage/CompressedFileStorageAccessor.h" + +using namespace Orthanc; + + +static void StringToVector(std::vector<uint8_t>& v, + const std::string& s) +{ + v.resize(s.size()); + for (size_t i = 0; i < s.size(); i++) + v[i] = s[i]; +} + + +TEST(FileStorage, Basic) +{ + FileStorage s("FileStorageUnitTests"); + + std::string data = Toolbox::GenerateUuid(); + std::string uid = s.Create(data); + std::string d; + s.ReadFile(d, uid); + ASSERT_EQ(d.size(), data.size()); + ASSERT_FALSE(memcmp(&d[0], &data[0], data.size())); + ASSERT_EQ(s.GetCompressedSize(uid), data.size()); +} + +TEST(FileStorage, Basic2) +{ + FileStorage s("FileStorageUnitTests"); + + std::vector<uint8_t> data; + StringToVector(data, Toolbox::GenerateUuid()); + std::string uid = s.Create(data); + std::string d; + s.ReadFile(d, uid); + ASSERT_EQ(d.size(), data.size()); + ASSERT_FALSE(memcmp(&d[0], &data[0], data.size())); + ASSERT_EQ(s.GetCompressedSize(uid), data.size()); +} + +TEST(FileStorage, EndToEnd) +{ + FileStorage s("FileStorageUnitTests"); + s.Clear(); + + std::list<std::string> u; + for (unsigned int i = 0; i < 10; i++) + { + u.push_back(s.Create(Toolbox::GenerateUuid())); + } + + std::set<std::string> ss; + s.ListAllFiles(ss); + ASSERT_EQ(10u, ss.size()); + + unsigned int c = 0; + for (std::list<std::string>::iterator + i = u.begin(); i != u.end(); i++, c++) + { + ASSERT_TRUE(ss.find(*i) != ss.end()); + if (c < 5) + s.Remove(*i); + } + + s.ListAllFiles(ss); + ASSERT_EQ(5u, ss.size()); + + s.Clear(); + s.ListAllFiles(ss); + ASSERT_EQ(0u, ss.size()); +} + + +TEST(FileStorageAccessor, Simple) +{ + FileStorage s("FileStorageUnitTests"); + FileStorageAccessor accessor(s); + + std::string data = "Hello world"; + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(data, r); + ASSERT_EQ(CompressionType_None, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(11u, info.GetCompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, NoCompression) +{ + FileStorage s("FileStorageUnitTests"); + CompressedFileStorageAccessor accessor(s); + + accessor.SetCompressionForNextOperations(CompressionType_None); + std::string data = "Hello world"; + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(data, r); + ASSERT_EQ(CompressionType_None, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(11u, info.GetCompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, NoCompression2) +{ + FileStorage s("FileStorageUnitTests"); + CompressedFileStorageAccessor accessor(s); + + accessor.SetCompressionForNextOperations(CompressionType_None); + std::vector<uint8_t> data; + StringToVector(data, "Hello world"); + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(0, memcmp(&r[0], &data[0], data.size())); + ASSERT_EQ(CompressionType_None, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(11u, info.GetCompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, Compression) +{ + FileStorage s("FileStorageUnitTests"); + CompressedFileStorageAccessor accessor(s); + + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + std::string data = "Hello world"; + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(data, r); + ASSERT_EQ(CompressionType_Zlib, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, Mix) +{ + FileStorage s("FileStorageUnitTests"); + CompressedFileStorageAccessor accessor(s); + + std::string r; + std::string compressedData = "Hello"; + std::string uncompressedData = "HelloWorld"; + + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom); + + accessor.SetCompressionForNextOperations(CompressionType_None); + FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom); + + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + accessor.Read(r, compressedInfo.GetUuid()); + ASSERT_EQ(compressedData, r); + + accessor.SetCompressionForNextOperations(CompressionType_None); + accessor.Read(r, compressedInfo.GetUuid()); + ASSERT_NE(compressedData, r); + + /* + // This test is too slow on Windows + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid()), OrthancException); + */ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/Lua.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,103 @@ +#include "gtest/gtest.h" + +#include "../Core/Lua/LuaFunctionCall.h" + + +TEST(Lua, Json) +{ + Orthanc::LuaContext lua; + lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); + lua.Execute("a={}"); + lua.Execute("a['x'] = 10"); + lua.Execute("a['y'] = {}"); + lua.Execute("a['y'][1] = 20"); + lua.Execute("a['y'][2] = 20"); + lua.Execute("PrintRecursive(a)"); + + lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end"); + + Json::Value v, vv, o; + //v["a"] = "b"; + v.append("hello"); + v.append("world"); + v.append("42"); + vv.append("sub"); + vv.append("set"); + v.append(vv); + o = Json::objectValue; + o["x"] = 10; + o["y"] = 20; + o["z"] = 20.5f; + v.append(o); + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushJSON(v); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException); + } + + o["bool"] = false; + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_FALSE(f.ExecutePredicate()); + } + + o["bool"] = true; + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_TRUE(f.ExecutePredicate()); + } +} + + +TEST(Lua, Existing) +{ + Orthanc::LuaContext lua; + lua.Execute("a={}"); + lua.Execute("function f() end"); + + ASSERT_TRUE(lua.IsExistingFunction("f")); + ASSERT_FALSE(lua.IsExistingFunction("a")); + ASSERT_FALSE(lua.IsExistingFunction("Dummy")); +} + + +TEST(Lua, Simple) +{ + Orthanc::LuaContext lua; + lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushString("hello"); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushBoolean(true); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushInteger(42); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushDouble(3.1415); + f.Execute(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/MemoryCache.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,197 @@ +#include "gtest/gtest.h" + +#include <glog/logging.h> +#include <memory> +#include <boost/thread.hpp> +#include <boost/lexical_cast.hpp> +#include "../Core/IDynamicObject.h" +#include "../Core/Cache/MemoryCache.h" + + +TEST(LRU, Basic) +{ + Orthanc::LeastRecentlyUsedIndex<std::string> r; + + r.Add("d"); + r.Add("a"); + r.Add("c"); + r.Add("b"); + + r.MakeMostRecent("a"); + r.MakeMostRecent("d"); + r.MakeMostRecent("b"); + r.MakeMostRecent("c"); + r.MakeMostRecent("d"); + r.MakeMostRecent("c"); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ("a", r.RemoveOldest()); + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ("b", r.RemoveOldest()); + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ("d", r.RemoveOldest()); + ASSERT_EQ("c", r.GetOldest()); + ASSERT_EQ("c", r.RemoveOldest()); + + ASSERT_TRUE(r.IsEmpty()); + + ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException); + ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException); +} + + +TEST(LRU, Payload) +{ + Orthanc::LeastRecentlyUsedIndex<std::string, int> r; + + r.Add("a", 420); + r.Add("b", 421); + r.Add("c", 422); + r.Add("d", 423); + + r.MakeMostRecent("a"); + r.MakeMostRecent("d"); + r.MakeMostRecent("b"); + r.MakeMostRecent("c"); + r.MakeMostRecent("d"); + r.MakeMostRecent("c"); + + ASSERT_TRUE(r.Contains("b")); + ASSERT_EQ(421, r.Invalidate("b")); + ASSERT_FALSE(r.Contains("b")); + + int p; + ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p); + ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p); + ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(420, r.GetOldestPayload()); + ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(423, r.GetOldestPayload()); + ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p); + + ASSERT_EQ("c", r.GetOldest()); + ASSERT_EQ(422, r.GetOldestPayload()); + ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p); + + ASSERT_TRUE(r.IsEmpty()); +} + + +TEST(LRU, PayloadUpdate) +{ + Orthanc::LeastRecentlyUsedIndex<std::string, int> r; + + r.Add("a", 420); + r.Add("b", 421); + r.Add("d", 423); + + r.MakeMostRecent("a", 424); + r.MakeMostRecent("d", 421); + + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(424, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_TRUE(r.IsEmpty()); +} + + + +TEST(LRU, PayloadUpdateBis) +{ + Orthanc::LeastRecentlyUsedIndex<std::string, int> r; + + r.AddOrMakeMostRecent("a", 420); + r.AddOrMakeMostRecent("b", 421); + r.AddOrMakeMostRecent("d", 423); + r.AddOrMakeMostRecent("a", 424); + r.AddOrMakeMostRecent("d", 421); + + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(424, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_TRUE(r.IsEmpty()); +} + + + + +namespace +{ + class Integer : public Orthanc::IDynamicObject + { + private: + std::string& log_; + int value_; + + public: + Integer(std::string& log, int v) : log_(log), value_(v) + { + } + + virtual ~Integer() + { + LOG(INFO) << "Removing cache entry for " << value_; + log_ += boost::lexical_cast<std::string>(value_) + " "; + } + + int GetValue() const + { + return value_; + } + }; + + class IntegerProvider : public Orthanc::ICachePageProvider + { + public: + std::string log_; + + Orthanc::IDynamicObject* Provide(const std::string& s) + { + LOG(INFO) << "Providing " << s; + return new Integer(log_, boost::lexical_cast<int>(s)); + } + }; +} + + +TEST(MemoryCache, Basic) +{ + IntegerProvider provider; + + { + Orthanc::MemoryCache cache(provider, 3); + cache.Access("42"); // 42 -> exit + cache.Access("43"); // 43, 42 -> exit + cache.Access("45"); // 45, 43, 42 -> exit + cache.Access("42"); // 42, 45, 43 -> exit + cache.Access("43"); // 43, 42, 45 -> exit + cache.Access("47"); // 45 is removed; 47, 43, 42 -> exit + cache.Access("44"); // 42 is removed; 44, 47, 43 -> exit + cache.Access("42"); // 43 is removed; 42, 44, 47 -> exit + // Closing the cache: 47, 44, 42 are successively removed + } + + ASSERT_EQ("45 42 43 47 44 42 ", provider.log_); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/MultiThreading.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,212 @@ +#include "gtest/gtest.h" + +#include "../Core/OrthancException.h" +#include "../Core/Toolbox.h" +#include "../Core/MultiThreading/ArrayFilledByThreads.h" +#include "../Core/MultiThreading/Locker.h" +#include "../Core/MultiThreading/Mutex.h" +#include "../Core/MultiThreading/ReaderWriterLock.h" +#include "../Core/MultiThreading/ThreadedCommandProcessor.h" + +using namespace Orthanc; + +namespace +{ + class DynamicInteger : public ICommand + { + private: + int value_; + std::set<int>& target_; + + public: + DynamicInteger(int value, std::set<int>& target) : + value_(value), target_(target) + { + } + + int GetValue() const + { + return value_; + } + + virtual bool Execute() + { + static boost::mutex mutex; + boost::mutex::scoped_lock lock(mutex); + target_.insert(value_); + return true; + } + }; + + class MyFiller : public ArrayFilledByThreads::IFiller + { + private: + int size_; + unsigned int created_; + std::set<int> set_; + + public: + MyFiller(int size) : size_(size), created_(0) + { + } + + virtual size_t GetFillerSize() + { + return size_; + } + + virtual IDynamicObject* GetFillerItem(size_t index) + { + static boost::mutex mutex; + boost::mutex::scoped_lock lock(mutex); + created_++; + return new DynamicInteger(index * 2, set_); + } + + unsigned int GetCreatedCount() const + { + return created_; + } + + std::set<int> GetSet() + { + return set_; + } + }; +} + + + + +TEST(MultiThreading, SharedMessageQueueBasic) +{ + std::set<int> s; + + SharedMessageQueue q; + ASSERT_TRUE(q.WaitEmpty(0)); + q.Enqueue(new DynamicInteger(10, s)); + ASSERT_FALSE(q.WaitEmpty(1)); + q.Enqueue(new DynamicInteger(20, s)); + q.Enqueue(new DynamicInteger(30, s)); + q.Enqueue(new DynamicInteger(40, s)); + + std::auto_ptr<DynamicInteger> i; + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue()); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue()); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue()); + ASSERT_FALSE(q.WaitEmpty(1)); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue()); + ASSERT_TRUE(q.WaitEmpty(0)); + ASSERT_EQ(NULL, q.Dequeue(1)); +} + + +TEST(MultiThreading, SharedMessageQueueClean) +{ + std::set<int> s; + + try + { + SharedMessageQueue q; + q.Enqueue(new DynamicInteger(10, s)); + q.Enqueue(new DynamicInteger(20, s)); + throw OrthancException("Nope"); + } + catch (OrthancException&) + { + } +} + + +TEST(MultiThreading, ArrayFilledByThreadEmpty) +{ + MyFiller f(0); + ArrayFilledByThreads a(f); + a.SetThreadCount(1); + ASSERT_EQ(0, a.GetSize()); +} + + +TEST(MultiThreading, ArrayFilledByThread1) +{ + MyFiller f(100); + ArrayFilledByThreads a(f); + a.SetThreadCount(1); + ASSERT_EQ(100, a.GetSize()); + for (size_t i = 0; i < a.GetSize(); i++) + { + ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue()); + } +} + + +TEST(MultiThreading, ArrayFilledByThread4) +{ + MyFiller f(100); + ArrayFilledByThreads a(f); + a.SetThreadCount(4); + ASSERT_EQ(100, a.GetSize()); + for (size_t i = 0; i < a.GetSize(); i++) + { + ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue()); + } + + ASSERT_EQ(100u, f.GetCreatedCount()); + + a.Invalidate(); + + ASSERT_EQ(100, a.GetSize()); + ASSERT_EQ(200u, f.GetCreatedCount()); + ASSERT_EQ(4u, a.GetThreadCount()); + ASSERT_TRUE(f.GetSet().empty()); + + for (size_t i = 0; i < a.GetSize(); i++) + { + ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue()); + } +} + + +TEST(MultiThreading, CommandProcessor) +{ + ThreadedCommandProcessor p(4); + + std::set<int> s; + + for (size_t i = 0; i < 100; i++) + { + p.Post(new DynamicInteger(i * 2, s)); + } + + p.Join(); + + for (size_t i = 0; i < 200; i++) + { + if (i % 2) + ASSERT_TRUE(s.find(i) == s.end()); + else + ASSERT_TRUE(s.find(i) != s.end()); + } +} + + +TEST(MultiThreading, Mutex) +{ + Mutex mutex; + Locker locker(mutex); +} + + +TEST(MultiThreading, ReaderWriterLock) +{ + ReaderWriterLock lock; + + { + Locker locker1(lock.ForReader()); + Locker locker2(lock.ForReader()); + } + + { + Locker locker3(lock.ForWriter()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/Png.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,153 @@ +#include "gtest/gtest.h" + +#include <stdint.h> +#include "../Core/FileFormats/PngReader.h" +#include "../Core/FileFormats/PngWriter.h" +#include "../Core/Toolbox.h" +#include "../Core/Uuid.h" + + +TEST(PngWriter, ColorPattern) +{ + Orthanc::PngWriter w; + int width = 17; + int height = 61; + int pitch = width * 3; + + std::vector<uint8_t> image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p += 3) + { + p[0] = (y % 3 == 0) ? 255 : 0; + p[1] = (y % 3 == 1) ? 255 : 0; + p[2] = (y % 3 == 2) ? 255 : 0; + } + } + + w.WriteToFile("ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "ColorPattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5); +} + +TEST(PngWriter, Gray8Pattern) +{ + Orthanc::PngWriter w; + int width = 17; + int height = 256; + int pitch = width; + + std::vector<uint8_t> image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p++) + { + *p = y; + } + } + + w.WriteToFile("Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "Gray8Pattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("5a9b98bea3d0a6d983980cc38bfbcdb3", md5); +} + +TEST(PngWriter, Gray16Pattern) +{ + Orthanc::PngWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 16; + + std::vector<uint8_t> image(height * pitch); + + int v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); + for (int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + w.WriteToFile("Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "Gray16Pattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("0785866a08bf0a02d2eeff87f658571c", md5); +} + +TEST(PngWriter, EndToEnd) +{ + Orthanc::PngWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 16; + + std::vector<uint8_t> image(height * pitch); + + int v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); + for (int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + std::string s; + w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + + { + Orthanc::PngReader r; + r.ReadFromMemory(s); + + ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r.GetWidth(), width); + ASSERT_EQ(r.GetHeight(), height); + + v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>((uint8_t*) r.GetBuffer() + y * r.GetPitch()); + ASSERT_EQ(p, r.GetBuffer(y)); + for (int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + } + + { + Orthanc::Toolbox::TemporaryFile tmp; + Orthanc::Toolbox::WriteFile(s, tmp.GetPath()); + + Orthanc::PngReader r2; + r2.ReadFromFile(tmp.GetPath()); + + ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r2.GetWidth(), width); + ASSERT_EQ(r2.GetHeight(), height); + + v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>((uint8_t*) r2.GetBuffer() + y * r2.GetPitch()); + ASSERT_EQ(p, r2.GetBuffer(y)); + for (int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/RestApi.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,128 @@ +#include "gtest/gtest.h" + +#include <ctype.h> +#include <glog/logging.h> + +#include "../Core/ChunkedBuffer.h" +#include "../Core/HttpClient.h" +#include "../Core/RestApi/RestApi.h" +#include "../Core/Uuid.h" +#include "../Core/OrthancException.h" +#include "../Core/Compression/ZlibCompressor.h" + +using namespace Orthanc; + +#if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS) +#error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS" +#endif + +TEST(HttpClient, Basic) +{ + HttpClient c; + ASSERT_FALSE(c.IsVerbose()); + c.SetVerbose(true); + ASSERT_TRUE(c.IsVerbose()); + c.SetVerbose(false); + ASSERT_FALSE(c.IsVerbose()); + +#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 + Json::Value v; + c.SetUrl("http://orthanc.googlecode.com/hg/Resources/Configuration.json"); + c.Apply(v); + ASSERT_TRUE(v.isMember("StorageDirectory")); + //ASSERT_EQ(GetLastStatusText()); + + v = Json::nullValue; + + HttpClient cc(c); + cc.SetUrl("https://orthanc.googlecode.com/hg/Resources/Configuration.json"); + cc.Apply(v); + ASSERT_TRUE(v.isMember("LuaScripts")); +#endif +} + +TEST(RestApi, ChunkedBuffer) +{ + ChunkedBuffer b; + ASSERT_EQ(0, b.GetNumBytes()); + + b.AddChunk("hello", 5); + ASSERT_EQ(5, b.GetNumBytes()); + + b.AddChunk("world", 5); + ASSERT_EQ(10, b.GetNumBytes()); + + std::string s; + b.Flatten(s); + ASSERT_EQ("helloworld", s); +} + +TEST(RestApi, ParseCookies) +{ + HttpHandler::Arguments headers; + HttpHandler::Arguments cookies; + + headers["cookie"] = "a=b;c=d;;;e=f;;g=h;"; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(4u, cookies.size()); + ASSERT_EQ("b", cookies["a"]); + ASSERT_EQ("d", cookies["c"]); + ASSERT_EQ("f", cookies["e"]); + ASSERT_EQ("h", cookies["g"]); + + headers["cookie"] = " name = value ; name2=value2"; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(2u, cookies.size()); + ASSERT_EQ("value", cookies["name"]); + ASSERT_EQ("value2", cookies["name2"]); + + headers["cookie"] = " ;;; "; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(0u, cookies.size()); + + headers["cookie"] = " ; n=v ;; "; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(1u, cookies.size()); + ASSERT_EQ("v", cookies["n"]); +} + +TEST(RestApi, RestApiPath) +{ + RestApiPath::Components args; + UriComponents trail; + + { + RestApiPath uri("/coucou/{abc}/d/*"); + ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); + ASSERT_EQ(1u, args.size()); + ASSERT_EQ(3u, trail.size()); + ASSERT_EQ("moi", args["abc"]); + ASSERT_EQ("e", trail[0]); + ASSERT_EQ("f", trail[1]); + ASSERT_EQ("g", trail[2]); + + ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f")); + ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/")); + ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d")); + ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi")); + } + + { + RestApiPath uri("/coucou/{abc}/d"); + ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); + ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d")); + ASSERT_EQ(1u, args.size()); + ASSERT_EQ(0u, trail.size()); + ASSERT_EQ("moi", args["abc"]); + } + + { + RestApiPath uri("/*"); + ASSERT_TRUE(uri.Match(args, trail, "/a/b/c")); + ASSERT_EQ(0u, args.size()); + ASSERT_EQ(3u, trail.size()); + ASSERT_EQ("a", trail[0]); + ASSERT_EQ("b", trail[1]); + ASSERT_EQ("c", trail[2]); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/SQLite.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,301 @@ +#include "gtest/gtest.h" + +#include "../Core/Toolbox.h" +#include "../Core/SQLite/Connection.h" +#include "../Core/SQLite/Statement.h" +#include "../Core/SQLite/Transaction.h" + +#include <sqlite3.h> + +using namespace Orthanc; + + +TEST(SQLite, Configuration) +{ + ASSERT_EQ(1, sqlite3_threadsafe()); +} + + +TEST(SQLite, Connection) +{ + Toolbox::RemoveFile("coucou"); + SQLite::Connection c; + c.Open("coucou"); + c.Execute("CREATE TABLE c(k INTEGER PRIMARY KEY AUTOINCREMENT, v INTEGER)"); + c.Execute("INSERT INTO c VALUES(NULL, 42);"); +} + + +TEST(SQLite, StatementReferenceBasic) +{ + sqlite3* db; + sqlite3_open(":memory:", &db); + + { + SQLite::StatementReference r(db, "SELECT * FROM sqlite_master"); + ASSERT_EQ(0u, r.GetReferenceCount()); + + { + SQLite::StatementReference r1(r); + ASSERT_EQ(1u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + + { + SQLite::StatementReference r2(r); + ASSERT_EQ(2u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + ASSERT_EQ(0u, r2.GetReferenceCount()); + + SQLite::StatementReference r3(r2); + ASSERT_EQ(3u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + ASSERT_EQ(0u, r2.GetReferenceCount()); + ASSERT_EQ(0u, r3.GetReferenceCount()); + } + + ASSERT_EQ(1u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + + { + SQLite::StatementReference r2(r); + ASSERT_EQ(2u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + ASSERT_EQ(0u, r2.GetReferenceCount()); + } + + ASSERT_EQ(1u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + } + + ASSERT_EQ(0u, r.GetReferenceCount()); + } + + sqlite3_close(db); +} + +TEST(SQLite, StatementBasic) +{ + SQLite::Connection c; + c.OpenInMemory(); + + SQLite::Statement s(c, "SELECT * from sqlite_master"); + s.Run(); + + for (unsigned int i = 0; i < 5; i++) + { + SQLite::Statement cs(c, SQLITE_FROM_HERE, "SELECT * from sqlite_master"); + cs.Step(); + } +} + + +namespace +{ + static bool destroyed; + + class MyFunc : public SQLite::IScalarFunction + { + public: + MyFunc() + { + destroyed = false; + } + + virtual ~MyFunc() + { + destroyed = true; + } + + virtual const char* GetName() const + { + return "MYFUNC"; + } + + virtual unsigned int GetCardinality() const + { + return 2; + } + + virtual void Compute(SQLite::FunctionContext& context) + { + context.SetIntResult(1000 + context.GetIntValue(0) * context.GetIntValue(1)); + } + }; + + class MyDelete : public SQLite::IScalarFunction + { + public: + std::set<int> deleted_; + + virtual const char* GetName() const + { + return "MYDELETE"; + } + + virtual unsigned int GetCardinality() const + { + return 1; + } + + virtual void Compute(SQLite::FunctionContext& context) + { + deleted_.insert(context.GetIntValue(0)); + context.SetNullResult(); + } + }; +} + +TEST(SQLite, ScalarFunction) +{ + { + SQLite::Connection c; + c.OpenInMemory(); + c.Register(new MyFunc()); + c.Execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v1 INTEGER, v2 INTEGER);"); + c.Execute("INSERT INTO t VALUES(NULL, 2, 3);"); + c.Execute("INSERT INTO t VALUES(NULL, 4, 4);"); + c.Execute("INSERT INTO t VALUES(NULL, 6, 5);"); + SQLite::Statement t(c, "SELECT MYFUNC(v1, v2), v1, v2 FROM t"); + int i = 0; + while (t.Step()) + { + ASSERT_EQ(t.ColumnInt(0), 1000 + t.ColumnInt(1) * t.ColumnInt(2)); + i++; + } + ASSERT_EQ(3, i); + ASSERT_FALSE(destroyed); + } + ASSERT_TRUE(destroyed); +} + +TEST(SQLite, CascadedDeleteCallback) +{ + SQLite::Connection c; + c.OpenInMemory(); + MyDelete *func = new MyDelete(); + c.Register(func); + c.Execute("CREATE TABLE parent(id INTEGER PRIMARY KEY, dummy INTEGER);"); + c.Execute("CREATE TABLE child(" + " id INTEGER PRIMARY KEY, " + " parent INTEGER REFERENCES parent(id) ON DELETE CASCADE, " + " value INTEGER);"); + c.Execute("CREATE TRIGGER childRemoved " + "AFTER DELETE ON child " + "FOR EACH ROW BEGIN " + " SELECT MYDELETE(old.value); " + "END;"); + + c.Execute("INSERT INTO parent VALUES(42, 100);"); + c.Execute("INSERT INTO parent VALUES(43, 101);"); + + c.Execute("INSERT INTO child VALUES(NULL, 42, 4200);"); + c.Execute("INSERT INTO child VALUES(NULL, 42, 4201);"); + + c.Execute("INSERT INTO child VALUES(NULL, 43, 4300);"); + c.Execute("INSERT INTO child VALUES(NULL, 43, 4301);"); + + // The following command deletes "parent(43, 101)", then in turns + // "child(NULL, 43, 4300/4301)", then calls the MyDelete on 4300 and + // 4301 + c.Execute("DELETE FROM parent WHERE dummy=101"); + + ASSERT_EQ(2u, func->deleted_.size()); + ASSERT_TRUE(func->deleted_.find(4300) != func->deleted_.end()); + ASSERT_TRUE(func->deleted_.find(4301) != func->deleted_.end()); +} + + +TEST(SQLite, EmptyTransactions) +{ + try + { + SQLite::Connection c; + c.OpenInMemory(); + + c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY);"); + c.Execute("INSERT INTO a VALUES(NULL)"); + + { + SQLite::Transaction t(c); + t.Begin(); + { + SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); + s.Step(); + } + //t.Commit(); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); + s.Step(); + } + } + catch (OrthancException& e) + { + fprintf(stderr, "Exception: [%s]\n", e.What()); + throw e; + } +} + + +TEST(SQLite, Types) +{ + SQLite::Connection c; + c.OpenInMemory(); + c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY, value)"); + + { + SQLite::Statement s(c, std::string("SELECT * FROM a")); + ASSERT_EQ(2, s.ColumnCount()); + ASSERT_FALSE(s.Step()); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a")); + ASSERT_FALSE(s.Step()); + ASSERT_EQ("SELECT * FROM a", s.GetOriginalSQLStatement()); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, "INSERT INTO a VALUES(NULL, ?);"); + s.BindNull(0); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindBool(0, true); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindInt(0, 42); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindInt64(0, 42ll); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindDouble(0, 42.5); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindCString(0, "Hello"); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindBlob(0, "Hello", 5); ASSERT_TRUE(s.Run()); s.Reset(); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a")); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_NULL, s.GetColumnType(1)); + ASSERT_TRUE(s.ColumnIsNull(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); + ASSERT_TRUE(s.ColumnBool(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); + ASSERT_EQ(42, s.ColumnInt(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); + ASSERT_EQ(42ll, s.ColumnInt64(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_FLOAT, s.GetColumnType(1)); + ASSERT_FLOAT_EQ(42.5, s.ColumnDouble(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_TEXT, s.GetColumnType(1)); + ASSERT_EQ("Hello", s.ColumnString(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_BLOB, s.GetColumnType(1)); + ASSERT_EQ(5, s.ColumnByteLength(1)); + ASSERT_TRUE(!memcmp("Hello", s.ColumnBlob(1), 5)); + + std::string t; + ASSERT_TRUE(s.ColumnBlobAsString(1, &t)); + ASSERT_EQ("Hello", t); + + ASSERT_FALSE(s.Step()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/SQLiteChromium.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,344 @@ +#include "gtest/gtest.h" + +#include "../Core/Toolbox.h" +#include "../Core/SQLite/Connection.h" +#include "../Core/SQLite/Statement.h" +#include "../Core/SQLite/Transaction.h" + +#include <sqlite3.h> + + +using namespace Orthanc; +using namespace Orthanc::SQLite; + + +/******************************************************************** + ** Tests from + ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc + ********************************************************************/ + +class SQLConnectionTest : public testing::Test +{ +public: + SQLConnectionTest() + { + } + + virtual ~SQLConnectionTest() + { + } + + virtual void SetUp() + { + db_.OpenInMemory(); + } + + virtual void TearDown() + { + db_.Close(); + } + + Connection& db() + { + return db_; + } + +private: + Connection db_; +}; + + + +TEST_F(SQLConnectionTest, Execute) +{ + // Valid statement should return true. + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + EXPECT_EQ(SQLITE_OK, db().GetErrorCode()); + + // Invalid statement should fail. + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode("CREATE TAB foo (a, b")); + EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode()); +} + +TEST_F(SQLConnectionTest, ExecuteWithErrorCode) { + ASSERT_EQ(SQLITE_OK, + db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)")); + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE")); + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode( + "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)")); +} + +TEST_F(SQLConnectionTest, CachedStatement) { + StatementId id1("foo", 12); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)")); + + // Create a new cached statement. + { + Statement s(db(), id1, "SELECT a FROM foo"); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + } + + // The statement should be cached still. + EXPECT_TRUE(db().HasCachedStatement(id1)); + + { + // Get the same statement using different SQL. This should ignore our + // SQL and use the cached one (so it will be valid). + Statement s(db(), id1, "something invalid("); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + } + + // Make sure other statements aren't marked as cached. + EXPECT_FALSE(db().HasCachedStatement(SQLITE_FROM_HERE)); +} + +TEST_F(SQLConnectionTest, IsSQLValidTest) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo")); + ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo")); +} + + + +TEST_F(SQLConnectionTest, DoesStuffExist) { + // Test DoesTableExist. + EXPECT_FALSE(db().DoesTableExist("foo")); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + EXPECT_TRUE(db().DoesTableExist("foo")); + + // Should be case sensitive. + EXPECT_FALSE(db().DoesTableExist("FOO")); + + // Test DoesColumnExist. + EXPECT_FALSE(db().DoesColumnExist("foo", "bar")); + EXPECT_TRUE(db().DoesColumnExist("foo", "a")); + + // Testing for a column on a nonexistent table. + EXPECT_FALSE(db().DoesColumnExist("bar", "b")); +} + +TEST_F(SQLConnectionTest, GetLastInsertRowId) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)")); + + ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)")); + + // Last insert row ID should be valid. + int64_t row = db().GetLastInsertRowId(); + EXPECT_LT(0, row); + + // It should be the primary key of the row we just inserted. + Statement s(db(), "SELECT value FROM foo WHERE id=?"); + s.BindInt64(0, row); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); +} + +TEST_F(SQLConnectionTest, Rollback) { + ASSERT_TRUE(db().BeginTransaction()); + ASSERT_TRUE(db().BeginTransaction()); + EXPECT_EQ(2, db().GetTransactionNesting()); + db().RollbackTransaction(); + EXPECT_FALSE(db().CommitTransaction()); + EXPECT_TRUE(db().BeginTransaction()); +} + + + + +/******************************************************************** + ** Tests from + ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/statement_unittest.cc + ********************************************************************/ + +namespace Orthanc +{ + namespace SQLite + { + class SQLStatementTest : public SQLConnectionTest + { + }; + + TEST_F(SQLStatementTest, Run) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); + + Statement s(db(), "SELECT b FROM foo WHERE a=?"); + // Stepping it won't work since we haven't bound the value. + EXPECT_FALSE(s.Step()); + + // Run should fail since this produces output, and we should use Step(). This + // gets a bit wonky since sqlite says this is OK so succeeded is set. + s.Reset(true); + s.BindInt(0, 3); + EXPECT_FALSE(s.Run()); + EXPECT_EQ(SQLITE_ROW, db().GetErrorCode()); + + // Resetting it should put it back to the previous state (not runnable). + s.Reset(true); + + // Binding and stepping should produce one row. + s.BindInt(0, 3); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + EXPECT_FALSE(s.Step()); + } + + TEST_F(SQLStatementTest, BasicErrorCallback) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); + // Insert in the foo table the primary key. It is an error to insert + // something other than an number. This error causes the error callback + // handler to be called with SQLITE_MISMATCH as error code. + Statement s(db(), "INSERT INTO foo (a) VALUES (?)"); + s.BindCString(0, "bad bad"); + EXPECT_THROW(s.Run(), OrthancException); + } + + TEST_F(SQLStatementTest, Reset) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)")); + + Statement s(db(), "SELECT b FROM foo WHERE a = ? "); + s.BindInt(0, 3); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + ASSERT_FALSE(s.Step()); + + s.Reset(false); + // Verify that we can get all rows again. + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + EXPECT_FALSE(s.Step()); + + s.Reset(true); + ASSERT_FALSE(s.Step()); + } + } +} + + + + + + +/******************************************************************** + ** Tests from + ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc + ********************************************************************/ + +class SQLTransactionTest : public SQLConnectionTest +{ +public: + virtual void SetUp() + { + SQLConnectionTest::SetUp(); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + } + + // Returns the number of rows in table "foo". + int CountFoo() + { + Statement count(db(), "SELECT count(*) FROM foo"); + count.Step(); + return count.ColumnInt(0); + } +}; + + +TEST_F(SQLTransactionTest, Commit) { + { + Transaction t(db()); + EXPECT_FALSE(t.IsOpen()); + t.Begin(); + EXPECT_TRUE(t.IsOpen()); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + + t.Commit(); + EXPECT_FALSE(t.IsOpen()); + } + + EXPECT_EQ(1, CountFoo()); +} + +TEST_F(SQLTransactionTest, Rollback) { + // Test some basic initialization, and that rollback runs when you exit the + // scope. + { + Transaction t(db()); + EXPECT_FALSE(t.IsOpen()); + t.Begin(); + EXPECT_TRUE(t.IsOpen()); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + } + + // Nothing should have been committed since it was implicitly rolled back. + EXPECT_EQ(0, CountFoo()); + + // Test explicit rollback. + Transaction t2(db()); + EXPECT_FALSE(t2.IsOpen()); + t2.Begin(); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + t2.Rollback(); + EXPECT_FALSE(t2.IsOpen()); + + // Nothing should have been committed since it was explicitly rolled back. + EXPECT_EQ(0, CountFoo()); +} + +// Rolling back any part of a transaction should roll back all of them. +TEST_F(SQLTransactionTest, NestedRollback) { + EXPECT_EQ(0, db().GetTransactionNesting()); + + // Outermost transaction. + { + Transaction outer(db()); + outer.Begin(); + EXPECT_EQ(1, db().GetTransactionNesting()); + + // The first inner one gets committed. + { + Transaction inner1(db()); + inner1.Begin(); + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + EXPECT_EQ(2, db().GetTransactionNesting()); + + inner1.Commit(); + EXPECT_EQ(1, db().GetTransactionNesting()); + } + + // One row should have gotten inserted. + EXPECT_EQ(1, CountFoo()); + + // The second inner one gets rolled back. + { + Transaction inner2(db()); + inner2.Begin(); + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + EXPECT_EQ(2, db().GetTransactionNesting()); + + inner2.Rollback(); + EXPECT_EQ(1, db().GetTransactionNesting()); + } + + // A third inner one will fail in Begin since one has already been rolled + // back. + EXPECT_EQ(1, db().GetTransactionNesting()); + { + Transaction inner3(db()); + EXPECT_THROW(inner3.Begin(), OrthancException); + EXPECT_EQ(1, db().GetTransactionNesting()); + } + } + EXPECT_EQ(0, db().GetTransactionNesting()); + EXPECT_EQ(0, CountFoo()); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/ServerIndexTests.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,573 @@ +#include "gtest/gtest.h" + +#include "../OrthancServer/DatabaseWrapper.h" +#include "../OrthancServer/ServerContext.h" +#include "../OrthancServer/ServerIndex.h" +#include "../Core/Uuid.h" +#include "../Core/DicomFormat/DicomNullValue.h" + +#include <ctype.h> +#include <glog/logging.h> +#include <algorithm> + +using namespace Orthanc; + +namespace +{ + enum DatabaseWrapperClass + { + DatabaseWrapperClass_SQLite + }; + + + class ServerIndexListener : public IServerIndexListener + { + public: + std::vector<std::string> deletedFiles_; + std::string ancestorId_; + ResourceType ancestorType_; + + void Reset() + { + ancestorId_ = ""; + deletedFiles_.clear(); + } + + virtual void SignalRemainingAncestor(ResourceType type, + const std::string& publicId) + { + ancestorId_ = publicId; + ancestorType_ = type; + } + + virtual void SignalFileDeleted(const FileInfo& info) + { + const std::string fileUuid = info.GetUuid(); + deletedFiles_.push_back(fileUuid); + LOG(INFO) << "A file must be removed: " << fileUuid; + } + }; + + + class DatabaseWrapperTest : public ::testing::TestWithParam<DatabaseWrapperClass> + { + protected: + std::auto_ptr<ServerIndexListener> listener_; + std::auto_ptr<DatabaseWrapper> index_; + + DatabaseWrapperTest() + { + } + + virtual void SetUp() + { + listener_.reset(new ServerIndexListener); + index_.reset(new DatabaseWrapper(*listener_)); + } + + virtual void TearDown() + { + index_.reset(NULL); + listener_.reset(NULL); + } + }; +} + + +INSTANTIATE_TEST_CASE_P(DatabaseWrapperName, + DatabaseWrapperTest, + ::testing::Values(DatabaseWrapperClass_SQLite)); + + +TEST_P(DatabaseWrapperTest, Simple) +{ + int64_t a[] = { + index_->CreateResource("a", ResourceType_Patient), // 0 + index_->CreateResource("b", ResourceType_Study), // 1 + index_->CreateResource("c", ResourceType_Series), // 2 + index_->CreateResource("d", ResourceType_Instance), // 3 + index_->CreateResource("e", ResourceType_Instance), // 4 + index_->CreateResource("f", ResourceType_Instance), // 5 + index_->CreateResource("g", ResourceType_Study) // 6 + }; + + ASSERT_EQ("a", index_->GetPublicId(a[0])); + ASSERT_EQ("b", index_->GetPublicId(a[1])); + ASSERT_EQ("c", index_->GetPublicId(a[2])); + ASSERT_EQ("d", index_->GetPublicId(a[3])); + ASSERT_EQ("e", index_->GetPublicId(a[4])); + ASSERT_EQ("f", index_->GetPublicId(a[5])); + ASSERT_EQ("g", index_->GetPublicId(a[6])); + + ASSERT_EQ(ResourceType_Patient, index_->GetResourceType(a[0])); + ASSERT_EQ(ResourceType_Study, index_->GetResourceType(a[1])); + ASSERT_EQ(ResourceType_Series, index_->GetResourceType(a[2])); + ASSERT_EQ(ResourceType_Instance, index_->GetResourceType(a[3])); + ASSERT_EQ(ResourceType_Instance, index_->GetResourceType(a[4])); + ASSERT_EQ(ResourceType_Instance, index_->GetResourceType(a[5])); + ASSERT_EQ(ResourceType_Study, index_->GetResourceType(a[6])); + + { + Json::Value t; + index_->GetAllPublicIds(t, ResourceType_Patient); + + ASSERT_EQ(1u, t.size()); + ASSERT_EQ("a", t[0u].asString()); + + index_->GetAllPublicIds(t, ResourceType_Series); + ASSERT_EQ(1u, t.size()); + ASSERT_EQ("c", t[0u].asString()); + + index_->GetAllPublicIds(t, ResourceType_Study); + ASSERT_EQ(2u, t.size()); + + index_->GetAllPublicIds(t, ResourceType_Instance); + ASSERT_EQ(3u, t.size()); + } + + index_->SetGlobalProperty(GlobalProperty_FlushSleep, "World"); + + index_->AttachChild(a[0], a[1]); + index_->AttachChild(a[1], a[2]); + index_->AttachChild(a[2], a[3]); + index_->AttachChild(a[2], a[4]); + index_->AttachChild(a[6], a[5]); + + int64_t parent; + ASSERT_FALSE(index_->LookupParent(parent, a[0])); + ASSERT_TRUE(index_->LookupParent(parent, a[1])); ASSERT_EQ(a[0], parent); + ASSERT_TRUE(index_->LookupParent(parent, a[2])); ASSERT_EQ(a[1], parent); + ASSERT_TRUE(index_->LookupParent(parent, a[3])); ASSERT_EQ(a[2], parent); + ASSERT_TRUE(index_->LookupParent(parent, a[4])); ASSERT_EQ(a[2], parent); + ASSERT_TRUE(index_->LookupParent(parent, a[5])); ASSERT_EQ(a[6], parent); + ASSERT_FALSE(index_->LookupParent(parent, a[6])); + + std::string s; + + ASSERT_FALSE(index_->GetParentPublicId(s, a[0])); + ASSERT_FALSE(index_->GetParentPublicId(s, a[6])); + ASSERT_TRUE(index_->GetParentPublicId(s, a[1])); ASSERT_EQ("a", s); + ASSERT_TRUE(index_->GetParentPublicId(s, a[2])); ASSERT_EQ("b", s); + ASSERT_TRUE(index_->GetParentPublicId(s, a[3])); ASSERT_EQ("c", s); + ASSERT_TRUE(index_->GetParentPublicId(s, a[4])); ASSERT_EQ("c", s); + ASSERT_TRUE(index_->GetParentPublicId(s, a[5])); ASSERT_EQ("g", s); + + std::list<std::string> l; + index_->GetChildrenPublicId(l, a[0]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("b", l.front()); + index_->GetChildrenPublicId(l, a[1]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("c", l.front()); + index_->GetChildrenPublicId(l, a[3]); ASSERT_EQ(0u, l.size()); + index_->GetChildrenPublicId(l, a[4]); ASSERT_EQ(0u, l.size()); + index_->GetChildrenPublicId(l, a[5]); ASSERT_EQ(0u, l.size()); + index_->GetChildrenPublicId(l, a[6]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("f", l.front()); + + index_->GetChildrenPublicId(l, a[2]); ASSERT_EQ(2u, l.size()); + if (l.front() == "d") + { + ASSERT_EQ("e", l.back()); + } + else + { + ASSERT_EQ("d", l.back()); + ASSERT_EQ("e", l.front()); + } + + std::list<MetadataType> md; + index_->ListAvailableMetadata(md, a[4]); + ASSERT_EQ(0u, md.size()); + + index_->AddAttachment(a[4], FileInfo("my json file", FileContentType_DicomAsJson, 42, "md5", + CompressionType_Zlib, 21, "compressedMD5")); + index_->AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42, "md5")); + index_->AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44, "md5")); + index_->SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE"); + + index_->ListAvailableMetadata(md, a[4]); + ASSERT_EQ(1u, md.size()); + ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front()); + index_->SetMetadata(a[4], MetadataType_ModifiedFrom, "TUTU"); + index_->ListAvailableMetadata(md, a[4]); + ASSERT_EQ(2u, md.size()); + index_->DeleteMetadata(a[4], MetadataType_ModifiedFrom); + index_->ListAvailableMetadata(md, a[4]); + ASSERT_EQ(1u, md.size()); + ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front()); + + ASSERT_EQ(21u + 42u + 44u, index_->GetTotalCompressedSize()); + ASSERT_EQ(42u + 42u + 44u, index_->GetTotalUncompressedSize()); + + DicomMap m; + m.SetValue(0x0010, 0x0010, "PatientName"); + index_->SetMainDicomTags(a[3], m); + + int64_t b; + ResourceType t; + ASSERT_TRUE(index_->LookupResource("g", b, t)); + ASSERT_EQ(7, b); + ASSERT_EQ(ResourceType_Study, t); + + ASSERT_TRUE(index_->LookupMetadata(s, a[4], MetadataType_Instance_RemoteAet)); + ASSERT_FALSE(index_->LookupMetadata(s, a[4], MetadataType_Instance_IndexInSeries)); + ASSERT_EQ("PINNACLE", s); + ASSERT_EQ("PINNACLE", index_->GetMetadata(a[4], MetadataType_Instance_RemoteAet)); + ASSERT_EQ("None", index_->GetMetadata(a[4], MetadataType_Instance_IndexInSeries, "None")); + + ASSERT_TRUE(index_->LookupGlobalProperty(s, GlobalProperty_FlushSleep)); + ASSERT_FALSE(index_->LookupGlobalProperty(s, static_cast<GlobalProperty>(42))); + ASSERT_EQ("World", s); + ASSERT_EQ("World", index_->GetGlobalProperty(GlobalProperty_FlushSleep)); + ASSERT_EQ("None", index_->GetGlobalProperty(static_cast<GlobalProperty>(42), "None")); + + FileInfo att; + ASSERT_TRUE(index_->LookupAttachment(att, a[4], FileContentType_DicomAsJson)); + ASSERT_EQ("my json file", att.GetUuid()); + ASSERT_EQ(21u, att.GetCompressedSize()); + ASSERT_EQ("md5", att.GetUncompressedMD5()); + ASSERT_EQ("compressedMD5", att.GetCompressedMD5()); + ASSERT_EQ(42u, att.GetUncompressedSize()); + ASSERT_EQ(CompressionType_Zlib, att.GetCompressionType()); + + ASSERT_TRUE(index_->LookupAttachment(att, a[6], FileContentType_Dicom)); + ASSERT_EQ("world", att.GetUuid()); + ASSERT_EQ(44u, att.GetCompressedSize()); + ASSERT_EQ("md5", att.GetUncompressedMD5()); + ASSERT_EQ("md5", att.GetCompressedMD5()); + ASSERT_EQ(44u, att.GetUncompressedSize()); + ASSERT_EQ(CompressionType_None, att.GetCompressionType()); + + ASSERT_EQ(0u, listener_->deletedFiles_.size()); + ASSERT_EQ(7u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(3u, index_->GetTableRecordCount("AttachedFiles")); + ASSERT_EQ(1u, index_->GetTableRecordCount("Metadata")); + ASSERT_EQ(1u, index_->GetTableRecordCount("MainDicomTags")); + index_->DeleteResource(a[0]); + + ASSERT_EQ(2u, listener_->deletedFiles_.size()); + ASSERT_FALSE(std::find(listener_->deletedFiles_.begin(), + listener_->deletedFiles_.end(), + "my json file") == listener_->deletedFiles_.end()); + ASSERT_FALSE(std::find(listener_->deletedFiles_.begin(), + listener_->deletedFiles_.end(), + "my dicom file") == listener_->deletedFiles_.end()); + + ASSERT_EQ(2u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index_->GetTableRecordCount("Metadata")); + ASSERT_EQ(1u, index_->GetTableRecordCount("AttachedFiles")); + ASSERT_EQ(0u, index_->GetTableRecordCount("MainDicomTags")); + index_->DeleteResource(a[5]); + ASSERT_EQ(0u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index_->GetTableRecordCount("AttachedFiles")); + ASSERT_EQ(2u, index_->GetTableRecordCount("GlobalProperties")); + + ASSERT_EQ(3u, listener_->deletedFiles_.size()); + ASSERT_FALSE(std::find(listener_->deletedFiles_.begin(), + listener_->deletedFiles_.end(), + "world") == listener_->deletedFiles_.end()); +} + + + + +TEST_P(DatabaseWrapperTest, Upward) +{ + int64_t a[] = { + index_->CreateResource("a", ResourceType_Patient), // 0 + index_->CreateResource("b", ResourceType_Study), // 1 + index_->CreateResource("c", ResourceType_Series), // 2 + index_->CreateResource("d", ResourceType_Instance), // 3 + index_->CreateResource("e", ResourceType_Instance), // 4 + index_->CreateResource("f", ResourceType_Study), // 5 + index_->CreateResource("g", ResourceType_Series), // 6 + index_->CreateResource("h", ResourceType_Series) // 7 + }; + + index_->AttachChild(a[0], a[1]); + index_->AttachChild(a[1], a[2]); + index_->AttachChild(a[2], a[3]); + index_->AttachChild(a[2], a[4]); + index_->AttachChild(a[1], a[6]); + index_->AttachChild(a[0], a[5]); + index_->AttachChild(a[5], a[7]); + + { + Json::Value j; + index_->GetChildren(j, a[0]); + ASSERT_EQ(2u, j.size()); + ASSERT_TRUE((j[0u] == "b" && j[1u] == "f") || + (j[1u] == "b" && j[0u] == "f")); + + index_->GetChildren(j, a[1]); + ASSERT_EQ(2u, j.size()); + ASSERT_TRUE((j[0u] == "c" && j[1u] == "g") || + (j[1u] == "c" && j[0u] == "g")); + + index_->GetChildren(j, a[2]); + ASSERT_EQ(2u, j.size()); + ASSERT_TRUE((j[0u] == "d" && j[1u] == "e") || + (j[1u] == "d" && j[0u] == "e")); + + index_->GetChildren(j, a[3]); ASSERT_EQ(0u, j.size()); + index_->GetChildren(j, a[4]); ASSERT_EQ(0u, j.size()); + index_->GetChildren(j, a[5]); ASSERT_EQ(1u, j.size()); ASSERT_EQ("h", j[0u].asString()); + index_->GetChildren(j, a[6]); ASSERT_EQ(0u, j.size()); + index_->GetChildren(j, a[7]); ASSERT_EQ(0u, j.size()); + } + + listener_->Reset(); + index_->DeleteResource(a[3]); + ASSERT_EQ("c", listener_->ancestorId_); + ASSERT_EQ(ResourceType_Series, listener_->ancestorType_); + + listener_->Reset(); + index_->DeleteResource(a[4]); + ASSERT_EQ("b", listener_->ancestorId_); + ASSERT_EQ(ResourceType_Study, listener_->ancestorType_); + + listener_->Reset(); + index_->DeleteResource(a[7]); + ASSERT_EQ("a", listener_->ancestorId_); + ASSERT_EQ(ResourceType_Patient, listener_->ancestorType_); + + listener_->Reset(); + index_->DeleteResource(a[6]); + ASSERT_EQ("", listener_->ancestorId_); // No more ancestor +} + + +TEST_P(DatabaseWrapperTest, PatientRecycling) +{ + std::vector<int64_t> patients; + for (int i = 0; i < 10; i++) + { + std::string p = "Patient " + boost::lexical_cast<std::string>(i); + patients.push_back(index_->CreateResource(p, ResourceType_Patient)); + index_->AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10, + "md5-" + boost::lexical_cast<std::string>(i))); + ASSERT_FALSE(index_->IsProtectedPatient(patients[i])); + } + + ASSERT_EQ(10u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(10u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + listener_->Reset(); + + index_->DeleteResource(patients[5]); + index_->DeleteResource(patients[0]); + ASSERT_EQ(8u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(8u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_EQ(2u, listener_->deletedFiles_.size()); + ASSERT_EQ("Patient 5", listener_->deletedFiles_[0]); + ASSERT_EQ("Patient 0", listener_->deletedFiles_[1]); + + int64_t p; + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]); + index_->DeleteResource(p); + index_->DeleteResource(patients[8]); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]); + index_->DeleteResource(p); + ASSERT_FALSE(index_->SelectPatientToRecycle(p)); + + ASSERT_EQ(10u, listener_->deletedFiles_.size()); + ASSERT_EQ(0u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index_->GetTableRecordCount("PatientRecyclingOrder")); +} + + +TEST_P(DatabaseWrapperTest, PatientProtection) +{ + std::vector<int64_t> patients; + for (int i = 0; i < 5; i++) + { + std::string p = "Patient " + boost::lexical_cast<std::string>(i); + patients.push_back(index_->CreateResource(p, ResourceType_Patient)); + index_->AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10, + "md5-" + boost::lexical_cast<std::string>(i))); + ASSERT_FALSE(index_->IsProtectedPatient(patients[i])); + } + + ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_FALSE(index_->IsProtectedPatient(patients[2])); + index_->SetProtectedPatient(patients[2], true); + ASSERT_TRUE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder")); + ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); + + index_->SetProtectedPatient(patients[2], true); + ASSERT_TRUE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder")); + index_->SetProtectedPatient(patients[2], false); + ASSERT_FALSE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); + index_->SetProtectedPatient(patients[2], false); + ASSERT_FALSE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); + index_->SetProtectedPatient(patients[2], true); + ASSERT_TRUE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder")); + index_->SetProtectedPatient(patients[2], false); + ASSERT_FALSE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); + index_->SetProtectedPatient(patients[3], true); + ASSERT_TRUE(index_->IsProtectedPatient(patients[3])); + ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, listener_->deletedFiles_.size()); + + // Unprotecting a patient puts it at the last position in the recycling queue + int64_t p; + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p, patients[1])); ASSERT_EQ(p, patients[4]); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); + index_->DeleteResource(p); + ASSERT_FALSE(index_->SelectPatientToRecycle(p, patients[2])); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); + index_->DeleteResource(p); + // "patients[3]" is still protected + ASSERT_FALSE(index_->SelectPatientToRecycle(p)); + + ASSERT_EQ(4u, listener_->deletedFiles_.size()); + ASSERT_EQ(1u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + index_->SetProtectedPatient(patients[3], false); + ASSERT_EQ(1u, index_->GetTableRecordCount("PatientRecyclingOrder")); + ASSERT_FALSE(index_->SelectPatientToRecycle(p, patients[3])); + ASSERT_TRUE(index_->SelectPatientToRecycle(p, patients[2])); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); + index_->DeleteResource(p); + + ASSERT_EQ(5u, listener_->deletedFiles_.size()); + ASSERT_EQ(0u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index_->GetTableRecordCount("PatientRecyclingOrder")); +} + + + +TEST_P(DatabaseWrapperTest, Sequence) +{ + ASSERT_EQ(1u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); + ASSERT_EQ(2u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); + ASSERT_EQ(3u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); + ASSERT_EQ(4u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); +} + + + +TEST_P(DatabaseWrapperTest, LookupTagValue) +{ + int64_t a[] = { + index_->CreateResource("a", ResourceType_Study), // 0 + index_->CreateResource("b", ResourceType_Study), // 1 + index_->CreateResource("c", ResourceType_Study), // 2 + index_->CreateResource("d", ResourceType_Series) // 3 + }; + + DicomMap m; + m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index_->SetMainDicomTags(a[0], m); + m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "1"); index_->SetMainDicomTags(a[1], m); + m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index_->SetMainDicomTags(a[2], m); + m.Clear(); m.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "0"); index_->SetMainDicomTags(a[3], m); + + std::list<int64_t> s; + + index_->LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "0"); + ASSERT_EQ(2u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end()); + + index_->LookupTagValue(s, "0"); + ASSERT_EQ(3u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[3]) != s.end()); + + index_->LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1"); + ASSERT_EQ(1u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end()); + + index_->LookupTagValue(s, "1"); + ASSERT_EQ(1u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end()); + + + /*{ + std::list<std::string> s; + context.GetIndex().LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1.2.250.1.74.20130819132500.29000036381059"); + for (std::list<std::string>::iterator i = s.begin(); i != s.end(); i++) + { + std::cout << "*** " << *i << std::endl;; + } + }*/ +} + + + +TEST(ServerIndex, AttachmentRecycling) +{ + const std::string path = "OrthancStorageUnitTests"; + Toolbox::RemoveFile(path + "/index"); + ServerContext context(path, ":memory:"); // The SQLite DB is in memory + ServerIndex& index = context.GetIndex(); + + index.SetMaximumStorageSize(10); + + Json::Value tmp; + index.ComputeStatistics(tmp); + ASSERT_EQ(0, tmp["CountPatients"].asInt()); + ASSERT_EQ(0, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString())); + + ServerIndex::Attachments attachments; + + std::vector<std::string> ids; + for (int i = 0; i < 10; i++) + { + std::string id = boost::lexical_cast<std::string>(i); + DicomMap instance; + instance.SetValue(DICOM_TAG_PATIENT_ID, "patient-" + id); + instance.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "study-" + id); + instance.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "series-" + id); + instance.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "instance-" + id); + ASSERT_EQ(StoreStatus_Success, index.Store(instance, attachments, "")); + + DicomInstanceHasher hasher(instance); + ids.push_back(hasher.HashPatient()); + ids.push_back(hasher.HashStudy()); + ids.push_back(hasher.HashSeries()); + ids.push_back(hasher.HashInstance()); + } + + index.ComputeStatistics(tmp); + ASSERT_EQ(10, tmp["CountPatients"].asInt()); + ASSERT_EQ(0, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString())); + + for (size_t i = 0; i < ids.size(); i++) + { + FileInfo info(Toolbox::GenerateUuid(), FileContentType_Dicom, 1, "md5"); + index.AddAttachment(info, ids[i]); + + index.ComputeStatistics(tmp); + ASSERT_GE(10, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString())); + } + + // Because the DB is in memory, the SQLite index must not have been created + ASSERT_THROW(Toolbox::GetFileSize(path + "/index"), OrthancException); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/UnitTestsMain.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,609 @@ +#include "../Core/EnumerationDictionary.h" + +#include "gtest/gtest.h" + +#include <ctype.h> + +#include "../Core/Compression/ZlibCompressor.h" +#include "../Core/DicomFormat/DicomTag.h" +#include "../Core/HttpServer/HttpHandler.h" +#include "../Core/OrthancException.h" +#include "../Core/Toolbox.h" +#include "../Core/Uuid.h" +#include "../OrthancServer/FromDcmtkBridge.h" +#include "../OrthancServer/OrthancInitialization.h" + +using namespace Orthanc; + + +TEST(Uuid, Generation) +{ + for (int i = 0; i < 10; i++) + { + std::string s = Toolbox::GenerateUuid(); + ASSERT_TRUE(Toolbox::IsUuid(s)); + } +} + +TEST(Uuid, Test) +{ + ASSERT_FALSE(Toolbox::IsUuid("")); + ASSERT_FALSE(Toolbox::IsUuid("012345678901234567890123456789012345")); + ASSERT_TRUE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-446655440000")); + ASSERT_FALSE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-44665544000_")); + ASSERT_FALSE(Toolbox::IsUuid("01234567890123456789012345678901234_")); + ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-44665544000")); + ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000")); + ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000 ok")); + ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000ok")); +} + +TEST(Toolbox, IsSHA1) +{ + ASSERT_FALSE(Toolbox::IsSHA1("")); + ASSERT_FALSE(Toolbox::IsSHA1("01234567890123456789012345678901234567890123")); + ASSERT_FALSE(Toolbox::IsSHA1("012345678901234567890123456789012345678901234")); + ASSERT_TRUE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b9")); + + std::string s; + Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog"); + ASSERT_TRUE(Toolbox::IsSHA1(s)); + ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s); + + ASSERT_FALSE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b_")); +} + +static void StringToVector(std::vector<uint8_t>& v, + const std::string& s) +{ + v.resize(s.size()); + for (size_t i = 0; i < s.size(); i++) + v[i] = s[i]; +} + + +TEST(Zlib, Basic) +{ + std::string s = Toolbox::GenerateUuid(); + s = s + s + s + s; + + std::string compressed, compressed2; + ZlibCompressor c; + c.Compress(compressed, s); + + std::vector<uint8_t> v, vv; + StringToVector(v, s); + c.Compress(compressed2, v); + ASSERT_EQ(compressed, compressed2); + + std::string uncompressed; + c.Uncompress(uncompressed, compressed); + ASSERT_EQ(s.size(), uncompressed.size()); + ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size())); + + StringToVector(vv, compressed); + c.Uncompress(uncompressed, vv); + ASSERT_EQ(s.size(), uncompressed.size()); + ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size())); +} + + +TEST(Zlib, Level) +{ + std::string s = Toolbox::GenerateUuid(); + s = s + s + s + s; + + std::string compressed, compressed2; + ZlibCompressor c; + c.SetCompressionLevel(9); + c.Compress(compressed, s); + + c.SetCompressionLevel(0); + c.Compress(compressed2, s); + + ASSERT_TRUE(compressed.size() < compressed2.size()); +} + + +TEST(Zlib, DISABLED_Corrupted) // Disabled because it may result in a crash +{ + std::string s = Toolbox::GenerateUuid(); + s = s + s + s + s; + + std::string compressed; + ZlibCompressor c; + c.Compress(compressed, s); + + compressed[compressed.size() - 1] = 'a'; + std::string u; + + ASSERT_THROW(c.Uncompress(u, compressed), OrthancException); +} + + +TEST(Zlib, Empty) +{ + std::string s = ""; + std::vector<uint8_t> v, vv; + + std::string compressed, compressed2; + ZlibCompressor c; + c.Compress(compressed, s); + c.Compress(compressed2, v); + ASSERT_EQ(compressed, compressed2); + + std::string uncompressed; + c.Uncompress(uncompressed, compressed); + ASSERT_EQ(0u, uncompressed.size()); + + StringToVector(vv, compressed); + c.Uncompress(uncompressed, vv); + ASSERT_EQ(0u, uncompressed.size()); +} + + +TEST(ParseGetQuery, Basic) +{ + HttpHandler::Arguments a; + HttpHandler::ParseGetQuery(a, "aaa=baaa&bb=a&aa=c"); + ASSERT_EQ(3u, a.size()); + ASSERT_EQ(a["aaa"], "baaa"); + ASSERT_EQ(a["bb"], "a"); + ASSERT_EQ(a["aa"], "c"); +} + +TEST(ParseGetQuery, BasicEmpty) +{ + HttpHandler::Arguments a; + HttpHandler::ParseGetQuery(a, "aaa&bb=aa&aa"); + ASSERT_EQ(3u, a.size()); + ASSERT_EQ(a["aaa"], ""); + ASSERT_EQ(a["bb"], "aa"); + ASSERT_EQ(a["aa"], ""); +} + +TEST(ParseGetQuery, Single) +{ + HttpHandler::Arguments a; + HttpHandler::ParseGetQuery(a, "aaa=baaa"); + ASSERT_EQ(1u, a.size()); + ASSERT_EQ(a["aaa"], "baaa"); +} + +TEST(ParseGetQuery, SingleEmpty) +{ + HttpHandler::Arguments a; + HttpHandler::ParseGetQuery(a, "aaa"); + ASSERT_EQ(1u, a.size()); + ASSERT_EQ(a["aaa"], ""); +} + +TEST(DicomFormat, Tag) +{ + ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010))); + + DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription"); + ASSERT_EQ(0x0008, t.GetGroup()); + ASSERT_EQ(0x103E, t.GetElement()); + + t = FromDcmtkBridge::ParseTag("0020-e040"); + ASSERT_EQ(0x0020, t.GetGroup()); + ASSERT_EQ(0xe040, t.GetElement()); + + // Test ==() and !=() operators + ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020)); + ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020)); +} + + +TEST(Uri, SplitUriComponents) +{ + UriComponents c; + Toolbox::SplitUriComponents(c, "/cou/hello/world"); + ASSERT_EQ(3u, c.size()); + ASSERT_EQ("cou", c[0]); + ASSERT_EQ("hello", c[1]); + ASSERT_EQ("world", c[2]); + + Toolbox::SplitUriComponents(c, "/cou/hello/world/"); + ASSERT_EQ(3u, c.size()); + ASSERT_EQ("cou", c[0]); + ASSERT_EQ("hello", c[1]); + ASSERT_EQ("world", c[2]); + + Toolbox::SplitUriComponents(c, "/cou/hello/world/a"); + ASSERT_EQ(4u, c.size()); + ASSERT_EQ("cou", c[0]); + ASSERT_EQ("hello", c[1]); + ASSERT_EQ("world", c[2]); + ASSERT_EQ("a", c[3]); + + Toolbox::SplitUriComponents(c, "/"); + ASSERT_EQ(0u, c.size()); + + Toolbox::SplitUriComponents(c, "/hello"); + ASSERT_EQ(1u, c.size()); + ASSERT_EQ("hello", c[0]); + + Toolbox::SplitUriComponents(c, "/hello/"); + ASSERT_EQ(1u, c.size()); + ASSERT_EQ("hello", c[0]); + + ASSERT_THROW(Toolbox::SplitUriComponents(c, ""), OrthancException); + ASSERT_THROW(Toolbox::SplitUriComponents(c, "a"), OrthancException); + ASSERT_THROW(Toolbox::SplitUriComponents(c, "/coucou//coucou"), OrthancException); + + c.clear(); + c.push_back("test"); + ASSERT_EQ("/", Toolbox::FlattenUri(c, 10)); +} + + +TEST(Uri, Child) +{ + UriComponents c1; Toolbox::SplitUriComponents(c1, "/hello/world"); + UriComponents c2; Toolbox::SplitUriComponents(c2, "/hello/hello"); + UriComponents c3; Toolbox::SplitUriComponents(c3, "/hello"); + UriComponents c4; Toolbox::SplitUriComponents(c4, "/world"); + UriComponents c5; Toolbox::SplitUriComponents(c5, "/"); + + ASSERT_TRUE(Toolbox::IsChildUri(c1, c1)); + ASSERT_FALSE(Toolbox::IsChildUri(c1, c2)); + ASSERT_FALSE(Toolbox::IsChildUri(c1, c3)); + ASSERT_FALSE(Toolbox::IsChildUri(c1, c4)); + ASSERT_FALSE(Toolbox::IsChildUri(c1, c5)); + + ASSERT_FALSE(Toolbox::IsChildUri(c2, c1)); + ASSERT_TRUE(Toolbox::IsChildUri(c2, c2)); + ASSERT_FALSE(Toolbox::IsChildUri(c2, c3)); + ASSERT_FALSE(Toolbox::IsChildUri(c2, c4)); + ASSERT_FALSE(Toolbox::IsChildUri(c2, c5)); + + ASSERT_TRUE(Toolbox::IsChildUri(c3, c1)); + ASSERT_TRUE(Toolbox::IsChildUri(c3, c2)); + ASSERT_TRUE(Toolbox::IsChildUri(c3, c3)); + ASSERT_FALSE(Toolbox::IsChildUri(c3, c4)); + ASSERT_FALSE(Toolbox::IsChildUri(c3, c5)); + + ASSERT_FALSE(Toolbox::IsChildUri(c4, c1)); + ASSERT_FALSE(Toolbox::IsChildUri(c4, c2)); + ASSERT_FALSE(Toolbox::IsChildUri(c4, c3)); + ASSERT_TRUE(Toolbox::IsChildUri(c4, c4)); + ASSERT_FALSE(Toolbox::IsChildUri(c4, c5)); + + ASSERT_TRUE(Toolbox::IsChildUri(c5, c1)); + ASSERT_TRUE(Toolbox::IsChildUri(c5, c2)); + ASSERT_TRUE(Toolbox::IsChildUri(c5, c3)); + ASSERT_TRUE(Toolbox::IsChildUri(c5, c4)); + ASSERT_TRUE(Toolbox::IsChildUri(c5, c5)); +} + +TEST(Uri, AutodetectMimeType) +{ + ASSERT_EQ("", Toolbox::AutodetectMimeType("../NOTES")); + ASSERT_EQ("", Toolbox::AutodetectMimeType("")); + ASSERT_EQ("", Toolbox::AutodetectMimeType("/")); + ASSERT_EQ("", Toolbox::AutodetectMimeType("a/a")); + + ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../NOTES.txt")); + ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../coucou.xml/NOTES.txt")); + ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("../.xml")); + + ASSERT_EQ("application/javascript", Toolbox::AutodetectMimeType("NOTES.js")); + ASSERT_EQ("application/json", Toolbox::AutodetectMimeType("NOTES.json")); + ASSERT_EQ("application/pdf", Toolbox::AutodetectMimeType("NOTES.pdf")); + ASSERT_EQ("text/css", Toolbox::AutodetectMimeType("NOTES.css")); + ASSERT_EQ("text/html", Toolbox::AutodetectMimeType("NOTES.html")); + ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("NOTES.txt")); + ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("NOTES.xml")); + ASSERT_EQ("image/gif", Toolbox::AutodetectMimeType("NOTES.gif")); + ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpg")); + ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpeg")); + ASSERT_EQ("image/png", Toolbox::AutodetectMimeType("NOTES.png")); +} + +TEST(Toolbox, ComputeMD5) +{ + std::string s; + + // # echo -n "Hello" | md5sum + + Toolbox::ComputeMD5(s, "Hello"); + ASSERT_EQ("8b1a9953c4611296a827abf8c47804d7", s); + Toolbox::ComputeMD5(s, ""); + ASSERT_EQ("d41d8cd98f00b204e9800998ecf8427e", s); +} + +TEST(Toolbox, ComputeSHA1) +{ + std::string s; + + Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog"); + ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s); + Toolbox::ComputeSHA1(s, ""); + ASSERT_EQ("da39a3ee-5e6b4b0d-3255bfef-95601890-afd80709", s); +} + + +TEST(Toolbox, Base64) +{ + ASSERT_EQ("", Toolbox::EncodeBase64("")); + ASSERT_EQ("YQ==", Toolbox::EncodeBase64("a")); + + const std::string hello = "SGVsbG8gd29ybGQ="; + ASSERT_EQ(hello, Toolbox::EncodeBase64("Hello world")); + ASSERT_EQ("Hello world", Toolbox::DecodeBase64(hello)); +} + +TEST(Toolbox, PathToExecutable) +{ + printf("[%s]\n", Toolbox::GetPathToExecutable().c_str()); + printf("[%s]\n", Toolbox::GetDirectoryOfExecutable().c_str()); +} + +TEST(Toolbox, StripSpaces) +{ + ASSERT_EQ("", Toolbox::StripSpaces(" \t \r \n ")); + ASSERT_EQ("coucou", Toolbox::StripSpaces(" coucou \t \r \n ")); + ASSERT_EQ("cou cou", Toolbox::StripSpaces(" cou cou \n ")); + ASSERT_EQ("c", Toolbox::StripSpaces(" \n\t c\r \n ")); +} + +TEST(Toolbox, Case) +{ + std::string s = "CoU"; + std::string ss; + + Toolbox::ToUpperCase(ss, s); + ASSERT_EQ("COU", ss); + Toolbox::ToLowerCase(ss, s); + ASSERT_EQ("cou", ss); + + s = "CoU"; + Toolbox::ToUpperCase(s); + ASSERT_EQ("COU", s); + + s = "CoU"; + Toolbox::ToLowerCase(s); + ASSERT_EQ("cou", s); +} + + +#include <glog/logging.h> + +TEST(Logger, Basic) +{ + LOG(INFO) << "I say hello"; +} + +TEST(Toolbox, ConvertFromLatin1) +{ + // This is a Latin-1 test string + const unsigned char data[10] = { 0xe0, 0xe9, 0xea, 0xe7, 0x26, 0xc6, 0x61, 0x62, 0x63, 0x00 }; + + /*FILE* f = fopen("/tmp/tutu", "w"); + fwrite(&data[0], 9, 1, f); + fclose(f);*/ + + std::string s((char*) &data[0], 10); + ASSERT_EQ("&abc", Toolbox::ConvertToAscii(s)); + + // Open in Emacs, then save with UTF-8 encoding, then "hexdump -C" + std::string utf8 = Toolbox::ConvertToUtf8(s, "ISO-8859-1"); + ASSERT_EQ(15u, utf8.size()); + ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[0])); + ASSERT_EQ(0xa0, static_cast<unsigned char>(utf8[1])); + ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[2])); + ASSERT_EQ(0xa9, static_cast<unsigned char>(utf8[3])); + ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[4])); + ASSERT_EQ(0xaa, static_cast<unsigned char>(utf8[5])); + ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[6])); + ASSERT_EQ(0xa7, static_cast<unsigned char>(utf8[7])); + ASSERT_EQ(0x26, static_cast<unsigned char>(utf8[8])); + ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[9])); + ASSERT_EQ(0x86, static_cast<unsigned char>(utf8[10])); + ASSERT_EQ(0x61, static_cast<unsigned char>(utf8[11])); + ASSERT_EQ(0x62, static_cast<unsigned char>(utf8[12])); + ASSERT_EQ(0x63, static_cast<unsigned char>(utf8[13])); + ASSERT_EQ(0x00, static_cast<unsigned char>(utf8[14])); // Null-terminated string +} + +TEST(Toolbox, UrlDecode) +{ + std::string s; + + s = "Hello%20World"; + Toolbox::UrlDecode(s); + ASSERT_EQ("Hello World", s); + + s = "%21%23%24%26%27%28%29%2A%2B%2c%2f%3A%3b%3d%3f%40%5B%5D%90%ff"; + Toolbox::UrlDecode(s); + std::string ss = "!#$&'()*+,/:;=?@[]"; + ss.push_back((char) 144); + ss.push_back((char) 255); + ASSERT_EQ(ss, s); + + s = "(2000%2C00A4)+Other"; + Toolbox::UrlDecode(s); + ASSERT_EQ("(2000,00A4) Other", s); +} + + +#if defined(__linux) +TEST(OrthancInitialization, AbsoluteDirectory) +{ + ASSERT_EQ("/tmp/hello", InterpretRelativePath("/tmp", "hello")); + ASSERT_EQ("/tmp", InterpretRelativePath("/tmp", "/tmp")); +} +#endif + + + +#include "../OrthancServer/ServerEnumerations.h" + +TEST(EnumerationDictionary, Simple) +{ + Toolbox::EnumerationDictionary<MetadataType> d; + + ASSERT_THROW(d.Translate("ReceptionDate"), OrthancException); + ASSERT_EQ(MetadataType_ModifiedFrom, d.Translate("5")); + ASSERT_EQ(256, d.Translate("256")); + + d.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate"); + + ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("ReceptionDate")); + ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("2")); + ASSERT_EQ("ReceptionDate", d.Translate(MetadataType_Instance_ReceptionDate)); + + ASSERT_THROW(d.Add(MetadataType_Instance_ReceptionDate, "Hello"), OrthancException); + ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "ReceptionDate"), OrthancException); // already used + ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "1024"), OrthancException); // cannot register numbers + d.Add(MetadataType_ModifiedFrom, "ModifiedFrom"); // ok +} + + +TEST(EnumerationDictionary, ServerEnumerations) +{ + ASSERT_STREQ("Patient", EnumerationToString(ResourceType_Patient)); + ASSERT_STREQ("Study", EnumerationToString(ResourceType_Study)); + ASSERT_STREQ("Series", EnumerationToString(ResourceType_Series)); + ASSERT_STREQ("Instance", EnumerationToString(ResourceType_Instance)); + + ASSERT_STREQ("ModifiedSeries", EnumerationToString(ChangeType_ModifiedSeries)); + + ASSERT_STREQ("Failure", EnumerationToString(StoreStatus_Failure)); + ASSERT_STREQ("Success", EnumerationToString(StoreStatus_Success)); + + ASSERT_STREQ("CompletedSeries", EnumerationToString(ChangeType_CompletedSeries)); + + ASSERT_EQ("IndexInSeries", EnumerationToString(MetadataType_Instance_IndexInSeries)); + ASSERT_EQ("LastUpdate", EnumerationToString(MetadataType_LastUpdate)); + + ASSERT_EQ(ResourceType_Patient, StringToResourceType("PATienT")); + ASSERT_EQ(ResourceType_Study, StringToResourceType("STudy")); + ASSERT_EQ(ResourceType_Series, StringToResourceType("SeRiEs")); + ASSERT_EQ(ResourceType_Instance, StringToResourceType("INStance")); + ASSERT_EQ(ResourceType_Instance, StringToResourceType("IMagE")); + ASSERT_THROW(StringToResourceType("heLLo"), OrthancException); + + ASSERT_EQ(2047, StringToMetadata("2047")); + ASSERT_THROW(StringToMetadata("Ceci est un test"), OrthancException); + ASSERT_THROW(RegisterUserMetadata(128, ""), OrthancException); // too low (< 1024) + ASSERT_THROW(RegisterUserMetadata(128000, ""), OrthancException); // too high (> 65535) + RegisterUserMetadata(2047, "Ceci est un test"); + ASSERT_EQ(2047, StringToMetadata("2047")); + ASSERT_EQ(2047, StringToMetadata("Ceci est un test")); +} + + + +TEST(Toolbox, WriteFile) +{ + std::string path; + + { + Toolbox::TemporaryFile tmp; + path = tmp.GetPath(); + + std::string s; + s.append("Hello"); + s.push_back('\0'); + s.append("World"); + ASSERT_EQ(11u, s.size()); + + Toolbox::WriteFile(s, path.c_str()); + + std::string t; + Toolbox::ReadFile(t, path.c_str()); + + ASSERT_EQ(11u, t.size()); + ASSERT_EQ(0, t[5]); + ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size())); + } + + std::string u; + ASSERT_THROW(Toolbox::ReadFile(u, path.c_str()), OrthancException); +} + + +TEST(Toolbox, Wildcard) +{ + ASSERT_EQ("abcd", Toolbox::WildcardToRegularExpression("abcd")); + ASSERT_EQ("ab.*cd", Toolbox::WildcardToRegularExpression("ab*cd")); + ASSERT_EQ("ab..cd", Toolbox::WildcardToRegularExpression("ab??cd")); + ASSERT_EQ("a.*b.c.*d", Toolbox::WildcardToRegularExpression("a*b?c*d")); + ASSERT_EQ("a\\{b\\]", Toolbox::WildcardToRegularExpression("a{b]")); +} + + +TEST(Toolbox, Tokenize) +{ + std::vector<std::string> t; + + Toolbox::TokenizeString(t, "", ','); + ASSERT_EQ(1, t.size()); + ASSERT_EQ("", t[0]); + + Toolbox::TokenizeString(t, "abc", ','); + ASSERT_EQ(1, t.size()); + ASSERT_EQ("abc", t[0]); + + Toolbox::TokenizeString(t, "ab,cd,ef,", ','); + ASSERT_EQ(4, t.size()); + ASSERT_EQ("ab", t[0]); + ASSERT_EQ("cd", t[1]); + ASSERT_EQ("ef", t[2]); + ASSERT_EQ("", t[3]); +} + + + +#if defined(__linux) +#include <endian.h> +#endif + +TEST(Toolbox, Endianness) +{ + // Parts of this test come from Adam Conrad + // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=728822#5 + +#if defined(_WIN32) + ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); + +#elif defined(__linux) + +#if !defined(__BYTE_ORDER) +# error Support your platform here +#endif + +# if __BYTE_ORDER == __BIG_ENDIAN + ASSERT_EQ(Endianness_Big, Toolbox::DetectEndianness()); +# else // __LITTLE_ENDIAN + ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); +# endif + +#else +#error Support your platform here +#endif +} + + +int main(int argc, char **argv) +{ + // Initialize Google's logging library. + FLAGS_logtostderr = true; + FLAGS_minloglevel = 0; + + // Go to trace-level verbosity + //FLAGS_v = 1; + + Toolbox::DetectEndianness(); + + google::InitGoogleLogging("Orthanc"); + + OrthancInitialize(); + ::testing::InitGoogleTest(&argc, argv); + int result = RUN_ALL_TESTS(); + OrthancFinalize(); + return result; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/Versions.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,100 @@ +#include "gtest/gtest.h" + +#include <stdint.h> +#include <math.h> +#include <png.h> +#include <ctype.h> +#include <zlib.h> +#include <curl/curl.h> +#include <boost/version.hpp> +#include <sqlite3.h> +#include <lua.h> +#include <openssl/opensslv.h> + + +TEST(Versions, Zlib) +{ + ASSERT_STREQ(zlibVersion(), ZLIB_VERSION); +} + +TEST(Versions, Curl) +{ + curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); + ASSERT_STREQ(LIBCURL_VERSION, v->version); +} + +TEST(Versions, Png) +{ + ASSERT_EQ(PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE, + png_access_version_number()); +} + +TEST(Versions, SQLite) +{ + // http://www.sqlite.org/capi3ref.html#sqlite3_libversion + assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER ); + assert(strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) == 0); + assert(strcmp(sqlite3_libversion(), SQLITE_VERSION) == 0); + + // Ensure that the SQLite version is above 3.7.0. + // "sqlite3_create_function_v2" is not defined in previous versions. + ASSERT_GE(SQLITE_VERSION_NUMBER, 3007000); +} + + +TEST(Versions, Lua) +{ + // Ensure that the Lua version is above 5.1.0. This version has + // introduced some API changes. + ASSERT_GE(LUA_VERSION_NUM, 501); +} + + +#if ORTHANC_STATIC == 1 +TEST(Versions, ZlibStatic) +{ + ASSERT_STREQ("1.2.7", zlibVersion()); +} + +TEST(Versions, BoostStatic) +{ + ASSERT_STREQ("1_55", BOOST_LIB_VERSION); +} + +TEST(Versions, CurlStatic) +{ + curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); + ASSERT_STREQ("7.26.0", v->version); +} + +TEST(Versions, PngStatic) +{ + ASSERT_EQ(10512, png_access_version_number()); + ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING); +} + +TEST(Versions, CurlSslStatic) +{ + curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW); + + // Check that SSL support is enabled when required + bool curlSupportsSsl = vinfo->features & CURL_VERSION_SSL; + +#if ORTHANC_SSL_ENABLED == 0 + ASSERT_FALSE(curlSupportsSsl); +#else + ASSERT_TRUE(curlSupportsSsl); +#endif +} + +TEST(Version, LuaStatic) +{ + ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE); +} + +TEST(Version, OpenSslStatic) +{ + ASSERT_EQ(0x1000107fL /* openssl-1.0.1g */, OPENSSL_VERSION_NUMBER); +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/Zip.cpp Tue Apr 22 16:47:21 2014 +0200 @@ -0,0 +1,133 @@ +#include "gtest/gtest.h" + +#include "../Core/OrthancException.h" +#include "../Core/Compression/ZipWriter.h" +#include "../Core/Compression/HierarchicalZipWriter.h" +#include "../Core/Toolbox.h" + + +using namespace Orthanc; + +TEST(ZipWriter, Basic) +{ + Orthanc::ZipWriter w; + w.SetOutputPath("hello.zip"); + w.Open(); + w.OpenFile("world/hello"); + w.Write("Hello world"); +} + + +TEST(ZipWriter, Basic64) +{ + Orthanc::ZipWriter w; + w.SetOutputPath("hello64.zip"); + w.SetZip64(true); + w.Open(); + w.OpenFile("world/hello"); + w.Write("Hello world"); +} + + +TEST(ZipWriter, Exceptions) +{ + Orthanc::ZipWriter w; + ASSERT_THROW(w.Open(), Orthanc::OrthancException); + w.SetOutputPath("hello3.zip"); + w.Open(); + ASSERT_THROW(w.Write("hello world"), Orthanc::OrthancException); +} + + + + + +namespace Orthanc +{ + // The namespace is necessary + // http://code.google.com/p/googletest/wiki/AdvancedGuide#Private_Class_Members + + TEST(HierarchicalZipWriter, Index) + { + HierarchicalZipWriter::Index i; + ASSERT_EQ("hello", i.OpenFile("hello")); + ASSERT_EQ("hello-2", i.OpenFile("hello")); + ASSERT_EQ("coucou", i.OpenFile("coucou")); + ASSERT_EQ("hello-3", i.OpenFile("hello")); + + i.OpenDirectory("coucou"); + + ASSERT_EQ("coucou-2/world", i.OpenFile("world")); + ASSERT_EQ("coucou-2/world-2", i.OpenFile("world")); + + i.OpenDirectory("world"); + + ASSERT_EQ("coucou-2/world-3/hello", i.OpenFile("hello")); + ASSERT_EQ("coucou-2/world-3/hello-2", i.OpenFile("hello")); + + i.CloseDirectory(); + + ASSERT_EQ("coucou-2/world-4", i.OpenFile("world")); + + i.CloseDirectory(); + + ASSERT_EQ("coucou-3", i.OpenFile("coucou")); + + ASSERT_THROW(i.CloseDirectory(), OrthancException); + } + + + TEST(HierarchicalZipWriter, Filenames) + { + ASSERT_EQ("trE hell", HierarchicalZipWriter::Index::KeepAlphanumeric(" ÊtrE hellô ")); + + // The "^" character is considered as a space in DICOM + ASSERT_EQ("Hel lo world", HierarchicalZipWriter::Index::KeepAlphanumeric(" Hel^^ ^\r\n\t^^lo \t <world> ")); + } +} + + +TEST(HierarchicalZipWriter, Basic) +{ + static const std::string SPACES = " "; + + HierarchicalZipWriter w("hello2.zip"); + + w.SetCompressionLevel(0); + + // Inside "/" + w.OpenFile("hello"); + w.Write(SPACES + "hello\n"); + w.OpenFile("hello"); + w.Write(SPACES + "hello-2\n"); + w.OpenDirectory("hello"); + + // Inside "/hello-3" + w.OpenFile("hello"); + w.Write(SPACES + "hello\n"); + w.OpenDirectory("hello"); + + w.SetCompressionLevel(9); + + // Inside "/hello-3/hello-2" + w.OpenFile("hello"); + w.Write(SPACES + "hello\n"); + w.OpenFile("hello"); + w.Write(SPACES + "hello-2\n"); + w.CloseDirectory(); + + // Inside "/hello-3" + w.OpenFile("hello"); + w.Write(SPACES + "hello-3\n"); + + /** + + TO CHECK THE CONTENT OF THE "hello2.zip" FILE: + + # unzip -v hello2.zip + + => There must be 6 files. The first 3 files must have a negative + compression ratio. + + **/ +}