changeset 758:67e6400fca03 query-retrieve

integration mainline -> query-retrieve
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 16 Apr 2014 16:34:09 +0200
parents 3bdb5db8e839 (current diff) 40d09221077a (diff)
children c2c28dd17e87
files Core/DicomFormat/DicomMap.cpp Core/DicomFormat/DicomMap.h Core/DicomFormat/DicomTag.h OrthancCppClient/SharedLibrary/Laaw/laaw-exports.h OrthancCppClient/SharedLibrary/Laaw/laaw.h OrthancServer/DicomProtocol/DicomUserConnection.cpp OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancRestApi.cpp OrthancServer/OrthancRestApi.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h OrthancServer/main.cpp UnitTestsSources/ServerIndex.cpp UnitTestsSources/main.cpp
diffstat 221 files changed, 7287 insertions(+), 5100 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Fri Jan 24 17:40:45 2014 +0100
+++ b/CMakeLists.txt	Wed Apr 16 16:34:09 2014 +0200
@@ -15,8 +15,9 @@
 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_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\" (only when using system version of DCMTK)") 
+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_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
@@ -93,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
   )
@@ -205,7 +207,13 @@
   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
@@ -240,21 +248,29 @@
 ## Build the unit tests
 #####################################################################
 
+if (UNIT_TESTS_WITH_HTTP_CONNEXIONS)
+  add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1)
+else()
+  add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=0)
+endif()
+
 add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake)
 add_executable(UnitTests
   ${GTEST_SOURCES}
+  UnitTestsSources/DicomMap.cpp
   UnitTestsSources/FileStorage.cpp
   UnitTestsSources/MemoryCache.cpp
   UnitTestsSources/Png.cpp
   UnitTestsSources/RestApi.cpp
   UnitTestsSources/SQLite.cpp
   UnitTestsSources/SQLiteChromium.cpp
-  UnitTestsSources/ServerIndex.cpp
+  UnitTestsSources/ServerIndexTests.cpp
   UnitTestsSources/Versions.cpp
   UnitTestsSources/Zip.cpp
   UnitTestsSources/Lua.cpp
-  UnitTestsSources/main.cpp
+  UnitTestsSources/MultiThreading.cpp
+  UnitTestsSources/UnitTestsMain.cpp
   )
 target_link_libraries(UnitTests ServerLibrary CoreLibrary)
 
--- a/Core/Cache/ICachePageProvider.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Cache/ICachePageProvider.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Cache/LeastRecentlyUsedIndex.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Cache/MemoryCache.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Cache/MemoryCache.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/ChunkedBuffer.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/ChunkedBuffer.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/BufferCompressor.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Compression/BufferCompressor.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/BufferCompressor.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Compression/BufferCompressor.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/HierarchicalZipWriter.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Compression/HierarchicalZipWriter.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Compression/HierarchicalZipWriter.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/ZipWriter.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Compression/ZipWriter.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/ZipWriter.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Compression/ZipWriter.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Compression/ZlibCompressor.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZlibCompressor.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Compression/ZlibCompressor.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomArray.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomArray.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomArray.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomElement.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomElement.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomInstanceHasher.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomInstanceHasher.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomInstanceHasher.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomInstanceHasher.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomMap.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomMap.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -199,7 +199,7 @@
     }
     else
     {
-      throw OrthancException("Inexistent tag");
+      throw OrthancException(ErrorCode_InexistentTag);
     }
   }
 
--- a/Core/DicomFormat/DicomMap.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomMap.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/DicomNullValue.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomNullValue.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomString.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomString.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomTag.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomTag.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomTag.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomTag.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/DicomValue.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/DicomFormat/DicomValue.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/EnumerationDictionary.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Enumerations.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -252,11 +252,11 @@
     std::string s(type);
     Toolbox::ToUpperCase(s);
 
-    if (s == "PATIENT")
+    if (s == "PATIENT" || s == "PATIENTS")
     {
       return ResourceType_Patient;
     }
-    else if (s == "STUDY")
+    else if (s == "STUDY" || s == "STUDIES")
     {
       return ResourceType_Study;
     }
@@ -264,7 +264,8 @@
     {
       return ResourceType_Series;
     }
-    else if (s == "INSTANCE" || s == "IMAGE")
+    else if (s == "INSTANCE"  || s == "IMAGE" || 
+             s == "INSTANCES" || s == "IMAGES")
     {
       return ResourceType_Instance;
     }
--- a/Core/Enumerations.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Enumerations.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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,7 +227,11 @@
   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
--- a/Core/FileFormats/PngReader.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileFormats/PngReader.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/FileFormats/PngReader.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileFormats/PngReader.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileFormats/PngWriter.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/FileFormats/PngWriter.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileFormats/PngWriter.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -42,21 +42,36 @@
                                                         size_t size,
                                                         FileContentType type)
   {
+    std::string md5;
+
+    if (storeMD5_)
+    {
+      Toolbox::ComputeMD5(md5, data, size);
+    }
+
     switch (compressionType_)
     {
     case CompressionType_None:
     {
       std::string uuid = storage_.Create(data, size);
-      return FileInfo(uuid, type, size);
+      return FileInfo(uuid, type, size, md5);
     }
 
     case CompressionType_Zlib:
     {
       std::string compressed;
       zlib_.Compress(compressed, data, size);
+
+      std::string compressedMD5;
+      
+      if (storeMD5_)
+      {
+        Toolbox::ComputeMD5(compressedMD5, compressed);
+      }
+
       std::string uuid = storage_.Create(compressed);
-      return FileInfo(uuid, type, size, 
-                      CompressionType_Zlib, compressed.size());
+      return FileInfo(uuid, type, size, md5,
+                      CompressionType_Zlib, compressed.size(), compressedMD5);
     }
 
     default:
--- a/Core/FileStorage/CompressedFileStorageAccessor.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileStorage/CompressedFileStorageAccessor.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileInfo.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileStorage/FileInfo.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -43,9 +43,13 @@
   private:
     std::string uuid_;
     FileContentType contentType_;
+
     uint64_t uncompressedSize_;
+    std::string uncompressedMD5_;
+
     CompressionType compressionType_;
     uint64_t compressedSize_;
+    std::string compressedMD5_;
 
   public:
     FileInfo()
@@ -57,12 +61,15 @@
      **/
     FileInfo(const std::string& uuid,
              FileContentType contentType,
-             uint64_t size) :
+             uint64_t size,
+             const std::string& md5) :
       uuid_(uuid),
       contentType_(contentType),
       uncompressedSize_(size),
+      uncompressedMD5_(md5),
       compressionType_(CompressionType_None),
-      compressedSize_(size)
+      compressedSize_(size),
+      compressedMD5_(md5)
     {
     }
 
@@ -72,13 +79,17 @@
     FileInfo(const std::string& uuid,
              FileContentType contentType,
              uint64_t uncompressedSize,
+             const std::string& uncompressedMD5,
              CompressionType compressionType,
-             uint64_t compressedSize) :
+             uint64_t compressedSize,
+             const std::string& compressedMD5) :
       uuid_(uuid),
       contentType_(contentType),
       uncompressedSize_(uncompressedSize),
+      uncompressedMD5_(uncompressedMD5),
       compressionType_(compressionType),
-      compressedSize_(compressedSize)
+      compressedSize_(compressedSize),
+      compressedMD5_(compressedMD5)
     {
     }
 
@@ -106,5 +117,15 @@
     {
       return compressedSize_;
     }
+
+    const std::string& GetCompressedMD5() const
+    {
+      return compressedMD5_;
+    }
+
+    const std::string& GetUncompressedMD5() const
+    {
+      return uncompressedMD5_;
+    }
   };
 }
--- a/Core/FileStorage/FileStorage.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileStorage/FileStorage.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/FileStorage.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileStorage/FileStorage.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorageAccessor.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileStorage/FileStorageAccessor.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -38,6 +38,13 @@
                                               size_t size,
                                               FileContentType type)
   {
-    return FileInfo(storage_.Create(data, size), type, size);
+    std::string md5;
+
+    if (storeMD5_)
+    {
+      Toolbox::ComputeMD5(md5, data, size);
+    }
+
+    return FileInfo(storage_.Create(data, size), type, size, md5);
   }
 }
--- a/Core/FileStorage/FileStorageAccessor.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileStorage/FileStorageAccessor.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/StorageAccessor.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileStorage/StorageAccessor.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/StorageAccessor.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/FileStorage/StorageAccessor.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpClient.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpClient.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/BufferHttpSender.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/FilesystemHttpHandler.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/FilesystemHttpHandler.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/FilesystemHttpSender.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/FilesystemHttpSender.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpFileSender.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/HttpFileSender.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/HttpFileSender.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpHandler.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/HttpHandler.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpHandler.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/HttpHandler.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/HttpOutput.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/HttpOutput.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/MongooseServer.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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"
 
@@ -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
   }
 
 
--- a/Core/HttpServer/MongooseServer.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/HttpServer/MongooseServer.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/ICommand.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/IDynamicObject.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Lua/LuaContext.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Lua/LuaContext.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Lua/LuaContext.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Lua/LuaException.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Lua/LuaFunctionCall.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Lua/LuaFunctionCall.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Lua/LuaFunctionCall.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/MultiThreading/ArrayFilledByThreads.h	Wed Apr 16 16:34:09 2014 +0200
@@ -2,7 +2,7 @@
 
 #include <boost/thread.hpp>
 
-#include "../ICommand.h"
+#include "../IDynamicObject.h"
 
 namespace Orthanc
 {
--- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/MultiThreading/BagOfRunnablesBySteps.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/MultiThreading/IRunnableBySteps.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/MultiThreading/SharedMessageQueue.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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,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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/MultiThreading/SharedMessageQueue.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/MultiThreading/ThreadedCommandProcessor.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/MultiThreading/ThreadedCommandProcessor.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/OrthancException.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/OrthancException.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/RestApi.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/RestApi/RestApi.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/RestApi.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/RestApi/RestApi.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/RestApi/RestApiOutput.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/RestApi/RestApiOutput.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/RestApi/RestApiPath.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/RestApi/RestApiPath.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/RestApi/RestApiPath.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/SQLite/Connection.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/Connection.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, 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/Connection.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/Connection.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/FunctionContext.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/FunctionContext.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Redistribution and use in source and binary forms, with or without
@@ -89,6 +89,12 @@
       CheckIndex(index);
       return std::string(reinterpret_cast<const char*>(sqlite3_value_text(argv_[index])));
     }
+
+    bool FunctionContext::IsNullValue(unsigned int index) const
+    {
+      CheckIndex(index);
+      return sqlite3_value_type(argv_[index]) == SQLITE_NULL;
+    }
   
     void FunctionContext::SetNullResult()
     {
--- a/Core/SQLite/FunctionContext.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/FunctionContext.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Redistribution and use in source and binary forms, with or without
@@ -74,6 +74,8 @@
       double GetDoubleValue(unsigned int index) const;
 
       std::string GetStringValue(unsigned int index) const;
+
+      bool IsNullValue(unsigned int index) const;
   
       void SetNullResult();
 
--- a/Core/SQLite/IScalarFunction.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/IScalarFunction.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Redistribution and use in source and binary forms, with or without
--- a/Core/SQLite/Statement.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/Statement.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
@@ -295,7 +295,7 @@
       return true;
       }*/
 
-    bool Statement::ColumnBlobAsVector(int col, std::vector<char>* val) const 
+    /*bool Statement::ColumnBlobAsVector(int col, std::vector<char>* val) const 
     {
       val->clear();
 
@@ -306,14 +306,14 @@
         memcpy(&(*val)[0], data, len);
       }
       return true;
-    }
+      }*/
 
-    bool Statement::ColumnBlobAsVector(
+    /*bool Statement::ColumnBlobAsVector(
       int col,
       std::vector<unsigned char>* val) const 
     {
       return ColumnBlobAsVector(col, reinterpret_cast< std::vector<char>* >(val));
-    }
+      }*/
 
   }
 }
--- a/Core/SQLite/Statement.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/Statement.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
@@ -89,10 +89,6 @@
         return reference_.GetWrappedObject();
       }
 
-      // Resets the statement to its initial condition. This includes any current
-      // result row, and also the bound variables if the |clear_bound_vars| is true.
-      void Reset(bool clear_bound_vars = true);
-
     public:
       Statement(Connection& database,
                 const std::string& sql);
@@ -166,9 +162,12 @@
       const void* ColumnBlob(int col) const;
       bool ColumnBlobAsString(int col, std::string* blob);
       //bool ColumnBlobAsString16(int col, string16* val) const;
-      bool ColumnBlobAsVector(int col, std::vector<char>* val) const;
-      bool ColumnBlobAsVector(int col, std::vector<unsigned char>* val) const;
+      //bool ColumnBlobAsVector(int col, std::vector<char>* val) const;
+      //bool ColumnBlobAsVector(int col, std::vector<unsigned char>* val) const;
 
+      // Resets the statement to its initial condition. This includes any current
+      // result row, and also the bound variables if the |clear_bound_vars| is true.
+      void Reset(bool clear_bound_vars = true);
     };
   }
 }
--- a/Core/SQLite/StatementId.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/StatementId.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementId.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/StatementId.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementReference.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/StatementReference.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, 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.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/StatementReference.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Transaction.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/Transaction.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Transaction.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/SQLite/Transaction.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/Toolbox.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Toolbox.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -157,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)
@@ -187,6 +176,7 @@
 #else
     signal(SIGINT, SignalHandler);
     signal(SIGQUIT, SignalHandler);
+    signal(SIGTERM, SignalHandler);
 #endif
   
     finish = false;
@@ -200,6 +190,7 @@
 #else
     signal(SIGINT, NULL);
     signal(SIGQUIT, NULL);
+    signal(SIGTERM, NULL);
 #endif
   }
 
@@ -217,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) 
@@ -449,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];
--- a/Core/Toolbox.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Toolbox.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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);
 
--- a/Core/Uuid.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Uuid.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/Uuid.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/Core/Uuid.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/LinuxCompilation.txt	Fri Jan 24 17:40:45 2014 +0100
+++ b/LinuxCompilation.txt	Wed Apr 16 16:34:09 2014 +0200
@@ -89,7 +89,6 @@
         -DUSE_SYSTEM_GOOGLE_LOG=OFF \
 	-DUSE_SYSTEM_MONGOOSE=OFF \
         -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \
-        -DDCMTK_DICTIONARY_DIR:PATH=/usr/share/dcmtk \
 	~/Orthanc
 
 
@@ -105,7 +104,6 @@
 # cmake -DALLOW_DOWNLOADS=ON \
 	-DUSE_SYSTEM_MONGOOSE=OFF \
         -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \
-        -DDCMTK_DICTIONARY_DIR:PATH=/usr/share/libdcmtk2 \
 	~/Orthanc
 
 Note: Have also a look at the official package:
@@ -146,9 +144,25 @@
 	~/Orthanc
 
 
+SUPPORTED - Ubuntu 13.10
+------------------------
 
-SUPPORTED - Fedora 18
----------------------
+# 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 \
--- a/NEWS	Fri Jan 24 17:40:45 2014 +0100
+++ b/NEWS	Wed Apr 16 16:34:09 2014 +0200
@@ -3,6 +3,39 @@
 
 
 
+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)
 ==========================
 
--- a/OrthancCppClient/Instance.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/Instance.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/Instance.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/OrthancClientException.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/OrthancConnection.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/OrthancConnection.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/Patient.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/Patient.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/Series.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/Series.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp	Wed Apr 16 16:34:09 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,7 +1476,7 @@
 
   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()
@@ -1440,12 +1486,12 @@
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFileVersion()
   {
-    return "0.7.0.2";
+    return "0.7.0.4";
   }
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFullVersion()
   {
-    return "0.7.2";
+    return "0.7.4";
   }
 
   LAAW_EXPORT_DLL_API void LAAW_CALL_CONVENTION LAAW_EXTERNC_FreeString(char* str)
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h	Wed Apr 16 16:34:09 2014 +0200
@@ -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);
     }
 
@@ -391,7 +391,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)
     {
@@ -784,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;
   };
 }
 
@@ -1746,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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def	Wed Apr 16 16:34:09 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc	Wed Apr 16 16:34:09 2014 +0200
@@ -1,7 +1,7 @@
 #include <winver.h>
 
 VS_VERSION_INFO VERSIONINFO
-   FILEVERSION 0,7,0,2
+   FILEVERSION 0,7,0,4
    PRODUCTVERSION 0,7,0,0
    FILEOS VOS_NT_WINDOWS32
    FILETYPE VFT_DLL
@@ -10,12 +10,12 @@
       BEGIN
          BLOCK "040904E4"
          BEGIN
-            VALUE "Comments", "Release 0.7.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.7.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"
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def	Wed Apr 16 16:34:09 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc	Wed Apr 16 16:34:09 2014 +0200
@@ -1,7 +1,7 @@
 #include <winver.h>
 
 VS_VERSION_INFO VERSIONINFO
-   FILEVERSION 0,7,0,2
+   FILEVERSION 0,7,0,4
    PRODUCTVERSION 0,7,0,0
    FILEOS VOS_NT_WINDOWS32
    FILETYPE VFT_DLL
@@ -10,12 +10,12 @@
       BEGIN
          BLOCK "040904E4"
          BEGIN
-            VALUE "Comments", "Release 0.7.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.7.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"
--- a/OrthancCppClient/SharedLibrary/Laaw/laaw-exports.h	Fri Jan 24 17:40:45 2014 +0100
+++ /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	Fri Jan 24 17:40:45 2014 +0100
+++ /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:34:09 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:34:09 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/Product.json	Wed Apr 16 16:34:09 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.7.2"
+  "Version" : "0.7.4"
 }
--- a/OrthancCppClient/SharedLibrary/SharedLibrary.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/SharedLibrary.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/Study.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancCppClient/Study.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancExplorer/libs/jquery.mobile.simpledialog2.js	Wed Apr 16 16:34:09 2014 +0200
@@ -3,8 +3,6 @@
  * Copyright (c) JTSage
  * CC 3.0 Attribution.  May be relicensed without permission/notifcation.
  * https://github.com/jtsage/jquery-mobile-simpledialog
- *
- * Modifications by Sebastien Jodogne
  */
 
 (function($, undefined ) {
--- a/OrthancServer/DatabaseWrapper.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DatabaseWrapper.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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);
       }
@@ -374,7 +388,7 @@
     }
   }
 
-  bool DatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target,
+  void DatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target,
                                               int64_t id)
   {
     target.clear();
@@ -386,8 +400,6 @@
     {
       target.push_back(static_cast<MetadataType>(s.ColumnInt(0)));
     }
-
-    return true;
   }
 
 
@@ -433,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)
   {
@@ -463,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);
 
@@ -475,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;
     }
   }
@@ -559,7 +587,7 @@
 
     while (s.Step())
     {
-      result.push_back(s.ColumnInt(0));
+      result.push_back(s.ColumnInt64(0));
     }
   }
 
@@ -588,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);
@@ -656,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);
 
@@ -718,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);
   }
 
     
@@ -726,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);
   }
 
 
@@ -820,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&)
     {
@@ -830,6 +873,7 @@
 
     if (!ok)
     {
+      LOG(ERROR) << "Incompatible version of the Orthanc database: " << version;
       throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
     }
 
--- a/OrthancServer/DatabaseWrapper.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DatabaseWrapper.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomServer.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomServer.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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>
@@ -94,7 +95,7 @@
 #endif
 
 
-  void DicomServer::ServerThread(DicomServer* server)
+  void DicomServer::InitializeDictionary()
   {
     /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */
     dcmDisableGethostbyaddr.set(OFTrue);
@@ -148,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
@@ -400,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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomServer.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -66,6 +66,8 @@
     static void ServerThread(DicomServer* server);
 
   public:
+    static void InitializeDictionary();
+
     DicomServer();
 
     ~DicomServer();
@@ -104,6 +106,8 @@
     void Start();
   
     void Stop();
+
+    bool IsMyAETitle(const std::string& aet) const;
   };
 
 }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -335,7 +335,8 @@
       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
@@ -419,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);
   }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/IApplicationEntityFilter.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/IFindRequestHandler.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/IFindRequestHandlerFactory.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/FromDcmtkBridge.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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,6 +130,11 @@
 #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)
@@ -214,36 +219,18 @@
   }
 
 
-  static void AnswerPixelData(RestApiOutput& output,
-                              DcmPixelData& pixelData,
-                              E_TransferSyntax transferSyntax)
+  static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData,
+                                             E_TransferSyntax transferSyntax)
   {
     DcmPixelSequence* pixelSequence = NULL;
     if (pixelData.getEncapsulatedRepresentation
         (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
     {
-      for (unsigned long i = 0; i < pixelSequence->card(); i++)
-      {
-        DcmPixelItem* pixelItem = NULL;
-        if (pixelSequence->getItem(pixelItem, i).good() && pixelItem)
-        {
-          Uint8* b = NULL;
-          if (pixelItem->getUint8Array(b).good() && b)
-          {
-            // JPEG-LS (lossless)
-            // http://gdcm.sourceforge.net/wiki/index.php/Tools/ffmpeg#JPEG_LS
-            // http://www.stat.columbia.edu/~jakulin/jpeg-ls/
-            // http://itohws03.ee.noda.sut.ac.jp/~matsuda/mrp/
-
-            printf("ITEM: %d %d\n", transferSyntax, pixelItem->getLength());
-            char buf[64];
-            sprintf(buf, "/tmp/toto-%06ld.jpg", i);
-            FILE* fp = fopen(buf, "wb");
-            fwrite(b, pixelItem->getLength(), 1, fp);
-            fclose(fp);
-          }
-        }
-      }
+      return pixelSequence->card();
+    }
+    else
+    {
+      return 1;
     }
   }
 
@@ -252,29 +239,13 @@
                                DcmElement& element,
                                E_TransferSyntax transferSyntax)
   {
-    // This element is not a sequence. Test if it is pixel data.
-    if (element.getTag().getGTag() == DICOM_TAG_PIXEL_DATA.GetGroup() &&
-        element.getTag().getETag() == DICOM_TAG_PIXEL_DATA.GetElement())
-    {
-      try
-      {
-        DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(element);
-        AnswerPixelData(output, pixelData, transferSyntax);
-        //return;
-      }
-      catch (std::bad_cast&)
-      {
-      }
-    }
-
-
     // This element is nor a sequence, neither a pixel-data
     std::string buffer;
     buffer.resize(65536);
     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)
     {
@@ -305,6 +276,92 @@
     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,
@@ -336,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++)
@@ -369,7 +442,7 @@
     }
     else
     {
-      SendPathValueForLeaf(output, uri.back(), *dicom, file_->getDataset()->getOriginalXfer());
+      SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax);
     }
   }
 
@@ -760,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);
     }
   }
 
--- a/OrthancServer/FromDcmtkBridge.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/FromDcmtkBridge.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/IServerIndexListener.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/CommandDispatcher.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -385,12 +385,8 @@
         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 =
             {
--- a/OrthancServer/Internals/CommandDispatcher.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/Internals/CommandDispatcher.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/FindScp.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/Internals/FindScp.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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,7 +95,7 @@
         *responseIdentifiers = NULL;   
         return;
       }
-  
+
       if (responseCount <= static_cast<int>(data.answers_.GetSize()))
       {
         response->DimseStatus = STATUS_Pending;
--- a/OrthancServer/Internals/FindScp.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/Internals/FindScp.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/MoveScp.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/Internals/MoveScp.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -82,6 +82,7 @@
         try
         {
           data.iterator_.reset(data.handler_->Handle(data.target_, data.input_));
+
           if (data.iterator_.get() == NULL)
           {
             // Internal error!
--- a/OrthancServer/Internals/MoveScp.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/Internals/MoveScp.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/StoreScp.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/Internals/StoreScp.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/Internals/StoreScp.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/OrthancFindRequestHandler.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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,20 +48,14 @@
             constraint.find('?') != std::string::npos);
   }
 
-  static std::string ToLowerCase(const std::string& s)
-  {
-    std::string result = s;
-    Toolbox::ToLowerCase(result);
-    return result;
-  }
-
   static bool ApplyRangeConstraint(const std::string& value,
                                    const std::string& constraint)
   {
     size_t separator = constraint.find('-');
-    std::string lower = ToLowerCase(constraint.substr(0, separator));
-    std::string upper = ToLowerCase(constraint.substr(separator + 1));
-    std::string v = ToLowerCase(value);
+    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)
     {
@@ -85,15 +79,17 @@
   static bool ApplyListConstraint(const std::string& value,
                                   const std::string& constraint)
   {
-    std::string v1 = ToLowerCase(value);
+    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++)
     {
-      Toolbox::ToLowerCase(items[i]);
-      if (items[i] == v1)
+      std::string lower;
+      Toolbox::ToLowerCase(lower, items[i]);
+      if (lower == v1)
       {
         return true;
       }
@@ -129,7 +125,10 @@
     }
     else
     {
-      return ToLowerCase(value) == ToLowerCase(constraint);
+      std::string v, c;
+      Toolbox::ToLowerCase(v, value);
+      Toolbox::ToLowerCase(c, constraint);
+      return v == c;
     }
   }
 
@@ -211,6 +210,10 @@
           value = resource.get(tag, Json::arrayValue).get("Value", "").asString();
           result.SetValue(query.GetElement(i).GetTag(), value);
         }
+        else
+        {
+          result.SetValue(query.GetElement(i).GetTag(), "");
+        }
       }
     }
 
@@ -287,221 +290,152 @@
   }
 
 
-  static bool LookupCandidateResourcesInternal(/* out */ std::list<std::string>& resources,
-                                               /* in */  ServerIndex& index,
-                                               /* in */  ResourceType level,
-                                               /* in */  const DicomMap& query,
-                                               /* in */  DicomTag tag)
-  {
-    if (query.HasTag(tag))
-    {
-      const DicomValue& value = query.GetValue(tag);
-      if (!value.IsNull())
-      {
-        std::string str = query.GetValue(tag).AsString();
-        if (!IsWildcard(str))
-        {
-          index.LookupTagValue(resources, tag, str/*, level*/);
-          return true;
-        }
-      }
-    }
-
-    return false;
-  }
-
-
-  static bool LookupCandidateResourcesInternal(/* inout */ std::set<std::string>& resources,
-                                               /* in */  bool alreadyFiltered,
-                                               /* in */  ServerIndex& index,
-                                               /* in */  ResourceType level,
-                                               /* in */  const DicomMap& query,
-                                               /* in */  DicomTag tag)
+  namespace
   {
-    assert(alreadyFiltered || resources.size() == 0);
-
-    if (!query.HasTag(tag))
-    {
-      return alreadyFiltered;
-    }
-
-    const DicomValue& value = query.GetValue(tag);
-    if (value.IsNull())
-    {
-      return alreadyFiltered;
-    }
-
-    std::string str = query.GetValue(tag).AsString();
-    if (IsWildcard(str))
+    class CandidateResources
     {
-      return alreadyFiltered;
-    }
-
-    std::list<std::string> matches;
-    index.LookupTagValue(matches, tag, str/*, level*/);
+    private:
+      ServerIndex&  index_;
+      ModalityManufacturer manufacturer_;
+      ResourceType  level_;
+      bool  isFilterApplied_;
+      std::set<std::string>  filtered_;
 
-    if (alreadyFiltered)
-    {
-      std::set<std::string> previous = resources;
-      
-      for (std::list<std::string>::const_iterator 
-             it = matches.begin(); it != matches.end(); it++)
+      static void ListToSet(std::set<std::string>& target,
+                            const std::list<std::string>& source)
       {
-        if (previous.find(*it) != previous.end())
+        for (std::list<std::string>::const_iterator
+               it = source.begin(); it != source.end(); ++it)
         {
-          resources.insert(*it);
+          target.insert(*it);
         }
       }
-    }
-    else
-    {
-      for (std::list<std::string>::const_iterator 
-             it = matches.begin(); it != matches.end(); it++)
-      {
-        resources.insert(*it);
-      }
-    }
-
-    return true;
-  }
-
-
-  static bool LookupCandidateResourcesAtOneLevel(/* out */ std::set<std::string>& resources,
-                                                 /* in */  ServerIndex& index,
-                                                 /* in */  ResourceType level,
-                                                 /* in */  const DicomMap& fullQuery,
-                                                 /* in */  ModalityManufacturer manufacturer)
-  {
-    DicomMap tmp;
-    fullQuery.ExtractMainDicomTagsForLevel(tmp, level);
-    DicomArray query(tmp);
-
-    if (query.GetSize() == 0)
-    {
-      return false;
-    }
 
-    for (size_t i = 0; i < query.GetSize(); i++)
-    {
-      const DicomTag tag = query.GetElement(i).GetTag();
-      const DicomValue& value = query.GetElement(i).GetValue();
-      if (!value.IsNull())
+      void ApplyExactFilter(const DicomTag& tag, const std::string& value)
       {
-        // TODO TODO TODO
-      }
-    }
-
-    printf(">>>>>>>>>>\n");
-    query.Print(stdout); 
-    printf("<<<<<<<<<<\n\n");
-    return true;
-  }
+        LOG(INFO) << "Applying exact filter on tag "
+                  << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")";
 
-
-  static void LookupCandidateResources(/* out */ std::list<std::string>& resources,
-                                       /* in */  ServerIndex& index,
-                                       /* in */  ResourceType level,
-                                       /* in */  const DicomMap& query,
-                                       /* in */  ModalityManufacturer manufacturer)
-  {
-#if 1
-    {
-      std::set<std::string> s;
-      LookupCandidateResourcesAtOneLevel(s, index, ResourceType_Patient, query, manufacturer);
-      LookupCandidateResourcesAtOneLevel(s, index, ResourceType_Study, query, manufacturer);
-      LookupCandidateResourcesAtOneLevel(s, index, ResourceType_Series, query, manufacturer);
-      LookupCandidateResourcesAtOneLevel(s, index, ResourceType_Instance, query, manufacturer);
-    }
+        std::list<std::string> resources;
+        index_.LookupTagValue(resources, tag, value, level_);
 
-    std::set<std::string> filtered;
-    bool isFiltered = false; 
-
-    // Filter by indexed tags, from most specific to least specific
-    //isFiltered = LookupCandidateResourcesInternal(filtered, isFiltered, index, level, query, DICOM_TAG_SOP_INSTANCE_UID);
-    isFiltered = LookupCandidateResourcesInternal(filtered, isFiltered, index, level, query, DICOM_TAG_SERIES_INSTANCE_UID);
-    //isFiltered = LookupCandidateResourcesInternal(filtered, isFiltered, index, level, query, DICOM_TAG_STUDY_INSTANCE_UID);
-    //isFiltered = LookupCandidateResourcesInternal(filtered, isFiltered, index, level, query, DICOM_TAG_PATIENT_ID);
-
-    resources.clear();
+        if (isFilterApplied_)
+        {
+          std::set<std::string>  s;
+          ListToSet(s, resources);
 
-    if (isFiltered)
-    {
-      for (std::set<std::string>::const_iterator
-             it = filtered.begin(); it != filtered.end(); it++)
-      {
-        resources.push_back(*it);
-      }
-    }
-    else
-    {
-      // No indexed tag matches the query. Return all the resources at this query level.
-      Json::Value allResources;
-      index.GetAllUuids(allResources, level);
-      assert(allResources.type() == Json::arrayValue);
-
-      for (Json::Value::ArrayIndex i = 0; i < allResources.size(); i++)
-      {
-        resources.push_back(allResources[i].asString());
-      }
-    }
+          std::set<std::string> tmp = filtered_;
+          filtered_.clear();
 
-#else
-
-    // TODO : Speed up using full querying against the MainDicomTags.
-
-    resources.clear();
-
-    bool done = false;
-
-    switch (level)
-    {
-      case ResourceType_Patient:
-        done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_PATIENT_ID);
-        break;
-
-      case ResourceType_Study:
-        done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_STUDY_INSTANCE_UID);
-        break;
-
-      case ResourceType_Series:
-        done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_SERIES_INSTANCE_UID);
-        break;
-
-      case ResourceType_Instance:
-        if (manufacturer == ModalityManufacturer_MedInria)
-        {
-          std::list<std::string> series;
-
-          if (LookupCandidateResourcesInternal(series, index, ResourceType_Series, query, DICOM_TAG_SERIES_INSTANCE_UID) &&
-              series.size() == 1)
+          for (std::set<std::string>::const_iterator 
+                 it = tmp.begin(); it != tmp.end(); ++it)
           {
-            index.GetChildInstances(resources, series.front());
-            done = true;
-          }          
+            if (s.find(*it) != s.end())
+            {
+              filtered_.insert(*it);
+            }
+          }
         }
         else
         {
-          done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_SOP_INSTANCE_UID);
+          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);
+          }
         }
 
-        break;
+        switch (level_)
+        {
+          case ResourceType_Patient:
+            level_ = ResourceType_Study;
+            break;
+
+          case ResourceType_Study:
+            level_ = ResourceType_Series;
+            break;
 
-      default:
-        break;
-    }
+          case ResourceType_Series:
+            level_ = ResourceType_Instance;
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+
+      void Flatten(std::list<std::string>& resources) const
+      {
+        resources.clear();
 
-    if (!done)
-    {
-      Json::Value allResources;
-      index.GetAllUuids(allResources, level);
-      assert(allResources.type() == Json::arrayValue);
+        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());
+          }
+        }
+      }
 
-      for (Json::Value::ArrayIndex i = 0; i < allResources.size(); i++)
+      void ApplyFilter(const DicomTag& tag, const DicomMap& query)
       {
-        resources.push_back(allResources[i].asString());
+        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);
+            }
+          }
+        }
       }
-    }
-#endif
+    };
   }
 
 
@@ -538,20 +472,26 @@
 
     ResourceType level = StringToResourceType(levelTmp->AsString().c_str());
 
-    switch (manufacturer)
+    if (level != ResourceType_Patient &&
+        level != ResourceType_Study &&
+        level != ResourceType_Series &&
+        level != ResourceType_Instance)
     {
-      case ModalityManufacturer_MedInria:
-        // MedInria makes FIND requests at the instance level before starting MOVE
-        break;
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
 
-      default:
-        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();
+      }
     }
 
 
@@ -562,9 +502,45 @@
      * 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;
-    LookupCandidateResources(resources, context_.GetIndex(), level, input, manufacturer);
+    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
@@ -587,9 +563,6 @@
      * Loop over all the resources for this query level.
      **/
 
-    DicomArray query(input);
-    query.Print(stdout);
-
     for (std::list<std::string>::const_iterator 
            resource = resources.begin(); resource != resources.end(); ++resource)
     {
@@ -614,3 +587,15 @@
     }
   }
 }
+
+
+
+/**
+ * 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 (
+ **/
--- a/OrthancServer/OrthancFindRequestHandler.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/OrthancInitialization.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/OrthancInitialization.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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>
@@ -107,7 +108,7 @@
       for (size_t i = 0; i < members.size(); i++)
       {
         std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString();
-        LOG(WARNING) << "Registering user-defined metadata: " << info;
+        LOG(INFO) << "Registering user-defined metadata: " << info;
 
         if (!parameter[members[i]].asBool())
         {
@@ -131,6 +132,40 @@
   }
 
 
+  static void RegisterUserContentType()
+  {
+    if (configuration_->isMember("UserContentType"))
+    {
+      const Json::Value& parameter = (*configuration_) ["UserContentType"];
+
+      Json::Value::Members members = parameter.getMemberNames();
+      for (size_t i = 0; i < members.size(); i++)
+      {
+        std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString();
+        LOG(INFO) << "Registering user-defined attachment type: " << info;
+
+        if (!parameter[members[i]].asBool())
+        {
+          LOG(ERROR) << "Not a number in this user-defined attachment type: " << info;
+          throw OrthancException(ErrorCode_BadParameterType);
+        }
+
+        int contentType = parameter[members[i]].asInt();
+
+        try
+        {
+          RegisterUserContentType(contentType, members[i]);
+        }
+        catch (OrthancException&)
+        {
+          LOG(ERROR) << "Cannot register this user-defined attachment type: " << info;
+          throw;
+        }
+      }
+    }
+  }
+
+
   void OrthancInitialize(const char* configurationFile)
   {
     boost::mutex::scoped_lock lock(globalMutex_);
@@ -142,6 +177,9 @@
     HttpClient::GlobalInitialize();
 
     RegisterUserMetadata();
+    RegisterUserContentType();
+
+    DicomServer::InitializeDictionary();
   }
 
 
@@ -214,7 +252,7 @@
 
     if (!configuration_->isMember("DicomModalities"))
     {
-      throw OrthancException("");
+      throw OrthancException(ErrorCode_BadFileFormat);
     }
 
     const Json::Value& modalities = (*configuration_) ["DicomModalities"];
@@ -222,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)
       {
@@ -240,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;
     }
   }
 
@@ -257,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;
     }
   }
 
@@ -398,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())
     {
@@ -462,6 +527,25 @@
   }
 
 
+  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,
@@ -478,8 +562,8 @@
       {
         std::string thisAet;
         GetDicomModalityUsingSymbolicName(*it, thisAet, address, port, manufacturer);
-        
-        if (aet == thisAet)
+
+        if (IsSameAETitle(aet, thisAet))
         {
           return true;
         }
--- a/OrthancServer/OrthancInitialization.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/OrthancInitialization.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -93,4 +93,7 @@
                                      const std::string& aet);
 
   bool IsKnownAETitle(const std::string& aet);
+
+  bool IsSameAETitle(const std::string& aet1,
+                     const std::string& aet2);
 }
--- a/OrthancServer/OrthancMoveRequestHandler.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -139,7 +139,6 @@
 
     ResourceType level = StringToResourceType(levelTmp->AsString().c_str());
 
-
     /**
      * Lookup for the resource to be sent.
      **/
--- a/OrthancServer/OrthancMoveRequestHandler.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/OrthancMoveRequestHandler.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/OrthancRestApi.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1927 +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>
-
-#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;
-
-
-#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
-{
-  // TODO IMPROVE MULTITHREADING
-  // Every call to "ParsedDicomFile" must lock this mutex!!!
-  static boost::mutex cacheMutex_;
-
-
-  // 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)
-  {
-    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);
-
-        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)
-  {
-    RETRIEVE_CONTEXT(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");
-  }
-
-
-
-  // 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,
-                              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)
-  {
-    RETRIEVE_CONTEXT(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
-  }
-
-
-  // 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)
-  {
-    boost::mutex::scoped_lock lock(cacheMutex_);
-
-    RETRIEVE_CONTEXT(call);
-    std::string id = call.GetUriComponent("id", "");
-    ParsedDicomFile& dicom = context.GetDicomFile(id);
-    dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri());
-  }
-
-
-
-  // Modification of DICOM instances ------------------------------------------
-
-  namespace
-  {
-    typedef std::set<DicomTag> Removals;
-    typedef std::map<DicomTag, std::string> Replacements;
-    typedef std::map< std::pair<DicomRootLevel, std::string>, std::string>  UidMap;
-  }
-
-  static void ReplaceInstanceInternal(ParsedDicomFile& toModify,
-                                      const Removals& removals,
-                                      const Replacements& replacements,
-                                      DicomReplaceMode mode,
-                                      bool removePrivateTags)
-  {
-    if (removePrivateTags)
-    {
-      toModify.RemovePrivateTags();
-    }
-
-    for (Removals::const_iterator it = removals.begin(); 
-         it != removals.end(); ++it)
-    {
-      toModify.Remove(*it);
-    }
-
-    for (Replacements::const_iterator it = replacements.begin(); 
-         it != replacements.end(); ++it)
-    {
-      toModify.Replace(it->first, it->second, mode);
-    }
-
-    // A new SOP instance UID is automatically generated
-    std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance);
-    toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, DicomReplaceMode_InsertIfAbsent);
-  }
-
-
-  static void ParseRemovals(Removals& target,
-                            const Json::Value& removals)
-  {
-    if (!removals.isArray())
-    {
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-
-    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)
-  {
-    boost::mutex::scoped_lock lock(cacheMutex_);
-    RETRIEVE_CONTEXT(call);
-    
-    std::string id = call.GetUriComponent("id", "");
-    ParsedDicomFile& dicom = context.GetDicomFile(id);
-    
-    std::auto_ptr<ParsedDicomFile> modified(dicom.Clone());
-    ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags);
-    modified->Answer(call.GetOutput());
-  }
-
-
-  static bool RetrieveMappedUid(ParsedDicomFile& dicom,
-                                DicomRootLevel level,
-                                Replacements& replacements,
-                                UidMap& uidMap)
-  {
-    std::auto_ptr<DicomTag> tag;
-
-    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_);
-    RETRIEVE_CONTEXT(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);
-    }
-  }
-
-
-  // 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	Fri Jan 24 17:40:45 2014 +0100
+++ /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;
-
-  private:
-    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:34:09 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:34:09 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:34:09 2014 +0200
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../ServerContext.h"
+#include "../../Core/RestApi/RestApi.h"
+
+#include <set>
+
+namespace Orthanc
+{
+  class OrthancRestApi : public RestApi
+  {
+  public:
+    typedef std::set<std::string> SetOfStrings;
+
+  private:
+    ServerContext& context_;
+
+    void RegisterSystem();
+
+    void RegisterChanges();
+
+    void RegisterResources();
+
+    void RegisterModalities();
+
+    void RegisterAnonymizeModify();
+
+    void RegisterArchive();
+
+  public:
+    OrthancRestApi(ServerContext& context);
+
+    static ServerContext& GetContext(RestApi::Call& call)
+    {
+      OrthancRestApi& that = dynamic_cast<OrthancRestApi&>(call.GetContext());
+      return that.context_;
+    }
+
+    static ServerIndex& GetIndex(RestApi::Call& call)
+    {
+      return GetContext(call).GetIndex();
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Wed Apr 16 16:34:09 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:34:09 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:34:09 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:34:09 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:34:09 2014 +0200
@@ -0,0 +1,109 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancRestApi.h"
+
+#include "../OrthancInitialization.h"
+
+#include <glog/logging.h>
+
+
+namespace Orthanc
+{
+  // System information -------------------------------------------------------
+
+  static void ServeRoot(RestApi::GetCall& call)
+  {
+    call.GetOutput().Redirect("app/explorer.html");
+  }
+ 
+  static void GetSystemInformation(RestApi::GetCall& call)
+  {
+    Json::Value result = Json::objectValue;
+
+    result["Version"] = ORTHANC_VERSION;
+    result["Name"] = GetGlobalStringParameter("Name", "");
+
+    call.GetOutput().AnswerJson(result);
+  }
+
+  static void GetStatistics(RestApi::GetCall& call)
+  {
+    Json::Value result = Json::objectValue;
+    OrthancRestApi::GetIndex(call).ComputeStatistics(result);
+    call.GetOutput().AnswerJson(result);
+  }
+
+  static void GenerateUid(RestApi::GetCall& call)
+  {
+    std::string level = call.GetArgument("level", "");
+    if (level == "patient")
+    {
+      call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient), "text/plain");
+    }
+    else if (level == "study")
+    {
+      call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study), "text/plain");
+    }
+    else if (level == "series")
+    {
+      call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series), "text/plain");
+    }
+    else if (level == "instance")
+    {
+      call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance), "text/plain");
+    }
+  }
+
+  static void ExecuteScript(RestApi::PostCall& call)
+  {
+    std::string result;
+    ServerContext& context = OrthancRestApi::GetContext(call);
+    context.GetLuaContext().Execute(result, call.GetPostBody());
+    call.GetOutput().AnswerBuffer(result, "text/plain");
+  }
+
+  static void GetNowIsoString(RestApi::GetCall& call)
+  {
+    call.GetOutput().AnswerBuffer(Toolbox::GetNowIsoString(), "text/plain");
+  }
+
+  void OrthancRestApi::RegisterSystem()
+  {
+    Register("/", ServeRoot);
+    Register("/system", GetSystemInformation);
+    Register("/statistics", GetStatistics);
+    Register("/tools/generate-uid", GenerateUid);
+    Register("/tools/execute-script", ExecuteScript);
+    Register("/tools/now", GetNowIsoString);
+  }
+}
--- a/OrthancServer/PrepareDatabase.sql	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/PrepareDatabase.sql	Wed Apr 16 16:34:09 2014 +0200
@@ -32,6 +32,8 @@
        compressedSize INTEGER,
        uncompressedSize INTEGER,
        compressionType INTEGER,
+       uncompressedMD5 TEXT,  -- New in Orthanc 0.7.3 (database v4)
+       compressedMD5 TEXT,    -- New in Orthanc 0.7.3 (database v4)
        PRIMARY KEY(id, fileType)
        );              
 
@@ -75,7 +77,9 @@
 AFTER DELETE ON AttachedFiles
 BEGIN
   SELECT SignalFileDeleted(old.uuid, old.fileType, old.uncompressedSize, 
-                           old.compressionType, old.compressedSize);
+                           old.compressionType, old.compressedSize,
+                           -- These 2 arguments are new in Orthanc 0.7.3 (database v4)
+                           old.uncompressedMD5, old.compressedMD5);
 END;
 
 CREATE TRIGGER ResourceDeleted
@@ -103,4 +107,4 @@
 
 -- Set the version of the database schema
 -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration
-INSERT INTO GlobalProperties VALUES (1, "3");
+INSERT INTO GlobalProperties VALUES (1, "4");
--- a/OrthancServer/ServerContext.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/ServerContext.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -116,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);
@@ -153,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))
@@ -176,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))
@@ -188,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))
@@ -196,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());
   }
 
@@ -229,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;
+    }
   }
 
 
@@ -269,4 +290,40 @@
     return Store(resultPublicId, dicom.GetDicom(), dicomBuffer, dicomSize);
   }
 
+  void ServerContext::SetStoreMD5ForAttachments(bool storeMD5)
+  {
+    LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no");
+    accessor_.SetStoreMD5(storeMD5);
+  }
+
+
+  bool ServerContext::AddAttachment(const std::string& resourceId,
+                                    FileContentType attachmentType,
+                                    const void* data,
+                                    size_t size)
+  {
+    LOG(INFO) << "Adding attachment " << EnumerationToString(attachmentType) << " to resource " << resourceId;
+    
+    if (compressionEnabled_)
+    {
+      accessor_.SetCompressionForNextOperations(CompressionType_Zlib);
+    }
+    else
+    {
+      accessor_.SetCompressionForNextOperations(CompressionType_None);
+    }      
+
+    FileInfo info = accessor_.Write(data, size, attachmentType);
+    StoreStatus status = index_.AddAttachment(info, resourceId);
+
+    if (status != StoreStatus_Success)
+    {
+      storage_.Remove(info.GetUuid());
+      return false;
+    }
+    else
+    {
+      return true;
+    }
+  }
 }
--- a/OrthancServer/ServerContext.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/ServerContext.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -91,6 +91,11 @@
 
     void RemoveFile(const std::string& fileUuid);
 
+    bool AddAttachment(const std::string& resourceId,
+                       FileContentType attachmentType,
+                       const void* data,
+                       size_t size);
+
     StoreStatus Store(const char* dicomInstance,
                       size_t dicomSize,
                       const DicomMap& dicomSummary,
@@ -118,9 +123,9 @@
         return Store(resultPublicId, &dicomContent[0], dicomContent.size());
     }
 
-    void AnswerFile(RestApiOutput& output,
-                    const std::string& instancePublicId,
-                    FileContentType content);
+    void AnswerDicomFile(RestApiOutput& output,
+                         const std::string& instancePublicId,
+                         FileContentType content);
 
     void ReadJson(Json::Value& result,
                   const std::string& instancePublicId);
@@ -128,7 +133,8 @@
     // TODO CACHING MECHANISM AT THIS POINT
     void ReadFile(std::string& result,
                   const std::string& instancePublicId,
-                  FileContentType content);
+                  FileContentType content,
+                  bool uncompressIfNeeded = true);
 
     // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD
     ParsedDicomFile& GetDicomFile(const std::string& instancePublicId);
@@ -137,5 +143,12 @@
     {
       return lua_;
     }
+
+    void SetStoreMD5ForAttachments(bool storeMD5);
+
+    bool IsStoreMD5ForAttachments() const
+    {
+      return accessor_.IsStoreMD5();
+    }
   };
 }
--- a/OrthancServer/ServerEnumerations.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/ServerEnumerations.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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,6 +41,7 @@
 {
   static boost::mutex enumerationsMutex_;
   static Toolbox::EnumerationDictionary<MetadataType> dictMetadataType_;
+  static Toolbox::EnumerationDictionary<FileContentType> dictContentType_;
 
   void InitializeServerEnumerations()
   {
@@ -53,6 +54,9 @@
     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,
@@ -83,6 +87,34 @@
     return dictMetadataType_.Translate(str);
   }
 
+  void RegisterUserContentType(int contentType,
+                               const std::string& name)
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+
+    if (contentType < static_cast<int>(FileContentType_StartUser) ||
+        contentType > static_cast<int>(FileContentType_EndUser))
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    dictContentType_.Add(static_cast<FileContentType>(contentType), name);
+  }
+
+  std::string EnumerationToString(FileContentType type)
+  {
+    // This function MUST return a "std::string" and not "const
+    // char*", as the result is not a static string
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+    return dictContentType_.Translate(type);
+  }
+
+  FileContentType StringToContentType(const std::string& str)
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+    return dictContentType_.Translate(str);
+  }
+
   std::string GetBasePath(ResourceType type,
                           const std::string& publicId)
   {
@@ -250,6 +282,9 @@
       
       case ModalityManufacturer_MedInria:
         return "MedInria";
+
+      case ModalityManufacturer_Dcm4Chee:
+        return "Dcm4Chee";
       
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
@@ -303,6 +338,10 @@
     {
       return ModalityManufacturer_MedInria;
     }
+    else if (manufacturer == "Dcm4Chee")
+    {
+      return ModalityManufacturer_Dcm4Chee;
+    }
     else
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
--- a/OrthancServer/ServerEnumerations.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/ServerEnumerations.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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,7 +57,8 @@
   {
     ModalityManufacturer_Generic,
     ModalityManufacturer_ClearCanvas,
-    ModalityManufacturer_MedInria
+    ModalityManufacturer_MedInria,
+    ModalityManufacturer_Dcm4Chee
   };
 
   enum DicomRequestType
@@ -121,13 +122,20 @@
   void RegisterUserMetadata(int metadata,
                             const std::string& name);
 
-  std::string GetBasePath(ResourceType type,
-                          const std::string& publicId);
-
   MetadataType StringToMetadata(const std::string& str);
 
   std::string EnumerationToString(MetadataType type);
 
+  void RegisterUserContentType(int contentType,
+                               const std::string& name);
+
+  FileContentType StringToContentType(const std::string& str);
+
+  std::string EnumerationToString(FileContentType type);
+
+  std::string GetBasePath(ResourceType type,
+                          const std::string& publicId);
+
   const char* EnumerationToString(SeriesStatus status);
 
   const char* EnumerationToString(StoreStatus status);
--- a/OrthancServer/ServerIndex.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/ServerIndex.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -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);
     }
@@ -819,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);
     }
@@ -1108,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)
   {
@@ -1208,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_);
@@ -1220,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);
   }
 
 
@@ -1322,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
@@ -1577,53 +1626,71 @@
   }
 
 
-  // TODO IS IT USEFUL???
-  void ServerIndex::LookupTagValue(std::set<std::string>& result,
-                                   DicomTag tag,
-                                   const std::string& value,
-                                   ResourceType type)
+  StoreStatus ServerIndex::AddAttachment(const FileInfo& attachment,
+                                         const std::string& publicId)
   {
-    std::list<std::string> lst;
-    LookupTagValue(lst, tag, value, type);
+    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());
 
-    result.clear();
-    for (std::list<std::string>::const_iterator
-           it = lst.begin(); it != lst.end(); it++)
+    // Locate the patient of the target resource
+    int64_t patientId = resourceId;
+    for (;;)
     {
-      result.insert(*it);
+      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;
   }
 
 
-  // TODO IS IT USEFUL???
-  void ServerIndex::LookupTagValue(std::set<std::string>& result,
-                                   DicomTag tag,
-                                   const std::string& value)
+  void ServerIndex::DeleteAttachment(const std::string& publicId,
+                                     FileContentType type)
   {
-    std::list<std::string> lst;
-    LookupTagValue(lst, tag, value);
+    boost::mutex::scoped_lock lock(mutex_);
+    listener_->Reset();
+
+    Transaction t(*this);
 
-    result.clear();
-    for (std::list<std::string>::const_iterator
-           it = lst.begin(); it != lst.end(); it++)
+    ResourceType rtype;
+    int64_t id;
+    if (!db_->LookupResource(publicId, id, rtype))
     {
-      result.insert(*it);
+      throw OrthancException(ErrorCode_UnknownResource);
     }
+
+    db_->DeleteAttachment(id, type);
+
+    t.Commit(0);
   }
 
 
-  // TODO IS IT USEFUL???
-  void ServerIndex::LookupTagValue(std::set<std::string>& result,
-                                   const std::string& value)
-  {
-    std::list<std::string> lst;
-    LookupTagValue(lst, value);
-
-    result.clear();
-    for (std::list<std::string>::const_iterator
-           it = lst.begin(); it != lst.end(); it++)
-    {
-      result.insert(*it);
-    }
-  }
 }
--- a/OrthancServer/ServerIndex.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/ServerIndex.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -163,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);
 
@@ -177,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);
 
@@ -214,20 +221,10 @@
     void LookupTagValue(std::list<std::string>& result,
                         const std::string& value);
 
-
-    // TODO IS IT USEFUL???
-    void LookupTagValue(std::set<std::string>& result,
-                        DicomTag tag,
-                        const std::string& value,
-                        ResourceType type);
+    StoreStatus AddAttachment(const FileInfo& attachment,
+                              const std::string& publicId);
 
-    // TODO IS IT USEFUL???
-    void LookupTagValue(std::set<std::string>& result,
-                        DicomTag tag,
-                        const std::string& value);
-
-    // TODO IS IT USEFUL???
-    void LookupTagValue(std::set<std::string>& result,
-                        const std::string& value);
+    void DeleteAttachment(const std::string& publicId,
+                          FileContentType type);
   };
 }
--- a/OrthancServer/ServerToolbox.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/ServerToolbox.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -35,6 +35,7 @@
 #include "../Core/OrthancException.h"
 
 #include <cassert>
+#include <glog/logging.h>
 
 namespace Orthanc
 {
@@ -82,4 +83,71 @@
       }
     }
   }
+
+
+  void LogMissingRequiredTag(const DicomMap& summary)
+  {
+    std::string s, t;
+
+    if (summary.HasTag(DICOM_TAG_PATIENT_ID))
+    {
+      if (t.size() > 0)
+        t += ", ";
+      t += "PatientID=" + summary.GetValue(DICOM_TAG_PATIENT_ID).AsString();
+    }
+    else
+    {
+      if (s.size() > 0)
+        s += ", ";
+      s += "PatientID";
+    }
+
+    if (summary.HasTag(DICOM_TAG_STUDY_INSTANCE_UID))
+    {
+      if (t.size() > 0)
+        t += ", ";
+      t += "StudyInstanceUID=" + summary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
+    }
+    else
+    {
+      if (s.size() > 0)
+        s += ", ";
+      s += "StudyInstanceUID";
+    }
+
+    if (summary.HasTag(DICOM_TAG_SERIES_INSTANCE_UID))
+    {
+      if (t.size() > 0)
+        t += ", ";
+      t += "SeriesInstanceUID=" + summary.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
+    }
+    else
+    {
+      if (s.size() > 0)
+        s += ", ";
+      s += "SeriesInstanceUID";
+    }
+
+    if (summary.HasTag(DICOM_TAG_SOP_INSTANCE_UID))
+    {
+      if (t.size() > 0)
+        t += ", ";
+      t += "SOPInstanceUID=" + summary.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
+    }
+    else
+    {
+      if (s.size() > 0)
+        s += ", ";
+      s += "SOPInstanceUID";
+    }
+
+    if (t.size() == 0)
+    {
+      LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)";
+    }
+    else
+    {
+      LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t;
+    }
+  }
 }
--- a/OrthancServer/ServerToolbox.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/ServerToolbox.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -32,10 +32,14 @@
 
 #pragma once
 
+#include "../Core/DicomFormat/DicomMap.h"
+
 #include <json/json.h>
 
 namespace Orthanc
 {
   void SimplifyTags(Json::Value& target,
                     const Json::Value& source);
+
+  void LogMissingRequiredTag(const DicomMap& summary);
 }
--- a/OrthancServer/ToDcmtkBridge.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/ToDcmtkBridge.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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/ToDcmtkBridge.h	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/ToDcmtkBridge.h	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU 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:34:09 2014 +0200
@@ -0,0 +1,24 @@
+-- This SQLite script updates the version of the Orthanc database from 3 to 4.
+
+-- Add 2 new columns at "AttachedFiles"
+
+ALTER TABLE AttachedFiles ADD COLUMN uncompressedMD5 TEXT;
+ALTER TABLE AttachedFiles ADD COLUMN compressedMD5 TEXT;
+
+-- Update the "AttachedFileDeleted" trigger
+
+DROP TRIGGER AttachedFileDeleted;
+
+CREATE TRIGGER AttachedFileDeleted
+AFTER DELETE ON AttachedFiles
+BEGIN
+  SELECT SignalFileDeleted(old.uuid, old.fileType, old.uncompressedSize, 
+                           old.compressionType, old.compressedSize,
+                           -- These 2 arguments are new in Orthanc 0.7.3 (database v4)
+                           old.uncompressedMD5, old.compressedMD5);
+END;
+
+-- Change the database version
+-- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration
+
+UPDATE GlobalProperties SET value="4" WHERE property=1;
--- a/OrthancServer/main.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/OrthancServer/main.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -30,7 +30,7 @@
  **/
 
 
-#include "OrthancRestApi.h"
+#include "OrthancRestApi/OrthancRestApi.h"
 
 #include <fstream>
 #include <glog/logging.h>
@@ -46,6 +46,7 @@
 #include "ServerContext.h"
 #include "OrthancFindRequestHandler.h"
 #include "OrthancMoveRequestHandler.h"
+#include "ServerToolbox.h"
 
 using namespace Orthanc;
 
@@ -131,7 +132,7 @@
 
     if (!IsKnownAETitle(callingAet))
     {
-      LOG(ERROR) << "Unkwnown remote DICOM modality AET: \"" << callingAet << "\"";
+      LOG(ERROR) << "Unknown remote DICOM modality AET: \"" << callingAet << "\"";
       return false;
     }
     else
@@ -233,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
@@ -285,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;
@@ -328,6 +334,7 @@
     LOG(WARNING) << "Index directory: " << indexDirectory;
 
     context.SetCompressionEnabled(GetGlobalBoolParameter("StorageCompression", false));
+    context.SetStoreMD5ForAttachments(GetGlobalBoolParameter("StoreMD5ForAttachments", true));
 
     std::list<std::string> luaScripts;
     GetGlobalListOfStringsParameter(luaScripts, "LuaScripts");
@@ -397,9 +404,6 @@
         httpServer.SetSslEnabled(false);
       }
 
-      LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber();
-      LOG(WARNING) << "HTTP server listening on port: " << httpServer.GetPortNumber();
-
 #if ORTHANC_STANDALONE == 1
       httpServer.RegisterHandler(new EmbeddedResourceHttpHandler("/app", EmbeddedResources::ORTHANC_EXPLORER));
 #else
@@ -408,9 +412,26 @@
 
       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";
+      }
 
 
       {
@@ -438,7 +459,7 @@
       LOG(WARNING) << "Orthanc has started";
       Toolbox::ServerBarrier();
 
-      // Stop
+      // We're done
       LOG(WARNING) << "Orthanc is stopping";
     }
 
@@ -457,5 +478,7 @@
 
   OrthancFinalize();
 
+  LOG(WARNING) << "Orthanc has stopped";
+
   return status;
 }
--- a/README	Fri Jan 24 17:40:45 2014 +0100
+++ b/README	Wed Apr 16 16:34:09 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/CMake/BoostConfiguration.cmake	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/BoostConfiguration.cmake	Wed Apr 16 16:34:09 2014 +0200
@@ -1,125 +1,125 @@
-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.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()
-
-  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()
+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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/BoostConfiguration.sh	Wed Apr 16 16:34:09 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Wed Apr 16 16:34:09 2014 +0200
@@ -1,138 +1,151 @@
-add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}")
-
-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}")
+# Lookup for DICOM dictionaries, if none is specified by the user
+if (DCMTK_DICTIONARY_DIR STREQUAL "")
+  find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic
+    /usr/share/dcmtk
+    /usr/share/libdcmtk2
+    )
+
+  message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}")
+  add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR_AUTO}")
+else()
+  add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}")
+endif()
+
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK)
+  SET(DCMTK_VERSION_NUMBER 360)
+  SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0)
+  DownloadPackage(
+    "219ad631b82031806147e4abbfba4fa4"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip" 
+    "${DCMTK_SOURCES_DIR}")
+
+  IF(CMAKE_CROSSCOMPILING)
+    SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.")
+  ENDIF()
+  SET(DCMTK_SOURCE_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0)
+  include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake)
+  include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake)
+
+  if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+    set(HAVE_SSTREAM 1)
+    set(HAVE_PROTOTYPE_BZERO 1)
+    set(HAVE_PROTOTYPE_GETHOSTNAME 1)
+    set(HAVE_PROTOTYPE_GETSOCKOPT 1)
+    set(HAVE_PROTOTYPE_SETSOCKOPT 1)
+    set(HAVE_PROTOTYPE_CONNECT 1)
+    set(HAVE_PROTOTYPE_BIND 1)
+    set(HAVE_PROTOTYPE_ACCEPT 1)
+    set(HAVE_PROTOTYPE_SETSOCKNAME 1)
+    set(HAVE_PROTOTYPE_GETSOCKNAME 1)
+  endif()
+
+  CONFIGURE_FILE(
+    ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in
+    ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h)
+
+  AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmnet/libsrc DCMTK_SOURCES)
+  AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmdata/libsrc DCMTK_SOURCES)
+  AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/ofstd/libsrc DCMTK_SOURCES)
+
+  # Source for the logging facility of DCMTK
+  AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES)
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+    list(REMOVE_ITEM DCMTK_SOURCES 
+      ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc
+      ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc
+      )
+  elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    list(REMOVE_ITEM DCMTK_SOURCES 
+      ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc
+      )
+
+    if (${CMAKE_COMPILER_IS_GNUCXX})
+      # This is a patch for MinGW64
+      execute_process(
+        COMMAND patch -p0 -i ${CMAKE_SOURCE_DIR}/Resources/Patches/dcmtk-mingw64.patch
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        )
+    endif()
+
+  endif()
+
+  list(REMOVE_ITEM DCMTK_SOURCES 
+    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc
+    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc
+    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc
+    )
+
+  # This fixes crashes related to the destruction of the DCMTK OFLogger
+  # http://support.dcmtk.org/docs-snapshot/file_macros.html
+  add_definitions(
+    -DLOG4CPLUS_DISABLE_FATAL=1
+    -DDCMTK_VERSION_NUMBER=360
+    )
+
+  include_directories(
+    #${DCMTK_SOURCES_DIR}
+    ${DCMTK_SOURCES_DIR}/config/include
+    ${DCMTK_SOURCES_DIR}/dcmnet/include
+    ${DCMTK_SOURCES_DIR}/ofstd/include
+    ${DCMTK_SOURCES_DIR}/oflog/include
+    ${DCMTK_SOURCES_DIR}/dcmdata/include
+    )
+
+  source_group(ThirdParty\\Dcmtk REGULAR_EXPRESSION ${DCMTK_SOURCES_DIR}/.*)
+
+  set(DCMTK_BUNDLES_LOG4CPLUS 1)
+
+  if (STANDALONE_BUILD)
+    add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=1)
+  else()
+    add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0)
+  endif()
+
+  set(DCMTK_DICTIONARIES
+    DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic
+    DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic
+    DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic
+    )
+
+else()
+  # The following line allows to manually add libraries at the
+  # command-line, which is necessary for Ubuntu/Debian packages
+  set(tmp "${DCMTK_LIBRARIES}")
+  include(FindDCMTK)
+  list(APPEND DCMTK_LIBRARIES "${tmp}")
+
+  include_directories(${DCMTK_INCLUDE_DIR})
+  link_libraries(${DCMTK_LIBRARIES})
+
+  add_definitions(
+    -DHAVE_CONFIG_H=1
+    )
+
+  if (EXISTS "${DCMTK_DIR}/config/cfunix.h")
+    set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/cfunix.h")
+  elseif (EXISTS "${DCMTK_DIR}/config/osconfig.h")  # This is for Arch Linux
+    set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/osconfig.h")
+  else()
+    message(FATAL_ERROR "Please install libdcmtk1-dev")
+  endif()
+
+  # Autodetection of the version of DCMTK
+  file(STRINGS
+    "${DCMTK_CONFIGURATION_FILE}" 
+    DCMTK_VERSION_NUMBER1 REGEX
+    ".*PACKAGE_VERSION .*")    
+
+  string(REGEX REPLACE
+    ".*PACKAGE_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$"
+    "\\1\\2\\3" 
+    DCMTK_VERSION_NUMBER 
+    ${DCMTK_VERSION_NUMBER1})
+
+  add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0)
+
+endif()
+
+add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER})
+message("DCMTK version: ${DCMTK_VERSION_NUMBER}")
--- a/Resources/CMake/DownloadPackage.cmake	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/DownloadPackage.cmake	Wed Apr 16 16:34:09 2014 +0200
@@ -1,133 +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}")
-
-      # 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()
+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/JsonCppConfiguration.cmake	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/JsonCppConfiguration.cmake	Wed Apr 16 16:34:09 2014 +0200
@@ -1,29 +1,29 @@
-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()
+if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP)
+  set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.6.0-rc2)
+  DownloadPackage(
+    "363e2f4cbd3aeb63bf4e571f377400fb"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-src-0.6.0-rc2.tar.gz"
+    "${JSONCPP_SOURCES_DIR}")
+
+  list(APPEND THIRD_PARTY_SOURCES
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp
+    )
+
+  include_directories(
+    ${JSONCPP_SOURCES_DIR}/include
+    )
+
+  source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*)
+
+else()
+  CHECK_INCLUDE_FILE_CXX(jsoncpp/json/reader.h HAVE_JSONCPP_H)
+  if (NOT HAVE_JSONCPP_H)
+    message(FATAL_ERROR "Please install the libjsoncpp-dev package")
+  endif()
+
+  include_directories(/usr/include/jsoncpp)
+  link_libraries(jsoncpp)
+
+endif()
--- a/Resources/CMake/LibCurlConfiguration.cmake	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/LibCurlConfiguration.cmake	Wed Apr 16 16:34:09 2014 +0200
@@ -1,101 +1,101 @@
-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()
+if (STATIC_BUILD OR NOT USE_SYSTEM_CURL)
+  SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.26.0)
+  DownloadPackage(
+    "3fa4d5236f2a36ca5c3af6715e837691"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/curl-7.26.0.tar.gz"
+    "${CURL_SOURCES_DIR}")
+
+  include_directories(${CURL_SOURCES_DIR}/include)
+  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES)
+  source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*)
+
+  #add_library(Curl STATIC ${CURL_SOURCES})
+  #link_libraries(Curl)  
+
+  add_definitions(
+    -DCURL_STATICLIB=1
+    -DBUILDING_LIBCURL=1
+    -DCURL_DISABLE_LDAPS=1
+    -DCURL_DISABLE_LDAP=1
+    -D_WIN32_WINNT=0x0501
+
+    -DCURL_DISABLE_DICT=1
+    -DCURL_DISABLE_FILE=1
+    -DCURL_DISABLE_FTP=1
+    -DCURL_DISABLE_GOPHER=1
+    -DCURL_DISABLE_LDAP=1
+    -DCURL_DISABLE_LDAPS=1
+    -DCURL_DISABLE_POP3=1
+    -DCURL_DISABLE_PROXY=1
+    -DCURL_DISABLE_RTSP=1
+    -DCURL_DISABLE_TELNET=1
+    -DCURL_DISABLE_TFTP=1
+    )
+
+  if (${ENABLE_SSL})
+    add_definitions(
+      #-DHAVE_LIBSSL=1
+      -DUSE_OPENSSL=1
+      -DUSE_SSLEAY=1
+      )
+  endif()
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+    if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
+      SET(TMP_OS "x86_64")
+    else()
+      SET(TMP_OS "x86")
+    endif()
+
+    set_property(
+      SOURCE ${CURL_SOURCES}
+      PROPERTY COMPILE_DEFINITIONS "HAVE_TIME_H;HAVE_STRUCT_TIMEVAL;HAVE_SYS_STAT_H;HAVE_SOCKET;HAVE_STRUCT_SOCKADDR_STORAGE;HAVE_SYS_SOCKET_H;HAVE_SOCKET;HAVE_SYS_SOCKET_H;HAVE_NETINET_IN_H;HAVE_NETDB_H;HAVE_FCNTL_O_NONBLOCK;HAVE_FCNTL_H;HAVE_SELECT;HAVE_ERRNO_H;HAVE_SEND;HAVE_RECV;OS=\"${TMP_OS}\"")
+
+    if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
+      add_definitions(
+        -DRECV_TYPE_ARG1=int
+        -DRECV_TYPE_ARG2=void*
+        -DRECV_TYPE_ARG3=size_t
+        -DRECV_TYPE_ARG4=int
+        -DRECV_TYPE_RETV=ssize_t
+        -DSEND_TYPE_ARG1=int
+        -DSEND_TYPE_ARG2=void*
+        -DSEND_QUAL_ARG2=const
+        -DSEND_TYPE_ARG3=size_t
+        -DSEND_TYPE_ARG4=int
+        -DSEND_TYPE_RETV=ssize_t
+        -DSIZEOF_SHORT=2
+        -DSIZEOF_INT=4
+        -DSIZEOF_SIZE_T=8
+        )
+    elseif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "4")
+      add_definitions(
+        -DRECV_TYPE_ARG1=int
+        -DRECV_TYPE_ARG2=void*
+        -DRECV_TYPE_ARG3=size_t
+        -DRECV_TYPE_ARG4=int
+        -DRECV_TYPE_RETV=int
+        -DSEND_TYPE_ARG1=int
+        -DSEND_TYPE_ARG2=void*
+        -DSEND_QUAL_ARG2=const
+        -DSEND_TYPE_ARG3=size_t
+        -DSEND_TYPE_ARG4=int
+        -DSEND_TYPE_RETV=int
+        -DSIZEOF_SHORT=2
+        -DSIZEOF_INT=4
+        -DSIZEOF_SIZE_T=4
+        )
+    else()
+      message(FATAL_ERROR "Support your platform here")
+    endif()
+  endif()
+
+else()
+  include(FindCURL)
+  include_directories(${CURL_INCLUDE_DIRS})
+  link_libraries(${CURL_LIBRARIES})
+
+  if (NOT ${CURL_FOUND})
+    message(FATAL_ERROR "Unable to find LibCurl")
+  endif()
+endif()
--- a/Resources/CMake/LibPngConfiguration.cmake	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/LibPngConfiguration.cmake	Wed Apr 16 16:34:09 2014 +0200
@@ -1,60 +1,60 @@
-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()
+if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG)
+  SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12)
+  DownloadPackage(
+    "8ea7f60347a306c5faf70b977fa80e28"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz"
+    "${LIBPNG_SOURCES_DIR}")
+
+  include_directories(
+    ${LIBPNG_SOURCES_DIR}
+    )
+
+  configure_file(
+    ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt
+    ${LIBPNG_SOURCES_DIR}/pnglibconf.h
+    COPY_ONLY)
+
+  set(LIBPNG_SOURCES
+    #${LIBPNG_SOURCES_DIR}/example.c
+    ${LIBPNG_SOURCES_DIR}/png.c
+    ${LIBPNG_SOURCES_DIR}/pngerror.c
+    ${LIBPNG_SOURCES_DIR}/pngget.c
+    ${LIBPNG_SOURCES_DIR}/pngmem.c
+    ${LIBPNG_SOURCES_DIR}/pngpread.c
+    ${LIBPNG_SOURCES_DIR}/pngread.c
+    ${LIBPNG_SOURCES_DIR}/pngrio.c
+    ${LIBPNG_SOURCES_DIR}/pngrtran.c
+    ${LIBPNG_SOURCES_DIR}/pngrutil.c
+    ${LIBPNG_SOURCES_DIR}/pngset.c
+    #${LIBPNG_SOURCES_DIR}/pngtest.c
+    ${LIBPNG_SOURCES_DIR}/pngtrans.c
+    ${LIBPNG_SOURCES_DIR}/pngwio.c
+    ${LIBPNG_SOURCES_DIR}/pngwrite.c
+    ${LIBPNG_SOURCES_DIR}/pngwtran.c
+    ${LIBPNG_SOURCES_DIR}/pngwutil.c
+    )
+
+  #set_property(
+  #  SOURCE ${LIBPNG_SOURCES}
+  #  PROPERTY COMPILE_FLAGS -UHAVE_CONFIG_H)
+
+  list(APPEND THIRD_PARTY_SOURCES ${LIBPNG_SOURCES})
+
+  add_definitions(
+    -DPNG_NO_CONSOLE_IO=1
+    -DPNG_NO_STDIO=1
+    )
+
+  source_group(ThirdParty\\Libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*)
+
+else()
+  include(FindPNG)
+
+  if (NOT ${PNG_FOUND})
+    message(FATAL_ERROR "Unable to find LibPNG")
+  endif()
+
+  include_directories(${PNG_INCLUDE_DIRS})
+  link_libraries(${PNG_LIBRARIES})
+  add_definitions(${PNG_DEFINITIONS})
+endif()
--- a/Resources/CMake/LuaConfiguration.cmake	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/LuaConfiguration.cmake	Wed Apr 16 16:34:09 2014 +0200
@@ -1,67 +1,67 @@
-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()
+if (STATIC_BUILD OR NOT USE_SYSTEM_LUA)
+  SET(LUA_SOURCES_DIR ${CMAKE_BINARY_DIR}/lua-5.1.5)
+  DownloadPackage(
+    "2e115fe26e435e33b0d5c022e4490567"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/lua-5.1.5.tar.gz"
+    "${LUA_SOURCES_DIR}")
+
+  add_definitions(
+    #-DLUA_LIB=1
+    #-Dluaall_c=1
+    #-DLUA_COMPAT_ALL=1  # Compile a generic version of Lua
+    )
+
+  include_directories(
+    ${LUA_SOURCES_DIR}/src
+    )
+
+  set(LUA_SOURCES
+    # Core Lua
+    ${LUA_SOURCES_DIR}/src/lapi.c
+    ${LUA_SOURCES_DIR}/src/lcode.c 
+    ${LUA_SOURCES_DIR}/src/ldebug.c 
+    ${LUA_SOURCES_DIR}/src/ldo.c 
+    ${LUA_SOURCES_DIR}/src/ldump.c 
+    ${LUA_SOURCES_DIR}/src/lfunc.c 
+    ${LUA_SOURCES_DIR}/src/lgc.c
+    ${LUA_SOURCES_DIR}/src/llex.c
+    ${LUA_SOURCES_DIR}/src/lmem.c 
+    ${LUA_SOURCES_DIR}/src/lobject.c 
+    ${LUA_SOURCES_DIR}/src/lopcodes.c 
+    ${LUA_SOURCES_DIR}/src/lparser.c
+    ${LUA_SOURCES_DIR}/src/lstate.c 
+    ${LUA_SOURCES_DIR}/src/lstring.c
+    ${LUA_SOURCES_DIR}/src/ltable.c
+    ${LUA_SOURCES_DIR}/src/ltm.c
+    ${LUA_SOURCES_DIR}/src/lundump.c 
+    ${LUA_SOURCES_DIR}/src/lvm.c 
+    ${LUA_SOURCES_DIR}/src/lzio.c
+
+    # Base Lua modules
+    ${LUA_SOURCES_DIR}/src/lauxlib.c
+    ${LUA_SOURCES_DIR}/src/lbaselib.c
+    ${LUA_SOURCES_DIR}/src/ldblib.c
+    ${LUA_SOURCES_DIR}/src/liolib.c
+    ${LUA_SOURCES_DIR}/src/lmathlib.c
+    ${LUA_SOURCES_DIR}/src/loslib.c
+    ${LUA_SOURCES_DIR}/src/ltablib.c
+    ${LUA_SOURCES_DIR}/src/lstrlib.c
+    ${LUA_SOURCES_DIR}/src/loadlib.c
+    ${LUA_SOURCES_DIR}/src/linit.c
+    )
+
+  add_library(Lua STATIC ${LUA_SOURCES})
+  link_libraries(Lua)
+
+  source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*)
+
+else()
+  include(FindLua51)
+
+  if (NOT LUA51_FOUND)
+    message(FATAL_ERROR "Please install the liblua-dev package")
+  endif()
+
+  include_directories(${LUA_INCLUDE_DIR})
+  link_libraries(${LUA_LIBRARIES})
+endif()
--- a/Resources/CMake/MongooseConfiguration.cmake	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/MongooseConfiguration.cmake	Wed Apr 16 16:34:09 2014 +0200
@@ -1,59 +1,59 @@
-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()
+if (STATIC_BUILD OR NOT USE_SYSTEM_MONGOOSE)
+  SET(MONGOOSE_SOURCES_DIR ${CMAKE_BINARY_DIR}/mongoose)
+  DownloadPackage(
+    "e718fc287b4eb1bd523be3fa00942bb0"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/mongoose-3.1.tgz"
+    "${MONGOOSE_SOURCES_DIR}")
+
+  # Patch mongoose
+  execute_process(
+    COMMAND patch mongoose.c ${CMAKE_SOURCE_DIR}/Resources/Patches/mongoose-patch.diff
+    WORKING_DIRECTORY ${MONGOOSE_SOURCES_DIR}
+    )
+
+  include_directories(
+    ${MONGOOSE_SOURCES_DIR}
+    )
+
+  list(APPEND THIRD_PARTY_SOURCES
+    ${MONGOOSE_SOURCES_DIR}/mongoose.c
+    )
+
+
+  if (${ENABLE_SSL})
+    add_definitions(
+      -DNO_SSL_DL=1
+      )
+    if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+      link_libraries(dl)
+    endif()
+
+  else()
+    add_definitions(
+      -DNO_SSL=1   # Remove SSL support from mongoose
+      )
+  endif()
+
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    if (${CMAKE_COMPILER_IS_GNUCXX})
+      # This is a patch for MinGW64
+      add_definitions(-D_TIMESPEC_DEFINED=1)
+    endif()
+  endif()
+
+  source_group(ThirdParty\\Mongoose REGULAR_EXPRESSION ${MONGOOSE_SOURCES_DIR}/.*)
+
+else()
+  CHECK_INCLUDE_FILE_CXX(mongoose.h HAVE_MONGOOSE_H)
+  if (NOT HAVE_MONGOOSE_H)
+    message(FATAL_ERROR "Please install the mongoose-devel package")
+  endif()
+
+  CHECK_LIBRARY_EXISTS(mongoose mg_start "" HAVE_MONGOOSE_LIB)
+  if (NOT HAVE_MONGOOSE_LIB)
+    message(FATAL_ERROR "Please install the mongoose-devel package")
+  endif()
+
+  link_libraries(mongoose)
+endif()
--- a/Resources/CMake/OpenSslConfiguration.cmake	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/OpenSslConfiguration.cmake	Wed Apr 16 16:34:09 2014 +0200
@@ -1,209 +1,209 @@
-if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL)
-  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 ("${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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/SQLiteConfiguration.cmake	Wed Apr 16 16:34:09 2014 +0200
@@ -1,43 +1,43 @@
-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()
+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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/CMake/ZlibConfiguration.cmake	Wed Apr 16 16:34:09 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 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}/.*)
+# This is the minizip distribution to create ZIP files
+list(APPEND THIRD_PARTY_SOURCES 
+  ${ORTHANC_ROOT}/Resources/minizip/ioapi.c
+  ${ORTHANC_ROOT}/Resources/minizip/zip.c
+  )
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB)
+  SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7)
+  DownloadPackage(
+    "60df6a37c56e7c1366cca812414f7b85"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz"
+    "${ZLIB_SOURCES_DIR}")
+
+  include_directories(
+    ${ZLIB_SOURCES_DIR}
+    )
+
+  list(APPEND THIRD_PARTY_SOURCES 
+    ${ZLIB_SOURCES_DIR}/adler32.c
+    ${ZLIB_SOURCES_DIR}/compress.c
+    ${ZLIB_SOURCES_DIR}/crc32.c 
+    ${ZLIB_SOURCES_DIR}/deflate.c 
+    ${ZLIB_SOURCES_DIR}/gzclose.c 
+    ${ZLIB_SOURCES_DIR}/gzlib.c 
+    ${ZLIB_SOURCES_DIR}/gzread.c 
+    ${ZLIB_SOURCES_DIR}/gzwrite.c 
+    ${ZLIB_SOURCES_DIR}/infback.c 
+    ${ZLIB_SOURCES_DIR}/inffast.c 
+    ${ZLIB_SOURCES_DIR}/inflate.c 
+    ${ZLIB_SOURCES_DIR}/inftrees.c 
+    ${ZLIB_SOURCES_DIR}/trees.c 
+    ${ZLIB_SOURCES_DIR}/uncompr.c 
+    ${ZLIB_SOURCES_DIR}/zutil.c
+    )
+
+else()
+  include(FindZLIB)
+  include_directories(${ZLIB_INCLUDE_DIRS})
+  link_libraries(${ZLIB_LIBRARIES})
+endif()
+
+source_group(ThirdParty\\ZLib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
--- a/Resources/Configuration.json	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/Configuration.json	Wed Apr 16 16:34:09 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), "ClearCanvas" and "MedInria". 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/EmbedResources.py	Wed Apr 16 16:34:09 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/Samples/ImportDicomFiles/ImportDicomFiles.py	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Wed Apr 16 16:34:09 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/Samples/OrthancClient/Basic/main.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, 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;
           }
         }
       }
--- a/Resources/Samples/OrthancClient/Vtk/main.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/Samples/OrthancClient/Vtk/main.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, 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
--- a/Resources/Samples/Python/AnonymizeAllPatients.py	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/Samples/Python/AnonymizeAllPatients.py	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,24 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+# Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
 
 URL = 'http://localhost:8042'
 
--- a/Resources/Samples/Python/ChangesLoop.py	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/Samples/Python/ChangesLoop.py	Wed Apr 16 16:34:09 2014 +0200
@@ -1,5 +1,24 @@
 #!/usr/bin/python
 
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+# Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
 import time
 import sys
 import RestToolbox
--- a/Resources/Samples/Python/DownloadAnonymized.py	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/Samples/Python/DownloadAnonymized.py	Wed Apr 16 16:34:09 2014 +0200
@@ -1,6 +1,24 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+# Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
 
 URL = 'http://localhost:8042'
 
--- a/Resources/Samples/Python/HighPerformanceAutoRouting.py	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/Samples/Python/HighPerformanceAutoRouting.py	Wed Apr 16 16:34:09 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/Resources/Samples/Python/RestToolbox.py	Wed Apr 16 16:34:09 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:34:09 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:34:09 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	Fri Jan 24 17:40:45 2014 +0100
+++ b/THANKS	Wed Apr 16 16:34:09 2014 +0200
@@ -19,6 +19,7 @@
 * 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/DicomMap.cpp	Wed Apr 16 16:34:09 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));
+}
--- a/UnitTestsSources/FileStorage.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/UnitTestsSources/FileStorage.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -15,6 +15,16 @@
 
 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");
@@ -25,6 +35,21 @@
   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)
@@ -99,6 +124,27 @@
 }
 
 
+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");
--- a/UnitTestsSources/Lua.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/UnitTestsSources/Lua.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -3,7 +3,7 @@
 #include "../Core/Lua/LuaFunctionCall.h"
 
 
-TEST(Lua, Simple)
+TEST(Lua, Json)
 {
   Orthanc::LuaContext lua;
   lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
@@ -70,3 +70,34 @@
   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/MultiThreading.cpp	Wed Apr 16 16:34:09 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());
+  }
+}
--- a/UnitTestsSources/Png.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/UnitTestsSources/Png.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -4,6 +4,7 @@
 #include "../Core/FileFormats/PngReader.h"
 #include "../Core/FileFormats/PngWriter.h"
 #include "../Core/Toolbox.h"
+#include "../Core/Uuid.h"
 
 
 TEST(PngWriter, ColorPattern)
@@ -107,19 +108,46 @@
   std::string s;
   w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]);
 
-  Orthanc::PngReader r;
-  r.ReadFromMemory(s);
+  {
+    Orthanc::PngReader r;
+    r.ReadFromMemory(s);
+
+    ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16);
+    ASSERT_EQ(r.GetWidth(), width);
+    ASSERT_EQ(r.GetHeight(), height);
 
-  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);
+      }
+    }
+  }
 
-  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++)
+    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++)
     {
-      ASSERT_EQ(*p, v);
+      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);
+      }
     }
   }
 }
--- a/UnitTestsSources/RestApi.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/UnitTestsSources/RestApi.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -3,6 +3,8 @@
 #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"
@@ -10,6 +12,51 @@
 
 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;
--- a/UnitTestsSources/SQLite.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/UnitTestsSources/SQLite.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -236,3 +236,66 @@
     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());
+  }
+}
--- a/UnitTestsSources/ServerIndex.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,497 +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;;
-      }      
-      }*/
-
-
-}
-
-
-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));
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/ServerIndexTests.cpp	Wed Apr 16 16:34:09 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:34:09 2014 +0200
@@ -0,0 +1,610 @@
+#include "../Core/EnumerationDictionary.h"
+
+#include "gtest/gtest.h"
+
+#include <ctype.h>
+
+#include "../Core/Compression/ZlibCompressor.h"
+#include "../Core/DicomFormat/DicomTag.h"
+#include "../Core/HttpServer/HttpHandler.h"
+#include "../Core/OrthancException.h"
+#include "../Core/Toolbox.h"
+#include "../Core/Uuid.h"
+#include "../OrthancServer/FromDcmtkBridge.h"
+#include "../OrthancServer/OrthancInitialization.h"
+
+using namespace Orthanc;
+
+
+TEST(Uuid, Generation)
+{
+  for (int i = 0; i < 10; i++)
+  {
+    std::string s = Toolbox::GenerateUuid();
+    ASSERT_TRUE(Toolbox::IsUuid(s));
+  }
+}
+
+TEST(Uuid, Test)
+{
+  ASSERT_FALSE(Toolbox::IsUuid(""));
+  ASSERT_FALSE(Toolbox::IsUuid("012345678901234567890123456789012345"));
+  ASSERT_TRUE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-446655440000"));
+  ASSERT_FALSE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-44665544000_"));
+  ASSERT_FALSE(Toolbox::IsUuid("01234567890123456789012345678901234_"));
+  ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-44665544000"));
+  ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000"));
+  ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000 ok"));
+  ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000ok"));
+}
+
+TEST(Toolbox, IsSHA1)
+{
+  ASSERT_FALSE(Toolbox::IsSHA1(""));
+  ASSERT_FALSE(Toolbox::IsSHA1("01234567890123456789012345678901234567890123"));
+  ASSERT_FALSE(Toolbox::IsSHA1("012345678901234567890123456789012345678901234"));
+  ASSERT_TRUE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b9"));
+
+  std::string s;
+  Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog");
+  ASSERT_TRUE(Toolbox::IsSHA1(s));
+  ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s);
+
+  ASSERT_FALSE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b_"));
+}
+
+static void StringToVector(std::vector<uint8_t>& v,
+                           const std::string& s)
+{
+  v.resize(s.size());
+  for (size_t i = 0; i < s.size(); i++)
+    v[i] = s[i];
+}
+
+
+TEST(Zlib, Basic)
+{
+  std::string s = Toolbox::GenerateUuid();
+  s = s + s + s + s;
+ 
+  std::string compressed, compressed2;
+  ZlibCompressor c;
+  c.Compress(compressed, s);
+
+  std::vector<uint8_t> v, vv;
+  StringToVector(v, s);
+  c.Compress(compressed2, v);
+  ASSERT_EQ(compressed, compressed2);
+
+  std::string uncompressed;
+  c.Uncompress(uncompressed, compressed);
+  ASSERT_EQ(s.size(), uncompressed.size());
+  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
+
+  StringToVector(vv, compressed);
+  c.Uncompress(uncompressed, vv);
+  ASSERT_EQ(s.size(), uncompressed.size());
+  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
+}
+
+
+TEST(Zlib, Level)
+{
+  std::string s = Toolbox::GenerateUuid();
+  s = s + s + s + s;
+ 
+  std::string compressed, compressed2;
+  ZlibCompressor c;
+  c.SetCompressionLevel(9);
+  c.Compress(compressed, s);
+
+  c.SetCompressionLevel(0);
+  c.Compress(compressed2, s);
+
+  ASSERT_TRUE(compressed.size() < compressed2.size());
+}
+
+
+TEST(Zlib, DISABLED_Corrupted)  // Disabled because it may result in a crash
+{
+  std::string s = Toolbox::GenerateUuid();
+  s = s + s + s + s;
+ 
+  std::string compressed;
+  ZlibCompressor c;
+  c.Compress(compressed, s);
+
+  compressed[compressed.size() - 1] = 'a';
+  std::string u;
+
+  ASSERT_THROW(c.Uncompress(u, compressed), OrthancException);
+}
+
+
+TEST(Zlib, Empty)
+{
+  std::string s = "";
+  std::vector<uint8_t> v, vv;
+ 
+  std::string compressed, compressed2;
+  ZlibCompressor c;
+  c.Compress(compressed, s);
+  c.Compress(compressed2, v);
+  ASSERT_EQ(compressed, compressed2);
+
+  std::string uncompressed;
+  c.Uncompress(uncompressed, compressed);
+  ASSERT_EQ(0u, uncompressed.size());
+
+  StringToVector(vv, compressed);
+  c.Uncompress(uncompressed, vv);
+  ASSERT_EQ(0u, uncompressed.size());
+}
+
+
+TEST(ParseGetQuery, Basic)
+{
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(a, "aaa=baaa&bb=a&aa=c");
+  ASSERT_EQ(3u, a.size());
+  ASSERT_EQ(a["aaa"], "baaa");
+  ASSERT_EQ(a["bb"], "a");
+  ASSERT_EQ(a["aa"], "c");
+}
+
+TEST(ParseGetQuery, BasicEmpty)
+{
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(a, "aaa&bb=aa&aa");
+  ASSERT_EQ(3u, a.size());
+  ASSERT_EQ(a["aaa"], "");
+  ASSERT_EQ(a["bb"], "aa");
+  ASSERT_EQ(a["aa"], "");
+}
+
+TEST(ParseGetQuery, Single)
+{
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(a, "aaa=baaa");
+  ASSERT_EQ(1u, a.size());
+  ASSERT_EQ(a["aaa"], "baaa");
+}
+
+TEST(ParseGetQuery, SingleEmpty)
+{
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(a, "aaa");
+  ASSERT_EQ(1u, a.size());
+  ASSERT_EQ(a["aaa"], "");
+}
+
+TEST(DicomFormat, Tag)
+{
+  ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010)));
+
+  DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription");
+  ASSERT_EQ(0x0008, t.GetGroup());
+  ASSERT_EQ(0x103E, t.GetElement());
+
+  t = FromDcmtkBridge::ParseTag("0020-e040");
+  ASSERT_EQ(0x0020, t.GetGroup());
+  ASSERT_EQ(0xe040, t.GetElement());
+
+  // Test ==() and !=() operators
+  ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020));
+  ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020));
+}
+
+
+TEST(Uri, SplitUriComponents)
+{
+  UriComponents c;
+  Toolbox::SplitUriComponents(c, "/cou/hello/world");
+  ASSERT_EQ(3u, c.size());
+  ASSERT_EQ("cou", c[0]);
+  ASSERT_EQ("hello", c[1]);
+  ASSERT_EQ("world", c[2]);
+
+  Toolbox::SplitUriComponents(c, "/cou/hello/world/");
+  ASSERT_EQ(3u, c.size());
+  ASSERT_EQ("cou", c[0]);
+  ASSERT_EQ("hello", c[1]);
+  ASSERT_EQ("world", c[2]);
+
+  Toolbox::SplitUriComponents(c, "/cou/hello/world/a");
+  ASSERT_EQ(4u, c.size());
+  ASSERT_EQ("cou", c[0]);
+  ASSERT_EQ("hello", c[1]);
+  ASSERT_EQ("world", c[2]);
+  ASSERT_EQ("a", c[3]);
+
+  Toolbox::SplitUriComponents(c, "/");
+  ASSERT_EQ(0u, c.size());
+
+  Toolbox::SplitUriComponents(c, "/hello");
+  ASSERT_EQ(1u, c.size());
+  ASSERT_EQ("hello", c[0]);
+
+  Toolbox::SplitUriComponents(c, "/hello/");
+  ASSERT_EQ(1u, c.size());
+  ASSERT_EQ("hello", c[0]);
+
+  ASSERT_THROW(Toolbox::SplitUriComponents(c, ""), OrthancException);
+  ASSERT_THROW(Toolbox::SplitUriComponents(c, "a"), OrthancException);
+  ASSERT_THROW(Toolbox::SplitUriComponents(c, "/coucou//coucou"), OrthancException);
+
+  c.clear();
+  c.push_back("test");
+  ASSERT_EQ("/", Toolbox::FlattenUri(c, 10));
+}
+
+
+TEST(Uri, Child)
+{
+  UriComponents c1;  Toolbox::SplitUriComponents(c1, "/hello/world");  
+  UriComponents c2;  Toolbox::SplitUriComponents(c2, "/hello/hello");  
+  UriComponents c3;  Toolbox::SplitUriComponents(c3, "/hello");  
+  UriComponents c4;  Toolbox::SplitUriComponents(c4, "/world");  
+  UriComponents c5;  Toolbox::SplitUriComponents(c5, "/");  
+
+  ASSERT_TRUE(Toolbox::IsChildUri(c1, c1));
+  ASSERT_FALSE(Toolbox::IsChildUri(c1, c2));
+  ASSERT_FALSE(Toolbox::IsChildUri(c1, c3));
+  ASSERT_FALSE(Toolbox::IsChildUri(c1, c4));
+  ASSERT_FALSE(Toolbox::IsChildUri(c1, c5));
+
+  ASSERT_FALSE(Toolbox::IsChildUri(c2, c1));
+  ASSERT_TRUE(Toolbox::IsChildUri(c2, c2));
+  ASSERT_FALSE(Toolbox::IsChildUri(c2, c3));
+  ASSERT_FALSE(Toolbox::IsChildUri(c2, c4));
+  ASSERT_FALSE(Toolbox::IsChildUri(c2, c5));
+
+  ASSERT_TRUE(Toolbox::IsChildUri(c3, c1));
+  ASSERT_TRUE(Toolbox::IsChildUri(c3, c2));
+  ASSERT_TRUE(Toolbox::IsChildUri(c3, c3));
+  ASSERT_FALSE(Toolbox::IsChildUri(c3, c4));
+  ASSERT_FALSE(Toolbox::IsChildUri(c3, c5));
+
+  ASSERT_FALSE(Toolbox::IsChildUri(c4, c1));
+  ASSERT_FALSE(Toolbox::IsChildUri(c4, c2));
+  ASSERT_FALSE(Toolbox::IsChildUri(c4, c3));
+  ASSERT_TRUE(Toolbox::IsChildUri(c4, c4));
+  ASSERT_FALSE(Toolbox::IsChildUri(c4, c5));
+
+  ASSERT_TRUE(Toolbox::IsChildUri(c5, c1));
+  ASSERT_TRUE(Toolbox::IsChildUri(c5, c2));
+  ASSERT_TRUE(Toolbox::IsChildUri(c5, c3));
+  ASSERT_TRUE(Toolbox::IsChildUri(c5, c4));
+  ASSERT_TRUE(Toolbox::IsChildUri(c5, c5));
+}
+
+TEST(Uri, AutodetectMimeType)
+{
+  ASSERT_EQ("", Toolbox::AutodetectMimeType("../NOTES"));
+  ASSERT_EQ("", Toolbox::AutodetectMimeType(""));
+  ASSERT_EQ("", Toolbox::AutodetectMimeType("/"));
+  ASSERT_EQ("", Toolbox::AutodetectMimeType("a/a"));
+
+  ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../NOTES.txt"));
+  ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../coucou.xml/NOTES.txt"));
+  ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("../.xml"));
+
+  ASSERT_EQ("application/javascript", Toolbox::AutodetectMimeType("NOTES.js"));
+  ASSERT_EQ("application/json", Toolbox::AutodetectMimeType("NOTES.json"));
+  ASSERT_EQ("application/pdf", Toolbox::AutodetectMimeType("NOTES.pdf"));
+  ASSERT_EQ("text/css", Toolbox::AutodetectMimeType("NOTES.css"));
+  ASSERT_EQ("text/html", Toolbox::AutodetectMimeType("NOTES.html"));
+  ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("NOTES.txt"));
+  ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("NOTES.xml"));
+  ASSERT_EQ("image/gif", Toolbox::AutodetectMimeType("NOTES.gif"));
+  ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpg"));
+  ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpeg"));
+  ASSERT_EQ("image/png", Toolbox::AutodetectMimeType("NOTES.png"));
+}
+
+TEST(Toolbox, ComputeMD5)
+{
+  std::string s;
+
+  // # echo -n "Hello" | md5sum
+
+  Toolbox::ComputeMD5(s, "Hello");
+  ASSERT_EQ("8b1a9953c4611296a827abf8c47804d7", s);
+  Toolbox::ComputeMD5(s, "");
+  ASSERT_EQ("d41d8cd98f00b204e9800998ecf8427e", s);
+}
+
+TEST(Toolbox, ComputeSHA1)
+{
+  std::string s;
+  
+  Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog");
+  ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s);
+  Toolbox::ComputeSHA1(s, "");
+  ASSERT_EQ("da39a3ee-5e6b4b0d-3255bfef-95601890-afd80709", s);
+}
+
+
+TEST(Toolbox, Base64)
+{
+  ASSERT_EQ("", Toolbox::EncodeBase64(""));
+  ASSERT_EQ("YQ==", Toolbox::EncodeBase64("a"));
+
+  const std::string hello = "SGVsbG8gd29ybGQ=";
+  ASSERT_EQ(hello, Toolbox::EncodeBase64("Hello world"));
+  ASSERT_EQ("Hello world", Toolbox::DecodeBase64(hello));
+}
+
+TEST(Toolbox, PathToExecutable)
+{
+  printf("[%s]\n", Toolbox::GetPathToExecutable().c_str());
+  printf("[%s]\n", Toolbox::GetDirectoryOfExecutable().c_str());
+}
+
+TEST(Toolbox, StripSpaces)
+{
+  ASSERT_EQ("", Toolbox::StripSpaces("       \t  \r   \n  "));
+  ASSERT_EQ("coucou", Toolbox::StripSpaces("    coucou   \t  \r   \n  "));
+  ASSERT_EQ("cou   cou", Toolbox::StripSpaces("    cou   cou    \n  "));
+  ASSERT_EQ("c", Toolbox::StripSpaces("    \n\t c\r    \n  "));
+}
+
+TEST(Toolbox, Case)
+{
+  std::string s = "CoU";
+  std::string ss;
+
+  Toolbox::ToUpperCase(ss, s);
+  ASSERT_EQ("COU", ss);
+  Toolbox::ToLowerCase(ss, s);
+  ASSERT_EQ("cou", ss); 
+
+  s = "CoU";
+  Toolbox::ToUpperCase(s);
+  ASSERT_EQ("COU", s);
+
+  s = "CoU";
+  Toolbox::ToLowerCase(s);
+  ASSERT_EQ("cou", s);
+}
+
+
+#include <glog/logging.h>
+
+TEST(Logger, Basic)
+{
+  LOG(INFO) << "I say hello";
+}
+
+TEST(Toolbox, ConvertFromLatin1)
+{
+  // This is a Latin-1 test string
+  const unsigned char data[10] = { 0xe0, 0xe9, 0xea, 0xe7, 0x26, 0xc6, 0x61, 0x62, 0x63, 0x00 };
+  
+  /*FILE* f = fopen("/tmp/tutu", "w");
+  fwrite(&data[0], 9, 1, f);
+  fclose(f);*/
+
+  std::string s((char*) &data[0], 10);
+  ASSERT_EQ("&abc", Toolbox::ConvertToAscii(s));
+
+  // Open in Emacs, then save with UTF-8 encoding, then "hexdump -C"
+  std::string utf8 = Toolbox::ConvertToUtf8(s, "ISO-8859-1");
+  ASSERT_EQ(15u, utf8.size());
+  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[0]));
+  ASSERT_EQ(0xa0, static_cast<unsigned char>(utf8[1]));
+  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[2]));
+  ASSERT_EQ(0xa9, static_cast<unsigned char>(utf8[3]));
+  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[4]));
+  ASSERT_EQ(0xaa, static_cast<unsigned char>(utf8[5]));
+  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[6]));
+  ASSERT_EQ(0xa7, static_cast<unsigned char>(utf8[7]));
+  ASSERT_EQ(0x26, static_cast<unsigned char>(utf8[8]));
+  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[9]));
+  ASSERT_EQ(0x86, static_cast<unsigned char>(utf8[10]));
+  ASSERT_EQ(0x61, static_cast<unsigned char>(utf8[11]));
+  ASSERT_EQ(0x62, static_cast<unsigned char>(utf8[12]));
+  ASSERT_EQ(0x63, static_cast<unsigned char>(utf8[13]));
+  ASSERT_EQ(0x00, static_cast<unsigned char>(utf8[14]));  // Null-terminated string
+}
+
+TEST(Toolbox, UrlDecode)
+{
+  std::string s;
+
+  s = "Hello%20World";
+  Toolbox::UrlDecode(s);
+  ASSERT_EQ("Hello World", s);
+
+  s = "%21%23%24%26%27%28%29%2A%2B%2c%2f%3A%3b%3d%3f%40%5B%5D%90%ff";
+  Toolbox::UrlDecode(s);
+  std::string ss = "!#$&'()*+,/:;=?@[]"; 
+  ss.push_back((char) 144); 
+  ss.push_back((char) 255);
+  ASSERT_EQ(ss, s);
+
+  s = "(2000%2C00A4)+Other";
+  Toolbox::UrlDecode(s);
+  ASSERT_EQ("(2000,00A4) Other", s);
+}
+
+
+#if defined(__linux)
+TEST(OrthancInitialization, AbsoluteDirectory)
+{
+  ASSERT_EQ("/tmp/hello", InterpretRelativePath("/tmp", "hello"));
+  ASSERT_EQ("/tmp", InterpretRelativePath("/tmp", "/tmp"));
+}
+#endif
+
+
+
+#include "../OrthancServer/ServerEnumerations.h"
+
+TEST(EnumerationDictionary, Simple)
+{
+  Toolbox::EnumerationDictionary<MetadataType>  d;
+
+  ASSERT_THROW(d.Translate("ReceptionDate"), OrthancException);
+  ASSERT_EQ(MetadataType_ModifiedFrom, d.Translate("5"));
+  ASSERT_EQ(256, d.Translate("256"));
+
+  d.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate");
+
+  ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("ReceptionDate"));
+  ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("2"));
+  ASSERT_EQ("ReceptionDate", d.Translate(MetadataType_Instance_ReceptionDate));
+
+  ASSERT_THROW(d.Add(MetadataType_Instance_ReceptionDate, "Hello"), OrthancException);
+  ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "ReceptionDate"), OrthancException); // already used
+  ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "1024"), OrthancException); // cannot register numbers
+  d.Add(MetadataType_ModifiedFrom, "ModifiedFrom");  // ok
+}
+
+
+TEST(EnumerationDictionary, ServerEnumerations)
+{
+  ASSERT_STREQ("Patient", EnumerationToString(ResourceType_Patient));
+  ASSERT_STREQ("Study", EnumerationToString(ResourceType_Study));
+  ASSERT_STREQ("Series", EnumerationToString(ResourceType_Series));
+  ASSERT_STREQ("Instance", EnumerationToString(ResourceType_Instance));
+
+  ASSERT_STREQ("ModifiedSeries", EnumerationToString(ChangeType_ModifiedSeries));
+
+  ASSERT_STREQ("Failure", EnumerationToString(StoreStatus_Failure));
+  ASSERT_STREQ("Success", EnumerationToString(StoreStatus_Success));
+
+  ASSERT_STREQ("CompletedSeries", EnumerationToString(ChangeType_CompletedSeries));
+
+  ASSERT_EQ("IndexInSeries", EnumerationToString(MetadataType_Instance_IndexInSeries));
+  ASSERT_EQ("LastUpdate", EnumerationToString(MetadataType_LastUpdate));
+
+  ASSERT_EQ(ResourceType_Patient, StringToResourceType("PATienT"));
+  ASSERT_EQ(ResourceType_Study, StringToResourceType("STudy"));
+  ASSERT_EQ(ResourceType_Series, StringToResourceType("SeRiEs"));
+  ASSERT_EQ(ResourceType_Instance, StringToResourceType("INStance"));
+  ASSERT_EQ(ResourceType_Instance, StringToResourceType("IMagE"));
+  ASSERT_THROW(StringToResourceType("heLLo"), OrthancException);
+
+  ASSERT_EQ(2047, StringToMetadata("2047"));
+  ASSERT_THROW(StringToMetadata("Ceci est un test"), OrthancException);
+  ASSERT_THROW(RegisterUserMetadata(128, ""), OrthancException); // too low (< 1024)
+  ASSERT_THROW(RegisterUserMetadata(128000, ""), OrthancException); // too high (> 65535)
+  RegisterUserMetadata(2047, "Ceci est un test");
+  ASSERT_EQ(2047, StringToMetadata("2047"));
+  ASSERT_EQ(2047, StringToMetadata("Ceci est un test"));
+}
+
+
+
+TEST(Toolbox, WriteFile)
+{
+  std::string path;
+
+  {
+    Toolbox::TemporaryFile tmp;
+    path = tmp.GetPath();
+
+    std::string s;
+    s.append("Hello");
+    s.push_back('\0');
+    s.append("World");
+    ASSERT_EQ(11u, s.size());
+
+    Toolbox::WriteFile(s, path.c_str());
+
+    std::string t;
+    Toolbox::ReadFile(t, path.c_str());
+
+    ASSERT_EQ(11u, t.size());
+    ASSERT_EQ(0, t[5]);
+    ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size()));
+  }
+
+  std::string u;
+  ASSERT_THROW(Toolbox::ReadFile(u, path.c_str()), OrthancException);
+}
+
+
+TEST(Toolbox, Wildcard)
+{
+  ASSERT_EQ("abcd", Toolbox::WildcardToRegularExpression("abcd"));
+  ASSERT_EQ("ab.*cd", Toolbox::WildcardToRegularExpression("ab*cd"));
+  ASSERT_EQ("ab..cd", Toolbox::WildcardToRegularExpression("ab??cd"));
+  ASSERT_EQ("a.*b.c.*d", Toolbox::WildcardToRegularExpression("a*b?c*d"));
+  ASSERT_EQ("a\\{b\\]", Toolbox::WildcardToRegularExpression("a{b]"));
+}
+
+
+TEST(Toolbox, Tokenize)
+{
+  std::vector<std::string> t;
+  
+  Toolbox::TokenizeString(t, "", ','); 
+  ASSERT_EQ(1, t.size());
+  ASSERT_EQ("", t[0]);
+  
+  Toolbox::TokenizeString(t, "abc", ','); 
+  ASSERT_EQ(1, t.size());
+  ASSERT_EQ("abc", t[0]);
+  
+  Toolbox::TokenizeString(t, "ab,cd,ef,", ','); 
+  ASSERT_EQ(4, t.size());
+  ASSERT_EQ("ab", t[0]);
+  ASSERT_EQ("cd", t[1]);
+  ASSERT_EQ("ef", t[2]);
+  ASSERT_EQ("", t[3]);
+}
+
+
+
+#if defined(__linux)
+#include <endian.h>
+#endif
+
+TEST(Toolbox, Endianness)
+{
+  // Parts of this test come from Adam Conrad
+  // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=728822#5
+
+#if defined(_WIN32)
+  ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness());
+
+#elif defined(__linux)
+
+#if !defined(__BYTE_ORDER)
+#  error Support your platform here
+#endif
+
+#  if __BYTE_ORDER == __BIG_ENDIAN
+  ASSERT_EQ(Endianness_Big, Toolbox::DetectEndianness());
+#  else // __LITTLE_ENDIAN
+  ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness());
+#  endif
+
+#else
+#error Support your platform here
+#endif
+}
+
+
+
+int main(int argc, char **argv)
+{
+  // Initialize Google's logging library.
+  FLAGS_logtostderr = true;
+  FLAGS_minloglevel = 0;
+
+  // Go to trace-level verbosity
+  //FLAGS_v = 1;
+
+  Toolbox::DetectEndianness();
+
+  google::InitGoogleLogging("Orthanc");
+
+  OrthancInitialize();
+  ::testing::InitGoogleTest(&argc, argv);
+  int result = RUN_ALL_TESTS();
+  OrthancFinalize();
+  return result;
+}
--- a/UnitTestsSources/Versions.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ b/UnitTestsSources/Versions.cpp	Wed Apr 16 16:34:09 2014 +0200
@@ -9,6 +9,7 @@
 #include <boost/version.hpp>
 #include <sqlite3.h>
 #include <lua.h>
+#include <openssl/opensslv.h>
 
 
 TEST(Versions, Zlib)
@@ -57,7 +58,7 @@
 
 TEST(Versions, BoostStatic)
 {
-  ASSERT_STREQ("1_54", BOOST_LIB_VERSION);
+  ASSERT_STREQ("1_55", BOOST_LIB_VERSION);
 }
 
 TEST(Versions, CurlStatic)
@@ -90,5 +91,10 @@
 {
   ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE);
 }
+
+TEST(Version, OpenSslStatic)
+{
+  ASSERT_EQ(0x1000107fL /* openssl-1.0.1g */, OPENSSL_VERSION_NUMBER);
+}
+
 #endif
-
--- a/UnitTestsSources/main.cpp	Fri Jan 24 17:40:45 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,575 +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 ==() 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);
-}
-
-
-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(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"));
-}
-
-
-
-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, Wildcard)
-{
-  ASSERT_EQ("abcd", Toolbox::WildcardToRegularExpression("abcd"));
-  ASSERT_EQ("ab.*cd", Toolbox::WildcardToRegularExpression("ab*cd"));
-  ASSERT_EQ("ab..cd", Toolbox::WildcardToRegularExpression("ab??cd"));
-  ASSERT_EQ("a.*b.c.*d", Toolbox::WildcardToRegularExpression("a*b?c*d"));
-  ASSERT_EQ("a\\{b\\]", Toolbox::WildcardToRegularExpression("a{b]"));
-}
-
-
-TEST(Toolbox, Tokenize)
-{
-  std::vector<std::string> t;
-  
-  Toolbox::TokenizeString(t, "", ','); 
-  ASSERT_EQ(1, t.size());
-  ASSERT_EQ("", t[0]);
-  
-  Toolbox::TokenizeString(t, "abc", ','); 
-  ASSERT_EQ(1, t.size());
-  ASSERT_EQ("abc", t[0]);
-  
-  Toolbox::TokenizeString(t, "ab,cd,ef,", ','); 
-  ASSERT_EQ(4, t.size());
-  ASSERT_EQ("ab", t[0]);
-  ASSERT_EQ("cd", t[1]);
-  ASSERT_EQ("ef", t[2]);
-  ASSERT_EQ("", t[3]);
-}
-
-
-
-#if defined(__linux)
-#include <endian.h>
-#endif
-
-TEST(Toolbox, Endianness)
-{
-  // Parts of this test come from Adam Conrad
-  // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=728822#5
-
-#if defined(_WIN32)
-  ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness());
-
-#elif defined(__linux)
-
-#if !defined(__BYTE_ORDER)
-#  error Support your platform here
-#endif
-
-#  if __BYTE_ORDER == __BIG_ENDIAN
-  ASSERT_EQ(Endianness_Big, Toolbox::DetectEndianness());
-#  else // __LITTLE_ENDIAN
-  ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness());
-#  endif
-
-#else
-#error Support your platform here
-#endif
-}
-
-
-
-int main(int argc, char **argv)
-{
-  // Initialize Google's logging library.
-  FLAGS_logtostderr = true;
-  FLAGS_minloglevel = 0;
-
-  // Go to trace-level verbosity
-  //FLAGS_v = 1;
-
-  Toolbox::DetectEndianness();
-
-  google::InitGoogleLogging("Orthanc");
-
-  OrthancInitialize();
-  ::testing::InitGoogleTest(&argc, argv);
-  int result = RUN_ALL_TESTS();
-  OrthancFinalize();
-  return result;
-}