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
Binary file Resources/OrthancLogo.png has changed
Binary file Resources/OrthancLogoDocumentation.png has changed
--- /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.
+
+  **/
+}