changeset 759:8cfc6119a5bd dicom-rt

integration mainline -> dicom-rt
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 16 Apr 2014 16:04:55 +0200
parents b82292ba2083 (current diff) 40d09221077a (diff)
children 12a3f2eaa99a
files CMakeLists.txt Core/Toolbox.cpp Core/Toolbox.h OrthancCppClient/SharedLibrary/Laaw/laaw-exports.h OrthancCppClient/SharedLibrary/Laaw/laaw.h OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/OrthancRestApi.cpp OrthancServer/OrthancRestApi.h OrthancServer/OrthancRestApi/OrthancRestApi.cpp OrthancServer/OrthancRestApi/OrthancRestApi.h OrthancServer/ServerContext.h OrthancServer/main.cpp Resources/Archives/MessageWithDestination.cpp Resources/Archives/OrthancCppClient.cmake Resources/Archives/PrepareDatabase-v1.sql UnitTests/FileStorage.cpp UnitTests/Lua.cpp UnitTests/MemoryCache.cpp UnitTests/Png.cpp UnitTests/RestApi.cpp UnitTests/SQLite.cpp UnitTests/SQLiteChromium.cpp UnitTests/ServerIndex.cpp UnitTests/Versions.cpp UnitTests/Zip.cpp UnitTests/main.cpp UnitTestsSources/UnitTestsMain.cpp
diffstat 241 files changed, 11248 insertions(+), 7197 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Oct 17 14:21:50 2013 +0200
+++ b/CMakeLists.txt	Wed Apr 16 16:04:55 2014 +0200
@@ -11,27 +11,31 @@
 #####################################################################
 
 # Parameters of the build
-SET(STATIC_BUILD ON CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+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 "/usr/share/dcmtk" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (ignored in standalone builds)") 
+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 to fine-tune linking against system libraries
-SET(USE_DYNAMIC_JSONCPP OFF CACHE BOOL "Use the dynamic version of JsonCpp")
-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")
-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_USE_GTEST_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
+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_USE_GTEST_SOURCE_PACKAGE)
+# 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)
@@ -63,6 +67,16 @@
   )
 
 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)
@@ -71,16 +85,6 @@
   add_definitions(-DORTHANC_SSL_ENABLED=0)
 endif()
 
-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/LibCurlConfiguration.cmake)
-include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake)
-include(${CMAKE_SOURCE_DIR}/Resources/CMake/LuaConfiguration.cmake)
-
 
 
 #####################################################################
@@ -90,6 +94,7 @@
 # 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
   )
@@ -202,15 +207,23 @@
   OrthancServer/Internals/MoveScp.cpp
   OrthancServer/Internals/StoreScp.cpp
   OrthancServer/OrthancInitialization.cpp
-  OrthancServer/OrthancRestApi.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
 
-  OrthancServer/RadiotherapyRestApi.cpp
+  #OrthancServer/RadiotherapyRestApi.cpp
   )
 
 # Ensure autogenerated code is built before building ServerLibrary
@@ -228,37 +241,43 @@
 
 install(
   TARGETS Orthanc
-  RUNTIME DESTINATION bin
+  RUNTIME DESTINATION sbin
   )
 
 
 
 #####################################################################
-## Build the unit tests if required
+## Build the unit tests
 #####################################################################
 
-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/Png.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)
+if (UNIT_TESTS_WITH_HTTP_CONNEXIONS)
+  add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1)
+else()
+  add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=0)
+endif()
 
-  if (${OPENSSL_SOURCES_LENGTH} GREATER 0)
-    target_link_libraries(UnitTests OpenSSL)
-  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()
 
 
--- a/Core/Cache/ICachePageProvider.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Cache/ICachePageProvider.h	Wed Apr 16 16:04:55 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/Cache/LeastRecentlyUsedIndex.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Cache/LeastRecentlyUsedIndex.h	Wed Apr 16 16:04:55 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/Cache/MemoryCache.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Cache/MemoryCache.cpp	Wed Apr 16 16:04:55 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/Cache/MemoryCache.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Cache/MemoryCache.h	Wed Apr 16 16:04:55 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/ChunkedBuffer.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/ChunkedBuffer.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/ChunkedBuffer.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Compression/BufferCompressor.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Compression/BufferCompressor.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Compression/HierarchicalZipWriter.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Compression/HierarchicalZipWriter.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Compression/ZipWriter.cpp	Wed Apr 16 16:04:55 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,7 +29,7 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-#ifdef _WIN32
+#ifndef NOMINMAX
 #define NOMINMAX
 #endif
 
@@ -80,6 +80,7 @@
   {
     compressionLevel_ = 6;
     hasFileInZip_ = false;
+    isZip64_ = false;
 
     pimpl_->file_ = NULL;
   }
@@ -117,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);
@@ -130,6 +140,12 @@
     path_ = path;
   }
 
+  void ZipWriter::SetZip64(bool isZip64)
+  {
+    Close();
+    isZip64_ = isZip64;
+  }
+
   void ZipWriter::SetCompressionLevel(uint8_t level)
   {
     if (level >= 10)
@@ -137,6 +153,7 @@
       throw OrthancException("ZIP compression level must be between 0 (no compression) and 9 (highest compression");
     }
 
+    Close();
     compressionLevel_ = level;
   }
 
@@ -147,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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Compression/ZipWriter.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Compression/ZlibCompressor.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Compression/ZlibCompressor.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomArray.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomArray.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomElement.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomInstanceHasher.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomInstanceHasher.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Wed Apr 16 16:04:55 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>
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomMap.cpp	Wed Apr 16 16:04:55 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
@@ -128,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;
     }
@@ -180,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()));
     }
@@ -199,7 +199,7 @@
     }
     else
     {
-      throw OrthancException("Inexistent tag");
+      throw OrthancException(ErrorCode_InexistentTag);
     }
   }
 
@@ -280,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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomMap.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomNullValue.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomString.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomTag.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomTag.h	Wed Apr 16 16:04:55 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
@@ -110,4 +110,9 @@
   // 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/DicomFormat/DicomValue.h	Wed Apr 16 16:04:55 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/EnumerationDictionary.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/EnumerationDictionary.h	Wed Apr 16 16:04:55 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/Enumerations.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Enumerations.cpp	Wed Apr 16 16:04:55 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 "Enumerations.h"
 
 #include "OrthancException.h"
+#include "Toolbox.h"
 
 namespace Orthanc
 {
@@ -222,4 +223,55 @@
       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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Enumerations.h	Wed Apr 16 16:04:55 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,7 +32,7 @@
 
 #pragma once
 
-#include "../OrthancCppClient/SharedLibrary/Laaw/laaw.h"
+#include <laaw/laaw.h>
 
 namespace Orthanc
 {
@@ -66,7 +66,9 @@
     ErrorCode_Timeout,
     ErrorCode_UnknownResource,
     ErrorCode_IncompatibleDatabaseVersion,
-    ErrorCode_FullStorage
+    ErrorCode_FullStorage,
+    ErrorCode_CorruptedFile,
+    ErrorCode_InexistentTag
   };
 
   /**
@@ -225,12 +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);
 }
--- a/Core/FileFormats/PngReader.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileFormats/PngReader.cpp	Wed Apr 16 16:04:55 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
@@ -242,7 +242,7 @@
                                           png_bytep outBytes, 
                                           png_size_t byteCountToRead)
   {
-    MemoryBuffer* from = (MemoryBuffer*) png_get_io_ptr(png_ptr);
+    MemoryBuffer* from = reinterpret_cast<MemoryBuffer*>(png_get_io_ptr(png_ptr));
 
     if (!from->ok_)
     {
--- a/Core/FileFormats/PngReader.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileFormats/PngReader.h	Wed Apr 16 16:04:55 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
@@ -96,6 +96,11 @@
 
     void ReadFromFile(const char* filename);
 
+    void ReadFromFile(const std::string& filename)
+    {
+      ReadFromFile(filename.c_str());
+    }
+
     void ReadFromMemory(const void* buffer,
                         size_t size);
 
--- a/Core/FileFormats/PngWriter.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileFormats/PngWriter.cpp	Wed Apr 16 16:04:55 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
@@ -174,15 +174,18 @@
       {
       case PixelFormat_Grayscale16:
       case PixelFormat_SignedGrayscale16:
-        png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]);
-
+      {
+        int transforms = 0;
         if (Toolbox::DetectEndianness() == Endianness_Little)
         {
-          // Must swap the endianness!!
-          png_write_png(pimpl_->png_, pimpl_->info_, PNG_TRANSFORM_SWAP_ENDIAN, NULL);
+          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]);
@@ -228,7 +231,7 @@
                              png_bytep data, 
                              png_size_t size)
   {
-    ChunkedBuffer* buffer = (ChunkedBuffer*) png_get_io_ptr(png_ptr);
+    ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr));
     buffer->AddChunk(reinterpret_cast<const char*>(data), size);
   }
 
--- a/Core/FileFormats/PngWriter.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileFormats/PngWriter.h	Wed Apr 16 16:04:55 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/CompressedFileStorageAccessor.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileStorage/CompressedFileStorageAccessor.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileStorage/FileInfo.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileStorage/FileStorage.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileStorage/FileStorage.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileStorage/FileStorageAccessor.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileStorage/FileStorageAccessor.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileStorage/StorageAccessor.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/FileStorage/StorageAccessor.h	Wed Apr 16 16:04:55 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)
--- a/Core/HttpClient.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpClient.cpp	Wed Apr 16 16:04:55 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
@@ -254,9 +254,4 @@
   {
     curl_global_cleanup();
   }
-
-  const char* HttpClient::GetLastStatusText() const
-  {
-    return EnumerationToString(lastStatus_);
-  }
 }
--- a/Core/HttpClient.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpClient.h	Wed Apr 16 16:04:55 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
@@ -115,8 +115,6 @@
       return lastStatus_;
     }
 
-    const char* GetLastStatusText() const;
-
     void SetCredentials(const char* username,
                         const char* password);
 
--- a/Core/HttpServer/BufferHttpSender.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/BufferHttpSender.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Wed Apr 16 16:04:55 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
@@ -79,7 +79,7 @@
       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(HttpStatus_404_NotFound);
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h	Wed Apr 16 16:04:55 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/FilesystemHttpHandler.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.cpp	Wed Apr 16 16:04:55 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/FilesystemHttpHandler.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.h	Wed Apr 16 16:04:55 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.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/HttpFileSender.cpp	Wed Apr 16 16:04:55 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.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/HttpFileSender.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/HttpHandler.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/HttpHandler.h	Wed Apr 16 16:04:55 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/HttpOutput.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/HttpOutput.cpp	Wed Apr 16 16:04:55 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
@@ -89,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";
     }
@@ -144,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));
     }
--- a/Core/HttpServer/HttpOutput.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/HttpOutput.h	Wed Apr 16 16:04:55 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/MongooseServer.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/MongooseServer.cpp	Wed Apr 16 16:04:55 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))
       {
@@ -570,7 +573,7 @@
   {
     if (event == MG_NEW_REQUEST) 
     {
-      MongooseServer* that = (MongooseServer*) (request->user_data);
+      MongooseServer* that = reinterpret_cast<MongooseServer*>(request->user_data);
       MongooseOutput output(connection);
 
       // Check remote calls
@@ -751,6 +754,16 @@
     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
   }
 
 
@@ -816,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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/HttpServer/MongooseServer.h	Wed Apr 16 16:04:55 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/ICommand.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/ICommand.h	Wed Apr 16 16:04:55 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/IDynamicObject.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/IDynamicObject.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Lua/LuaContext.cpp	Wed Apr 16 16:04:55 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.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Lua/LuaContext.h	Wed Apr 16 16:04:55 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/LuaException.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Lua/LuaException.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Lua/LuaFunctionCall.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Lua/LuaFunctionCall.h	Wed Apr 16 16:04:55 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/MultiThreading/ArrayFilledByThreads.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/MultiThreading/ArrayFilledByThreads.h	Wed Apr 16 16:04:55 2014 +0200
@@ -2,7 +2,7 @@
 
 #include <boost/thread.hpp>
 
-#include "../ICommand.h"
+#include "../IDynamicObject.h"
 
 namespace Orthanc
 {
--- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Wed Apr 16 16:04:55 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/MultiThreading/BagOfRunnablesBySteps.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/MultiThreading/BagOfRunnablesBySteps.h	Wed Apr 16 16:04:55 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/MultiThreading/IRunnableBySteps.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/MultiThreading/IRunnableBySteps.h	Wed Apr 16 16:04:55 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/MultiThreading/SharedMessageQueue.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/MultiThreading/SharedMessageQueue.cpp	Wed Apr 16 16:04:55 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,7 +42,7 @@
 
   SharedMessageQueue::~SharedMessageQueue()
   {
-    for (Queue::iterator it = queue_.begin(); it != queue_.end(); it++)
+    for (Queue::iterator it = queue_.begin(); it != queue_.end(); ++it)
     {
       delete *it;
     }
@@ -89,7 +89,11 @@
 
     std::auto_ptr<IDynamicObject> message(queue_.front());
     queue_.pop_front();
-    emptied_.notify_all();
+
+    if (queue_.empty())
+    {
+      emptied_.notify_all();
+    }
 
     return message.release();
   }
@@ -101,7 +105,7 @@
     boost::mutex::scoped_lock lock(mutex_);
     
     // Wait for the queue to become empty
-    if (!queue_.empty())
+    while (!queue_.empty())
     {
       if (millisecondsTimeout == 0)
       {
--- a/Core/MultiThreading/SharedMessageQueue.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/MultiThreading/SharedMessageQueue.h	Wed Apr 16 16:04:55 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/MultiThreading/ThreadedCommandProcessor.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/MultiThreading/ThreadedCommandProcessor.cpp	Wed Apr 16 16:04:55 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
@@ -158,6 +158,11 @@
 
   void ThreadedCommandProcessor::Post(ICommand* command)
   {
+    if (command == NULL)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
     boost::mutex::scoped_lock lock(mutex_);
     queue_.Enqueue(command);
     remainingCommands_++;
--- a/Core/MultiThreading/ThreadedCommandProcessor.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/MultiThreading/ThreadedCommandProcessor.h	Wed Apr 16 16:04:55 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/OrthancException.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/OrthancException.cpp	Wed Apr 16 16:04:55 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
@@ -105,6 +105,12 @@
       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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/OrthancException.h	Wed Apr 16 16:04:55 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/RestApi/RestApi.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/RestApi/RestApi.cpp	Wed Apr 16 16:04:55 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;
     } 
@@ -194,21 +194,13 @@
     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);
           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);
         }
       }
@@ -216,21 +208,13 @@
     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);
           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);
         }
       }
@@ -238,21 +222,13 @@
     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);
           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);
         }
       }
@@ -260,19 +236,13 @@
     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);
           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);
         }
       }
--- a/Core/RestApi/RestApi.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/RestApi/RestApi.h	Wed Apr 16 16:04:55 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();
--- a/Core/RestApi/RestApiOutput.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/RestApi/RestApiOutput.cpp	Wed Apr 16 16:04:55 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/RestApiOutput.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/RestApi/RestApiOutput.h	Wed Apr 16 16:04:55 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.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/RestApi/RestApiPath.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/RestApi/RestApiPath.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/Connection.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/Connection.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/FunctionContext.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/FunctionContext.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/IScalarFunction.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/Statement.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/Statement.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/StatementId.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/StatementId.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/StatementReference.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/StatementReference.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/Transaction.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/SQLite/Transaction.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Toolbox.cpp	Wed Apr 16 16:04:55 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,6 +42,7 @@
 #include <boost/uuid/sha1.hpp>
 #include <algorithm>
 #include <ctype.h>
+#include <boost/regex.hpp> 
 
 #if defined(_WIN32)
 #include <windows.h>
@@ -156,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)
@@ -186,6 +176,7 @@
 #else
     signal(SIGINT, SignalHandler);
     signal(SIGQUIT, SignalHandler);
+    signal(SIGTERM, SignalHandler);
 #endif
   
     finish = false;
@@ -199,6 +190,7 @@
 #else
     signal(SIGINT, NULL);
     signal(SIGQUIT, NULL);
+    signal(SIGTERM, NULL);
 #endif
   }
 
@@ -216,6 +208,20 @@
   }
 
 
+  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) 
@@ -448,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];
@@ -728,45 +750,58 @@
   }
 
 
+  std::string Toolbox::WildcardToRegularExpression(const std::string& source)
+  {
+    // TODO - Speed up this with a regular expression
 
-  void Toolbox::Split(std::vector<std::string>& result,
-                      const std::string& source,
-                      char delimiter)
+    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)
   {
-    if (source.size() == 0)
+    result.clear();
+
+    std::string currentItem;
+
+    for (size_t i = 0; i < value.size(); i++)
     {
-      result.clear();
-      return;
-    }
-
-    size_t count = 1;
-    for (size_t i = 0; i < source.size(); i++)
-    {
-      if (source[i] == delimiter)
+      if (value[i] == separator)
       {
-        count++;
+        result.push_back(currentItem);
+        currentItem.clear();
+      }
+      else
+      {
+        currentItem.push_back(value[i]);
       }
     }
 
-    result.clear();
-    result.resize(count);
-
-    size_t pos = 0;
-    size_t start = 0;
-    while (start < source.size())
-    {
-      assert(pos < count);
+    result.push_back(currentItem);
+  }
+}
 
-      size_t end = start;
-      while (end < source.size() && 
-             source[end] != delimiter)
-      {
-        end++;
-      }
-
-      result[pos++] = source.substr(start, end - start);
-      start = end + 1;
-    }
-  }
-
-}
--- a/Core/Toolbox.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Toolbox.h	Wed Apr 16 16:04:55 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,9 +50,15 @@
   {
     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);
@@ -60,8 +66,6 @@
     void WriteFile(const std::string& content,
                    const std::string& path);
 
-    void Sleep(uint32_t seconds);
-
     void USleep(uint64_t microSeconds);
 
     void RemoveFile(const std::string& path);
@@ -82,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);
 
@@ -109,8 +117,10 @@
 
     Endianness DetectEndianness();
 
-    void Split(std::vector<std::string>& result,
-               const std::string& source,
-               char delimiter);
+    std::string WildcardToRegularExpression(const std::string& s);
+
+    void TokenizeString(std::vector<std::string>& result,
+                        const std::string& source,
+                        char separator);
   }
 }
--- a/Core/Uuid.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Uuid.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Core/Uuid.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/INSTALL	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/NEWS	Wed Apr 16 16:04:55 2014 +0200
@@ -1,6 +1,71 @@
 Pending changes in the mainline
 ===============================
 
+
+
+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
 
--- a/OrthancCppClient/Instance.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/Instance.cpp	Wed Apr 16 16:04:55 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
@@ -265,4 +265,21 @@
     }
   }
 
+
+  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();
+  }
 }
--- a/OrthancCppClient/Instance.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/Instance.h	Wed Apr 16 16:04:55 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
@@ -57,6 +57,7 @@
     std::auto_ptr<Orthanc::PngReader> reader_;
     Orthanc::ImageExtractionMode mode_;
     std::auto_ptr<std::string> dicom_;
+    std::string content_;
 
     void DownloadImage();
 
@@ -185,5 +186,17 @@
 
     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;
   };
 }
--- a/OrthancCppClient/OrthancClientException.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/OrthancClientException.h	Wed Apr 16 16:04:55 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 "../Core/OrthancException.h"
-#include "SharedLibrary/Laaw/laaw.h"
+#include <laaw/laaw.h>
 
 namespace OrthancClient
 {
--- a/OrthancCppClient/OrthancConnection.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/OrthancConnection.cpp	Wed Apr 16 16:04:55 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/OrthancCppClient/OrthancConnection.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/OrthancConnection.h	Wed Apr 16 16:04:55 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/OrthancCppClient/Patient.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/Patient.cpp	Wed Apr 16 16:04:55 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/OrthancCppClient/Patient.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/Patient.h	Wed Apr 16 16:04:55 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/OrthancCppClient/Series.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/Series.cpp	Wed Apr 16 16:04:55 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
@@ -273,7 +273,11 @@
     ReadSeries();
     status_ = Status3DImage_NotTested;
     url_ = std::string(connection_.GetOrthancUrl()) + "/series/" + id_;
+
     isVoxelSizeRead_ = false;
+    voxelSizeX_ = 0;
+    voxelSizeY_ = 0;
+    voxelSizeZ_ = 0;
 
     instances_.SetThreadCount(connection.GetThreadCount());
   }
@@ -465,7 +469,7 @@
     }
 
     uint8_t* stackTarget = reinterpret_cast<uint8_t*>(target);
-    for (Instances::iterator it = instances.begin(); it != instances.end(); it++)
+    for (Instances::iterator it = instances.begin(); it != instances.end(); ++it)
     {
       processor.Post(new ImageDownloadCommand(*it->second, format, mode, stackTarget, lineStride));
       stackTarget += stackStride;
--- a/OrthancCppClient/Series.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/Series.h	Wed Apr 16 16:04:55 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/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp	Wed Apr 16 16:04:55 2014 +0200
@@ -30,7 +30,7 @@
  **/
 
 
-#include <laaw.h>
+#include <laaw/laaw.h>
 #include <string.h>  // For strcpy() and strlen()
 #include <stdlib.h>  // For free()
 
@@ -1412,6 +1412,52 @@
         }
       }
 
+      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()
   {
@@ -1430,22 +1476,22 @@
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetCopyright()
   {
-    return "(c) 2012-2013, Sebastien Jodogne, CHU of Liege";
+    return "(c) 2012-2014, Sebastien Jodogne, CHU of Liege";
   }
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetVersion()
   {
-    return "0.6";
+    return "0.7";
   }
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFileVersion()
   {
-    return "0.6.0.2";
+    return "0.7.0.4";
   }
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFullVersion()
   {
-    return "0.6.2";
+    return "0.7.4";
   }
 
   LAAW_EXPORT_DLL_API void LAAW_CALL_CONVENTION LAAW_EXTERNC_FreeString(char* str)
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h	Wed Apr 16 16:04:55 2014 +0200
@@ -81,9 +81,9 @@
 
 /* cf. http://sourceforge.net/p/predef/wiki/Architectures/ */
 #ifdef __amd64__
-#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH  "libOrthancClient.so.0.6"
+#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH  "libOrthancClient.so.0.7"
 #else
-#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH  "libOrthancClient.so.0.6"
+#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH  "libOrthancClient.so.0.7"
 #endif
 
 #define LAAW_ORTHANC_CLIENT_CALL_CONV
@@ -203,7 +203,7 @@
   {
   private:
     LAAW_ORTHANC_CLIENT_HANDLE_TYPE  handle_;
-    LAAW_ORTHANC_CLIENT_FUNCTION_TYPE  functionsIndex_[60 + 1];
+    LAAW_ORTHANC_CLIENT_FUNCTION_TYPE  functionsIndex_[62 + 1];
 
 
 
@@ -239,7 +239,7 @@
     void FreeString(char* str)
     {
       typedef void (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (char*);
-      Function function = (Function) GetFunction(60);
+      Function function = (Function) GetFunction(62);
       function(str);
     }
 
@@ -372,8 +372,8 @@
 }
 
 
-
-void ::OrthancClient::Internals::Library::LoadFunctions()
+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");
@@ -386,12 +386,12 @@
    * It is assumed that the API does not change when the revision
    * number (MAJOR.MINOR.REVISION) changes.
    **/
-  if (strcmp(getVersion(), "0.6"))
+  if (strcmp(getVersion(), "0.7"))
   {
     throw ::OrthancClient::OrthancClientException("Mismatch between the C++ header and the library version");
   }
 
-  functionsIndex_[60] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_FreeString", "4");
+  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");
@@ -450,11 +450,13 @@
   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 <= 60; i++)
+  for (unsigned int i = 0; i <= 62; i++)
   {
     if (functionsIndex_[i] == (LAAW_ORTHANC_CLIENT_FUNCTION_TYPE) NULL)
     {
@@ -462,6 +464,7 @@
     }
   }
 }
+}}
 namespace OrthancClient
 {
   class OrthancConnection;
@@ -598,7 +601,7 @@
      *
      * @param other The original object.
      **/
-    OrthancConnection(const OrthancConnection& other) : pimpl_(other.pimpl_), isReference_(true) { }
+    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();
@@ -641,7 +644,7 @@
      *
      * @param other The original object.
      **/
-    Patient(const Patient& other) : pimpl_(other.pimpl_), isReference_(true) { }
+    Patient(const Patient& other) : isReference_(true), pimpl_(other.pimpl_) { }
     inline Patient(::OrthancClient::OrthancConnection& connection, const ::std::string& id);
     inline ~Patient();
     inline void Reload();
@@ -679,7 +682,7 @@
      *
      * @param other The original object.
      **/
-    Series(const Series& other) : pimpl_(other.pimpl_), isReference_(true) { }
+    Series(const Series& other) : isReference_(true), pimpl_(other.pimpl_) { }
     inline Series(::OrthancClient::OrthancConnection& connection, const ::std::string& id);
     inline ~Series();
     inline void Reload();
@@ -726,7 +729,7 @@
      *
      * @param other The original object.
      **/
-    Study(const Study& other) : pimpl_(other.pimpl_), isReference_(true) { }
+    Study(const Study& other) : isReference_(true), pimpl_(other.pimpl_) { }
     inline Study(::OrthancClient::OrthancConnection& connection, const ::std::string& id);
     inline ~Study();
     inline void Reload();
@@ -764,7 +767,7 @@
      *
      * @param other The original object.
      **/
-    Instance(const Instance& other) : pimpl_(other.pimpl_), isReference_(true) { }
+    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;
@@ -783,6 +786,8 @@
     inline const void* GetDicom();
     inline void DiscardImage();
     inline void DiscardDicom();
+    inline void LoadTagContent(const ::std::string& path);
+    inline ::std::string GetLoadedTagContent() const;
   };
 }
 
@@ -832,7 +837,7 @@
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*);
     Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(2);
     char* error = function(pimpl_);
-    ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
+    error = error;  // Remove warning about unused variable
   }
   /**
   * @brief Returns the number of threads for this connection.
@@ -1002,7 +1007,7 @@
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*);
     Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(13);
     char* error = function(pimpl_);
-    ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
+    error = error;  // Remove warning about unused variable
   }
   /**
   * @brief Reload the studies of this patient.
@@ -1116,7 +1121,7 @@
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*);
     Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(20);
     char* error = function(pimpl_);
-    ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
+    error = error;  // Remove warning about unused variable
   }
   /**
   * @brief Reload the instances of this series.
@@ -1377,7 +1382,7 @@
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*);
     Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(36);
     char* error = function(pimpl_);
-    ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
+    error = error;  // Remove warning about unused variable
   }
   /**
   * @brief Reload the series of this study.
@@ -1491,7 +1496,7 @@
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*);
     Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(43);
     char* error = function(pimpl_);
-    ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
+    error = error;  // Remove warning about unused variable
   }
   /**
   * @brief Get the %Orthanc identifier of this identifier.
@@ -1745,5 +1750,35 @@
     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_);
+  }
 }
 
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def	Wed Apr 16 16:04:55 2014 +0200
@@ -58,6 +58,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
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc	Wed Apr 16 16:04:55 2014 +0200
@@ -1,8 +1,8 @@
 #include <winver.h>
 
 VS_VERSION_INFO VERSIONINFO
-   FILEVERSION 0,6,0,2
-   PRODUCTVERSION 0,6,0,0
+   FILEVERSION 0,7,0,4
+   PRODUCTVERSION 0,7,0,0
    FILEOS VOS_NT_WINDOWS32
    FILETYPE VFT_DLL
    BEGIN
@@ -10,16 +10,16 @@
       BEGIN
          BLOCK "040904E4"
          BEGIN
-            VALUE "Comments", "Release 0.6.2"
+            VALUE "Comments", "Release 0.7.4"
             VALUE "CompanyName", "CHU of Liege"
             VALUE "FileDescription", "Native client to the REST API of Orthanc"
-            VALUE "FileVersion", "0.6.0.2"
+            VALUE "FileVersion", "0.7.0.4"
             VALUE "InternalName", "OrthancClient"
-            VALUE "LegalCopyright", "(c) 2012-2013, Sebastien Jodogne, CHU of Liege"
+            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.6"
+            VALUE "ProductVersion", "0.7"
          END
       END
 
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def	Wed Apr 16 16:04:55 2014 +0200
@@ -58,6 +58,8 @@
   LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e
   LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a
   LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c
+  LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd
+  LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb
   LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d
   LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207
   LAAW_EXTERNC_GetDescription
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc	Wed Apr 16 16:04:55 2014 +0200
@@ -1,8 +1,8 @@
 #include <winver.h>
 
 VS_VERSION_INFO VERSIONINFO
-   FILEVERSION 0,6,0,2
-   PRODUCTVERSION 0,6,0,0
+   FILEVERSION 0,7,0,4
+   PRODUCTVERSION 0,7,0,0
    FILEOS VOS_NT_WINDOWS32
    FILETYPE VFT_DLL
    BEGIN
@@ -10,16 +10,16 @@
       BEGIN
          BLOCK "040904E4"
          BEGIN
-            VALUE "Comments", "Release 0.6.2"
+            VALUE "Comments", "Release 0.7.4"
             VALUE "CompanyName", "CHU of Liege"
             VALUE "FileDescription", "Native client to the REST API of Orthanc"
-            VALUE "FileVersion", "0.6.0.2"
+            VALUE "FileVersion", "0.7.0.4"
             VALUE "InternalName", "OrthancClient"
-            VALUE "LegalCopyright", "(c) 2012-2013, Sebastien Jodogne, CHU of Liege"
+            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.6"
+            VALUE "ProductVersion", "0.7"
          END
       END
 
--- a/OrthancCppClient/SharedLibrary/Laaw/laaw-exports.h	Thu Oct 17 14:21:50 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-/**
- * 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
--- a/OrthancCppClient/SharedLibrary/Laaw/laaw.h	Thu Oct 17 14:21:50 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-/**
- * 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/Laaw/laaw/laaw-exports.h	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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();
+    }
+  };
+}
--- a/OrthancCppClient/SharedLibrary/Product.json	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/SharedLibrary/Product.json	Wed Apr 16 16:04:55 2014 +0200
@@ -2,7 +2,7 @@
   "Product" : "OrthancClient",
   "Description" : "Native client to the REST API of Orthanc",
   "Company" : "CHU of Liege",
-  "Copyright" : "(c) 2012-2013, Sebastien Jodogne, 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.6.2"
+  "Version" : "0.7.4"
 }
--- a/OrthancCppClient/SharedLibrary/SharedLibrary.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/SharedLibrary/SharedLibrary.cpp	Wed Apr 16 16:04:55 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/OrthancCppClient/Study.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/Study.cpp	Wed Apr 16 16:04:55 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/OrthancCppClient/Study.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancCppClient/Study.h	Wed Apr 16 16:04:55 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/OrthancExplorer/libs/jquery.mobile.simpledialog2.js	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancExplorer/libs/jquery.mobile.simpledialog2.js	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DatabaseWrapper.cpp	Wed Apr 16 16:04:55 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;
@@ -369,7 +388,7 @@
     }
   }
 
-  bool DatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target,
+  void DatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target,
                                               int64_t id)
   {
     target.clear();
@@ -381,8 +400,6 @@
     {
       target.push_back(static_cast<MetadataType>(s.ColumnInt(0)));
     }
-
-    return true;
   }
 
 
@@ -428,16 +445,30 @@
   void DatabaseWrapper::AddAttachment(int64_t id,
                                       const FileInfo& attachment)
   {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?)");
+    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.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)
   {
@@ -458,7 +489,7 @@
                                          FileContentType contentType)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT uuid, uncompressedSize, compressionType, compressedSize FROM AttachedFiles WHERE id=? AND fileType=?");
+                        "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?");
     s.BindInt64(0, id);
     s.BindInt(1, contentType);
 
@@ -470,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;
     }
   }
@@ -554,7 +587,7 @@
 
     while (s.Step())
     {
-      result.push_back(s.ColumnInt(0));
+      result.push_back(s.ColumnInt64(0));
     }
   }
 
@@ -583,9 +616,9 @@
 
     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);
@@ -651,17 +684,17 @@
   }
 
 
-  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);
 
@@ -713,7 +746,7 @@
                         "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?");
     s.BindInt64(0, since);
     s.BindInt(1, maxResults + 1);
-    GetExportedResources(target, s, since, maxResults);
+    GetExportedResourcesInternal(target, s, since, maxResults);
   }
 
     
@@ -721,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);
   }
 
 
@@ -807,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
@@ -815,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&)
     {
@@ -825,6 +873,7 @@
 
     if (!ok)
     {
+      LOG(ERROR) << "Incompatible version of the Orthanc database: " << version;
       throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
     }
 
--- a/OrthancServer/DatabaseWrapper.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DatabaseWrapper.h	Wed Apr 16 16:04:55 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,
@@ -115,7 +115,7 @@
                         int64_t id,
                         MetadataType type);
 
-    bool ListAvailableMetadata(std::list<MetadataType>& target,
+    void ListAvailableMetadata(std::list<MetadataType>& target,
                                int64_t id);
 
     std::string GetMetadata(int64_t id,
@@ -129,6 +129,9 @@
     void AddAttachment(int64_t id,
                        const FileInfo& attachment);
 
+    void DeleteAttachment(int64_t id,
+                          FileContentType attachment);
+
     void ListAvailableAttachments(std::list<FileContentType>& result,
                                   int64_t id);
 
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomServer.cpp	Wed Apr 16 16:04:55 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)
@@ -89,10 +91,11 @@
       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);
@@ -146,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
@@ -192,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;
@@ -203,6 +211,8 @@
     checkCalledAet_ = true;
     clientTimeout_ = 30;
     isThreaded_ = true;
+    continue_ = false;
+    started_ = false;
   }
 
   DicomServer::~DicomServer()
@@ -395,4 +405,16 @@
 
     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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomServer.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Apr 16 16:04:55 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,17 +335,16 @@
       break;
 
     case FindRootModel_Instance:
-      if (manufacturer_ == ModalityManufacturer_ClearCanvas)
+      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
-        printf("CLEAR CANVAS\n");
         DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE");
       }
       else
       {
-        printf("GENERIC\n");
         DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE");
       }
 
@@ -380,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);
   }
@@ -460,12 +501,14 @@
   }
 
 
-  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;
@@ -480,43 +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)
   {
-    Close();
-    manufacturer_ = 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));
@@ -542,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_));
@@ -591,7 +667,7 @@
       is.setBuffer(buffer, size);
     is.setEos();
       
-    pimpl_->Store(is);
+    pimpl_->Store(is, *this);
   }
 
   void DicomUserConnection::Store(const std::string& buffer)
@@ -606,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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Wed Apr 16 16:04:55 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,6 +56,7 @@
     boost::shared_ptr<PImpl> pimpl_;
 
     // Connection parameters
+    std::string preferredTransferSyntax_;
     std::string localAet_;
     std::string distantAet_;
     std::string distantHost_;
@@ -64,7 +65,7 @@
 
     void CheckIsOpen() const;
 
-    void SetupPresentationContexts();
+    void SetupPresentationContexts(const std::string& preferredTransferSyntax);
 
     void Find(DicomFindAnswers& result,
               FindRootModel model,
@@ -115,6 +116,15 @@
       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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Wed Apr 16 16:04:55 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
@@ -98,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>
@@ -120,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)
@@ -208,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)
     {
@@ -231,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;
       }
     }
@@ -246,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);
@@ -268,7 +385,7 @@
         //element->getVR() != EVR_UNKNOWN &&  // This would forbid private tags
         element->getVR() != EVR_SQ)
     {
-      AnswerDicomField(output, *element);
+      AnswerDicomField(output, *element, transferSyntax);
     }
   }
 
@@ -276,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++)
@@ -309,7 +442,7 @@
     }
     else
     {
-      SendPathValueForLeaf(output, uri.back(), *dicom);
+      SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax);
     }
   }
 
@@ -623,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)
@@ -700,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);
     }
   }
 
@@ -1543,7 +1676,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();
@@ -1563,7 +1696,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();
     }
@@ -1604,25 +1737,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;
@@ -1632,15 +1781,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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/FromDcmtkBridge.h	Wed Apr 16 16:04:55 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/IServerIndexListener.h	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/IServerIndexListener.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Wed Apr 16 16:04:55 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
@@ -238,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,
@@ -267,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();
@@ -282,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();
@@ -293,7 +325,7 @@
 #if ORTHANC_PROMISCUOUS == 1
       /* accept everything not known not to be a storage SOP class */
       cond = acceptUnknownContextsWithPreferredTransferSyntaxes(
-        assoc->params, transferSyntaxes, numTransferSyntaxes);
+        assoc->params, &transferSyntaxes[0], transferSyntaxes.size());
       if (cond.bad())
       {
         LOG(INFO) << cond.text();
@@ -326,6 +358,9 @@
         AssociationCleanup(assoc);
         return NULL;
       }
+
+      std::string callingIP;
+      std::string callingTitle;
   
       /* check the AETs */
       {
@@ -347,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 =
             {
@@ -369,7 +400,7 @@
         }
 
         if (server.HasApplicationEntityFilter() &&
-            !server.GetApplicationEntityFilter().IsAllowed(callingIP, callingTitle))
+            !server.GetApplicationEntityFilter().IsAllowedConnection(callingIP, callingTitle))
         {
           T_ASC_RejectParameters rej =
             {
@@ -416,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()
@@ -463,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
@@ -520,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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/Internals/CommandDispatcher.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/Internals/FindScp.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/Internals/FindScp.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/Internals/MoveScp.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/Internals/MoveScp.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Wed Apr 16 16:04:55 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,
@@ -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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/Internals/StoreScp.h	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Wed Apr 16 16:04:55 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/HttpClient.h"
 #include "../Core/OrthancException.h"
 #include "../Core/Toolbox.h"
+#include "DicomProtocol/DicomServer.h"
 #include "ServerEnumerations.h"
 
 #include <boost/lexical_cast.hpp>
@@ -45,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);
@@ -62,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
@@ -144,10 +122,44 @@
         {
           RegisterUserMetadata(metadata, members[i]);
         }
-        catch (OrthancException e)
+        catch (OrthancException&)
         {
           LOG(ERROR) << "Cannot register this user-defined metadata: " << info;
-          throw e;
+          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;
         }
       }
     }
@@ -165,6 +177,9 @@
     HttpClient::GlobalInitialize();
 
     RegisterUserMetadata();
+    RegisterUserContentType();
+
+    DicomServer::InitializeDictionary();
   }
 
 
@@ -227,17 +242,17 @@
 
 
 
-  void GetDicomModality(const std::string& name,
-                        std::string& aet,
-                        std::string& address,
-                        int& port,
-                        ModalityManufacturer& manufacturer)
+  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"];
@@ -245,14 +260,30 @@
         !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)
       {
@@ -263,9 +294,11 @@
         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;
     }
   }
 
@@ -280,43 +313,52 @@
 
     if (!configuration_->isMember("OrthancPeers"))
     {
-      throw OrthancException("");
-    }
-
-    const Json::Value& modalities = (*configuration_) ["OrthancPeers"];
-    if (modalities.type() != Json::objectValue ||
-        !modalities.isMember(name))
-    {
-      throw OrthancException("");
+      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
+      const Json::Value& modalities = (*configuration_) ["OrthancPeers"];
+      if (modalities.type() != Json::objectValue ||
+          !modalities.isMember(name))
       {
         throw OrthancException(ErrorCode_BadFileFormat);
       }
-    }
-    catch (...)
-    {
-      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 += '/';
+      }
     }
-
-    if (url.size() != 0 && url[url.size() - 1] != '/')
+    catch (OrthancException& e)
     {
-      url += '/';
+      LOG(ERROR) << "Syntax error in the definition of peer \"" << name 
+                 << "\". Please check your configuration file.";
+      throw e;
     }
   }
 
@@ -421,7 +463,7 @@
        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())
     {
@@ -464,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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/OrthancInitialization.h	Wed Apr 16 16:04:55 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 @@
 #include <json/json.h>
 #include <stdint.h>
 #include "../Core/HttpServer/MongooseServer.h"
+#include "DicomProtocol/DicomUserConnection.h"
 #include "ServerEnumerations.h"
 
 namespace Orthanc
@@ -54,11 +55,17 @@
   bool GetGlobalBoolParameter(const std::string& parameter,
                               bool defaultValue);
 
-  void GetDicomModality(const std::string& name,
-                        std::string& aet,
-                        std::string& address,
-                        int& port,
-                        ModalityManufacturer& manufacturer);
+  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,
@@ -78,4 +85,15 @@
 
   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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1876 +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/Compression/HierarchicalZipWriter.h"
-#include "../Core/HttpClient.h"
-#include "../Core/HttpServer/FilesystemHttpSender.h"
-#include "../Core/Uuid.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::SetOfStrings& modalities =                      \
-    dynamic_cast<OrthancRestApi&>(call.GetContext()).GetModalities();
-
-#define RETRIEVE_PEERS(call)                                            \
-  const OrthancRestApi::SetOfStrings& peers =                           \
-    dynamic_cast<OrthancRestApi&>(call.GetContext()).GetPeers();
-
-
-
-namespace Orthanc
-{
-  // DICOM SCU ----------------------------------------------------------------
-
-  static void ConnectToModality(DicomUserConnection& connection,
-                                const std::string& name)
-  {
-    std::string aet, address;
-    int port;
-    ModalityManufacturer manufacturer;
-    GetDicomModality(name, aet, address, port, manufacturer);
-    connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC"));
-    connection.SetDistantApplicationEntityTitle(aet);
-    connection.SetDistantHost(address);
-    connection.SetDistantPort(port);
-    connection.SetDistantManufacturer(manufacturer);
-    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 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;
-    ConnectToModality(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;
-    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 bool GetInstancesToExport(std::list<std::string>& instances,
-                                   const std::string& remote,
-                                   RestApi::PostCall& call)
-  {
-    RETRIEVE_CONTEXT(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);
-        instances.merge(tmp);
-        assert(tmp.size() == 0);
-      }
-    }
-    else
-    {
-      // Neither a string, nor a list of strings. Bad request.
-      return false;
-    }
-
-    return true;
-  }
-
-
-  static void DicomStore(RestApi::PostCall& call)
-  {
-    RETRIEVE_CONTEXT(call);
-
-    std::string remote = call.GetUriComponent("id", "");
-
-    std::list<std::string> instances;
-    if (!GetInstancesToExport(instances, remote, call))
-    {
-      return;
-    }
-
-    DicomUserConnection connection;
-    ConnectToModality(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");
-  }
-
-
-
-  // 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");
-    }
-  }
-
-  static void ExecuteScript(RestApi::PostCall& call)
-  {
-    std::string result;
-    RETRIEVE_CONTEXT(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");
-  }
-
-
-
-
-
-
-  // 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 DeleteChanges(RestApi::DeleteCall& call)
-  {
-    RETRIEVE_CONTEXT(call);
-    context.GetIndex().DeleteChanges();
-    call.GetOutput().AnswerBuffer("", "text/plain");
-  }
-
-
-  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);
-    }
-  }
-
-
-  static void DeleteExports(RestApi::DeleteCall& call)
-  {
-    RETRIEVE_CONTEXT(call);
-    context.GetIndex().DeleteExportedResources();
-    call.GetOutput().AnswerBuffer("", "text/plain");
-  }
-
-  
-  // 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);
-  }
-
-
-  static void ExportInstanceFile(RestApi::PostCall& call)
-  {
-    RETRIEVE_CONTEXT(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)
-  {
-    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"] = EnumerationToString(status);
-    call.GetOutput().AnswerJson(result);
-  }
-
-
-
-  // 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)
-  {
-    RETRIEVE_MODALITIES(call);
-
-    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)
-  {
-    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-instance");
-      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)
-  {
-    RETRIEVE_CONTEXT(call);
-    boost::mutex::scoped_lock lock(context.GetDicomFileMutex());
-
-    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)
-  {
-    RETRIEVE_CONTEXT(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)
-  {
-    RETRIEVE_CONTEXT(call);
-    boost::mutex::scoped_lock lock(context.GetDicomFileMutex());
-    
-    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);
-
-    RETRIEVE_CONTEXT(call);
-    boost::mutex::scoped_lock lock(context.GetDicomFileMutex());
-
-    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);
-
-      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);
-    }
-  }
-
-
-  // Handling of metadata -----------------------------------------------------
-
-  static void ListMetadata(RestApi::GetCall& call)
-  {
-    RETRIEVE_CONTEXT(call);
-    
-    std::string publicId = call.GetUriComponent("id", "");
-    std::list<MetadataType> metadata;
-    if (context.GetIndex().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)
-  {
-    RETRIEVE_CONTEXT(call);
-    
-    std::string publicId = call.GetUriComponent("id", "");
-    std::string name = call.GetUriComponent("name", "");
-    MetadataType metadata = StringToMetadata(name);
-
-    std::string value;
-    if (context.GetIndex().LookupMetadata(value, publicId, metadata))
-    {
-      call.GetOutput().AnswerBuffer(value, "text/plain");
-    }
-  }
-
-
-  static void DeleteMetadata(RestApi::DeleteCall& call)
-  {
-    RETRIEVE_CONTEXT(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
-      context.GetIndex().DeleteMetadata(publicId, metadata);
-      call.GetOutput().AnswerBuffer("", "text/plain");
-    }
-  }
-
-
-  static void SetMetadata(RestApi::PutCall& call)
-  {
-    RETRIEVE_CONTEXT(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
-      context.GetIndex().SetMetadata(publicId, metadata, value);
-      call.GetOutput().AnswerBuffer("", "text/plain");
-    }
-  }
-
-
-  static void GetResourceStatistics(RestApi::GetCall& call)
-  {
-    RETRIEVE_CONTEXT(call);
-    std::string publicId = call.GetUriComponent("id", "");
-    Json::Value result;
-    context.GetIndex().GetStatistics(result, publicId);
-    call.GetOutput().AnswerJson(result);
-  }
-
-
-
-  // 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)
-  {
-    RETRIEVE_PEERS(call);
-
-    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)
-  {
-    RETRIEVE_PEERS(call);
-
-    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)
-  {
-    RETRIEVE_CONTEXT(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");
-  }
-
-
-
-
-
-  // Registration of the various REST handlers --------------------------------
-
-  OrthancRestApi::OrthancRestApi(ServerContext& context) : 
-    context_(context)
-  {
-    GetListOfDicomModalities(modalities_);
-    GetListOfOrthancPeers(peers_);
-
-    Register("/", ServeRoot);
-    Register("/system", GetSystemInformation);
-    Register("/statistics", GetStatistics);
-    Register("/changes", GetChanges);
-    Register("/changes", DeleteChanges);
-    Register("/exports", GetExports);
-    Register("/exports", DeleteExports);
-
-    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("/instances/{id}/statistics", GetResourceStatistics);
-    Register("/patients/{id}/statistics", GetResourceStatistics);
-    Register("/studies/{id}/statistics", GetResourceStatistics);
-    Register("/series/{id}/statistics", GetResourceStatistics);
-
-    Register("/instances/{id}/metadata", ListMetadata);
-    Register("/instances/{id}/metadata/{name}", DeleteMetadata);
-    Register("/instances/{id}/metadata/{name}", GetMetadata);
-    Register("/instances/{id}/metadata/{name}", SetMetadata);
-    Register("/patients/{id}/metadata", ListMetadata);
-    Register("/patients/{id}/metadata/{name}", DeleteMetadata);
-    Register("/patients/{id}/metadata/{name}", GetMetadata);
-    Register("/patients/{id}/metadata/{name}", SetMetadata);
-    Register("/series/{id}/metadata", ListMetadata);
-    Register("/series/{id}/metadata/{name}", DeleteMetadata);
-    Register("/series/{id}/metadata/{name}", GetMetadata);
-    Register("/series/{id}/metadata/{name}", SetMetadata);
-    Register("/studies/{id}/metadata", ListMetadata);
-    Register("/studies/{id}/metadata/{name}", DeleteMetadata);
-    Register("/studies/{id}/metadata/{name}", GetMetadata);
-    Register("/studies/{id}/metadata/{name}", SetMetadata);
-
-    Register("/patients/{id}/protected", IsProtectedPatient);
-    Register("/patients/{id}/protected", SetPatientProtection);
-    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}/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}/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("/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);
-
-    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);
-    Register("/tools/execute-script", ExecuteScript);
-    Register("/tools/now", GetNowIsoString);
-  }
-}
--- a/OrthancServer/OrthancRestApi.h	Thu Oct 17 14:21:50 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +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> SetOfStrings;
-
-  protected:
-    ServerContext& context_;
-    SetOfStrings modalities_;
-    SetOfStrings peers_;
-
-  public:
-    OrthancRestApi(ServerContext& context);
-
-    ServerContext& GetContext()
-    {
-      return context_;
-    }
-
-    SetOfStrings& GetModalities()
-    {
-      return modalities_;
-    }
-
-    SetOfStrings& GetPeers()
-    {
-      return peers_;
-    }
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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;
+
+  protected:
+    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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/PrepareDatabase.sql	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/ServerContext.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/ServerContext.h	Wed Apr 16 16:04:55 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,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,
@@ -122,9 +127,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);
@@ -132,7 +137,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);
@@ -148,5 +154,12 @@
     {
       return lua_;
     }
+
+    void SetStoreMD5ForAttachments(bool storeMD5);
+
+    bool IsStoreMD5ForAttachments() const
+    {
+      return accessor_.IsStoreMD5();
+    }
   };
 }
--- a/OrthancServer/ServerEnumerations.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Wed Apr 16 16:04:55 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 "../Core/OrthancException.h"
 #include "../Core/EnumerationDictionary.h"
+#include "../Core/Toolbox.h"
 
 #include <boost/thread.hpp>
 
@@ -40,6 +41,7 @@
 {
   static boost::mutex enumerationsMutex_;
   static Toolbox::EnumerationDictionary<MetadataType> dictMetadataType_;
+  static Toolbox::EnumerationDictionary<FileContentType> dictContentType_;
 
   void InitializeServerEnumerations()
   {
@@ -52,10 +54,13 @@
     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)
+                            const std::string& name)
   {
     boost::mutex::scoped_lock lock(enumerationsMutex_);
 
@@ -82,25 +87,32 @@
     return dictMetadataType_.Translate(str);
   }
 
-  const char* EnumerationToString(ResourceType type)
+  void RegisterUserContentType(int contentType,
+                               const std::string& name)
   {
-    switch (type)
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+
+    if (contentType < static_cast<int>(FileContentType_StartUser) ||
+        contentType > static_cast<int>(FileContentType_EndUser))
     {
-      case ResourceType_Patient:
-        return "Patient";
-
-      case ResourceType_Study:
-        return "Study";
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
 
-      case ResourceType_Series:
-        return "Series";
+    dictContentType_.Add(static_cast<FileContentType>(contentType), name);
+  }
 
-      case ResourceType_Instance:
-        return "Instance";
-      
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
+  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,
@@ -268,12 +280,50 @@
       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")
@@ -284,11 +334,17 @@
     {
       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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/ServerEnumerations.h	Wed Apr 16 16:04:55 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
@@ -54,7 +56,18 @@
   enum ModalityManufacturer
   {
     ModalityManufacturer_Generic,
-    ModalityManufacturer_ClearCanvas
+    ModalityManufacturer_ClearCanvas,
+    ModalityManufacturer_MedInria,
+    ModalityManufacturer_Dcm4Chee
+  };
+
+  enum DicomRequestType
+  {
+    DicomRequestType_Echo,
+    DicomRequestType_Find,
+    DicomRequestType_Get,
+    DicomRequestType_Move,
+    DicomRequestType_Store
   };
 
 
@@ -71,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,
@@ -115,17 +120,22 @@
   void InitializeServerEnumerations();
 
   void RegisterUserMetadata(int metadata,
-                            const std::string name);
+                            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);
 
-  MetadataType StringToMetadata(const std::string& str);
-
-  const char* EnumerationToString(ResourceType type);
-
-  std::string EnumerationToString(MetadataType type);
-
   const char* EnumerationToString(SeriesStatus status);
 
   const char* EnumerationToString(StoreStatus status);
@@ -134,6 +144,8 @@
 
   const char* EnumerationToString(ModalityManufacturer manufacturer);
 
+  const char* EnumerationToString(DicomRequestType type);
+
   ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer);
 
   ResourceType GetParentResourceType(ResourceType type);
--- a/OrthancServer/ServerIndex.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/ServerIndex.cpp	Wed Apr 16 16:04:55 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,7 +91,7 @@
       {
         for (std::list<std::string>::iterator 
                it = pendingFilesToRemove_.begin();
-             it != pendingFilesToRemove_.end(); it++)
+             it != pendingFilesToRemove_.end(); ++it)
         {
           context_.RemoveFile(*it);
         }
@@ -255,6 +255,7 @@
 
     try
     {
+      boost::mutex::scoped_lock lock(that->mutex_);
       std::string sleepString = that->db_->GetGlobalProperty(GlobalProperty_FlushSleep);
       sleep = boost::lexical_cast<unsigned int>(sleepString);
     }
@@ -290,11 +291,11 @@
                                                int64_t series,
                                                const DicomMap& dicomSummary)
   {
-    const DicomValue* value;
-    const DicomValue* value2;
-          
     try
     {
+      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)
       {
@@ -407,7 +408,7 @@
       // 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();
       }
@@ -512,7 +513,7 @@
 
       // 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);
       }
@@ -593,10 +594,6 @@
     try
     {
       expected = boost::lexical_cast<size_t>(s);
-      if (expected < 0)
-      {
-        return SeriesStatus_Unknown;
-      }
     }
     catch (boost::bad_lexical_cast&)
     {
@@ -609,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);
@@ -623,7 +620,7 @@
         return SeriesStatus_Unknown;
       }
 
-      if (index <= 0 || index > expected)
+      if (!(index > 0 && index <= expected))
       {
         // Out-of-range instance index
         return SeriesStatus_Inconsistent;
@@ -715,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);
       }
@@ -823,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);
     }
@@ -1112,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)
   {
@@ -1153,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);
         }
@@ -1212,7 +1239,7 @@
   }
 
 
-  bool ServerIndex::ListAvailableMetadata(std::list<MetadataType>& target,
+  void ServerIndex::ListAvailableMetadata(std::list<MetadataType>& target,
                                           const std::string& publicId)
   {
     boost::mutex::scoped_lock lock(mutex_);
@@ -1224,7 +1251,25 @@
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    return db_->ListAvailableMetadata(target, id);
+    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);
   }
 
 
@@ -1301,26 +1346,22 @@
   }
 
 
-  void ServerIndex::GetStatistics(Json::Value& target,
-                                  const std::string& publicId)
+  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)
   {
-    boost::mutex::scoped_lock lock(mutex_);
+    std::stack<int64_t> toExplore;
+    toExplore.push(id);
 
-    ResourceType type;
-    int64_t top;
-    if (!db_->LookupResource(publicId, top, type))
-    {
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
-
-    std::stack<int64_t> toExplore;
-    toExplore.push(top);
-
-    int countInstances = 0;
-    int countSeries = 0;
-    int countStudies = 0;
-    uint64_t compressedSize = 0;
-    uint64_t uncompressedSize = 0;
+    countInstances = 0;
+    countSeries = 0;
+    countStudies = 0;
+    compressedSize = 0;
+    uncompressedSize = 0;
 
     while (!toExplore.empty())
     {
@@ -1330,22 +1371,22 @@
 
       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)
       {
-        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();
-          }
-        }
-
         countInstances++;
       }
       else
@@ -1368,13 +1409,46 @@
         std::list<int64_t> tmp;
         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);
         }
       }
     }
 
+    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);
@@ -1400,6 +1474,27 @@
   }
 
 
+  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);
@@ -1473,6 +1568,29 @@
 
   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();
@@ -1483,7 +1601,7 @@
     db_->LookupTagValue(id, tag, value);
 
     for (std::list<int64_t>::const_iterator 
-           it = id.begin(); it != id.end(); it++)
+           it = id.begin(); it != id.end(); ++it)
     {
       result.push_back(db_->GetPublicId(*it));
     }
@@ -1501,9 +1619,78 @@
     db_->LookupTagValue(id, value);
 
     for (std::list<int64_t>::const_iterator 
-           it = id.begin(); it != id.end(); it++)
+           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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/ServerIndex.h	Wed Apr 16 16:04:55 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
@@ -90,6 +90,14 @@
     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;
 
@@ -155,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);
 
@@ -169,9 +180,13 @@
                         const std::string& publicId,
                         MetadataType type);
 
-    bool ListAvailableMetadata(std::list<MetadataType>& target,
+    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);
 
@@ -187,11 +202,29 @@
     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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/ServerToolbox.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/ServerToolbox.h	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/ToDcmtkBridge.cpp	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/ToDcmtkBridge.h	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/OrthancServer/main.cpp	Wed Apr 16 16:04:55 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,8 +30,7 @@
  **/
 
 
-//#include "OrthancRestApi.h"
-#include "RadiotherapyRestApi.h"
+#include "OrthancRestApi/OrthancRestApi.h"
 
 #include <fstream>
 #include <glog/logging.h>
@@ -42,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)
   {
   }
@@ -73,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,
@@ -130,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()
@@ -149,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:
@@ -240,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
@@ -292,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;
@@ -335,11 +334,12 @@
     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;
@@ -370,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);
@@ -403,26 +404,39 @@
         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
       httpServer.RegisterHandler(new FilesystemHttpHandler("/app", ORTHANC_PATH "/OrthancExplorer"));
 #endif
 
-      //httpServer.RegisterHandler(new OrthancRestApi(context));
-      httpServer.RegisterHandler(new RadiotherapyRestApi(context));
+      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";
     }
 
@@ -441,5 +455,7 @@
 
   OrthancFinalize();
 
+  LOG(WARNING) << "Orthanc has stopped";
+
   return status;
 }
--- a/README	Thu Oct 17 14:21:50 2013 +0200
+++ b/README	Wed Apr 16 16:04:55 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.
--- a/Resources/Archives/MessageWithDestination.cpp	Thu Oct 17 14:21:50 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/OrthancCppClient.cmake	Thu Oct 17 14:21:50 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-include_directories(${ORTHANC_ROOT}/OrthancCppClient/Package/Laaw)
-
-set(STATIC_BUILD ON)
-include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/LibCurlConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/LibPngConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake)
-
-if (${CMAKE_COMPILER_IS_GNUCXX})
-  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic -Wno-implicit-function-declaration")  # --std=c99 makes libcurl not to compile
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wno-long-long -Wno-variadic-macros")
-  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
-elseif (${MSVC})
-  add_definitions(-D_CRT_SECURE_NO_WARNINGS=1)  
-endif()
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-  if (${CMAKE_SIZEOF_VOID_P} EQUAL 4)
-    set(WINDOWS_DEF ${ORTHANC_ROOT}/OrthancCppClient/Package/Build/Windows32.def)
-  elseif (${CMAKE_SIZEOF_VOID_P} EQUAL 8)
-    set(WINDOWS_DEF ${ORTHANC_ROOT}/OrthancCppClient/Package/Build/Windows64.def)
-  else()
-    message(FATAL_ERROR "Support your platform here")
-  endif()
-endif()
-
-add_library(OrthancCppClient SHARED
-  ${THIRD_PARTY_SOURCES}
-  ${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/Package/SharedLibrary.cpp
-  ${ORTHANC_ROOT}/Resources/sha1/sha1.cpp
-  ${ORTHANC_ROOT}/Resources/md5/md5.c
-  ${ORTHANC_ROOT}/Resources/base64/base64.cpp
-  ${WINDOWS_DEF}
-  )
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
-  set_target_properties(OrthancCppClient
-    PROPERTIES LINK_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined -Wl,--version-script=${ORTHANC_ROOT}/OrthancCppClient/Package/Laaw/VersionScript.map"
-    )
-  target_link_libraries(OrthancCppClient pthread)
-else()
-  set_target_properties(OrthancCppClient
-    PROPERTIES LINK_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++"
-    )
-  target_link_libraries(OrthancCppClient ws2_32)
-endif()
--- a/Resources/Archives/PrepareDatabase-v1.sql	Thu Oct 17 14:21:50 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/BoostConfiguration.cmake	Wed Apr 16 16:04:55 2014 +0200
@@ -1,122 +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)
-  # Parameters for Boost 1.54.0
-  SET(BOOST_NAME boost_1_54_0)
-  SET(BOOST_BCP_SUFFIX bcpdigest-0.6.2)
-  SET(BOOST_MD5 "a464288a976ba133f9b325f454cb503d")
-  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()
-
-  list(APPEND BOOST_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()
+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()
--- a/Resources/CMake/BoostConfiguration.sh	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/BoostConfiguration.sh	Wed Apr 16 16:04:55 2014 +0200
@@ -9,21 +9,25 @@
 ## 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_54_0
-rm -rf /tmp/bcp/boost_1_54_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.54.0..."
-tar xfz boost_1_54_0.tar.gz 
+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_54_0
-bcp --boost=/tmp/boost_1_54_0 thread system locale date_time filesystem math/special_functions algorithm uuid /tmp/bcp/boost_1_54_0
+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_54_0_bcpdigest-0.6.2.tar.gz boost_1_54_0
-ls -l boost_1_54_0_bcpdigest-0.6.2.tar.gz
-md5sum boost_1_54_0_bcpdigest-0.6.2.tar.gz
-readlink -f boost_1_54_0_bcpdigest-0.6.2.tar.gz
+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/DcmtkConfiguration.cmake	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Wed Apr 16 16:04:55 2014 +0200
@@ -1,138 +1,151 @@
-add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}")
-
-if (${STATIC_BUILD})
-  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}")
+# 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/DownloadPackage.cmake	Wed Apr 16 16:04:55 2014 +0200
@@ -1,124 +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()
-
-
-##
-## 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}")
-      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()
+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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/GoogleLogConfiguration.cmake	Wed Apr 16 16:04:55 2014 +0200
@@ -1,10 +1,22 @@
-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(
     "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
     ${GOOGLE_LOG_SOURCES_DIR}/src/glog/raw_logging.h
--- a/Resources/CMake/GoogleTestConfiguration.cmake	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/GoogleTestConfiguration.cmake	Wed Apr 16 16:04:55 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,7 +7,7 @@
     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(
     "4577b49f2973c90bf9ba69aa8166b786"
--- a/Resources/CMake/JsonCppConfiguration.cmake	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/JsonCppConfiguration.cmake	Wed Apr 16 16:04:55 2014 +0200
@@ -1,29 +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.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}/.*)
-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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/LibCurlConfiguration.cmake	Wed Apr 16 16:04:55 2014 +0200
@@ -1,101 +1,101 @@
-if (${STATIC_BUILD})
-  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()
+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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/LibPngConfiguration.cmake	Wed Apr 16 16:04:55 2014 +0200
@@ -1,60 +1,60 @@
-if (${STATIC_BUILD})
-  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()
+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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/LuaConfiguration.cmake	Wed Apr 16 16:04:55 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(
-    "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()
+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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/MongooseConfiguration.cmake	Wed Apr 16 16:04:55 2014 +0200
@@ -1,59 +1,59 @@
-if (STATIC_BUILD OR NOT USE_DYNAMIC_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()
+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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/OpenSslConfiguration.cmake	Wed Apr 16 16:04:55 2014 +0200
@@ -1,209 +1,209 @@
-if (${STATIC_BUILD})
-  SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.1c)
-  DownloadPackage(
-    "ae412727c8c15b67880aef7bd2999b2e"
-    "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
-
-    ${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 (${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")
-
-  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()
+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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/SQLiteConfiguration.cmake	Wed Apr 16 16:04:55 2014 +0200
@@ -1,43 +1,43 @@
-if (STATIC_BUILD OR NOT USE_DYNAMIC_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_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()
--- a/Resources/CMake/ZlibConfiguration.cmake	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/CMake/ZlibConfiguration.cmake	Wed Apr 16 16:04:55 2014 +0200
@@ -1,42 +1,42 @@
-# 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})
-  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}/.*)
+# 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/Configuration.json	Wed Apr 16 16:04:55 2014 +0200
@@ -1,136 +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 ]
-
-    /**
-     * A fourth parameter is available to enable patches for a
-     * specific PACS manufacturer. The allowed values are currently
-     * "Generic" (default value) and "ClearCanvas". 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
-  },
-
-  // Number of seconds without receiving any instance before a
-  // patient, a study or a series is considered as stable.
-  "StableAge" : 60
-}
+{
+  /**
+   * 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
+}
--- a/Resources/EmbedResources.py	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/EmbedResources.py	Wed Apr 16 16:04:55 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/Orthanc.doxygen	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/Orthanc.doxygen	Wed Apr 16 16:04:55 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
 
@@ -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   = 1
-
 # 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.
 
--- a/Resources/OrthancClient.doxygen	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/OrthancClient.doxygen	Wed Apr 16 16:04:55 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 Client"
 
@@ -31,7 +32,7 @@
 # This could be handy for archiving the generated documentation or
 # if some version control system is used.
 
-PROJECT_NUMBER         = 
+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
@@ -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,7 @@
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  =  @CMAKE_SOURCE_DIR@/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h
+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
@@ -636,13 +681,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 +791,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 +876,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 +897,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 +911,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 +941,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 +1109,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   = 1
-
 # 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 +1124,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 +1167,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 +1176,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
@@ -1141,7 +1197,7 @@
 # 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           = YES
+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
@@ -1246,6 +1302,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 +1339,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 +1530,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 +1609,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 +1623,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 +1654,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 +1703,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 +1712,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.
 
--- a/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Wed Apr 16 16:04:55 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))
--- a/Resources/Samples/OrthancClient/Basic/main.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/Samples/OrthancClient/Basic/main.cpp	Wed Apr 16 16:04:55 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
  *
  * Permission is hereby granted, free of charge, to any person
@@ -57,6 +57,10 @@
           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;
           }
         }
       }
@@ -66,7 +70,7 @@
 
     return 0;
   }
-  catch (OrthancClient::OrthancClientException e)
+  catch (OrthancClient::OrthancClientException& e)
   {
     std::cerr << "EXCEPTION: [" << e.What() << "]" << std::endl;
     return -1;
--- a/Resources/Samples/OrthancClient/Vtk/main.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/Samples/OrthancClient/Vtk/main.cpp	Wed Apr 16 16:04:55 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
  *
  * Permission is hereby granted, free of charge, to any person
@@ -173,7 +173,7 @@
 
     return 0;
   }
-  catch (OrthancClient::OrthancClientException e)
+  catch (OrthancClient::OrthancClientException& e)
   {
     std::cerr << "EXCEPTION: [" << e.What() << "]" << std::endl;
     return -1;
--- a/Resources/Samples/Python/AnonymizeAllPatients.py	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/Samples/Python/AnonymizeAllPatients.py	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/Samples/Python/ChangesLoop.py	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/Samples/Python/DownloadAnonymized.py	Wed Apr 16 16:04:55 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	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/Samples/Python/HighPerformanceAutoRouting.py	Wed Apr 16 16:04:55 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'
--- a/Resources/Samples/Python/RestToolbox.py	Thu Oct 17 14:21:50 2013 +0200
+++ b/Resources/Samples/Python/RestToolbox.py	Wed Apr 16 16:04:55 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Tools/CMakeLists.txt	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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/THANKS	Thu Oct 17 14:21:50 2013 +0200
+++ b/THANKS	Wed Apr 16 16:04:55 2014 +0200
@@ -11,13 +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
+* 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 support.
+* 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
@@ -25,20 +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 packaging. 
-
-
-Fedora and Red Hat
-------------------
-
-* Mario Ceresa (mrceresa@gmail.com), for help about 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	Thu Oct 17 14:21:50 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	Thu Oct 17 14:21:50 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	Thu Oct 17 14:21:50 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,197 +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(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_);
-}
--- a/UnitTests/Png.cpp	Thu Oct 17 14:21:50 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-#include "gtest/gtest.h"
-
-#include <stdint.h>
-#include "../Core/FileFormats/PngReader.h"
-#include "../Core/FileFormats/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]);
-}
-
-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.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());
-    for (int x = 0; x < width; x++, p++, v++)
-    {
-      ASSERT_EQ(*p, v);
-    }
-  }
-
-}
--- a/UnitTests/RestApi.cpp	Thu Oct 17 14:21:50 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	Thu Oct 17 14:21:50 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	Thu Oct 17 14:21:50 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	Thu Oct 17 14:21:50 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,489 +0,0 @@
-#include "gtest/gtest.h"
-
-#include "../OrthancServer/DatabaseWrapper.h"
-#include "../Core/Uuid.h"
-
-#include <ctype.h>
-#include <glog/logging.h>
-#include <algorithm>
-
-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());
-  }
-
-  std::list<MetadataType> md;
-  index.ListAvailableMetadata(md, a[4]);
-  ASSERT_EQ(0u, md.size());
-
-  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");
-  
-  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_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));
-}
-
-
-
-TEST(DatabaseWrapper, LookupTagValue)
-{
-  ServerIndexListener listener;
-  DatabaseWrapper index(listener);
-
-  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;;
-      }      
-      }*/
-
-
-}
--- a/UnitTests/Versions.cpp	Thu Oct 17 14:21:50 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_54", 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	Thu Oct 17 14:21:50 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	Thu Oct 17 14:21:50 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,542 +0,0 @@
-#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"
-#include "../Core/MultiThreading/SharedMessageQueue.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);
-}
-
-
-#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(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"));
-}
-
-
-
-class DynamicInteger : public IDynamicObject
-{
-private:
-  int value_;
-
-public:
-  DynamicInteger(int value) : value_(value)
-  {
-  }
-
-  int GetValue() const
-  {
-    return value_;
-  }
-};
-
-
-TEST(SharedMessageQueue, Basic)
-{
-  SharedMessageQueue q;
-  ASSERT_TRUE(q.WaitEmpty(0));
-  q.Enqueue(new DynamicInteger(10));
-  ASSERT_FALSE(q.WaitEmpty(1));
-  q.Enqueue(new DynamicInteger(20));
-  q.Enqueue(new DynamicInteger(30));
-  q.Enqueue(new DynamicInteger(40));
-
-  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(SharedMessageQueue, Clean)
-{
-  try
-  {
-    SharedMessageQueue q;
-    q.Enqueue(new DynamicInteger(10));
-    q.Enqueue(new DynamicInteger(20));  
-    throw OrthancException("Nope");
-  }
-  catch (OrthancException&)
-  {
-  }
-}
-
-
-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, Split)
-{
-  std::vector<std::string> s;
-  
-  Toolbox::Split(s, "", '|'); 
-  ASSERT_EQ(0, s.size());
-  
-  Toolbox::Split(s, "aaaaa", '|'); 
-  ASSERT_EQ(1, s.size());
-  ASSERT_EQ("aaaaa", s[0]);
-  
-  Toolbox::Split(s, "aaa|aa", '|'); 
-  ASSERT_EQ(2, s.size());
-  ASSERT_EQ("aaa", s[0]);
-  ASSERT_EQ("aa", s[1]);
-  
-  Toolbox::Split(s, "a|aa|ab", '|'); 
-  ASSERT_EQ(3, s.size());
-  ASSERT_EQ("a", s[0]);
-  ASSERT_EQ("aa", s[1]);
-  ASSERT_EQ("ab", s[2]);
-  
-  Toolbox::Split(s, "||ab", '|'); 
-  ASSERT_EQ(3, s.size());
-  ASSERT_EQ("", s[0]);
-  ASSERT_EQ("", s[1]);
-  ASSERT_EQ("ab", s[2]);
-  
-  Toolbox::Split(s, "|", '|'); 
-  ASSERT_EQ(2, s.size());
-  ASSERT_EQ("", s[0]);
-  ASSERT_EQ("", s[1]);
-  
-  Toolbox::Split(s, "||", '|'); 
-  ASSERT_EQ(3, s.size());
-  ASSERT_EQ("", s[0]);
-  ASSERT_EQ("", s[1]);
-  ASSERT_EQ("", s[2]);
-}
-
-
-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/DicomMap.cpp	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 2014 +0200
@@ -0,0 +1,187 @@
+#include "gtest/gtest.h"
+
+#include "../Core/OrthancException.h"
+#include "../Core/Toolbox.h"
+#include "../Core/MultiThreading/ArrayFilledByThreads.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());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/Png.cpp	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 2014 +0200
@@ -0,0 +1,644 @@
+#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]);
+  
+  Toolbox::TokenizeString(t, "", '|'); 
+  ASSERT_EQ(1, t.size());
+  
+  Toolbox::TokenizeString(t, "aaaaa", '|'); 
+  ASSERT_EQ(1, t.size());
+  ASSERT_EQ("aaaaa", t[0]);
+  
+  Toolbox::TokenizeString(t, "aaa|aa", '|'); 
+  ASSERT_EQ(2, t.size());
+  ASSERT_EQ("aaa", t[0]);
+  ASSERT_EQ("aa", t[1]);
+  
+  Toolbox::TokenizeString(t, "a|aa|ab", '|'); 
+  ASSERT_EQ(3, t.size());
+  ASSERT_EQ("a", t[0]);
+  ASSERT_EQ("aa", t[1]);
+  ASSERT_EQ("ab", t[2]);
+  
+  Toolbox::TokenizeString(t, "||ab", '|'); 
+  ASSERT_EQ(3, t.size());
+  ASSERT_EQ("", t[0]);
+  ASSERT_EQ("", t[1]);
+  ASSERT_EQ("ab", t[2]);
+  
+  Toolbox::TokenizeString(t, "|", '|'); 
+  ASSERT_EQ(2, t.size());
+  ASSERT_EQ("", t[0]);
+  ASSERT_EQ("", t[1]);
+  
+  Toolbox::TokenizeString(t, "||", '|'); 
+  ASSERT_EQ(3, t.size());
+  ASSERT_EQ("", t[0]);
+  ASSERT_EQ("", t[1]);
+  ASSERT_EQ("", t[2]);
+}
+
+
+#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	Wed Apr 16 16:04:55 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	Wed Apr 16 16:04:55 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.
+
+  **/
+}