changeset 290:b3322636b06d

merge
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 14 Dec 2012 11:24:24 +0100
parents ffd98d2f0b91 (diff) b0734fbcdd97 (current diff)
children 4d7469f72a0b
files
diffstat 168 files changed, 12143 insertions(+), 3031 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Oct 04 15:09:56 2012 +0200
+++ b/CMakeLists.txt	Fri Dec 14 11:24:24 2012 +0100
@@ -1,34 +1,73 @@
 cmake_minimum_required(VERSION 2.8)
 
 project(Orthanc)
-include(${CMAKE_SOURCE_DIR}/Resources/CMake/AutoGeneratedCode.cmake)
-include(${CMAKE_SOURCE_DIR}/Resources/CMake/DownloadPackage.cmake)
+
+# Version of the build, should always be "mainline" except in release branches
+add_definitions(
+  -DORTHANC_VERSION="mainline"
+  )
+
+# Parameters of the build
+SET(STATIC_BUILD ON CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+SET(STANDALONE_BUILD OFF CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)")
+SET(ENABLE_SSL ON CACHE BOOL "Include support for SSL")
+SET(BUILD_UNIT_TESTS ON CACHE BOOL "Build the unit tests")
+
+# Advanced parameters (for Debian packaging)
+SET(USE_DYNAMIC_JSONCPP OFF CACHE BOOL "Use the dynamic version of JsonCpp (only for Debian sid)")
+SET(USE_DYNAMIC_GOOGLE_LOG ON CACHE BOOL "Use the dynamic version of Google Log")
+SET(USE_DYNAMIC_GOOGLE_TEST ON CACHE BOOL "Use the dynamic version of Google Test (not for Debian sid)")
+SET(USE_DYNAMIC_SQLITE ON CACHE BOOL "Use the dynamic version of SQLite")
+SET(DEBIAN_FORCE_HARDENING OFF CACHE BOOL "Force the injection of Debian hardening flags (unrecommended)")
+SET(DEBIAN_USE_GTEST_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (only for Debian sid)")
+SET(ONLY_CORE_LIBRARY OFF CACHE BOOL "Only build the core library")
+
+mark_as_advanced(USE_DYNAMIC_JSONCPP)
+mark_as_advanced(USE_DYNAMIC_GOOGLE_LOG)
+mark_as_advanced(USE_DYNAMIC_GOOGLE_TEST)
+mark_as_advanced(USE_DYNAMIC_SQLITE)
+mark_as_advanced(DEBIAN_FORCE_HARDENING)
+mark_as_advanced(DEBIAN_USE_STATIC_GOOGLE_TEST)
+mark_as_advanced(ONLY_CORE_LIBRARY)
+
+# Some basic inclusions
 include(CheckIncludeFiles)
 include(CheckIncludeFileCXX)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/AutoGeneratedCode.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/DownloadPackage.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/Compiler.cmake)
 
-SET(STATIC_BUILD ON CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
-SET(STANDALONE_BUILD OFF CACHE BOOL "Standalone build (necessary for cross-compilation or binary releases)")
-SET(ENABLE_SSL ON CACHE BOOL "Include support for SSL")
-SET(DEBIAN_HARDENING OFF CACHE BOOL "Use Debian hardening flags")
-
+# Configuration of the standalone builds
 if (${CMAKE_CROSSCOMPILING})
+  # Cross-compilation implies the standalone build
   SET(STANDALONE_BUILD ON)
 endif()
 
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-  CHECK_INCLUDE_FILES(rpc.h HAVE_UUID_H)
+if (${STANDALONE_BUILD})
+  # We embed all the resources in the binaries for standalone builds
+  add_definitions(-DORTHANC_STANDALONE=1)
+  EmbedResources(
+    PREPARE_DATABASE OrthancServer/PrepareDatabase.sql
+    ORTHANC_EXPLORER OrthancExplorer
+    CONFIGURATION_SAMPLE Resources/Configuration.json
+    )
 else()
-  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
-endif()
-
-if (NOT HAVE_UUID_H)
-  message(FATAL_ERROR "Please install the uuid-dev package")
+  add_definitions(
+    -DORTHANC_STANDALONE=0
+    -DORTHANC_PATH=\"${CMAKE_SOURCE_DIR}\"
+    )
+  EmbedResources(
+    PREPARE_DATABASE OrthancServer/PrepareDatabase.sql
+    CONFIGURATION_SAMPLE Resources/Configuration.json
+    )
 endif()
 
 
+# Prepare the third-party dependencies
 SET(THIRD_PARTY_SOURCES
   ${CMAKE_SOURCE_DIR}/Resources/md5/md5.c
   ${CMAKE_SOURCE_DIR}/Resources/base64/base64.cpp
+  ${CMAKE_SOURCE_DIR}/Resources/sha1/sha1.cpp
   )
 
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.cmake)
@@ -41,8 +80,11 @@
 endif()
 
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/BoostConfiguration.cmake)
-include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake)
-include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake)
+
+if(NOT ONLY_CORE_LIBRARY)
+  include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake)
+endif()
+
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/MongooseConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/ZlibConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/SQLiteConfiguration.cmake)
@@ -51,126 +93,38 @@
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake)
 
 
-if (${CMAKE_COMPILER_IS_GNUCXX})
-  set(CMAKE_C_FLAGS "-Wall -Wno-long-long -Wno-implicit-function-declaration")  
-  # --std=c99 makes libcurl not to compile
-  # -pedantic gives a lot of warnings on OpenSSL 
-  set(CMAKE_CXX_FLAGS "-Wall -pedantic -Wno-long-long -Wno-variadic-macros")
-elseif (${MSVC})
-  # http://stackoverflow.com/a/6510446
-  foreach(flag_var
-    CMAKE_C_FLAGS_DEBUG
-    CMAKE_CXX_FLAGS_DEBUG
-    CMAKE_C_FLAGS_RELEASE 
-    CMAKE_CXX_FLAGS_RELEASE
-    CMAKE_C_FLAGS_MINSIZEREL 
-    CMAKE_CXX_FLAGS_MINSIZEREL 
-    CMAKE_C_FLAGS_RELWITHDEBINFO 
-    CMAKE_CXX_FLAGS_RELWITHDEBINFO) 
-    string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
-    string(REGEX REPLACE "/MDd" "/MTd" ${flag_var} "${${flag_var}}")
-  endforeach(flag_var)
-  add_definitions(
-    -D_CRT_SECURE_NO_WARNINGS=1
-    -D_CRT_SECURE_NO_DEPRECATE=1
-    )
-  include_directories(${CMAKE_SOURCE_DIR}/Resources/VisualStudio)
-  link_libraries(netapi32)
-endif()
-
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
-  if (DEBIAN_HARDENING)
-    execute_process(
-      COMMAND dpkg-buildflags --get CPPFLAGS 
-      OUTPUT_VARIABLE DEBIAN_CPP_FLAGS
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
-    execute_process(
-      COMMAND dpkg-buildflags --get CFLAGS 
-      OUTPUT_VARIABLE DEBIAN_C_FLAGS
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
-    execute_process(
-      COMMAND dpkg-buildflags --get CXXFLAGS 
-      OUTPUT_VARIABLE DEBIAN_CXX_FLAGS
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
-    execute_process(
-      COMMAND dpkg-buildflags --get LDFLAGS 
-      OUTPUT_VARIABLE DEBIAN_LD_FLAGS
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
-
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DEBIAN_C_FLAGS} ${DEBIAN_CPP_FLAGS}")
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEBIAN_CXX_FLAGS} ${DEBIAN_CPP_FLAGS}")
-  endif()
-
-  add_definitions(
-    -D_LARGEFILE64_SOURCE=1 
-    -D_FILE_OFFSET_BITS=64
-    )
-  set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed ${DEBIAN_LD_FLAGS}")
-  set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--no-undefined ${DEBIAN_LD_FLAGS}")
-  set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined ${DEBIAN_LD_FLAGS}")
-
-  # http://www.mail-archive.com/cmake@cmake.org/msg08837.html
-  set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
-  link_libraries(uuid pthread rt)
-
-elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-  add_definitions(
-    -DWINVER=0x0501
-    -D_CRT_SECURE_NO_WARNINGS=1
-    )
-  link_libraries(rpcrt4 ws2_32)
-endif()
-
-
-if (${STATIC_BUILD})
-  add_definitions(-DORTHANC_STATIC=1)
-else()
-  add_definitions(-DORTHANC_STATIC=0)
-endif()
-
-if (${STANDALONE_BUILD})
-  add_definitions(
-    -DORTHANC_STANDALONE=1
-    )
-
-  EmbedResources(
-    PREPARE_DATABASE OrthancServer/PrepareDatabase.sql
-    ORTHANC_EXPLORER OrthancExplorer
-    )
-
-else()
-  add_definitions(
-    -DORTHANC_STANDALONE=0
-    -DORTHANC_PATH=\"${CMAKE_SOURCE_DIR}\"
-    )
-
-  EmbedResources(
-    PREPARE_DATABASE OrthancServer/PrepareDatabase.sql
-    )
-endif()
-
-
+# The main instructions to build the Orthanc binaries
 add_library(CoreLibrary
   STATIC
   ${AUTOGENERATED_SOURCES}
   ${THIRD_PARTY_SOURCES}
 
+  Core/Cache/MemoryCache.cpp
   Core/ChunkedBuffer.cpp
   Core/Compression/BufferCompressor.cpp
   Core/Compression/ZlibCompressor.cpp
   Core/Compression/ZipWriter.cpp
+  Core/Compression/HierarchicalZipWriter.cpp
   Core/OrthancException.cpp
   Core/DicomFormat/DicomArray.cpp
   Core/DicomFormat/DicomMap.cpp
   Core/DicomFormat/DicomTag.cpp
   Core/DicomFormat/DicomIntegerPixelAccessor.cpp
-  Core/FileStorage.cpp
+  Core/DicomFormat/DicomInstanceHasher.cpp
+  Core/FileStorage/FileStorage.cpp
+  Core/FileStorage/StorageAccessor.cpp
+  Core/FileStorage/CompressedFileStorageAccessor.cpp
+  Core/FileStorage/FileStorageAccessor.cpp
   Core/HttpServer/EmbeddedResourceHttpHandler.cpp
   Core/HttpServer/FilesystemHttpHandler.cpp
   Core/HttpServer/HttpHandler.cpp
   Core/HttpServer/HttpOutput.cpp
   Core/HttpServer/MongooseServer.cpp
+  Core/HttpServer/HttpFileSender.cpp
+  Core/HttpServer/FilesystemHttpSender.cpp
+  Core/RestApi/RestApiPath.cpp
+  Core/RestApi/RestApiOutput.cpp
+  Core/RestApi/RestApi.cpp
   Core/MultiThreading/BagOfRunnablesBySteps.cpp
   Core/PngWriter.cpp
   Core/SQLite/Connection.cpp
@@ -186,40 +140,65 @@
   OrthancCppClient/HttpException.cpp
   )  
 
-add_library(ServerLibrary
-  OrthancServer/DicomProtocol/DicomFindAnswers.cpp
-  OrthancServer/DicomProtocol/DicomServer.cpp
-  OrthancServer/DicomProtocol/DicomUserConnection.cpp
-  OrthancServer/FromDcmtkBridge.cpp
-  OrthancServer/Internals/CommandDispatcher.cpp
-  OrthancServer/Internals/FindScp.cpp
-  OrthancServer/Internals/MoveScp.cpp
-  OrthancServer/Internals/StoreScp.cpp
-  OrthancServer/OrthancInitialization.cpp
-  OrthancServer/OrthancRestApi.cpp
-  OrthancServer/ServerIndex.cpp
-  OrthancServer/ToDcmtkBridge.cpp
-  )
+
+
+if(NOT ONLY_CORE_LIBRARY)
+  add_library(ServerLibrary
+    ${DCMTK_SOURCES}
+    OrthancServer/DicomProtocol/DicomFindAnswers.cpp
+    OrthancServer/DicomProtocol/DicomServer.cpp
+    OrthancServer/DicomProtocol/DicomUserConnection.cpp
+    OrthancServer/FromDcmtkBridge.cpp
+    OrthancServer/Internals/CommandDispatcher.cpp
+    OrthancServer/Internals/FindScp.cpp
+    OrthancServer/Internals/MoveScp.cpp
+    OrthancServer/Internals/StoreScp.cpp
+    OrthancServer/OrthancInitialization.cpp
+    OrthancServer/OrthancRestApi.cpp
+    OrthancServer/ServerIndex.cpp
+    OrthancServer/ToDcmtkBridge.cpp
+    OrthancServer/DatabaseWrapper.cpp
+    OrthancServer/ServerContext.cpp
+    OrthancServer/ServerEnumerations.cpp
+    OrthancServer/ServerToolbox.cpp
+    )
+
+  # Ensure autogenerated code is built before building ServerLibrary
+  add_dependencies(ServerLibrary CoreLibrary)
+
+  add_executable(Orthanc
+    OrthancServer/main.cpp
+    )
 
-# Ensure autogenerated code is built before building ServerLibrary
-add_dependencies(ServerLibrary CoreLibrary)
+  target_link_libraries(Orthanc ServerLibrary CoreLibrary)
 
-add_executable(Orthanc
-  OrthancServer/main.cpp
-  )
+  install(
+    TARGETS Orthanc
+    RUNTIME DESTINATION bin
+    )
 
-add_executable(UnitTests
-  ${GTEST_SOURCES}
-  UnitTests/main.cpp
-  UnitTests/SQLite.cpp
-  UnitTests/SQLiteChromium.cpp
-  UnitTests/Versions.cpp
-  UnitTests/Zip.cpp
-  )
+  # Build the unit tests if required
+  if (BUILD_UNIT_TESTS)
+    add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1)
+    include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake)
+    add_executable(UnitTests
+      ${GTEST_SOURCES}
+      UnitTests/RestApi.cpp
+      UnitTests/SQLite.cpp
+      UnitTests/SQLiteChromium.cpp
+      UnitTests/ServerIndex.cpp
+      UnitTests/Versions.cpp
+      UnitTests/Zip.cpp
+      UnitTests/FileStorage.cpp
+      UnitTests/MemoryCache.cpp
+      UnitTests/main.cpp
+      )
+    target_link_libraries(UnitTests ServerLibrary CoreLibrary)
+  endif()
+endif()
 
-TARGET_LINK_LIBRARIES(Orthanc ServerLibrary CoreLibrary)
-TARGET_LINK_LIBRARIES(UnitTests ServerLibrary CoreLibrary)
 
+# Generate the Doxygen documentation if Doxygen is present
 find_package(Doxygen)
 if (DOXYGEN_FOUND)
   configure_file(
@@ -231,4 +210,7 @@
     WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
     COMMENT "Generating API documentation with Doxygen" VERBATIM
     )
-endif(DOXYGEN_FOUND)
+else()
+  message("Doxygen not found. The documentation will not be built.")
+endif()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/CacheIndex.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,250 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <list>
+#include <map>
+#include <boost/noncopyable.hpp>
+#include <cassert>
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  /**
+   * This class implements the index of a cache with least recently
+   * used (LRU) recycling policy. All the items of the cache index
+   * can be associated with a payload.
+   * Reference: http://stackoverflow.com/a/2504317
+   **/
+  template <typename T, typename Payload = NullType>
+  class CacheIndex : public boost::noncopyable
+  {
+  private:
+    typedef std::list< std::pair<T, Payload> >  Queue;
+    typedef std::map<T, typename Queue::iterator>  Index;
+
+    Index  index_;
+    Queue  queue_;
+
+    /**
+     * Internal method for debug builds to check whether the internal
+     * data structures are not corrupted.
+     **/
+    void CheckInvariants() const;
+
+  public:
+    /**
+     * Add a new element to the cache index, and make it the most
+     * recent element.
+     * \param id The ID of the element.
+     * \param payload The payload of the element.
+     **/
+    void Add(T id, Payload payload = Payload());
+
+    /**
+     * When accessing an element of the cache, this method tags the
+     * element as the most recently used.
+     * \param id The most recently accessed item.
+     **/
+    void TagAsMostRecent(T id);
+
+    /**
+     * Remove an element from the cache index.
+     * \param id The item to remove.
+     **/
+    Payload Invalidate(T id);
+
+    /**
+     * Get the oldest element in the cache and remove it.
+     * \return The oldest item.
+     **/
+    T RemoveOldest()
+    {
+      Payload p;
+      return RemoveOldest(p);
+    }
+
+    /**
+     * Get the oldest element in the cache, remove it and return the
+     * associated payload.
+     * \param payload Where to store the associated payload.
+     * \return The oldest item.
+     **/
+    T RemoveOldest(Payload& payload);
+
+    /**
+     * Check whether an element is contained in the cache.
+     * \param id The item.
+     * \return \c true iff the item is indexed by the cache.
+     **/
+    bool Contains(T id) const
+    {
+      return index_.find(id) != index_.end();
+    }
+
+    bool Contains(T id, Payload& payload) const
+    {
+      typename Index::const_iterator it = index_.find(id);
+      if (it == index_.end())
+      {
+        return false;
+      }
+      else
+      {
+        payload = it->second->second;
+        return true;
+      }
+    }
+
+    /**
+     * Return the number of elements in the cache.
+     * \return The number of elements.
+     **/
+    size_t GetSize() const
+    {
+      assert(index_.size() == queue_.size());
+      return queue_.size();
+    }
+
+    /**
+     * Check whether the cache index is empty.
+     * \return \c true iff the cache is empty.
+     **/
+    bool IsEmpty() const
+    {
+      return index_.empty();
+    }
+  };
+
+
+
+
+  /******************************************************************
+   ** Implementation of the template
+   ******************************************************************/
+
+  template <typename T, typename Payload>
+  void CacheIndex<T, Payload>::CheckInvariants() const
+  {
+#ifndef NDEBUG
+    assert(index_.size() == queue_.size());
+
+    for (typename Index::const_iterator 
+           it = index_.begin(); it != index_.end(); it++)
+    {
+      assert(it->second != queue_.end());
+      assert(it->second->first == it->first);
+    }
+#endif
+  }
+
+
+  template <typename T, typename Payload>
+  void CacheIndex<T, Payload>::Add(T id, Payload payload)
+  {
+    if (Contains(id))
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    queue_.push_front(std::make_pair(id, payload));
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void CacheIndex<T, Payload>::TagAsMostRecent(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  Payload CacheIndex<T, Payload>::Invalidate(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    Payload payload = it->second->second;
+    queue_.erase(it->second);
+    index_.erase(it);
+
+    CheckInvariants();
+    return payload;
+  }
+
+
+  template <typename T, typename Payload>
+  T CacheIndex<T, Payload>::RemoveOldest(Payload& payload)
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+    payload = item.second;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/ICachePageProvider.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include "../IDynamicObject.h"
+
+namespace Orthanc
+{
+  class ICachePageProvider
+  {
+  public:
+    virtual ~ICachePageProvider()
+    {
+    }
+
+    virtual IDynamicObject* Provide(const std::string& id) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/MemoryCache.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,94 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "MemoryCache.h"
+
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  MemoryCache::Page& MemoryCache::Load(const std::string& id)
+  {
+    // Reuse the cache entry if it already exists
+    Page* p = NULL;
+    if (index_.Contains(id, p))
+    {
+      VLOG(1) << "Reusing a cache page";
+      assert(p != NULL);
+      index_.TagAsMostRecent(id);
+      return *p;
+    }
+
+    // The id is not in the cache yet. Make some room if the cache
+    // is full.
+    if (index_.GetSize() == cacheSize_)
+    {
+      VLOG(1) << "Dropping the oldest cache page";
+      index_.RemoveOldest(p);
+      delete p;
+    }
+
+    // Create a new cache page
+    std::auto_ptr<Page> result(new Page);
+    result->id_ = id;
+    result->content_.reset(provider_.Provide(id));
+
+    // Add the newly create page to the cache
+    VLOG(1) << "Registering new data in a cache page";
+    p = result.release();
+    index_.Add(id, p);
+    return *p;
+  }
+
+  MemoryCache::MemoryCache(ICachePageProvider& provider,
+                           size_t cacheSize) : 
+    provider_(provider),
+    cacheSize_(cacheSize)
+  {
+  }
+
+  MemoryCache::~MemoryCache()
+  {
+    while (!index_.IsEmpty())
+    {
+      Page* element = NULL;
+      index_.RemoveOldest(element);
+      assert(element != NULL);
+      delete element;
+    }
+  }
+
+  IDynamicObject& MemoryCache::Access(const std::string& id)
+  {
+    return *Load(id).content_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/MemoryCache.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,67 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 <memory>
+#include "CacheIndex.h"
+#include "ICachePageProvider.h"
+
+namespace Orthanc
+{
+  /**
+   * WARNING: This class is NOT thread-safe.
+   **/
+  class MemoryCache
+  {
+  private:
+    struct Page
+    {
+      std::string id_;
+      std::auto_ptr<IDynamicObject> content_;
+    };
+
+    ICachePageProvider& provider_;
+    size_t cacheSize_;
+    CacheIndex<std::string, Page*>  index_;
+
+    Page& Load(const std::string& id);
+
+  public:
+    MemoryCache(ICachePageProvider& provider,
+                size_t cacheSize);
+
+    ~MemoryCache();
+
+    IDynamicObject& Access(const std::string& id);
+  };
+}
--- a/Core/ChunkedBuffer.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/ChunkedBuffer.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/ChunkedBuffer.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/ChunkedBuffer.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/Compression/BufferCompressor.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/Compression/BufferCompressor.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/Compression/BufferCompressor.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/Compression/BufferCompressor.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Compression/HierarchicalZipWriter.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,180 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "HierarchicalZipWriter.h"
+
+#include "../Toolbox.h"
+#include "../OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  std::string HierarchicalZipWriter::Index::KeepAlphanumeric(const std::string& source)
+  {
+    std::string result;
+
+    bool lastSpace = false;
+
+    result.reserve(source.size());
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      char c = source[i];
+      if (c == '^')
+        c = ' ';
+
+      if (c < 128 && 
+          c >= 0)
+      {
+        if (isspace(c)) 
+        {
+          if (!lastSpace)
+          {
+            lastSpace = true;
+            result.push_back(' ');
+          }
+        }
+        else if (isalnum(c) || 
+                 c == '.' || 
+                 c == '_')
+        {
+          result.push_back(c);
+          lastSpace = false;
+        }
+      }
+    }
+
+    return Toolbox::StripSpaces(result);
+  }
+
+  std::string HierarchicalZipWriter::Index::GetCurrentDirectoryPath() const
+  {
+    std::string result;
+
+    Stack::const_iterator it = stack_.begin();
+    it++;  // Skip the root node (to avoid absolute paths)
+
+    while (it != stack_.end())
+    {
+      result += (*it)->name_ + "/";
+      it++;
+    }
+
+    return result;
+  }
+
+  std::string HierarchicalZipWriter::Index::EnsureUniqueFilename(const char* filename)
+  {
+    std::string standardized = KeepAlphanumeric(filename);
+
+    Directory& d = *stack_.back();
+    Directory::Content::iterator it = d.content_.find(standardized);
+
+    if (it == d.content_.end())
+    {
+      d.content_[standardized] = 1;
+      return standardized;
+    }
+    else
+    {
+      it->second++;
+      return standardized + "-" + boost::lexical_cast<std::string>(it->second);
+    }    
+  }
+
+  HierarchicalZipWriter::Index::Index()
+  {
+    stack_.push_back(new Directory);
+  }
+
+  HierarchicalZipWriter::Index::~Index()
+  {
+    for (Stack::iterator it = stack_.begin(); it != stack_.end(); it++)
+    {
+      delete *it;
+    }
+  }
+
+  std::string HierarchicalZipWriter::Index::OpenFile(const char* name)
+  {
+    return GetCurrentDirectoryPath() + EnsureUniqueFilename(name);
+  }
+
+  void HierarchicalZipWriter::Index::OpenDirectory(const char* name)
+  {
+    std::string d = EnsureUniqueFilename(name);
+
+    // Push the new directory onto the stack
+    stack_.push_back(new Directory);
+    stack_.back()->name_ = d;
+  }
+
+  void HierarchicalZipWriter::Index::CloseDirectory()
+  {
+    if (IsRoot())
+    {
+      // Cannot close the root node
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    delete stack_.back();
+    stack_.pop_back();
+  }
+
+
+  HierarchicalZipWriter::HierarchicalZipWriter(const char* path)
+  {
+    writer_.SetOutputPath(path);
+    writer_.Open();
+  }
+
+  HierarchicalZipWriter::~HierarchicalZipWriter()
+  {
+    writer_.Close();
+  }
+
+  void HierarchicalZipWriter::OpenFile(const char* name)
+  {
+    std::string p = indexer_.OpenFile(name);
+    writer_.OpenFile(p.c_str());
+  }
+
+  void HierarchicalZipWriter::OpenDirectory(const char* name)
+  {
+    indexer_.OpenDirectory(name);
+  }
+
+  void HierarchicalZipWriter::CloseDirectory()
+  {
+    indexer_.CloseDirectory();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Compression/HierarchicalZipWriter.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,123 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "ZipWriter.h"
+
+#include <map>
+#include <list>
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  class HierarchicalZipWriter
+  {
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+    FRIEND_TEST(HierarchicalZipWriter, Index);
+    FRIEND_TEST(HierarchicalZipWriter, Filenames);
+#endif
+
+  private:
+    class Index
+    {
+    private:
+      struct Directory
+      {
+        typedef std::map<std::string, unsigned int>  Content;
+
+        std::string name_;
+        Content  content_;
+      };
+
+      typedef std::list<Directory*> Stack;
+  
+      Stack stack_;
+
+      std::string GetCurrentDirectoryPath() const;
+
+      std::string EnsureUniqueFilename(const char* filename);
+
+    public:
+      Index();
+
+      ~Index();
+
+      bool IsRoot() const
+      {
+        return stack_.size() == 1;
+      }
+
+      std::string OpenFile(const char* name);
+
+      void OpenDirectory(const char* name);
+
+      void CloseDirectory();
+
+      static std::string KeepAlphanumeric(const std::string& source);
+    };
+
+    Index indexer_;
+    ZipWriter writer_;
+
+  public:
+    HierarchicalZipWriter(const char* path);
+
+    ~HierarchicalZipWriter();
+
+    void SetCompressionLevel(uint8_t level)
+    {
+      writer_.SetCompressionLevel(level);
+    }
+
+    uint8_t GetCompressionLevel() const
+    {
+      return writer_.GetCompressionLevel();
+    }
+
+    void OpenFile(const char* name);
+
+    void OpenDirectory(const char* name);
+
+    void CloseDirectory();
+
+    void Write(const char* data, size_t length)
+    {
+      writer_.Write(data, length);
+    }
+
+    void Write(const std::string& data)
+    {
+      writer_.Write(data);
+    }
+  };
+}
--- a/Core/Compression/ZipWriter.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/Compression/ZipWriter.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -1,3 +1,35 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "ZipWriter.h"
 
 #include "../../Resources/minizip/zip.h"
@@ -104,7 +136,7 @@
     compressionLevel_ = level;
   }
 
-  void ZipWriter::CreateFileInZip(const char* path)
+  void ZipWriter::OpenFile(const char* path)
   {
     Open();
 
@@ -139,7 +171,7 @@
   {
     if (!hasFileInZip_)
     {
-      throw OrthancException("Call first CreateFileInZip()");
+      throw OrthancException("Call first OpenFile()");
     }
 
     const size_t maxBytesInAStep = std::numeric_limits<int32_t>::max();
--- a/Core/Compression/ZipWriter.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/Compression/ZipWriter.h	Fri Dec 14 11:24:24 2012 +0100
@@ -1,9 +1,45 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 <stdint.h>
 #include <string>
 #include <boost/shared_ptr.hpp>
 
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+#include <gtest/gtest_prod.h>
+#endif
+
 namespace Orthanc
 {
   class ZipWriter
@@ -41,7 +77,7 @@
       return path_;
     }
 
-    void CreateFileInZip(const char* path);
+    void OpenFile(const char* path);
 
     void Write(const char* data, size_t length);
 
--- a/Core/Compression/ZlibCompressor.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/Compression/ZlibCompressor.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -98,7 +110,15 @@
 
     size_t uncompressedLength;
     memcpy(&uncompressedLength, compressed, sizeof(size_t));
-    uncompressed.resize(uncompressedLength);
+    
+    try
+    {
+      uncompressed.resize(uncompressedLength);
+    }
+    catch (...)
+    {
+      throw OrthancException("Zlib: Corrupted compressed buffer");
+    }
 
     uLongf tmp = uncompressedLength;
     int error = uncompress
--- a/Core/Compression/ZlibCompressor.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/Compression/ZlibCompressor.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/DicomFormat/DicomArray.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomArray.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/DicomFormat/DicomArray.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomArray.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/DicomFormat/DicomElement.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomElement.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomFormat/DicomInstanceHasher.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,94 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "DicomInstanceHasher.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  DicomInstanceHasher::DicomInstanceHasher(const DicomMap& instance)
+  {
+    patientId_ = instance.GetValue(DICOM_TAG_PATIENT_ID).AsString();
+    studyUid_ = instance.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
+    seriesUid_ = instance.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
+    instanceUid_ = instance.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
+
+    if (patientId_.size() == 0 ||
+        studyUid_.size() == 0 ||
+        seriesUid_.size() == 0 ||
+        instanceUid_.size() == 0)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+  const std::string& DicomInstanceHasher::HashPatient()
+  {
+    if (patientHash_.size() == 0)
+    {
+      Toolbox::ComputeSHA1(patientHash_, patientId_);
+    }
+
+    return patientHash_;
+  }
+
+  const std::string& DicomInstanceHasher::HashStudy()
+  {
+    if (studyHash_.size() == 0)
+    {
+      Toolbox::ComputeSHA1(studyHash_, patientId_ + "|" + studyUid_);
+    }
+
+    return studyHash_;
+  }
+
+  const std::string& DicomInstanceHasher::HashSeries()
+  {
+    if (seriesHash_.size() == 0)
+    {
+      Toolbox::ComputeSHA1(seriesHash_, patientId_ + "|" + studyUid_ + "|" + seriesUid_);
+    }
+
+    return seriesHash_;
+  }
+
+  const std::string& DicomInstanceHasher::HashInstance()
+  {
+    if (instanceHash_.size() == 0)
+    {
+      Toolbox::ComputeSHA1(instanceHash_, patientId_ + "|" + studyUid_ + "|" + seriesUid_ + "|" + instanceUid_);
+    }
+
+    return instanceHash_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomFormat/DicomInstanceHasher.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,93 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "DicomMap.h"
+
+namespace Orthanc
+{
+  /**
+   * This class implements the hashing mechanism that is used to
+   * convert DICOM unique identifiers to Orthanc identifiers. Any
+   * Orthanc identifier for a DICOM resource corresponds to the SHA-1
+   * hash of the DICOM identifiers. 
+
+   * \note SHA-1 hash is used because it is less sensitive to
+   * collision attacks than MD5. <a
+   * href="http://en.wikipedia.org/wiki/SHA-256#Comparison_of_SHA_functions">[Reference]</a>
+   **/
+  class DicomInstanceHasher
+  {
+  private:
+    std::string patientId_;
+    std::string studyUid_;
+    std::string seriesUid_;
+    std::string instanceUid_;
+
+    std::string patientHash_;
+    std::string studyHash_;
+    std::string seriesHash_;
+    std::string instanceHash_;
+
+  public:
+    DicomInstanceHasher(const DicomMap& instance);
+
+    const std::string& GetPatientId() const
+    {
+      return patientId_;
+    }
+
+    const std::string& GetStudyUid() const
+    {
+      return studyUid_;
+    }
+
+    const std::string& GetSeriesUid() const
+    {
+      return seriesUid_;
+    }
+
+    const std::string& GetInstanceUid() const
+    {
+      return instanceUid_;
+    }
+
+    const std::string& HashPatient();
+
+    const std::string& HashStudy();
+
+    const std::string& HashSeries();
+
+    const std::string& HashInstance();
+  };
+}
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/DicomFormat/DicomMap.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomMap.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/DicomFormat/DicomMap.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomMap.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/DicomFormat/DicomNullValue.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomNullValue.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/DicomFormat/DicomString.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomString.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/DicomFormat/DicomTag.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomTag.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/DicomFormat/DicomTag.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomTag.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/DicomFormat/DicomValue.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/DicomFormat/DicomValue.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -20,18 +32,15 @@
 
 #pragma once
 
-#include <boost/noncopyable.hpp>
+#include "../IDynamicObject.h"
+
 #include <string>
 
 namespace Orthanc
 {
-  class DicomValue : public boost::noncopyable
+  class DicomValue : public IDynamicObject
   {
   public:
-    virtual ~DicomValue()
-    {
-    }
-
     virtual DicomValue* Clone() const = 0;
 
     virtual std::string AsString() const = 0;
--- a/Core/Enumerations.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/Enumerations.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -34,12 +46,18 @@
     ErrorCode_ParameterOutOfRange,
     ErrorCode_NotEnoughMemory,
     ErrorCode_BadParameterType,
+    ErrorCode_BadSequenceOfCalls,
+    ErrorCode_InexistentItem,
 
     // Specific error codes
     ErrorCode_UriSyntax,
     ErrorCode_InexistentFile,
     ErrorCode_CannotWriteFile,
-    ErrorCode_BadFileFormat
+    ErrorCode_BadFileFormat,
+    ErrorCode_Timeout,
+    ErrorCode_UnknownResource,
+    ErrorCode_IncompatibleDatabaseVersion,
+    ErrorCode_FullStorage
   };
 
   enum PixelFormat
@@ -48,4 +66,23 @@
     PixelFormat_Grayscale8,
     PixelFormat_Grayscale16
   };
+
+
+  /**
+   * WARNING: Do not change the explicit values in the enumerations
+   * below this point. This would result in incompatible databases
+   * between versions of Orthanc!
+   **/
+
+  enum CompressionType
+  {
+    CompressionType_None = 1,
+    CompressionType_Zlib = 2
+  };
+
+  enum FileContentType
+  {
+    FileContentType_Dicom = 1,
+    FileContentType_Json = 2
+  };
 }
--- a/Core/FileStorage.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,294 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 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 "FileStorage.h"
-
-// http://stackoverflow.com/questions/1576272/storing-large-number-of-files-in-file-system
-// http://stackoverflow.com/questions/446358/storing-a-large-number-of-images
-
-#include "OrthancException.h"
-#include "Toolbox.h"
-#include "Uuid.h"
-
-#include <boost/filesystem/fstream.hpp>
-
-static std::string ToString(const boost::filesystem::path& p)
-{
-#if BOOST_HAS_FILESYSTEM_V3 == 1
-  return p.filename().string();
-#else
-  return p.filename();
-#endif
-}
-
-
-namespace Orthanc
-{
-  boost::filesystem::path FileStorage::GetPath(const std::string& uuid) const
-  {
-    namespace fs = boost::filesystem;
-
-    if (!Toolbox::IsUuid(uuid))
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    fs::path path = root_;
-
-    path /= std::string(&uuid[0], &uuid[2]);
-    path /= std::string(&uuid[2], &uuid[4]);
-    path /= uuid;
-
-#if BOOST_HAS_FILESYSTEM_V3 == 1
-    path.make_preferred();
-#endif
-
-    return path;
-  }
-
-  FileStorage::FileStorage(std::string root)
-  {
-    namespace fs = boost::filesystem;
-
-    //root_ = boost::filesystem::absolute(root).string();
-    root_ = root;
-
-    if (fs::exists(root))
-    {
-      if (!fs::is_directory(root))
-      {
-        throw OrthancException("The file storage root directory is a file");
-      }
-    }
-    else
-    {
-      if (!fs::create_directories(root))
-      {
-        throw OrthancException("Unable to create the file storage root directory");
-      }
-    }
-  }
-
-  std::string FileStorage::CreateFileWithoutCompression(const void* content, size_t size)
-  {
-    std::string uuid;
-    boost::filesystem::path path;
-    
-    for (;;)
-    {
-      uuid = Toolbox::GenerateUuid();
-      path = GetPath(uuid);
-
-      if (!boost::filesystem::exists(path))
-      {
-        // OK, this is indeed a new file
-        break;
-      }
-
-      // Extremely improbable case: This Uuid has already been created
-      // in the past. Try again.
-    }
-
-    if (boost::filesystem::exists(path.parent_path()))
-    {
-      if (!boost::filesystem::is_directory(path.parent_path()))
-      {
-        throw OrthancException("The subdirectory to be created is already occupied by a regular file");        
-      }
-    }
-    else
-    {
-      if (!boost::filesystem::create_directories(path.parent_path()))
-      {
-        throw OrthancException("Unable to create a subdirectory in the file storage");        
-      }
-    }
-
-    boost::filesystem::ofstream f;
-    f.open(path, std::ofstream::out | std::ios::binary);
-    if (!f.good())
-    {
-      throw OrthancException("Unable to create a new file in the file storage");
-    }
-
-    if (size != 0)
-    {
-      f.write(static_cast<const char*>(content), size);
-      if (!f.good())
-      {
-        f.close();
-        throw OrthancException("Unable to write to the new file in the file storage");
-      }
-    }
-
-    f.close();
-
-    return uuid;
-  } 
-
-
-  std::string FileStorage::Create(const void* content, size_t size)
-  {
-    if (!HasBufferCompressor() || size == 0)
-    {
-      return CreateFileWithoutCompression(content, size);
-    }
-    else
-    {
-      std::string compressed;
-      compressor_->Compress(compressed, content, size);
-      assert(compressed.size() > 0);
-      return CreateFileWithoutCompression(&compressed[0], compressed.size());
-    }
-  }
-
-
-  std::string FileStorage::Create(const std::vector<uint8_t>& content)
-  {
-    if (content.size() == 0)
-      return Create(NULL, 0);
-    else
-      return Create(&content[0], content.size());
-  }
-
-  std::string FileStorage::Create(const std::string& content)
-  {
-    if (content.size() == 0)
-      return Create(NULL, 0);
-    else
-      return Create(&content[0], content.size());
-  }
-
-  void FileStorage::ReadFile(std::string& content,
-                             const std::string& uuid) const
-  {
-    content.clear();
-
-    if (HasBufferCompressor())
-    {
-      std::string compressed;
-      Toolbox::ReadFile(compressed, ToString(GetPath(uuid)));
-
-      if (compressed.size() != 0)
-      {
-        compressor_->Uncompress(content, compressed);
-      }
-    }
-    else
-    {
-      Toolbox::ReadFile(content, GetPath(uuid).string());
-    }
-  }
-
-
-  uintmax_t FileStorage::GetCompressedSize(const std::string& uuid) const
-  {
-    boost::filesystem::path path = GetPath(uuid);
-    return boost::filesystem::file_size(path);
-  }
-
-
-
-  void FileStorage::ListAllFiles(std::set<std::string>& result) const
-  {
-    namespace fs = boost::filesystem;
-
-    result.clear();
-
-    if (fs::exists(root_) && fs::is_directory(root_))
-    {
-      for (fs::recursive_directory_iterator current(root_), end; current != end ; ++current)
-      {
-        if (fs::is_regular_file(current->status()))
-        {
-          try
-          {
-            fs::path d = current->path();
-            std::string uuid = ToString(d);
-            if (Toolbox::IsUuid(uuid))
-            {
-              fs::path p0 = d.parent_path().parent_path().parent_path();
-              std::string p1 = ToString(d.parent_path().parent_path());
-              std::string p2 = ToString(d.parent_path());
-              if (p1.length() == 2 &&
-                  p2.length() == 2 &&
-                  p1 == uuid.substr(0, 2) &&
-                  p2 == uuid.substr(2, 2) &&
-                  p0 == root_)
-              {
-                result.insert(uuid);
-              }
-            }
-          }
-          catch (fs::filesystem_error)
-          {
-          }
-        }
-      }
-    }
-  }
-
-
-  void FileStorage::Clear()
-  {
-    namespace fs = boost::filesystem;
-    typedef std::set<std::string> List;
-
-    List result;
-    ListAllFiles(result);
-
-    for (List::const_iterator it = result.begin(); it != result.end(); it++)
-    {
-      Remove(*it);
-    }
-  }
-
-
-  void FileStorage::Remove(const std::string& uuid)
-  {
-    namespace fs = boost::filesystem;
-
-    fs::path p = GetPath(uuid);
-    fs::remove(p);
-
-    // Remove the two parent directories, ignoring the error code if
-    // these directories are not empty
-
-#if BOOST_HAS_FILESYSTEM_V3 == 1
-    boost::system::error_code err;
-    fs::remove(p.parent_path(), err);
-    fs::remove(p.parent_path().parent_path(), err);
-#else
-    fs::remove(p.parent_path());
-    fs::remove(p.parent_path().parent_path());
-#endif
-  }
-
-
-  uintmax_t FileStorage::GetCapacity() const
-  {
-    return boost::filesystem::space(root_).capacity;
-  }
-
-  uintmax_t FileStorage::GetAvailableSpace() const
-  {
-    return boost::filesystem::space(root_).available;
-  }
-}
--- a/Core/FileStorage.h	Thu Oct 04 15:09:56 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 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/>.
- **/
-
-
-#pragma once
-
-#include <boost/filesystem.hpp>
-#include <set>
-
-#include "Compression/BufferCompressor.h"
-
-namespace Orthanc
-{
-  class FileStorage : public boost::noncopyable
-  {
-    friend class HttpOutput;
-
-  private:
-    std::auto_ptr<BufferCompressor> compressor_;
-
-    boost::filesystem::path root_;
-
-    boost::filesystem::path GetPath(const std::string& uuid) const;
-
-    std::string CreateFileWithoutCompression(const void* content, size_t size);
-
-  public:
-    FileStorage(std::string root);
-
-    void SetBufferCompressor(BufferCompressor* compressor)  // Takes the ownership
-    {
-      compressor_.reset(compressor);
-    }
-
-    bool HasBufferCompressor() const
-    {
-      return compressor_.get() != NULL;
-    }
-
-    std::string Create(const void* content, size_t size);
-
-    std::string Create(const std::vector<uint8_t>& content);
-
-    std::string Create(const std::string& content);
-
-    void ReadFile(std::string& content,
-                  const std::string& uuid) const;
-
-    void ListAllFiles(std::set<std::string>& result) const;
-
-    uintmax_t GetCompressedSize(const std::string& uuid) const;
-
-    void Clear();
-
-    void Remove(const std::string& uuid);
-
-    uintmax_t GetCapacity() const;
-
-    uintmax_t GetAvailableSpace() const;
-
-    std::string GetPath() const
-    {
-      return root_.string();
-    }
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,120 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "CompressedFileStorageAccessor.h"
+
+#include "../OrthancException.h"
+#include "FileStorageAccessor.h"
+#include "../HttpServer/BufferHttpSender.h"
+
+namespace Orthanc
+{
+  FileInfo CompressedFileStorageAccessor::WriteInternal(const void* data,
+                                                        size_t size,
+                                                        FileContentType type)
+  {
+    switch (compressionType_)
+    {
+    case CompressionType_None:
+    {
+      std::string uuid = storage_.Create(data, size);
+      return FileInfo(uuid, type, size);
+    }
+
+    case CompressionType_Zlib:
+    {
+      std::string compressed;
+      zlib_.Compress(compressed, data, size);
+      std::string uuid = storage_.Create(compressed);
+      return FileInfo(uuid, type, size, 
+                      CompressionType_Zlib, compressed.size());
+    }
+
+    default:
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+  CompressedFileStorageAccessor::CompressedFileStorageAccessor(FileStorage& storage) : 
+    storage_(storage)
+  {
+    compressionType_ = CompressionType_None;
+  }
+
+  void CompressedFileStorageAccessor::Read(std::string& content,
+                                           const std::string& uuid)
+  {
+    switch (compressionType_)
+    {
+    case CompressionType_None:
+      storage_.ReadFile(content, uuid);
+      break;
+
+    case CompressionType_Zlib:
+    {
+      std::string compressed;
+      storage_.ReadFile(compressed, uuid);
+      zlib_.Uncompress(content, compressed);
+      break;
+    }
+
+    default:
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+  HttpFileSender* CompressedFileStorageAccessor::ConstructHttpFileSender(const std::string& uuid)
+  {
+    switch (compressionType_)
+    {
+    case CompressionType_None:
+    {
+      FileStorageAccessor uncompressedAccessor(storage_);
+      return uncompressedAccessor.ConstructHttpFileSender(uuid);
+    }
+
+    case CompressionType_Zlib:
+    {
+      std::string compressed;
+      storage_.ReadFile(compressed, uuid);
+
+      std::auto_ptr<BufferHttpSender> sender(new BufferHttpSender);
+      zlib_.Uncompress(sender->GetBuffer(), compressed);
+
+      return sender.release();
+    }        
+
+    default:
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileStorage/CompressedFileStorageAccessor.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,71 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "StorageAccessor.h"
+#include "FileStorage.h"
+#include "../Compression/ZlibCompressor.h"
+
+namespace Orthanc
+{
+  class CompressedFileStorageAccessor : public StorageAccessor
+  {
+  private:
+    FileStorage& storage_;
+    ZlibCompressor zlib_;
+    CompressionType compressionType_;
+
+  protected:
+    virtual FileInfo WriteInternal(const void* data,
+                                   size_t size,
+                                   FileContentType type);
+
+  public: 
+    CompressedFileStorageAccessor(FileStorage& storage);
+
+    void SetCompressionForNextOperations(CompressionType compression)
+    {
+      compressionType_ = compression;
+    }
+    
+    CompressionType GetCompressionForNextOperations()
+    {
+      return compressionType_;
+    }
+
+    virtual void Read(std::string& content,
+                      const std::string& uuid);
+
+    virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileStorage/FileInfo.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,110 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <stdint.h>
+#include "../Enumerations.h"
+
+namespace Orthanc
+{
+  struct FileInfo
+  {
+  private:
+    std::string uuid_;
+    FileContentType contentType_;
+    uint64_t uncompressedSize_;
+    CompressionType compressionType_;
+    uint64_t compressedSize_;
+
+  public:
+    FileInfo()
+    {
+    }
+
+    /**
+     * Constructor for an uncompressed attachment.
+     **/
+    FileInfo(const std::string& uuid,
+             FileContentType contentType,
+             uint64_t size) :
+      uuid_(uuid),
+      contentType_(contentType),
+      uncompressedSize_(size),
+      compressionType_(CompressionType_None),
+      compressedSize_(size)
+    {
+    }
+
+    /**
+     * Constructor for a compressed attachment.
+     **/
+    FileInfo(const std::string& uuid,
+             FileContentType contentType,
+             uint64_t uncompressedSize,
+             CompressionType compressionType,
+             uint64_t compressedSize) :
+      uuid_(uuid),
+      contentType_(contentType),
+      uncompressedSize_(uncompressedSize),
+      compressionType_(compressionType),
+      compressedSize_(compressedSize)
+    {
+    }
+
+    const std::string& GetUuid() const
+    {
+      return uuid_;
+    }
+
+    FileContentType GetContentType() const
+    {
+      return contentType_;
+    }
+
+    uint64_t GetUncompressedSize() const
+    {
+      return uncompressedSize_;
+    }
+
+    CompressionType GetCompressionType() const
+    {
+      return compressionType_;
+    }
+
+    uint64_t GetCompressedSize() const
+    {
+      return compressedSize_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileStorage/FileStorage.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,323 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "FileStorage.h"
+
+// http://stackoverflow.com/questions/1576272/storing-large-number-of-files-in-file-system
+// http://stackoverflow.com/questions/446358/storing-a-large-number-of-images
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+#include "../Uuid.h"
+
+#include <boost/filesystem/fstream.hpp>
+#include <glog/logging.h>
+
+static std::string ToString(const boost::filesystem::path& p)
+{
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+  return p.filename().string();
+#else
+  return p.filename();
+#endif
+}
+
+
+namespace Orthanc
+{
+  boost::filesystem::path FileStorage::GetPath(const std::string& uuid) const
+  {
+    namespace fs = boost::filesystem;
+
+    if (!Toolbox::IsUuid(uuid))
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    fs::path path = root_;
+
+    path /= std::string(&uuid[0], &uuid[2]);
+    path /= std::string(&uuid[2], &uuid[4]);
+    path /= uuid;
+
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+    path.make_preferred();
+#endif
+
+    return path;
+  }
+
+  FileStorage::FileStorage(std::string root)
+  {
+    namespace fs = boost::filesystem;
+
+    //root_ = boost::filesystem::absolute(root).string();
+    root_ = root;
+
+    if (fs::exists(root))
+    {
+      if (!fs::is_directory(root))
+      {
+        throw OrthancException("The file storage root directory is a file");
+      }
+    }
+    else
+    {
+      if (!fs::create_directories(root))
+      {
+        throw OrthancException("Unable to create the file storage root directory");
+      }
+    }
+  }
+
+  std::string FileStorage::CreateFileWithoutCompression(const void* content, size_t size)
+  {
+    std::string uuid;
+    boost::filesystem::path path;
+    
+    for (;;)
+    {
+      uuid = Toolbox::GenerateUuid();
+      path = GetPath(uuid);
+
+      if (!boost::filesystem::exists(path))
+      {
+        // OK, this is indeed a new file
+        break;
+      }
+
+      // Extremely improbable case: This Uuid has already been created
+      // in the past. Try again.
+    }
+
+    if (boost::filesystem::exists(path.parent_path()))
+    {
+      if (!boost::filesystem::is_directory(path.parent_path()))
+      {
+        throw OrthancException("The subdirectory to be created is already occupied by a regular file");        
+      }
+    }
+    else
+    {
+      if (!boost::filesystem::create_directories(path.parent_path()))
+      {
+        throw OrthancException("Unable to create a subdirectory in the file storage");        
+      }
+    }
+
+    boost::filesystem::ofstream f;
+    f.open(path, std::ofstream::out | std::ios::binary);
+    if (!f.good())
+    {
+      throw OrthancException("Unable to create a new file in the file storage");
+    }
+
+    if (size != 0)
+    {
+      f.write(static_cast<const char*>(content), size);
+      if (!f.good())
+      {
+        f.close();
+        throw OrthancException("Unable to write to the new file in the file storage");
+      }
+    }
+
+    f.close();
+
+    return uuid;
+  } 
+
+
+  std::string FileStorage::Create(const void* content, size_t size)
+  {
+    if (!HasBufferCompressor() || size == 0)
+    {
+      return CreateFileWithoutCompression(content, size);
+    }
+    else
+    {
+      std::string compressed;
+      compressor_->Compress(compressed, content, size);
+      assert(compressed.size() > 0);
+      return CreateFileWithoutCompression(&compressed[0], compressed.size());
+    }
+  }
+
+
+  std::string FileStorage::Create(const std::vector<uint8_t>& content)
+  {
+    if (content.size() == 0)
+      return Create(NULL, 0);
+    else
+      return Create(&content[0], content.size());
+  }
+
+  std::string FileStorage::Create(const std::string& content)
+  {
+    if (content.size() == 0)
+      return Create(NULL, 0);
+    else
+      return Create(&content[0], content.size());
+  }
+
+  void FileStorage::ReadFile(std::string& content,
+                             const std::string& uuid) const
+  {
+    content.clear();
+
+    if (HasBufferCompressor())
+    {
+      std::string compressed;
+      Toolbox::ReadFile(compressed, ToString(GetPath(uuid)));
+
+      if (compressed.size() != 0)
+      {
+        compressor_->Uncompress(content, compressed);
+      }
+    }
+    else
+    {
+      Toolbox::ReadFile(content, GetPath(uuid).string());
+    }
+  }
+
+
+  uintmax_t FileStorage::GetCompressedSize(const std::string& uuid) const
+  {
+    boost::filesystem::path path = GetPath(uuid);
+    return boost::filesystem::file_size(path);
+  }
+
+
+
+  void FileStorage::ListAllFiles(std::set<std::string>& result) const
+  {
+    namespace fs = boost::filesystem;
+
+    result.clear();
+
+    if (fs::exists(root_) && fs::is_directory(root_))
+    {
+      for (fs::recursive_directory_iterator current(root_), end; current != end ; ++current)
+      {
+        if (fs::is_regular_file(current->status()))
+        {
+          try
+          {
+            fs::path d = current->path();
+            std::string uuid = ToString(d);
+            if (Toolbox::IsUuid(uuid))
+            {
+              fs::path p0 = d.parent_path().parent_path().parent_path();
+              std::string p1 = ToString(d.parent_path().parent_path());
+              std::string p2 = ToString(d.parent_path());
+              if (p1.length() == 2 &&
+                  p2.length() == 2 &&
+                  p1 == uuid.substr(0, 2) &&
+                  p2 == uuid.substr(2, 2) &&
+                  p0 == root_)
+              {
+                result.insert(uuid);
+              }
+            }
+          }
+          catch (fs::filesystem_error)
+          {
+          }
+        }
+      }
+    }
+  }
+
+
+  void FileStorage::Clear()
+  {
+    namespace fs = boost::filesystem;
+    typedef std::set<std::string> List;
+
+    List result;
+    ListAllFiles(result);
+
+    for (List::const_iterator it = result.begin(); it != result.end(); it++)
+    {
+      Remove(*it);
+    }
+  }
+
+
+  void FileStorage::Remove(const std::string& uuid)
+  {
+    LOG(INFO) << "Deleting file " << uuid;
+    namespace fs = boost::filesystem;
+
+    fs::path p = GetPath(uuid);
+
+    try
+    {
+      fs::remove(p);
+    }
+    catch (...)
+    {
+      // Ignore the error
+    }
+
+    // Remove the two parent directories, ignoring the error code if
+    // these directories are not empty
+
+    try
+    {
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+      boost::system::error_code err;
+      fs::remove(p.parent_path(), err);
+      fs::remove(p.parent_path().parent_path(), err);
+#else
+      fs::remove(p.parent_path());
+      fs::remove(p.parent_path().parent_path());
+#endif
+    }
+    catch (...)
+    {
+      // Ignore the error
+    }
+  }
+
+
+  uintmax_t FileStorage::GetCapacity() const
+  {
+    return boost::filesystem::space(root_).capacity;
+  }
+
+  uintmax_t FileStorage::GetAvailableSpace() const
+  {
+    return boost::filesystem::space(root_).available;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileStorage/FileStorage.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,96 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/filesystem.hpp>
+#include <set>
+
+#include "../Compression/BufferCompressor.h"
+
+namespace Orthanc
+{
+  class FileStorage : public boost::noncopyable
+  {
+    // TODO REMOVE THIS
+    friend class FilesystemHttpSender;
+    friend class FileStorageAccessor;
+
+  private:
+    std::auto_ptr<BufferCompressor> compressor_;
+
+    boost::filesystem::path root_;
+
+    boost::filesystem::path GetPath(const std::string& uuid) const;
+
+    std::string CreateFileWithoutCompression(const void* content, size_t size);
+
+  public:
+    FileStorage(std::string root);
+
+    void SetBufferCompressor(BufferCompressor* compressor)  // Takes the ownership
+    {
+      compressor_.reset(compressor);
+    }
+
+    bool HasBufferCompressor() const
+    {
+      return compressor_.get() != NULL;
+    }
+
+    std::string Create(const void* content, size_t size);
+
+    std::string Create(const std::vector<uint8_t>& content);
+
+    std::string Create(const std::string& content);
+
+    void ReadFile(std::string& content,
+                  const std::string& uuid) const;
+
+    void ListAllFiles(std::set<std::string>& result) const;
+
+    uintmax_t GetCompressedSize(const std::string& uuid) const;
+
+    void Clear();
+
+    void Remove(const std::string& uuid);
+
+    uintmax_t GetCapacity() const;
+
+    uintmax_t GetAvailableSpace() const;
+
+    std::string GetPath() const
+    {
+      return root_.string();
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileStorage/FileStorageAccessor.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,43 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "FileStorageAccessor.h"
+
+namespace Orthanc
+{
+  FileInfo FileStorageAccessor::WriteInternal(const void* data,
+                                              size_t size,
+                                              FileContentType type)
+  {
+    return FileInfo(storage_.Create(data, size), type, size);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileStorage/FileStorageAccessor.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,67 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "StorageAccessor.h"
+#include "FileStorage.h"
+#include "../HttpServer/FilesystemHttpSender.h"
+
+namespace Orthanc
+{
+  class FileStorageAccessor : public StorageAccessor
+  {
+  private:
+    FileStorage& storage_;
+    
+  protected:
+    virtual FileInfo WriteInternal(const void* data,
+                                   size_t size,
+                                   FileContentType type);
+
+  public:
+    FileStorageAccessor(FileStorage& storage) : storage_(storage)
+    {
+    }
+
+    virtual void Read(std::string& content,
+                      const std::string& uuid)
+    {
+      storage_.ReadFile(content, uuid);
+    }
+
+    virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid)
+    {
+      return new FilesystemHttpSender(storage_.GetPath(uuid));
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileStorage/StorageAccessor.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,62 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "StorageAccessor.h"
+
+namespace Orthanc
+{
+  FileInfo StorageAccessor::Write(const std::vector<uint8_t>& content,
+                                  FileContentType type)
+  {
+    if (content.size() == 0)
+    {
+      return WriteInternal(NULL, 0, type);
+    }
+    else
+    {
+      return WriteInternal(&content[0], content.size(), type);
+    }
+  }
+
+  FileInfo StorageAccessor::Write(const std::string& content,
+                                  FileContentType type)
+  {
+    if (content.size() == 0)
+    {
+      return WriteInternal(NULL, 0, type);
+    }
+    else
+    {
+      return WriteInternal(&content[0], content.size(), type);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileStorage/StorageAccessor.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,75 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "FileInfo.h"
+#include "../HttpServer/HttpFileSender.h"
+
+#include <vector>
+#include <string>
+#include <boost/noncopyable.hpp>
+#include <stdint.h>
+
+namespace Orthanc
+{
+  class StorageAccessor : boost::noncopyable
+  {
+  protected:
+    virtual FileInfo WriteInternal(const void* data,
+                                   size_t size,
+                                   FileContentType type) = 0;
+
+  public:
+    virtual ~StorageAccessor()
+    {
+    }
+
+    FileInfo Write(const void* data,
+                   size_t size,
+                   FileContentType type)
+    {
+      return WriteInternal(data, size, type);
+    }
+
+    FileInfo Write(const std::vector<uint8_t>& content,
+                   FileContentType type);
+
+    FileInfo Write(const std::string& content,
+                   FileContentType type);
+
+    virtual void Read(std::string& content,
+                      const std::string& uuid) = 0;
+
+    virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/BufferHttpSender.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,68 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "HttpFileSender.h"
+
+namespace Orthanc
+{
+  class BufferHttpSender : public HttpFileSender
+  {
+  private:
+    std::string buffer_;
+
+  protected:
+    virtual uint64_t GetFileSize()
+    {
+      return buffer_.size();
+    }
+
+    virtual bool SendData(HttpOutput& output)
+    {
+      if (buffer_.size())
+        output.Send(&buffer_[0], buffer_.size());
+
+      return true;
+    }
+
+  public:
+    std::string& GetBuffer() 
+    {
+      return buffer_;
+    }
+
+    const std::string& GetBuffer() const
+    {
+      return buffer_;
+    }
+  };
+}
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -23,6 +35,7 @@
 #include "../OrthancException.h"
 
 #include <stdio.h>
+#include <glog/logging.h>
 
 
 namespace Orthanc
@@ -67,6 +80,7 @@
     }
     catch (OrthancException& e)
     {
+      LOG(WARNING) << "Unable to find HTTP resource: " << resourcePath;
       output.SendHeader(Orthanc_HttpStatus_404_NotFound);
     }
   } 
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/HttpServer/FilesystemHttpHandler.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -21,6 +33,7 @@
 #include "FilesystemHttpHandler.h"
 
 #include "../OrthancException.h"
+#include "FilesystemHttpSender.h"
 
 #include <boost/filesystem.hpp>
 
@@ -41,7 +54,7 @@
   {
     namespace fs = boost::filesystem;
 
-    output.SendOkHeader("text/html");
+    output.SendCustomOkHeader("Content-Type: text/html\r\n");
     output.SendString("<html>");
     output.SendString("  <body>");
     output.SendString("    <h1>Subdirectories</h1>");
@@ -136,7 +149,9 @@
 
     if (fs::exists(p) && fs::is_regular_file(p))
     {
-      output.AnswerFileAutodetectContentType(p.string());
+      FilesystemHttpSender(p).Send(output);
+
+      //output.AnswerFileAutodetectContentType(p.string());
     }
     else if (listDirectoryContent_ &&
              fs::exists(p) && 
--- a/Core/HttpServer/FilesystemHttpHandler.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,102 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "FilesystemHttpSender.h"
+
+#include "../Toolbox.h"
+
+#include <stdio.h>
+
+namespace Orthanc
+{
+  void FilesystemHttpSender::Setup()
+  {
+    //SetDownloadFilename(path_.filename().string());
+
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+    SetContentType(Toolbox::AutodetectMimeType(path_.filename().string()));
+#else
+    SetContentType(Toolbox::AutodetectMimeType(path_.filename()));
+#endif
+  }
+
+  uint64_t FilesystemHttpSender::GetFileSize()
+  {
+    return Toolbox::GetFileSize(path_.string());
+  }
+
+  bool FilesystemHttpSender::SendData(HttpOutput& output)
+  {
+    FILE* fp = fopen(path_.string().c_str(), "rb");
+    if (!fp)
+    {
+      return false;
+    }
+
+    std::vector<uint8_t> buffer(1024 * 1024);  // Chunks of 1MB
+
+    for (;;)
+    {
+      size_t nbytes = fread(&buffer[0], 1, buffer.size(), fp);
+      if (nbytes == 0)
+      {
+        break;
+      }
+      else
+      {
+        output.Send(&buffer[0], nbytes);
+      }
+    }
+
+    fclose(fp);
+
+    return true;
+  }
+
+  FilesystemHttpSender::FilesystemHttpSender(const char* path)
+  {
+    path_ = std::string(path);
+    Setup();
+  }
+
+  FilesystemHttpSender::FilesystemHttpSender(const boost::filesystem::path& path)
+  {
+    path_ = path;
+    Setup();
+  }
+
+  FilesystemHttpSender::FilesystemHttpSender(const FileStorage& storage,
+                                             const std::string& uuid)
+  {
+    path_ = storage.GetPath(uuid).string();
+    Setup();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/FilesystemHttpSender.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,59 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "HttpFileSender.h"
+#include "../FileStorage/FileStorage.h"
+
+namespace Orthanc
+{
+  class FilesystemHttpSender : public HttpFileSender
+  {
+  private:
+    boost::filesystem::path path_;
+
+    void Setup();
+
+  protected:
+    virtual uint64_t GetFileSize();
+
+    virtual bool SendData(HttpOutput& output);
+
+  public:
+    FilesystemHttpSender(const char* path);
+
+    FilesystemHttpSender(const boost::filesystem::path& path);
+
+    FilesystemHttpSender(const FileStorage& storage,
+                         const std::string& uuid);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/HttpFileSender.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,66 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "HttpFileSender.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  void HttpFileSender::SendHeader(HttpOutput& output)
+  {
+    std::string header;
+    header += "Content-Length: " + boost::lexical_cast<std::string>(GetFileSize()) + "\r\n";
+
+    if (contentType_.size() > 0)
+    {
+      header += "Content-Type: " + contentType_ + "\r\n";
+    }
+
+    if (downloadFilename_.size() > 0)
+    {
+      header += "Content-Disposition: attachment; filename=\"" + downloadFilename_ + "\"\r\n";
+    }
+  
+    output.SendCustomOkHeader(header);
+  }
+
+  void HttpFileSender::Send(HttpOutput& output)
+  {
+    SendHeader(output);
+
+    if (!SendData(output))
+    {
+      output.SendHeader(Orthanc_HttpStatus_500_InternalServerError);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/HttpFileSender.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,89 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "HttpOutput.h"
+
+namespace Orthanc
+{
+  class HttpFileSender
+  {
+  private:
+    std::string contentType_;
+    std::string downloadFilename_;
+
+    void SendHeader(HttpOutput& output);
+
+  protected:
+    virtual uint64_t GetFileSize() = 0;
+
+    virtual bool SendData(HttpOutput& output) = 0;
+
+  public:
+    virtual ~HttpFileSender()
+    {
+    }
+
+    void ResetContentType()
+    {
+      contentType_.clear();
+    }
+
+    void SetContentType(const std::string& contentType)
+    {
+      contentType_ = contentType;
+    }
+
+    const std::string& GetContentType() const
+    {
+      return contentType_;
+    }
+
+    void ResetDownloadFilename()
+    {
+      downloadFilename_.clear();
+    }
+
+    void SetDownloadFilename(const std::string& filename)
+    {
+      downloadFilename_ = filename;
+    }
+
+    const std::string& GetDownloadFilename() const
+    {
+      return downloadFilename_;
+    }
+
+    void Send(HttpOutput& output);
+  };
+}
--- a/Core/HttpServer/HttpHandler.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/HttpServer/HttpHandler.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -64,12 +76,12 @@
 
 
 
-  std::string HttpHandler::GetArgument(const Arguments& arguments,
+  std::string HttpHandler::GetArgument(const Arguments& getArguments,
                                        const std::string& name,
                                        const std::string& defaultValue)
   {
-    Arguments::const_iterator it = arguments.find(name);
-    if (it == arguments.end())
+    Arguments::const_iterator it = getArguments.find(name);
+    if (it == getArguments.end())
     {
       return defaultValue;
     }
--- a/Core/HttpServer/HttpHandler.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/HttpServer/HttpHandler.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -43,13 +55,13 @@
                         const std::string& method,
                         const UriComponents& uri,
                         const Arguments& headers,
-                        const Arguments& arguments,
+                        const Arguments& getArguments,
                         const std::string& postData) = 0;
 
     static void ParseGetQuery(HttpHandler::Arguments& result, 
                               const char* query);
 
-    static std::string GetArgument(const Arguments& arguments,
+    static std::string GetArgument(const Arguments& getArguments,
                                    const std::string& name,
                                    const std::string& defaultValue);
   };
--- a/Core/HttpServer/HttpOutput.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/HttpServer/HttpOutput.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -32,38 +44,19 @@
   void HttpOutput::SendString(const std::string& s)
   {
     if (s.size() > 0)
+    {
       Send(&s[0], s.size());
-  }
-
-  void HttpOutput::SendOkHeader(const std::string& contentType)
-  {
-    SendOkHeader(contentType.c_str(), false, 0);
-  }
-
-  void HttpOutput::SendOkHeader()
-  {
-    SendOkHeader(NULL, false, 0);
+    }
   }
 
-  void HttpOutput::SendOkHeader(uint64_t contentLength)
-  {
-    SendOkHeader(NULL, true, contentLength);
-  }
-
-  void HttpOutput::SendOkHeader(const std::string& contentType,
-                                uint64_t contentLength)
-  {
-    SendOkHeader(contentType.c_str(), true, contentLength);
-  }
-
-
   void HttpOutput::SendOkHeader(const char* contentType,
                                 bool hasContentLength,
-                                uint64_t contentLength)
+                                uint64_t contentLength,
+                                const char* contentFilename)
   {
     std::string s = "HTTP/1.1 200 OK\r\n";
 
-    if (contentType)
+    if (contentType && contentType[0] != '\0')
     {
       s += "Content-Type: " + std::string(contentType) + "\r\n";
     }
@@ -73,12 +66,24 @@
       s += "Content-Length: " + boost::lexical_cast<std::string>(contentLength) + "\r\n";
     }
 
+    if (contentFilename && contentFilename[0] != '\0')
+    {
+      s += "Content-Disposition: attachment; filename=\"" + std::string(contentFilename) + "\"\r\n";
+    }
+
     s += "\r\n";
 
     Send(&s[0], s.size());
   }
 
 
+  void HttpOutput::SendCustomOkHeader(const std::string& customHeader)
+  {
+    std::string s = "HTTP/1.1 200 OK\r\n" + customHeader + "\r\n";
+    Send(&s[0], s.size());
+  }
+
+
   void HttpOutput::SendMethodNotAllowedError(const std::string& allowed)
   {
     std::string s = 
@@ -114,7 +119,7 @@
   void HttpOutput::AnswerBufferWithContentType(const std::string& buffer,
                                                const std::string& contentType)
   {
-    SendOkHeader(contentType.c_str(), true, buffer.size());
+    SendOkHeader(contentType.c_str(), true, buffer.size(), NULL);
     SendString(buffer);
   }
 
@@ -123,60 +128,10 @@
                                                size_t size,
                                                const std::string& contentType)
   {
-    SendOkHeader(contentType.c_str(), true, size);
+    SendOkHeader(contentType.c_str(), true, size, NULL);
     Send(buffer, size);
   }
 
-
-  void HttpOutput::AnswerFileWithContentType(const std::string& path,
-                                             const std::string& contentType)
-  {
-    uint64_t fileSize = Toolbox::GetFileSize(path);
-  
-    FILE* fp = fopen(path.c_str(), "rb");
-    if (!fp)
-    {
-      SendHeaderInternal(Orthanc_HttpStatus_500_InternalServerError);
-      return;
-    }
-  
-    SendOkHeader(contentType.c_str(), true, fileSize);
-
-    std::vector<uint8_t> buffer(1024 * 1024);  // Chunks of 1MB
-
-    for (;;)
-    {
-      size_t nbytes = fread(&buffer[0], 1, buffer.size(), fp);
-      if (nbytes == 0)
-      {
-        break;
-      }
-      else
-      {
-        Send(&buffer[0], nbytes);
-      }
-    }
-
-    fclose(fp);
-  }
-
-
-  void HttpOutput::AnswerFileAutodetectContentType(const std::string& path)
-  {
-    AnswerFileWithContentType(path, Toolbox::AutodetectMimeType(path));
-  }
-
-
-  void HttpOutput::AnswerFile(const FileStorage& storage,
-                              const std::string& uuid,
-                              const std::string& contentType)
-  {
-    boost::filesystem::path p(storage.GetPath(uuid));
-    AnswerFileWithContentType(p.string(), contentType);
-  }
-
-
-
   void HttpOutput::Redirect(const std::string& path)
   {
     std::string s = 
--- a/Core/HttpServer/HttpOutput.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/HttpServer/HttpOutput.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -23,7 +35,6 @@
 #include <string>
 #include <stdint.h>
 #include "../Enumerations.h"
-#include "../FileStorage.h"
 
 namespace Orthanc
 {
@@ -32,10 +43,6 @@
   private:
     void SendHeaderInternal(Orthanc_HttpStatus status);
 
-    void SendOkHeader(const char* contentType,
-                      bool hasContentLength,
-                      uint64_t contentLength);
-
   public:
     virtual ~HttpOutput()
     {
@@ -43,28 +50,22 @@
 
     virtual void Send(const void* buffer, size_t length) = 0;
 
-    void SendString(const std::string& s);
-
-    void SendOkHeader();
+    void SendOkHeader(const char* contentType,
+                      bool hasContentLength,
+                      uint64_t contentLength,
+                      const char* contentFilename);
 
-    void SendOkHeader(uint64_t contentLength);
+    void SendCustomOkHeader(const std::string& customHeader);
 
-    void SendOkHeader(const std::string& contentType);
-
-    void SendOkHeader(const std::string& contentType,
-                      uint64_t contentLength);
+    void SendString(const std::string& s);
 
     void SendMethodNotAllowedError(const std::string& allowed);
 
     void SendHeader(Orthanc_HttpStatus status);
 
-
-    // Higher-level constructs to send entire files or buffers -------------------
+    void Redirect(const std::string& path);
 
-    void AnswerBuffer(const std::string& buffer)
-    {
-      AnswerBufferWithContentType(buffer, "");
-    }
+    // Higher-level constructs to send entire buffers ----------------------------
 
     void AnswerBufferWithContentType(const std::string& buffer,
                                      const std::string& contentType);
@@ -72,27 +73,5 @@
     void AnswerBufferWithContentType(const void* buffer,
                                      size_t size,
                                      const std::string& contentType);
-
-    void AnswerFile(const std::string& path)
-    {
-      AnswerFileWithContentType(path, "");
-    }
-
-    void AnswerFileWithContentType(const std::string& path,
-                                   const std::string& contentType);
-
-    void AnswerFileAutodetectContentType(const std::string& path); 
-
-    void AnswerFile(const FileStorage& storage,
-                    const std::string& uuid)
-    {
-      AnswerFile(storage, uuid, "");
-    }
-
-    void AnswerFile(const FileStorage& storage,
-                    const std::string& uuid,
-                    const std::string& contentType);
-
-    void Redirect(const std::string& path);
   };
 }
--- a/Core/HttpServer/MongooseServer.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/HttpServer/MongooseServer.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -63,7 +75,10 @@
 
       virtual void Send(const void* buffer, size_t length)
       {
-        mg_write(connection_, buffer, length);
+        if (length > 0)
+        {
+          mg_write(connection_, buffer, length);
+        }
       }
     };
 
@@ -477,7 +492,8 @@
       {
         HttpHandler::ParseGetQuery(arguments, request->query_string);
       }
-      else if (!strcmp(request->request_method, "POST"))
+      else if (!strcmp(request->request_method, "POST") ||
+               !strcmp(request->request_method, "PUT"))
       {
         HttpHandler::Arguments::const_iterator ct = headers.find("content-type");
         if (ct == headers.end())
@@ -510,7 +526,7 @@
           return (void*) "";
 
         case PostDataStatus_Pending:
-          output.AnswerBuffer("");
+          output.AnswerBufferWithContentType(NULL, 0, "");
           return (void*) "";
 
         default:
@@ -573,7 +589,7 @@
   }
 
 
-  void MongooseServer::SetPort(uint16_t port)
+  void MongooseServer::SetPortNumber(uint16_t port)
   {
     Stop();
     port_ = port;
--- a/Core/HttpServer/MongooseServer.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/HttpServer/MongooseServer.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -24,6 +36,7 @@
 
 #include <list>
 #include <map>
+#include <set>
 #include <stdint.h>
 #include <boost/shared_ptr.hpp>
 
@@ -57,9 +70,9 @@
 
     ~MongooseServer();
 
-    void SetPort(uint16_t port);
+    void SetPortNumber(uint16_t port);
 
-    uint16_t GetPort() const
+    uint16_t GetPortNumber() const
     {
       return port_;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/IDynamicObject.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  /**
+   * This class should be the ancestor to any class whose type is
+   * determined at the runtime, and that can be dynamically allocated.
+   * Being a child of IDynamicObject only implies the existence of a
+   * virtual destructor.
+   **/
+  class IDynamicObject : public boost::noncopyable
+  {
+  public:
+    virtual ~IDynamicObject()
+    {
+    }
+  };
+}
--- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/MultiThreading/BagOfRunnablesBySteps.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/MultiThreading/BagOfRunnablesBySteps.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/MultiThreading/IRunnableBySteps.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/MultiThreading/IRunnableBySteps.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/OrthancException.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/OrthancException.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -39,39 +51,57 @@
   {
     switch (error)
     {
-    case ErrorCode_Success:
-      return "Success";
+      case ErrorCode_Success:
+        return "Success";
+
+      case ErrorCode_ParameterOutOfRange:
+        return "Parameter out of range";
 
-    case ErrorCode_ParameterOutOfRange:
-      return "Parameter out of range";
+      case ErrorCode_NotImplemented:
+        return "Not implemented yet";
+
+      case ErrorCode_InternalError:
+        return "Internal error";
 
-    case ErrorCode_NotImplemented:
-      return "Not implemented yet";
+      case ErrorCode_NotEnoughMemory:
+        return "Not enough memory";
+
+      case ErrorCode_UriSyntax:
+        return "Badly formatted URI";
 
-    case ErrorCode_InternalError:
-      return "Internal error";
+      case ErrorCode_BadParameterType:
+        return "Bad type for a parameter";
 
-    case ErrorCode_NotEnoughMemory:
-      return "Not enough memory";
+      case ErrorCode_InexistentFile:
+        return "Inexistent file";
 
-    case ErrorCode_UriSyntax:
-      return "Badly formatted URI";
+      case ErrorCode_BadFileFormat:
+        return "Bad file format";
+
+      case ErrorCode_CannotWriteFile:
+        return "Cannot write to file";
 
-    case ErrorCode_BadParameterType:
-      return "Bad type for a parameter";
+      case ErrorCode_Timeout:
+        return "Timeout";
 
-    case ErrorCode_InexistentFile:
-      return "Inexistent file";
+      case ErrorCode_UnknownResource:
+        return "Unknown resource";
 
-    case ErrorCode_BadFileFormat:
-      return "Bad file format";
+      case ErrorCode_BadSequenceOfCalls:
+        return "Bad sequence of calls";
+
+      case ErrorCode_IncompatibleDatabaseVersion:
+        return "Incompatible version of the database";
 
-    case ErrorCode_CannotWriteFile:
-      return "Cannot write to file";
+      case ErrorCode_FullStorage:
+        return "The file storage is full";
 
-    case ErrorCode_Custom:
-    default:
-      return "???";
+      case ErrorCode_InexistentItem:
+        return "Accessing an inexistent item";
+
+      case ErrorCode_Custom:
+      default:
+        return "???";
     }
   }
 }
--- a/Core/OrthancException.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/OrthancException.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/PngWriter.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/PngWriter.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/PngWriter.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/PngWriter.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApi.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,288 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "RestApi.h"
+
+#include <stdlib.h>   // To define "_exit()" under Windows
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  bool RestApi::IsGetAccepted(const UriComponents& uri)
+  {
+    for (GetHandlers::const_iterator it = getHandlers_.begin();
+         it != getHandlers_.end(); it++)
+    {
+      if (it->first->Match(uri))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool RestApi::IsPutAccepted(const UriComponents& uri)
+  {
+    for (PutHandlers::const_iterator it = putHandlers_.begin();
+         it != putHandlers_.end(); it++)
+    {
+      if (it->first->Match(uri))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool RestApi::IsPostAccepted(const UriComponents& uri)
+  {
+    for (PostHandlers::const_iterator it = postHandlers_.begin();
+         it != postHandlers_.end(); it++)
+    {
+      if (it->first->Match(uri))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool RestApi::IsDeleteAccepted(const UriComponents& uri)
+  {
+    for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
+         it != deleteHandlers_.end(); it++)
+    {
+      if (it->first->Match(uri))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  static void AddMethod(std::string& target,
+                        const std::string& method)
+  {
+    if (target.size() > 0)
+      target += "," + method;
+    else
+      target = method;
+  }
+
+  std::string  RestApi::GetAcceptedMethods(const UriComponents& uri)
+  {
+    std::string s;
+
+    if (IsGetAccepted(uri))
+      AddMethod(s, "GET");
+
+    if (IsPutAccepted(uri))
+      AddMethod(s, "PUT");
+
+    if (IsPostAccepted(uri))
+      AddMethod(s, "POST");
+
+    if (IsDeleteAccepted(uri))
+      AddMethod(s, "DELETE");
+
+    return s;
+  }
+
+  RestApi::~RestApi()
+  {
+    for (GetHandlers::iterator it = getHandlers_.begin(); 
+         it != getHandlers_.end(); it++)
+    {
+      delete it->first;
+    } 
+
+    for (PutHandlers::iterator it = putHandlers_.begin(); 
+         it != putHandlers_.end(); it++)
+    {
+      delete it->first;
+    } 
+
+    for (PostHandlers::iterator it = postHandlers_.begin(); 
+         it != postHandlers_.end(); it++)
+    {
+      delete it->first;
+    } 
+
+    for (DeleteHandlers::iterator it = deleteHandlers_.begin(); 
+         it != deleteHandlers_.end(); it++)
+    {
+      delete it->first;
+    } 
+  }
+
+  bool RestApi::IsServedUri(const UriComponents& uri)
+  {
+    return (IsGetAccepted(uri) ||
+            IsPutAccepted(uri) ||
+            IsPostAccepted(uri) ||
+            IsDeleteAccepted(uri));
+  }
+
+  void RestApi::Handle(HttpOutput& output,
+                       const std::string& method,
+                       const UriComponents& uri,
+                       const Arguments& headers,
+                       const Arguments& getArguments,
+                       const std::string& postData)
+  {
+    bool ok = false;
+    RestApiOutput restOutput(output);
+    RestApiPath::Components components;
+    UriComponents trailing;
+
+    if (method == "GET")
+    {
+      for (GetHandlers::const_iterator it = getHandlers_.begin();
+           it != getHandlers_.end(); it++)
+      {
+        if (it->first->Match(components, trailing, uri))
+        {
+          LOG(INFO) << "REST GET call on: " << Toolbox::FlattenUri(uri);
+          ok = true;
+          GetCall call;
+          call.output_ = &restOutput;
+          call.context_ = this;
+          call.httpHeaders_ = &headers;
+          call.uriComponents_ = &components;
+          call.trailing_ = &trailing;
+          call.fullUri_ = &uri;
+          
+          call.getArguments_ = &getArguments;
+          it->second(call);
+        }
+      }
+    }
+    else if (method == "PUT")
+    {
+      for (PutHandlers::const_iterator it = putHandlers_.begin();
+           it != putHandlers_.end(); it++)
+      {
+        if (it->first->Match(components, trailing, uri))
+        {
+          LOG(INFO) << "REST PUT call on: " << Toolbox::FlattenUri(uri);
+          ok = true;
+          PutCall call;
+          call.output_ = &restOutput;
+          call.context_ = this;
+          call.httpHeaders_ = &headers;
+          call.uriComponents_ = &components;
+          call.trailing_ = &trailing;
+          call.fullUri_ = &uri;
+           
+          call.data_ = &postData;
+          it->second(call);
+        }
+      }
+    }
+    else if (method == "POST")
+    {
+      for (PostHandlers::const_iterator it = postHandlers_.begin();
+           it != postHandlers_.end(); it++)
+      {
+        if (it->first->Match(components, trailing, uri))
+        {
+          LOG(INFO) << "REST POST call on: " << Toolbox::FlattenUri(uri);
+          ok = true;
+          PostCall call;
+          call.output_ = &restOutput;
+          call.context_ = this;
+          call.httpHeaders_ = &headers;
+          call.uriComponents_ = &components;
+          call.trailing_ = &trailing;
+          call.fullUri_ = &uri;
+           
+          call.data_ = &postData;
+          it->second(call);
+        }
+      }
+    }
+    else if (method == "DELETE")
+    {
+      for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
+           it != deleteHandlers_.end(); it++)
+      {
+        if (it->first->Match(components, trailing, uri))
+        {
+          LOG(INFO) << "REST DELETE call on: " << Toolbox::FlattenUri(uri);
+          ok = true;
+          DeleteCall call;
+          call.output_ = &restOutput;
+          call.context_ = this;
+          call.httpHeaders_ = &headers;
+          call.uriComponents_ = &components;
+          call.trailing_ = &trailing;
+          call.fullUri_ = &uri;
+          it->second(call);
+        }
+      }
+    }
+
+    if (!ok)
+    {
+      LOG(INFO) << "REST method " << method << " not allowed on: " << Toolbox::FlattenUri(uri);
+      output.SendMethodNotAllowedError(GetAcceptedMethods(uri));
+    }
+  }
+
+  void RestApi::Register(const std::string& path,
+                         GetHandler handler)
+  {
+    getHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+  }
+
+  void RestApi::Register(const std::string& path,
+                         PutHandler handler)
+  {
+    putHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+  }
+
+  void RestApi::Register(const std::string& path,
+                         PostHandler handler)
+  {
+    postHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+  }
+
+  void RestApi::Register(const std::string& path,
+                         DeleteHandler handler)
+  {
+    deleteHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApi.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,200 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "../HttpServer/HttpHandler.h"
+#include "RestApiPath.h"
+#include "RestApiOutput.h"
+
+#include <list>
+
+namespace Orthanc
+{
+  class RestApi : public HttpHandler
+  {
+  private:
+    class SharedCall
+    {
+      friend class RestApi;
+
+    private:
+      RestApiOutput* output_;
+      RestApi* context_;
+      const HttpHandler::Arguments* httpHeaders_;
+      const RestApiPath::Components* uriComponents_;
+      const UriComponents* trailing_;
+      const UriComponents* fullUri_;
+
+    public:
+      RestApiOutput& GetOutput()
+      {
+        return *output_;
+      }
+
+      RestApi& GetContext()
+      {
+        return *context_;
+      }
+    
+      const UriComponents& GetFullUri() const
+      {
+        return *fullUri_;
+      }
+    
+      const UriComponents& GetTrailingUri() const
+      {
+        return *trailing_;
+      }
+
+      std::string GetUriComponent(const std::string& name,
+                                  const std::string& defaultValue) const
+      {
+        return HttpHandler::GetArgument(*uriComponents_, name, defaultValue);
+      }
+
+      std::string GetHttpHeader(const std::string& name,
+                                const std::string& defaultValue) const
+      {
+        return HttpHandler::GetArgument(*httpHeaders_, name, defaultValue);
+      }
+    };
+
+ 
+  public:
+    class GetCall : public SharedCall
+    {
+      friend class RestApi;
+
+    private:
+      const HttpHandler::Arguments* getArguments_;
+
+    public:
+      std::string GetArgument(const std::string& name,
+                              const std::string& defaultValue) const
+      {
+        return HttpHandler::GetArgument(*getArguments_, name, defaultValue);
+      }
+
+      bool HasArgument(const std::string& name) const
+      {
+        return getArguments_->find(name) != getArguments_->end();
+      }
+    };
+
+    class PutCall : public SharedCall
+    {
+      friend class RestApi;
+
+    private:
+      const std::string* data_;
+
+    public:
+      const std::string& GetPutBody()
+      {
+        return *data_;
+      }
+    };
+
+    class PostCall : public SharedCall
+    {
+      friend class RestApi;
+
+    private:
+      const std::string* data_;
+
+    public:
+      const std::string& GetPostBody()
+      {
+        return *data_;
+      }
+    };
+
+    class DeleteCall : public SharedCall
+    {
+    };
+
+    typedef void (*GetHandler) (GetCall& call);
+    
+    typedef void (*DeleteHandler) (DeleteCall& call);
+    
+    typedef void (*PutHandler) (PutCall& call);
+    
+    typedef void (*PostHandler) (PostCall& call);
+    
+  private:
+    typedef std::list< std::pair<RestApiPath*, GetHandler> > GetHandlers;
+    typedef std::list< std::pair<RestApiPath*, PutHandler> > PutHandlers;
+    typedef std::list< std::pair<RestApiPath*, PostHandler> > PostHandlers;
+    typedef std::list< std::pair<RestApiPath*, DeleteHandler> > DeleteHandlers;
+
+    GetHandlers  getHandlers_;
+    PutHandlers  putHandlers_;
+    PostHandlers  postHandlers_;
+    DeleteHandlers  deleteHandlers_;
+
+    bool IsGetAccepted(const UriComponents& uri);
+    bool IsPutAccepted(const UriComponents& uri);
+    bool IsPostAccepted(const UriComponents& uri);
+    bool IsDeleteAccepted(const UriComponents& uri);
+
+    std::string  GetAcceptedMethods(const UriComponents& uri);
+
+  public:
+    RestApi()
+    {
+    }
+
+    ~RestApi();
+
+    virtual bool IsServedUri(const UriComponents& uri);
+
+    virtual void Handle(HttpOutput& output,
+                        const std::string& method,
+                        const UriComponents& uri,
+                        const Arguments& headers,
+                        const Arguments& getArguments,
+                        const std::string& postData);
+
+    void Register(const std::string& path,
+                  GetHandler handler);
+
+    void Register(const std::string& path,
+                  PutHandler handler);
+
+    void Register(const std::string& path,
+                  PostHandler handler);
+
+    void Register(const std::string& path,
+                  DeleteHandler handler);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiOutput.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,103 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "RestApiOutput.h"
+
+#include "../OrthancException.h"
+
+namespace Orthanc
+{
+  RestApiOutput::RestApiOutput(HttpOutput& output) : 
+    output_(output)
+  {
+    alreadySent_ = false;
+  }
+
+  RestApiOutput::~RestApiOutput()
+  {
+    if (!alreadySent_)
+    {
+      output_.SendHeader(Orthanc_HttpStatus_400_BadRequest);
+    }
+  }
+  
+  void RestApiOutput::CheckStatus()
+  {
+    if (alreadySent_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  void RestApiOutput::AnswerFile(HttpFileSender& sender)
+  {
+    CheckStatus();
+    sender.Send(output_);
+    alreadySent_ = true;
+  }
+
+  void RestApiOutput::AnswerJson(const Json::Value& value)
+  {
+    CheckStatus();
+    Json::StyledWriter writer;
+    std::string s = writer.write(value);
+    output_.AnswerBufferWithContentType(s, "application/json");
+    alreadySent_ = true;
+  }
+
+  void RestApiOutput::AnswerBuffer(const std::string& buffer,
+                                   const std::string& contentType)
+  {
+    CheckStatus();
+    output_.AnswerBufferWithContentType(buffer, contentType);
+    alreadySent_ = true;
+  }
+
+  void RestApiOutput::Redirect(const std::string& path)
+  {
+    CheckStatus();
+    output_.Redirect(path);
+    alreadySent_ = true;
+  }
+
+  void RestApiOutput::SignalError(Orthanc_HttpStatus status)
+  {
+    if (status != Orthanc_HttpStatus_415_UnsupportedMediaType)
+    {
+      throw OrthancException("This HTTP status is not allowed in a REST API");
+    }
+
+    CheckStatus();
+    output_.SendHeader(status);
+    alreadySent_ = true;    
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiOutput.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "../HttpServer/HttpOutput.h"
+#include "../HttpServer/HttpFileSender.h"
+
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class RestApiOutput
+  {
+  private:
+    HttpOutput& output_;
+    bool alreadySent_;
+
+    void CheckStatus();
+
+  public:
+    RestApiOutput(HttpOutput& output);
+
+    ~RestApiOutput();
+
+    HttpOutput& GetLowLevelOutput()
+    {
+      return output_;
+    }
+
+    void MarkLowLevelOutputDone()
+    {
+      alreadySent_ = true;
+    }
+
+    void AnswerFile(HttpFileSender& sender);
+
+    void AnswerJson(const Json::Value& value);
+
+    void AnswerBuffer(const std::string& buffer,
+                      const std::string& contentType);
+
+    void SignalError(Orthanc_HttpStatus status);
+
+    void Redirect(const std::string& path);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiPath.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,137 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "RestApiPath.h"
+
+#include <cassert>
+
+namespace Orthanc
+{
+  RestApiPath::RestApiPath(const std::string& uri)
+  {
+    Toolbox::SplitUriComponents(uri_, uri);
+
+    if (uri_.size() == 0)
+    {
+      hasTrailing_ = false;
+      return;
+    }
+
+    if (uri_.back() == "*")
+    {
+      hasTrailing_ = true;
+      uri_.pop_back();
+    }
+    else
+    {
+      hasTrailing_ = false;
+    }
+
+    components_.resize(uri_.size());
+    for (size_t i = 0; i < uri_.size(); i++)
+    {
+      size_t s = uri_[i].size();
+      assert(s > 0);
+
+      if (uri_[i][0] == '{' && 
+          uri_[i][s - 1] == '}')
+      {
+        components_[i] = uri_[i].substr(1, s - 2);
+        uri_[i] = "";
+      }
+      else
+      {
+        components_[i] = "";
+      }
+    }
+  }
+
+  bool RestApiPath::Match(Components& components,
+                          UriComponents& trailing,
+                          const std::string& uriRaw) const
+  {
+    UriComponents uri;
+    Toolbox::SplitUriComponents(uri, uriRaw);
+    return Match(components, trailing, uri);
+  }
+
+  bool RestApiPath::Match(Components& components,
+                          UriComponents& trailing,
+                          const UriComponents& uri) const
+  {
+    if (uri.size() < uri_.size())
+    {
+      return false;
+    }
+
+    if (!hasTrailing_ && uri.size() > uri_.size())
+    {
+      return false;
+    }
+
+    components.clear();
+    trailing.clear();
+
+    assert(uri_.size() <= uri.size());
+    for (size_t i = 0; i < uri_.size(); i++)
+    {
+      if (components_[i].size() == 0)
+      {
+        // This URI component is not a free parameter
+        if (uri_[i] != uri[i])
+        {
+          return false;
+        }
+      }
+      else
+      {
+        // This URI component is a free parameter
+        components[components_[i]] = uri[i];
+      }
+    }
+
+    if (hasTrailing_)
+    {
+      trailing.assign(uri.begin() + uri_.size(), uri.end());
+    }
+
+    return true;
+  }
+
+
+  bool RestApiPath::Match(const UriComponents& uri) const
+  {
+    Components components;
+    UriComponents trailing;
+    return Match(components, trailing, uri);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiPath.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,63 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "../Toolbox.h"
+#include <map>
+
+namespace Orthanc
+{
+  class RestApiPath
+  {
+  private:
+    UriComponents uri_;
+    bool hasTrailing_;
+    std::vector<std::string> components_;
+
+  public:
+    typedef std::map<std::string, std::string> Components;
+
+    RestApiPath(const std::string& uri);
+
+    // This version is slower
+    bool Match(Components& components,
+               UriComponents& trailing,
+               const std::string& uriRaw) const;
+
+    bool Match(Components& components,
+               UriComponents& trailing,
+               const UriComponents& uri) const;
+
+    bool Match(const UriComponents& uri) const;
+  };
+}
--- a/Core/SQLite/Connection.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/SQLite/Connection.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -41,6 +41,7 @@
 #include <sqlite3.h>
 #include <string.h>
 
+#include <glog/logging.h>
 
 
 namespace Orthanc
@@ -88,6 +89,8 @@
       // http://www.sqlite.org/pragma.html
       Execute("PRAGMA FOREIGN_KEYS=ON;");
 
+      Execute("PRAGMA RECURSIVE_TRIGGERS=ON;");
+
       // Performance tuning
       Execute("PRAGMA SYNCHRONOUS=NORMAL;");
       Execute("PRAGMA JOURNAL_MODE=WAL;");
@@ -149,6 +152,7 @@
 
     bool Connection::Execute(const char* sql) 
     {
+      VLOG(1) << "SQLite::Connection::Execute " << sql;
       CheckIsOpen();
 
       int error = sqlite3_exec(db_, sql, NULL, NULL, NULL);
@@ -368,5 +372,16 @@
       return func;
     }
 
+
+    void Connection::FlushToDisk()
+    {
+      VLOG(1) << "SQLite::Connection::FlushToDisk";
+      int err = sqlite3_wal_checkpoint(db_, NULL);
+
+      if (err != SQLITE_OK)
+      {
+        throw OrthancException("SQLite: Unable to flush the database");
+      }
+    }
   }
 }
--- a/Core/SQLite/Connection.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/SQLite/Connection.h	Fri Dec 14 11:24:24 2012 +0100
@@ -111,6 +111,8 @@
         return Execute(sql.c_str());
       }
 
+      void FlushToDisk();
+
       IScalarFunction* Register(IScalarFunction* func);  // Takes the ownership of the function
 
       // Info querying -------------------------------------------------------------
@@ -169,7 +171,7 @@
 
       bool BeginTransaction();
       void RollbackTransaction();
-      bool CommitTransaction();
+      bool CommitTransaction();      
     };
   }
 }
--- a/Core/SQLite/FunctionContext.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/SQLite/FunctionContext.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -72,6 +72,12 @@
       return sqlite3_value_int(argv_[index]);
     }
 
+    int64_t FunctionContext::GetInt64Value(unsigned int index) const
+    {
+      CheckIndex(index);
+      return sqlite3_value_int64(argv_[index]);
+    }
+
     double FunctionContext::GetDoubleValue(unsigned int index) const
     {
       CheckIndex(index);
--- a/Core/SQLite/FunctionContext.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/SQLite/FunctionContext.h	Fri Dec 14 11:24:24 2012 +0100
@@ -69,6 +69,8 @@
 
       int GetIntValue(unsigned int index) const;
 
+      int64_t GetInt64Value(unsigned int index) const;
+
       double GetDoubleValue(unsigned int index) const;
 
       std::string GetStringValue(unsigned int index) const;
--- a/Core/SQLite/Statement.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/SQLite/Statement.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -42,6 +42,7 @@
 #include <boost/lexical_cast.hpp>
 #include <sqlite3.h>
 #include <string.h>
+#include <glog/logging.h>
 
 namespace Orthanc
 {
@@ -106,11 +107,13 @@
 
     bool Statement::Run()
     {
+      VLOG(1) << "SQLite::Statement::Run " << sqlite3_sql(GetStatement());
       return CheckError(sqlite3_step(GetStatement())) == SQLITE_DONE;
     }
 
     bool Statement::Step()
     {
+      VLOG(1) << "SQLite::Statement::Step " << sqlite3_sql(GetStatement());
       return CheckError(sqlite3_step(GetStatement())) == SQLITE_ROW;
     }
 
@@ -121,6 +124,7 @@
       // spurious error callback.
       if (clear_bound_vars)
         sqlite3_clear_bindings(GetStatement());
+      //VLOG(1) << "SQLite::Statement::Reset";
       sqlite3_reset(GetStatement());
     }
 
--- a/Core/SQLite/Statement.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/SQLite/Statement.h	Fri Dec 14 11:24:24 2012 +0100
@@ -44,6 +44,10 @@
 #include <stdint.h>
 #include <boost/noncopyable.hpp>
 
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+#include <gtest/gtest_prod.h>
+#endif
+
 struct sqlite3_stmt;
 
 
@@ -68,6 +72,11 @@
     {
       friend class Connection;
 
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+      FRIEND_TEST(SQLStatementTest, Run);
+      FRIEND_TEST(SQLStatementTest, Reset);
+#endif
+
     private:
       StatementReference  reference_;
 
@@ -80,6 +89,10 @@
         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);
@@ -95,14 +108,15 @@
                 const StatementId& id,
                 const char* sql);
 
+      ~Statement()
+      {
+        Reset();
+      }
+
       bool Run();
 
       bool Step();
 
-      // 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);
-
       // Diagnostics --------------------------------------------------------------
 
       std::string GetOriginalSQLStatement();
--- a/Core/Toolbox.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/Toolbox.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -25,6 +37,7 @@
 #include <string.h>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
 #include <algorithm>
 #include <ctype.h>
 
@@ -51,6 +64,7 @@
 
 #include "../Resources/md5/md5.h"
 #include "../Resources/base64/base64.h"
+#include "../Resources/sha1/sha1.h"
 
 
 #if BOOST_HAS_LOCALE == 0
@@ -274,6 +288,15 @@
     {
       components.push_back(std::string(&uri[start], end - start));
     }
+
+    for (size_t i = 0; i < components.size(); i++)
+    {
+      if (components[i].size() == 0)
+      {
+        // Empty component, as in: "/coucou//e"
+        throw OrthancException(ErrorCode_UriSyntax);
+      }
+    }
   }
 
 
@@ -506,4 +529,67 @@
 
     return result;
   }
+
+  void Toolbox::ComputeSHA1(std::string& result,
+                            const std::string& data)
+  {
+    SHA1 sha1;
+    if (data.size() > 0)
+    {
+      sha1.Input(&data[0], data.size());
+    }
+
+    unsigned digest[5];
+
+    // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide
+    assert(sizeof(unsigned) == 4 && sizeof(digest) == (160 / 8)); 
+    
+    if (sha1.Result(digest))
+    {
+      result.resize(8 * 5 + 4);
+      sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x",
+              digest[0],
+              digest[1],
+              digest[2],
+              digest[3],
+              digest[4]);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+  std::string Toolbox::GetNowIsoString()
+  {
+    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+    return boost::posix_time::to_iso_string(now);
+  }
+
+  std::string Toolbox::StripSpaces(const std::string& source)
+  {
+    size_t first = 0;
+
+    while (first < source.length() &&
+           isspace(source[first]))
+    {
+      first++;
+    }
+
+    if (first == source.length())
+    {
+      // String containing only spaces
+      return "";
+    }
+
+    size_t last = source.length();
+    while (last > first &&
+           isspace(source[last - 1]))
+    {
+      last--;
+    }          
+    
+    assert(first <= last);
+    return source.substr(first, last - first);
+  }
 }
--- a/Core/Toolbox.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/Toolbox.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -28,6 +40,10 @@
 {
   typedef std::vector<std::string> UriComponents;
 
+  class NullType
+  {
+  };
+
   namespace Toolbox
   {
     void ServerBarrier();
@@ -61,6 +77,9 @@
     void ComputeMD5(std::string& result,
                     const std::string& data);
 
+    void ComputeSHA1(std::string& result,
+                     const std::string& data);
+
     std::string EncodeBase64(const std::string& data);
 
     std::string GetPathToExecutable();
@@ -71,5 +90,9 @@
                               const char* fromEncoding);
 
     std::string ConvertToAscii(const std::string& source);
+
+    std::string StripSpaces(const std::string& source);
+
+    std::string GetNowIsoString();
   }
 }
--- a/Core/Uuid.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/Uuid.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/Core/Uuid.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Core/Uuid.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/INSTALL	Thu Oct 04 15:09:56 2012 +0200
+++ b/INSTALL	Fri Dec 14 11:24:24 2012 +0100
@@ -67,7 +67,7 @@
 # cmake -DSTATIC_BUILD=OFF -DCMAKE_BUILD_TYPE=DEBUG ~/Orthanc
 # make
 
- 
+
 
 Cross-Compilation for Windows under Linux
 -----------------------------------------
@@ -104,3 +104,17 @@
   Visual Studio 2005:
   http://en.wikipedia.org/wiki/Microsoft_Windows_SDK.
   Read the CMake FAQ: http://goo.gl/By90B 
+
+
+
+Debian/Ubuntu specific
+----------------------
+
+When dynamically linking against the system libraries, you have to
+manually add the "wrap" and "oflog" libraries at the configuration
+time (because of a packaging error in "libdcmtk"):
+
+# cd ~/OrthancBuild
+# cmake "-DDCMTK_LIBRARIES=wrap;oflog" -DSTATIC_BUILD=OFF -DCMAKE_BUILD_TYPE=DEBUG ~/Orthanc
+# make
+
--- a/NEWS	Thu Oct 04 15:09:56 2012 +0200
+++ b/NEWS	Fri Dec 14 11:24:24 2012 +0100
@@ -1,6 +1,54 @@
 Pending changes in the mainline
 ===============================
 
+* Recycling of disk space
+* Protection of patients against recycling (also in Orthanc Explorer)
+* Raw access to the value of the DICOM tags in the REST API
+
+
+Version 0.3.1 (2012/12/05)
+==========================
+
+* Download archives of patients, studies and series as ZIP files
+* Orthanc now checks the version of its database schema before starting
+
+
+Version 0.3.0 (2012/11/30)
+==========================
+
+Major changes
+-------------
+
+* Transparent compression of the DICOM instances on the disk
+* The patient/study/series/instances are now indexed by SHA-1 digests
+  of their DICOM Instance IDs (and not by UUIDs anymore): The same
+  DICOM objects are thus always identified by the same Orthanc IDs
+* Log of exported instances through DICOM C-Store SCU ("/exported" URI)
+* Full refactoring of the DB schema and of the REST API
+* Introduction of generic classes for REST APIs (in Core/RestApi)
+
+Minor changes
+-------------
+
+* "/statistics" URI
+* "last" flag to retrieve the last change from the "/changes" URI
+* Generate a sample configuration file from command line
+* "CompletedSeries" event in the changes API
+* Thread to continuously flush DB to disk (SQLite checkpoints for
+  improved robustness)
+
+
+Version 0.2.3 (2012/10/26)
+==========================
+
+* Use HTTP Content-Disposition to set a filename when downloading JSON/DCM
+* URI "/system" for general information about Orthanc
+* Versioning info and help on the command line
+* Improved logging
+* Possibility of dynamic linking against jsoncpp, sqlite, boost and dmctk
+  for Debian packaging
+* Fix some bugs
+* Switch to default 8042 port for HTTP
 
 
 Version 0.2.2 (2012/10/04)
@@ -10,7 +58,6 @@
 * Fixes to Debian packaging
 
 
-
 Version 0.2.1 (2012/09/28)
 ==========================
 
@@ -19,7 +66,6 @@
 * Ready for Debian packaging
 
 
-
 Version 0.2.0 (2012/09/16)
 ==========================
 
@@ -41,7 +87,6 @@
 * Standalone build for cross-compilation
 
 
-
 Version 0.1.1 (2012/07/20)
 ==========================
 
@@ -50,7 +95,6 @@
 * Add path to storage in Configuration.json
 
 
-
 Version 0.1.0 (2012/07/19)
 ==========================
 
--- a/OrthancCppClient/HttpClient.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancCppClient/HttpClient.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -197,4 +197,12 @@
       return false;
     }
   }
+
+
+  void HttpClient::SetPassword(const char* username,
+			       const char* password)
+  {
+    std::string s = std::string(username) + ":" + std::string(password);
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, s.c_str()));
+  }
 }
--- a/OrthancCppClient/HttpClient.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancCppClient/HttpClient.h	Fri Dec 14 11:24:24 2012 +0100
@@ -109,5 +109,7 @@
       return HttpException::GetDescription(lastStatus_);
     }
 
+    void SetPassword(const char* username,
+                     const char* password);
   };
 }
--- a/OrthancExplorer/explorer.css	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancExplorer/explorer.css	Fri Dec 14 11:24:24 2012 +0100
@@ -32,3 +32,12 @@
     width: 0%; 
     background-color: green; 
 }
+
+.ui-title a {
+    text-decoration: none;
+    color: white !important;
+}
+
+.switch-container .ui-slider-switch {
+    width: 100%;
+}
\ No newline at end of file
--- a/OrthancExplorer/explorer.html	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancExplorer/explorer.html	Fri Dec 14 11:24:24 2012 +0100
@@ -20,7 +20,7 @@
     <script src="libs/date.js"></script>
     <script src="libs/jquery.mobile.simpledialog2.js"></script>
     <script src="libs/slimbox2.js"></script>
-    <script src="libs/jquery.blockUI.js"></script>
+    <script src="libs/jquery.blockui.js"></script>
 
     <!-- https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin -->
     <script src="libs/jquery-file-upload/js/vendor/jquery.ui.widget.js"></script>
@@ -34,7 +34,7 @@
   <body>
     <div data-role="page" id="find-patients" >
       <div data-role="header" >
-	<h1>Find a patient</h1>
+	<h1><span class="orthanc-name"></span>Find a patient</h1>
         <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a>
       </div>
       <div data-role="content">
@@ -45,7 +45,7 @@
 
     <div data-role="page" id="upload" >
       <div data-role="header" >
-	<h1>Upload DICOM files</h1>
+	<h1><span class="orthanc-name"></span>Upload DICOM files</h1>
         <a href="#find-patients" data-icon="search" class="ui-btn-left" data-direction="reverse">Find patient</a>
       </div>
       <div data-role="content">
@@ -71,7 +71,7 @@
 
     <div data-role="page" id="patient" >
       <div data-role="header" >
-	<h1>List of the studies of one patient</h1>
+	<h1><span class="orthanc-name"></span>Patient</h1>
         <a href="#find-patients" data-icon="search" class="ui-btn-left" data-direction="reverse">Find patient</a>
         <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a>
       </div>
@@ -82,8 +82,15 @@
               <ul data-role="listview" data-inset="true" data-theme="a"  id="patient-info">
               </ul>
               <p>
-                <a href="#find-patients" data-role="button" data-icon="search">Go to patient finder</a>
+                <div class="switch-container">
+                  <select name="protection" id="protection" data-role="slider">
+	            <option value="off">Unprotected</option>
+	            <option value="on">Protected</option>
+                  </select>
+                </div>
+                <!--a href="#find-patients" data-role="button" data-icon="search">Go to patient finder</a-->
                 <a href="#" data-role="button" data-icon="delete" id="patient-delete">Delete this patient</a>
+                <a href="#" data-role="button" data-icon="gear" id="patient-archive">Download ZIP</a>
               </p>
             </div>
           </div>
@@ -99,7 +106,11 @@
 
     <div data-role="page" id="study">
       <div data-role="header">
-	<h1>List of the series of one study</h1>
+	<h1>
+          <span class="orthanc-name"></span>
+          <a href="#" class="patient-link">Patient</a> &raquo; 
+          Study
+        </h1>
         <a href="#find-patients" data-icon="search" class="ui-btn-left" data-direction="reverse">Find patient</a>
         <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a>
       </div>
@@ -111,6 +122,7 @@
               </ul>
               <p>
                 <a href="#" data-role="button" data-icon="delete" id="study-delete">Delete this study</a>
+                <a href="#" data-role="button" data-icon="gear" id="study-archive">Download ZIP</a>
               </p>
             </div>
           </div>
@@ -126,7 +138,13 @@
 
     <div data-role="page" id="series">
       <div data-role="header">
-	<h1>List of the instances of one series</h1>
+	<h1>
+          <span class="orthanc-name"></span>
+          <a href="#" class="patient-link">Patient</a> &raquo; 
+          <a href="#" class="study-link">Study</a> &raquo; 
+          Series
+        </h1>
+
         <a href="#find-patients" data-icon="search" class="ui-btn-left" data-direction="reverse">Find patient</a>
         <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a>
       </div>
@@ -138,8 +156,9 @@
               </ul>
               <p>
                 <a href="#" data-role="button" data-icon="delete" id="series-delete">Delete this series</a>
-                <a href="#" data-role="button" data-icon="arrow-d" id="series-preview">Preview this series</a>
-                <a href="#" data-role="button" data-icon="arrow-d" id="series-store">Store in another DICOM modality</a>
+                <a href="#" data-role="button" data-icon="search" id="series-preview">Preview this series</a>
+                <a href="#" data-role="button" data-icon="forward" id="series-store">Store in another DICOM modality</a>
+                <a href="#" data-role="button" data-icon="gear" id="series-archive">Download ZIP</a>
               </p>
             </div>
           </div>
@@ -155,7 +174,13 @@
 
     <div data-role="page" id="instance">
       <div data-role="header">
-	<h1>One DICOM instance</h1>
+	<h1>
+          <span class="orthanc-name"></span>
+          <a href="#" class="patient-link">Patient</a> &raquo; 
+          <a href="#" class="study-link">Study</a> &raquo; 
+          <a href="#" class="series-link">Series</a> &raquo; 
+          Instance
+        </h1>
         <a href="#find-patients" data-icon="search" class="ui-btn-left" data-direction="reverse">Find patient</a>
         <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a>
       </div>
@@ -169,8 +194,8 @@
                 <a href="#" data-role="button" data-icon="delete" id="instance-delete">Delete this instance</a>
                 <a href="#" data-role="button" data-icon="arrow-d" id="instance-download-dicom">Download the DICOM file</a>
                 <a href="#" data-role="button" data-icon="arrow-d" id="instance-download-json">Download the JSON file</a>
-                <a href="#" data-role="button" data-icon="arrow-d" id="instance-preview">Preview the instance</a>
-                <a href="#" data-role="button" data-icon="arrow-d" id="instance-store">Store in another DICOM modality</a>
+                <a href="#" data-role="button" data-icon="search" id="instance-preview">Preview the instance</a>
+                <a href="#" data-role="button" data-icon="forward" id="instance-store">Store in another DICOM modality</a>
               </p>
             </div>
           </div>
--- a/OrthancExplorer/explorer.js	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancExplorer/explorer.js	Fri Dec 14 11:24:24 2012 +0100
@@ -319,6 +319,19 @@
 }
 
 
+$('[data-role="page"]').live('pagebeforeshow', function() {
+  $.ajax({
+    url: '../system',
+    dataType: 'json',
+    async: false,
+    success: function(s) {
+      if (s.Name != "") {
+        $('.orthanc-name').html('<a class="ui-link" href="explorer.html">' + s.Name + '</a> &raquo; ');
+      }
+    }
+  });
+});
+
 
 
 $('#find-patients').live('pagebeforeshow', function() {
@@ -365,6 +378,18 @@
         }
 
         target.listview('refresh');
+
+        // Check whether this patient is protected
+        $.ajax({
+          url: '../patients/' + $.mobile.pageData.uuid + '/protected',
+          type: 'GET',
+          dataType: 'text',
+          async: false,
+          success: function (s) {
+            var v = (s == '1') ? 'on' : 'off';
+            $('#protection').val(v).slider('refresh');
+          }
+        });
       });
     });
   }
@@ -378,6 +403,7 @@
         GetMultipleResources('series', study.Series, function(series) {
           SortOnDicomTag(series, 'SeriesDate', false, true);
 
+          $('#study .patient-link').attr('href', '#patient?uuid=' + patient.ID);
           $('#study-info li').remove();
           $('#study-info')
             .append('<li data-role="list-divider">Patient</li>')
@@ -412,6 +438,9 @@
           GetMultipleResources('instances', series.Instances, function(instances) {
             Sort(instances, function(x) { return x.IndexInSeries; }, true, false);
 
+            $('#series .patient-link').attr('href', '#patient?uuid=' + patient.ID);
+            $('#series .study-link').attr('href', '#study?uuid=' + study.ID);
+
             $('#series-info li').remove();
             $('#series-info')
               .append('<li data-role="list-divider">Patient</li>')
@@ -494,6 +523,10 @@
       GetSingleResource('series', instance.ParentSeries, function(series) {
         GetSingleResource('studies', series.ParentStudy, function(study) {
           GetSingleResource('patients', study.ParentPatient, function(patient) {
+
+            $('#instance .patient-link').attr('href', '#patient?uuid=' + patient.ID);
+            $('#instance .study-link').attr('href', '#study?uuid=' + study.ID);
+            $('#instance .series-link').attr('href', '#series?uuid=' + series.ID);
             
             $('#instance-info li').remove();
             $('#instance-info')
@@ -536,7 +569,7 @@
       if (ancestor == null)
         $.mobile.changePage('#find-patients');
       else
-        $.mobile.changePage('#' + ancestor.Type + '?uuid=' + ancestor.ID);
+        $.mobile.changePage('#' + ancestor.Type.toLowerCase() + '?uuid=' + ancestor.ID);
     }
   });
 }
@@ -605,6 +638,7 @@
 });
 
 
+
 $('#instance-preview').live('click', function(e) {
   if ($.mobile.pageData) {
     GetSingleResource('instances', $.mobile.pageData.uuid + '/frames', function(frames) {
@@ -747,3 +781,30 @@
   else
     $('.tag-name').hide();
 });
+
+
+$('#patient-archive').live('click', function(e) {
+  e.preventDefault();  //stop the browser from following
+  window.location.href = '../patients/' + $.mobile.pageData.uuid + '/archive';
+});
+
+$('#study-archive').live('click', function(e) {
+  e.preventDefault();  //stop the browser from following
+  window.location.href = '../studies/' + $.mobile.pageData.uuid + '/archive';
+});
+
+$('#series-archive').live('click', function(e) {
+  e.preventDefault();  //stop the browser from following
+  window.location.href = '../series/' + $.mobile.pageData.uuid + '/archive';
+});
+
+$('#protection').live('change', function(e) {
+  var isProtected = e.target.value == "on";
+  $.ajax({
+    url: '../patients/' + $.mobile.pageData.uuid + '/protected',
+    type: 'PUT',
+    dataType: 'text',
+    data: isProtected ? '1' : '0',
+    async: false
+  });
+});
Binary file OrthancExplorer/images/Unsupported.png has changed
Binary file OrthancExplorer/images/unsupported.png has changed
--- a/OrthancExplorer/libs/jquery.blockUI.js	Thu Oct 04 15:09:56 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,499 +0,0 @@
-/*!
- * jQuery blockUI plugin
- * Version 2.39 (23-MAY-2011)
- * @requires jQuery v1.2.3 or later
- *
- * Examples at: http://malsup.com/jquery/block/
- * Copyright (c) 2007-2010 M. Alsup
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
- *
- * Thanks to Amir-Hossein Sobhi for some excellent contributions!
- */
-
-;(function($) {
-
-if (/1\.(0|1|2)\.(0|1|2)/.test($.fn.jquery) || /^1.1/.test($.fn.jquery)) {
-	alert('blockUI requires jQuery v1.2.3 or later!  You are using v' + $.fn.jquery);
-	return;
-}
-
-$.fn._fadeIn = $.fn.fadeIn;
-
-var noOp = function() {};
-
-// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
-// retarded userAgent strings on Vista)
-var mode = document.documentMode || 0;
-var setExpr = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8);
-var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent) && !mode;
-
-// global $ methods for blocking/unblocking the entire page
-$.blockUI   = function(opts) { install(window, opts); };
-$.unblockUI = function(opts) { remove(window, opts); };
-
-// convenience method for quick growl-like notifications  (http://www.google.com/search?q=growl)
-$.growlUI = function(title, message, timeout, onClose) {
-	var $m = $('<div class="growlUI"></div>');
-	if (title) $m.append('<h1>'+title+'</h1>');
-	if (message) $m.append('<h2>'+message+'</h2>');
-	if (timeout == undefined) timeout = 3000;
-	$.blockUI({
-		message: $m, fadeIn: 700, fadeOut: 1000, centerY: false,
-		timeout: timeout, showOverlay: false,
-		onUnblock: onClose, 
-		css: $.blockUI.defaults.growlCSS
-	});
-};
-
-// plugin method for blocking element content
-$.fn.block = function(opts) {
-	return this.unblock({ fadeOut: 0 }).each(function() {
-		if ($.css(this,'position') == 'static')
-			this.style.position = 'relative';
-		if ($.browser.msie)
-			this.style.zoom = 1; // force 'hasLayout'
-		install(this, opts);
-	});
-};
-
-// plugin method for unblocking element content
-$.fn.unblock = function(opts) {
-	return this.each(function() {
-		remove(this, opts);
-	});
-};
-
-$.blockUI.version = 2.39; // 2nd generation blocking at no extra cost!
-
-// override these in your code to change the default behavior and style
-$.blockUI.defaults = {
-	// message displayed when blocking (use null for no message)
-	message:  '<h1>Please wait...</h1>',
-
-	title: null,	  // title string; only used when theme == true
-	draggable: true,  // only used when theme == true (requires jquery-ui.js to be loaded)
-	
-	theme: false, // set to true to use with jQuery UI themes
-	
-	// styles for the message when blocking; if you wish to disable
-	// these and use an external stylesheet then do this in your code:
-	// $.blockUI.defaults.css = {};
-	css: {
-		padding:	0,
-		margin:		0,
-		width:		'30%',
-		top:		'40%',
-		left:		'35%',
-		textAlign:	'center',
-		color:		'#000',
-		border:		'3px solid #aaa',
-		backgroundColor:'#fff',
-		cursor:		'wait'
-	},
-	
-	// minimal style set used when themes are used
-	themedCSS: {
-		width:	'30%',
-		top:	'40%',
-		left:	'35%'
-	},
-
-	// styles for the overlay
-	overlayCSS:  {
-		backgroundColor: '#000',
-		opacity:	  	 0.6,
-		cursor:		  	 'wait'
-	},
-
-	// styles applied when using $.growlUI
-	growlCSS: {
-		width:  	'350px',
-		top:		'10px',
-		left:   	'',
-		right:  	'10px',
-		border: 	'none',
-		padding:	'5px',
-		opacity:	0.6,
-		cursor: 	'default',
-		color:		'#fff',
-		backgroundColor: '#000',
-		'-webkit-border-radius': '10px',
-		'-moz-border-radius':	 '10px',
-		'border-radius': 		 '10px'
-	},
-	
-	// IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
-	// (hat tip to Jorge H. N. de Vasconcelos)
-	iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
-
-	// force usage of iframe in non-IE browsers (handy for blocking applets)
-	forceIframe: false,
-
-	// z-index for the blocking overlay
-	baseZ: 1000,
-
-	// set these to true to have the message automatically centered
-	centerX: true, // <-- only effects element blocking (page block controlled via css above)
-	centerY: true,
-
-	// allow body element to be stetched in ie6; this makes blocking look better
-	// on "short" pages.  disable if you wish to prevent changes to the body height
-	allowBodyStretch: true,
-
-	// enable if you want key and mouse events to be disabled for content that is blocked
-	bindEvents: true,
-
-	// be default blockUI will supress tab navigation from leaving blocking content
-	// (if bindEvents is true)
-	constrainTabKey: true,
-
-	// fadeIn time in millis; set to 0 to disable fadeIn on block
-	fadeIn:  200,
-
-	// fadeOut time in millis; set to 0 to disable fadeOut on unblock
-	fadeOut:  400,
-
-	// time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
-	timeout: 0,
-
-	// disable if you don't want to show the overlay
-	showOverlay: true,
-
-	// if true, focus will be placed in the first available input field when
-	// page blocking
-	focusInput: true,
-
-	// suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
-	applyPlatformOpacityRules: true,
-	
-	// callback method invoked when fadeIn has completed and blocking message is visible
-	onBlock: null,
-
-	// callback method invoked when unblocking has completed; the callback is
-	// passed the element that has been unblocked (which is the window object for page
-	// blocks) and the options that were passed to the unblock call:
-	//	 onUnblock(element, options)
-	onUnblock: null,
-
-	// don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
-	quirksmodeOffsetHack: 4,
-
-	// class name of the message block
-	blockMsgClass: 'blockMsg'
-};
-
-// private data and functions follow...
-
-var pageBlock = null;
-var pageBlockEls = [];
-
-function install(el, opts) {
-	var full = (el == window);
-	var msg = opts && opts.message !== undefined ? opts.message : undefined;
-	opts = $.extend({}, $.blockUI.defaults, opts || {});
-	opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
-	var css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
-	var themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
-	msg = msg === undefined ? opts.message : msg;
-
-	// remove the current block (if there is one)
-	if (full && pageBlock)
-		remove(window, {fadeOut:0});
-
-	// if an existing element is being used as the blocking content then we capture
-	// its current place in the DOM (and current display style) so we can restore
-	// it when we unblock
-	if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
-		var node = msg.jquery ? msg[0] : msg;
-		var data = {};
-		$(el).data('blockUI.history', data);
-		data.el = node;
-		data.parent = node.parentNode;
-		data.display = node.style.display;
-		data.position = node.style.position;
-		if (data.parent)
-			data.parent.removeChild(node);
-	}
-
-	$(el).data('blockUI.onUnblock', opts.onUnblock);
-	var z = opts.baseZ;
-
-	// blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
-	// layer1 is the iframe layer which is used to supress bleed through of underlying content
-	// layer2 is the overlay layer which has opacity and a wait cursor (by default)
-	// layer3 is the message content that is displayed while blocking
-
-	var lyr1 = ($.browser.msie || opts.forceIframe) 
-		? $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>')
-		: $('<div class="blockUI" style="display:none"></div>');
-	
-	var lyr2 = opts.theme 
-	 	? $('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+ (z++) +';display:none"></div>')
-	 	: $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
-
-	var lyr3, s;
-	if (opts.theme && full) {
-		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:fixed">' +
-				'<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>' +
-				'<div class="ui-widget-content ui-dialog-content"></div>' +
-			'</div>';
-	}
-	else if (opts.theme) {
-		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:absolute">' +
-				'<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>' +
-				'<div class="ui-widget-content ui-dialog-content"></div>' +
-			'</div>';
-	}
-	else if (full) {
-		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:'+(z+10)+';display:none;position:fixed"></div>';
-	}			 
-	else {
-		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:'+(z+10)+';display:none;position:absolute"></div>';
-	}
-	lyr3 = $(s);
-
-	// if we have a message, style it
-	if (msg) {
-		if (opts.theme) {
-			lyr3.css(themedCSS);
-			lyr3.addClass('ui-widget-content');
-		}
-		else 
-			lyr3.css(css);
-	}
-
-	// style the overlay
-	if (!opts.theme && (!opts.applyPlatformOpacityRules || !($.browser.mozilla && /Linux/.test(navigator.platform))))
-		lyr2.css(opts.overlayCSS);
-	lyr2.css('position', full ? 'fixed' : 'absolute');
-
-	// make iframe layer transparent in IE
-	if ($.browser.msie || opts.forceIframe)
-		lyr1.css('opacity',0.0);
-
-	//$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
-	var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
-	$.each(layers, function() {
-		this.appendTo($par);
-	});
-	
-	if (opts.theme && opts.draggable && $.fn.draggable) {
-		lyr3.draggable({
-			handle: '.ui-dialog-titlebar',
-			cancel: 'li'
-		});
-	}
-
-	// ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
-	var expr = setExpr && (!$.boxModel || $('object,embed', full ? null : el).length > 0);
-	if (ie6 || expr) {
-		// give body 100% height
-		if (full && opts.allowBodyStretch && $.boxModel)
-			$('html,body').css('height','100%');
-
-		// fix ie6 issue when blocked element has a border width
-		if ((ie6 || !$.boxModel) && !full) {
-			var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
-			var fixT = t ? '(0 - '+t+')' : 0;
-			var fixL = l ? '(0 - '+l+')' : 0;
-		}
-
-		// simulate fixed position
-		$.each([lyr1,lyr2,lyr3], function(i,o) {
-			var s = o[0].style;
-			s.position = 'absolute';
-			if (i < 2) {
-				full ? s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"')
-					 : s.setExpression('height','this.parentNode.offsetHeight + "px"');
-				full ? s.setExpression('width','jQuery.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"')
-					 : s.setExpression('width','this.parentNode.offsetWidth + "px"');
-				if (fixL) s.setExpression('left', fixL);
-				if (fixT) s.setExpression('top', fixT);
-			}
-			else if (opts.centerY) {
-				if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
-				s.marginTop = 0;
-			}
-			else if (!opts.centerY && full) {
-				var top = (opts.css && opts.css.top) ? parseInt(opts.css.top) : 0;
-				var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
-				s.setExpression('top',expression);
-			}
-		});
-	}
-
-	// show the message
-	if (msg) {
-		if (opts.theme)
-			lyr3.find('.ui-widget-content').append(msg);
-		else
-			lyr3.append(msg);
-		if (msg.jquery || msg.nodeType)
-			$(msg).show();
-	}
-
-	if (($.browser.msie || opts.forceIframe) && opts.showOverlay)
-		lyr1.show(); // opacity is zero
-	if (opts.fadeIn) {
-		var cb = opts.onBlock ? opts.onBlock : noOp;
-		var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
-		var cb2 = msg ? cb : noOp;
-		if (opts.showOverlay)
-			lyr2._fadeIn(opts.fadeIn, cb1);
-		if (msg)
-			lyr3._fadeIn(opts.fadeIn, cb2);
-	}
-	else {
-		if (opts.showOverlay)
-			lyr2.show();
-		if (msg)
-			lyr3.show();
-		if (opts.onBlock)
-			opts.onBlock();
-	}
-
-	// bind key and mouse events
-	bind(1, el, opts);
-
-	if (full) {
-		pageBlock = lyr3[0];
-		pageBlockEls = $(':input:enabled:visible',pageBlock);
-		if (opts.focusInput)
-			setTimeout(focus, 20);
-	}
-	else
-		center(lyr3[0], opts.centerX, opts.centerY);
-
-	if (opts.timeout) {
-		// auto-unblock
-		var to = setTimeout(function() {
-			full ? $.unblockUI(opts) : $(el).unblock(opts);
-		}, opts.timeout);
-		$(el).data('blockUI.timeout', to);
-	}
-};
-
-// remove the block
-function remove(el, opts) {
-	var full = (el == window);
-	var $el = $(el);
-	var data = $el.data('blockUI.history');
-	var to = $el.data('blockUI.timeout');
-	if (to) {
-		clearTimeout(to);
-		$el.removeData('blockUI.timeout');
-	}
-	opts = $.extend({}, $.blockUI.defaults, opts || {});
-	bind(0, el, opts); // unbind events
-
-	if (opts.onUnblock === null) {
-		opts.onUnblock = $el.data('blockUI.onUnblock');
-		$el.removeData('blockUI.onUnblock');
-	}
-
-	var els;
-	if (full) // crazy selector to handle odd field errors in ie6/7
-		els = $('body').children().filter('.blockUI').add('body > .blockUI');
-	else
-		els = $('.blockUI', el);
-
-	if (full)
-		pageBlock = pageBlockEls = null;
-
-	if (opts.fadeOut) {
-		els.fadeOut(opts.fadeOut);
-		setTimeout(function() { reset(els,data,opts,el); }, opts.fadeOut);
-	}
-	else
-		reset(els, data, opts, el);
-};
-
-// move blocking element back into the DOM where it started
-function reset(els,data,opts,el) {
-	els.each(function(i,o) {
-		// remove via DOM calls so we don't lose event handlers
-		if (this.parentNode)
-			this.parentNode.removeChild(this);
-	});
-
-	if (data && data.el) {
-		data.el.style.display = data.display;
-		data.el.style.position = data.position;
-		if (data.parent)
-			data.parent.appendChild(data.el);
-		$(el).removeData('blockUI.history');
-	}
-
-	if (typeof opts.onUnblock == 'function')
-		opts.onUnblock(el,opts);
-};
-
-// bind/unbind the handler
-function bind(b, el, opts) {
-	var full = el == window, $el = $(el);
-
-	// don't bother unbinding if there is nothing to unbind
-	if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
-		return;
-	if (!full)
-		$el.data('blockUI.isBlocked', b);
-
-	// don't bind events when overlay is not in use or if bindEvents is false
-	if (!opts.bindEvents || (b && !opts.showOverlay)) 
-		return;
-
-	// bind anchors and inputs for mouse and key events
-	var events = 'mousedown mouseup keydown keypress';
-	b ? $(document).bind(events, opts, handler) : $(document).unbind(events, handler);
-
-// former impl...
-//	   var $e = $('a,:input');
-//	   b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
-};
-
-// event handler to suppress keyboard/mouse events when blocking
-function handler(e) {
-	// allow tab navigation (conditionally)
-	if (e.keyCode && e.keyCode == 9) {
-		if (pageBlock && e.data.constrainTabKey) {
-			var els = pageBlockEls;
-			var fwd = !e.shiftKey && e.target === els[els.length-1];
-			var back = e.shiftKey && e.target === els[0];
-			if (fwd || back) {
-				setTimeout(function(){focus(back)},10);
-				return false;
-			}
-		}
-	}
-	var opts = e.data;
-	// allow events within the message content
-	if ($(e.target).parents('div.' + opts.blockMsgClass).length > 0)
-		return true;
-
-	// allow events for content that is not being blocked
-	return $(e.target).parents().children().filter('div.blockUI').length == 0;
-};
-
-function focus(back) {
-	if (!pageBlockEls)
-		return;
-	var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
-	if (e)
-		e.focus();
-};
-
-function center(el, x, y) {
-	var p = el.parentNode, s = el.style;
-	var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
-	var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
-	if (x) s.left = l > 0 ? (l+'px') : '0';
-	if (y) s.top  = t > 0 ? (t+'px') : '0';
-};
-
-function sz(el, p) {
-	return parseInt($.css(el,p))||0;
-};
-
-})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancExplorer/libs/jquery.blockui.js	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,499 @@
+/*!
+ * jQuery blockUI plugin
+ * Version 2.39 (23-MAY-2011)
+ * @requires jQuery v1.2.3 or later
+ *
+ * Examples at: http://malsup.com/jquery/block/
+ * Copyright (c) 2007-2010 M. Alsup
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Thanks to Amir-Hossein Sobhi for some excellent contributions!
+ */
+
+;(function($) {
+
+if (/1\.(0|1|2)\.(0|1|2)/.test($.fn.jquery) || /^1.1/.test($.fn.jquery)) {
+	alert('blockUI requires jQuery v1.2.3 or later!  You are using v' + $.fn.jquery);
+	return;
+}
+
+$.fn._fadeIn = $.fn.fadeIn;
+
+var noOp = function() {};
+
+// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
+// retarded userAgent strings on Vista)
+var mode = document.documentMode || 0;
+var setExpr = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8);
+var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent) && !mode;
+
+// global $ methods for blocking/unblocking the entire page
+$.blockUI   = function(opts) { install(window, opts); };
+$.unblockUI = function(opts) { remove(window, opts); };
+
+// convenience method for quick growl-like notifications  (http://www.google.com/search?q=growl)
+$.growlUI = function(title, message, timeout, onClose) {
+	var $m = $('<div class="growlUI"></div>');
+	if (title) $m.append('<h1>'+title+'</h1>');
+	if (message) $m.append('<h2>'+message+'</h2>');
+	if (timeout == undefined) timeout = 3000;
+	$.blockUI({
+		message: $m, fadeIn: 700, fadeOut: 1000, centerY: false,
+		timeout: timeout, showOverlay: false,
+		onUnblock: onClose, 
+		css: $.blockUI.defaults.growlCSS
+	});
+};
+
+// plugin method for blocking element content
+$.fn.block = function(opts) {
+	return this.unblock({ fadeOut: 0 }).each(function() {
+		if ($.css(this,'position') == 'static')
+			this.style.position = 'relative';
+		if ($.browser.msie)
+			this.style.zoom = 1; // force 'hasLayout'
+		install(this, opts);
+	});
+};
+
+// plugin method for unblocking element content
+$.fn.unblock = function(opts) {
+	return this.each(function() {
+		remove(this, opts);
+	});
+};
+
+$.blockUI.version = 2.39; // 2nd generation blocking at no extra cost!
+
+// override these in your code to change the default behavior and style
+$.blockUI.defaults = {
+	// message displayed when blocking (use null for no message)
+	message:  '<h1>Please wait...</h1>',
+
+	title: null,	  // title string; only used when theme == true
+	draggable: true,  // only used when theme == true (requires jquery-ui.js to be loaded)
+	
+	theme: false, // set to true to use with jQuery UI themes
+	
+	// styles for the message when blocking; if you wish to disable
+	// these and use an external stylesheet then do this in your code:
+	// $.blockUI.defaults.css = {};
+	css: {
+		padding:	0,
+		margin:		0,
+		width:		'30%',
+		top:		'40%',
+		left:		'35%',
+		textAlign:	'center',
+		color:		'#000',
+		border:		'3px solid #aaa',
+		backgroundColor:'#fff',
+		cursor:		'wait'
+	},
+	
+	// minimal style set used when themes are used
+	themedCSS: {
+		width:	'30%',
+		top:	'40%',
+		left:	'35%'
+	},
+
+	// styles for the overlay
+	overlayCSS:  {
+		backgroundColor: '#000',
+		opacity:	  	 0.6,
+		cursor:		  	 'wait'
+	},
+
+	// styles applied when using $.growlUI
+	growlCSS: {
+		width:  	'350px',
+		top:		'10px',
+		left:   	'',
+		right:  	'10px',
+		border: 	'none',
+		padding:	'5px',
+		opacity:	0.6,
+		cursor: 	'default',
+		color:		'#fff',
+		backgroundColor: '#000',
+		'-webkit-border-radius': '10px',
+		'-moz-border-radius':	 '10px',
+		'border-radius': 		 '10px'
+	},
+	
+	// IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
+	// (hat tip to Jorge H. N. de Vasconcelos)
+	iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
+
+	// force usage of iframe in non-IE browsers (handy for blocking applets)
+	forceIframe: false,
+
+	// z-index for the blocking overlay
+	baseZ: 1000,
+
+	// set these to true to have the message automatically centered
+	centerX: true, // <-- only effects element blocking (page block controlled via css above)
+	centerY: true,
+
+	// allow body element to be stetched in ie6; this makes blocking look better
+	// on "short" pages.  disable if you wish to prevent changes to the body height
+	allowBodyStretch: true,
+
+	// enable if you want key and mouse events to be disabled for content that is blocked
+	bindEvents: true,
+
+	// be default blockUI will supress tab navigation from leaving blocking content
+	// (if bindEvents is true)
+	constrainTabKey: true,
+
+	// fadeIn time in millis; set to 0 to disable fadeIn on block
+	fadeIn:  200,
+
+	// fadeOut time in millis; set to 0 to disable fadeOut on unblock
+	fadeOut:  400,
+
+	// time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
+	timeout: 0,
+
+	// disable if you don't want to show the overlay
+	showOverlay: true,
+
+	// if true, focus will be placed in the first available input field when
+	// page blocking
+	focusInput: true,
+
+	// suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
+	applyPlatformOpacityRules: true,
+	
+	// callback method invoked when fadeIn has completed and blocking message is visible
+	onBlock: null,
+
+	// callback method invoked when unblocking has completed; the callback is
+	// passed the element that has been unblocked (which is the window object for page
+	// blocks) and the options that were passed to the unblock call:
+	//	 onUnblock(element, options)
+	onUnblock: null,
+
+	// don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
+	quirksmodeOffsetHack: 4,
+
+	// class name of the message block
+	blockMsgClass: 'blockMsg'
+};
+
+// private data and functions follow...
+
+var pageBlock = null;
+var pageBlockEls = [];
+
+function install(el, opts) {
+	var full = (el == window);
+	var msg = opts && opts.message !== undefined ? opts.message : undefined;
+	opts = $.extend({}, $.blockUI.defaults, opts || {});
+	opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
+	var css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
+	var themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
+	msg = msg === undefined ? opts.message : msg;
+
+	// remove the current block (if there is one)
+	if (full && pageBlock)
+		remove(window, {fadeOut:0});
+
+	// if an existing element is being used as the blocking content then we capture
+	// its current place in the DOM (and current display style) so we can restore
+	// it when we unblock
+	if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
+		var node = msg.jquery ? msg[0] : msg;
+		var data = {};
+		$(el).data('blockUI.history', data);
+		data.el = node;
+		data.parent = node.parentNode;
+		data.display = node.style.display;
+		data.position = node.style.position;
+		if (data.parent)
+			data.parent.removeChild(node);
+	}
+
+	$(el).data('blockUI.onUnblock', opts.onUnblock);
+	var z = opts.baseZ;
+
+	// blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
+	// layer1 is the iframe layer which is used to supress bleed through of underlying content
+	// layer2 is the overlay layer which has opacity and a wait cursor (by default)
+	// layer3 is the message content that is displayed while blocking
+
+	var lyr1 = ($.browser.msie || opts.forceIframe) 
+		? $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>')
+		: $('<div class="blockUI" style="display:none"></div>');
+	
+	var lyr2 = opts.theme 
+	 	? $('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+ (z++) +';display:none"></div>')
+	 	: $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
+
+	var lyr3, s;
+	if (opts.theme && full) {
+		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:fixed">' +
+				'<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>' +
+				'<div class="ui-widget-content ui-dialog-content"></div>' +
+			'</div>';
+	}
+	else if (opts.theme) {
+		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:absolute">' +
+				'<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>' +
+				'<div class="ui-widget-content ui-dialog-content"></div>' +
+			'</div>';
+	}
+	else if (full) {
+		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:'+(z+10)+';display:none;position:fixed"></div>';
+	}			 
+	else {
+		s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:'+(z+10)+';display:none;position:absolute"></div>';
+	}
+	lyr3 = $(s);
+
+	// if we have a message, style it
+	if (msg) {
+		if (opts.theme) {
+			lyr3.css(themedCSS);
+			lyr3.addClass('ui-widget-content');
+		}
+		else 
+			lyr3.css(css);
+	}
+
+	// style the overlay
+	if (!opts.theme && (!opts.applyPlatformOpacityRules || !($.browser.mozilla && /Linux/.test(navigator.platform))))
+		lyr2.css(opts.overlayCSS);
+	lyr2.css('position', full ? 'fixed' : 'absolute');
+
+	// make iframe layer transparent in IE
+	if ($.browser.msie || opts.forceIframe)
+		lyr1.css('opacity',0.0);
+
+	//$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
+	var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
+	$.each(layers, function() {
+		this.appendTo($par);
+	});
+	
+	if (opts.theme && opts.draggable && $.fn.draggable) {
+		lyr3.draggable({
+			handle: '.ui-dialog-titlebar',
+			cancel: 'li'
+		});
+	}
+
+	// ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
+	var expr = setExpr && (!$.boxModel || $('object,embed', full ? null : el).length > 0);
+	if (ie6 || expr) {
+		// give body 100% height
+		if (full && opts.allowBodyStretch && $.boxModel)
+			$('html,body').css('height','100%');
+
+		// fix ie6 issue when blocked element has a border width
+		if ((ie6 || !$.boxModel) && !full) {
+			var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
+			var fixT = t ? '(0 - '+t+')' : 0;
+			var fixL = l ? '(0 - '+l+')' : 0;
+		}
+
+		// simulate fixed position
+		$.each([lyr1,lyr2,lyr3], function(i,o) {
+			var s = o[0].style;
+			s.position = 'absolute';
+			if (i < 2) {
+				full ? s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"')
+					 : s.setExpression('height','this.parentNode.offsetHeight + "px"');
+				full ? s.setExpression('width','jQuery.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"')
+					 : s.setExpression('width','this.parentNode.offsetWidth + "px"');
+				if (fixL) s.setExpression('left', fixL);
+				if (fixT) s.setExpression('top', fixT);
+			}
+			else if (opts.centerY) {
+				if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
+				s.marginTop = 0;
+			}
+			else if (!opts.centerY && full) {
+				var top = (opts.css && opts.css.top) ? parseInt(opts.css.top) : 0;
+				var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
+				s.setExpression('top',expression);
+			}
+		});
+	}
+
+	// show the message
+	if (msg) {
+		if (opts.theme)
+			lyr3.find('.ui-widget-content').append(msg);
+		else
+			lyr3.append(msg);
+		if (msg.jquery || msg.nodeType)
+			$(msg).show();
+	}
+
+	if (($.browser.msie || opts.forceIframe) && opts.showOverlay)
+		lyr1.show(); // opacity is zero
+	if (opts.fadeIn) {
+		var cb = opts.onBlock ? opts.onBlock : noOp;
+		var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
+		var cb2 = msg ? cb : noOp;
+		if (opts.showOverlay)
+			lyr2._fadeIn(opts.fadeIn, cb1);
+		if (msg)
+			lyr3._fadeIn(opts.fadeIn, cb2);
+	}
+	else {
+		if (opts.showOverlay)
+			lyr2.show();
+		if (msg)
+			lyr3.show();
+		if (opts.onBlock)
+			opts.onBlock();
+	}
+
+	// bind key and mouse events
+	bind(1, el, opts);
+
+	if (full) {
+		pageBlock = lyr3[0];
+		pageBlockEls = $(':input:enabled:visible',pageBlock);
+		if (opts.focusInput)
+			setTimeout(focus, 20);
+	}
+	else
+		center(lyr3[0], opts.centerX, opts.centerY);
+
+	if (opts.timeout) {
+		// auto-unblock
+		var to = setTimeout(function() {
+			full ? $.unblockUI(opts) : $(el).unblock(opts);
+		}, opts.timeout);
+		$(el).data('blockUI.timeout', to);
+	}
+};
+
+// remove the block
+function remove(el, opts) {
+	var full = (el == window);
+	var $el = $(el);
+	var data = $el.data('blockUI.history');
+	var to = $el.data('blockUI.timeout');
+	if (to) {
+		clearTimeout(to);
+		$el.removeData('blockUI.timeout');
+	}
+	opts = $.extend({}, $.blockUI.defaults, opts || {});
+	bind(0, el, opts); // unbind events
+
+	if (opts.onUnblock === null) {
+		opts.onUnblock = $el.data('blockUI.onUnblock');
+		$el.removeData('blockUI.onUnblock');
+	}
+
+	var els;
+	if (full) // crazy selector to handle odd field errors in ie6/7
+		els = $('body').children().filter('.blockUI').add('body > .blockUI');
+	else
+		els = $('.blockUI', el);
+
+	if (full)
+		pageBlock = pageBlockEls = null;
+
+	if (opts.fadeOut) {
+		els.fadeOut(opts.fadeOut);
+		setTimeout(function() { reset(els,data,opts,el); }, opts.fadeOut);
+	}
+	else
+		reset(els, data, opts, el);
+};
+
+// move blocking element back into the DOM where it started
+function reset(els,data,opts,el) {
+	els.each(function(i,o) {
+		// remove via DOM calls so we don't lose event handlers
+		if (this.parentNode)
+			this.parentNode.removeChild(this);
+	});
+
+	if (data && data.el) {
+		data.el.style.display = data.display;
+		data.el.style.position = data.position;
+		if (data.parent)
+			data.parent.appendChild(data.el);
+		$(el).removeData('blockUI.history');
+	}
+
+	if (typeof opts.onUnblock == 'function')
+		opts.onUnblock(el,opts);
+};
+
+// bind/unbind the handler
+function bind(b, el, opts) {
+	var full = el == window, $el = $(el);
+
+	// don't bother unbinding if there is nothing to unbind
+	if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
+		return;
+	if (!full)
+		$el.data('blockUI.isBlocked', b);
+
+	// don't bind events when overlay is not in use or if bindEvents is false
+	if (!opts.bindEvents || (b && !opts.showOverlay)) 
+		return;
+
+	// bind anchors and inputs for mouse and key events
+	var events = 'mousedown mouseup keydown keypress';
+	b ? $(document).bind(events, opts, handler) : $(document).unbind(events, handler);
+
+// former impl...
+//	   var $e = $('a,:input');
+//	   b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
+};
+
+// event handler to suppress keyboard/mouse events when blocking
+function handler(e) {
+	// allow tab navigation (conditionally)
+	if (e.keyCode && e.keyCode == 9) {
+		if (pageBlock && e.data.constrainTabKey) {
+			var els = pageBlockEls;
+			var fwd = !e.shiftKey && e.target === els[els.length-1];
+			var back = e.shiftKey && e.target === els[0];
+			if (fwd || back) {
+				setTimeout(function(){focus(back)},10);
+				return false;
+			}
+		}
+	}
+	var opts = e.data;
+	// allow events within the message content
+	if ($(e.target).parents('div.' + opts.blockMsgClass).length > 0)
+		return true;
+
+	// allow events for content that is not being blocked
+	return $(e.target).parents().children().filter('div.blockUI').length == 0;
+};
+
+function focus(back) {
+	if (!pageBlockEls)
+		return;
+	var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
+	if (e)
+		e.focus();
+};
+
+function center(el, x, y) {
+	var p = el.parentNode, s = el.style;
+	var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
+	var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
+	if (x) s.left = l > 0 ? (l+'px') : '0';
+	if (y) s.top  = t > 0 ? (t+'px') : '0';
+};
+
+function sz(el, p) {
+	return parseInt($.css(el,p))||0;
+};
+
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DatabaseWrapper.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,853 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "DatabaseWrapper.h"
+
+#include "../Core/DicomFormat/DicomArray.h"
+#include "../Core/Uuid.h"
+#include "EmbeddedResources.h"
+
+#include <glog/logging.h>
+#include <stdio.h>
+
+namespace Orthanc
+{
+
+  namespace Internals
+  {
+    class SignalFileDeleted : public SQLite::IScalarFunction
+    {
+    private:
+      IServerIndexListener& listener_;
+
+    public:
+      SignalFileDeleted(IServerIndexListener& listener) :
+        listener_(listener)
+      {
+      }
+
+      virtual const char* GetName() const
+      {
+        return "SignalFileDeleted";
+      }
+
+      virtual unsigned int GetCardinality() const
+      {
+        return 5;
+      }
+
+      virtual void Compute(SQLite::FunctionContext& context)
+      {
+        FileInfo info(context.GetStringValue(0),
+                      static_cast<FileContentType>(context.GetIntValue(1)),
+                      static_cast<uint64_t>(context.GetInt64Value(2)),
+                      static_cast<CompressionType>(context.GetIntValue(3)),
+                      static_cast<uint64_t>(context.GetInt64Value(4)));
+        
+        listener_.SignalFileDeleted(info);
+      }
+    };
+
+    class SignalRemainingAncestor : public SQLite::IScalarFunction
+    {
+    private:
+      bool hasRemainingAncestor_;
+      std::string remainingPublicId_;
+      ResourceType remainingType_;
+
+    public:
+      void Reset()
+      {
+        hasRemainingAncestor_ = false;
+      }
+
+      virtual const char* GetName() const
+      {
+        return "SignalRemainingAncestor";
+      }
+
+      virtual unsigned int GetCardinality() const
+      {
+        return 2;
+      }
+
+      virtual void Compute(SQLite::FunctionContext& context)
+      {
+        VLOG(1) << "There exists a remaining ancestor with public ID \""
+                << context.GetStringValue(0)
+                << "\" of type "
+                << context.GetIntValue(1);
+
+        if (!hasRemainingAncestor_ ||
+            remainingType_ >= context.GetIntValue(1))
+        {
+          hasRemainingAncestor_ = true;
+          remainingPublicId_ = context.GetStringValue(0);
+          remainingType_ = static_cast<ResourceType>(context.GetIntValue(1));
+        }
+      }
+
+      bool HasRemainingAncestor() const
+      {
+        return hasRemainingAncestor_;
+      }
+
+      const std::string& GetRemainingAncestorId() const
+      {
+        assert(hasRemainingAncestor_);
+        return remainingPublicId_;
+      }
+
+      ResourceType GetRemainingAncestorType() const
+      {
+        assert(hasRemainingAncestor_);
+        return remainingType_;
+      }
+    };
+  }
+
+
+  
+  void DatabaseWrapper::SetGlobalProperty(GlobalProperty property,
+                                          const std::string& value)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)");
+    s.BindInt(0, property);
+    s.BindString(1, value);
+    s.Run();
+  }
+
+  bool DatabaseWrapper::LookupGlobalProperty(std::string& target,
+                                             GlobalProperty property)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT value FROM GlobalProperties WHERE property=?");
+    s.BindInt(0, property);
+
+    if (!s.Step())
+    {
+      return false;
+    }
+    else
+    {
+      target = s.ColumnString(0);
+      return true;
+    }
+  }
+
+  std::string DatabaseWrapper::GetGlobalProperty(GlobalProperty property,
+                                                 const std::string& defaultValue)
+  {
+    std::string s;
+    if (LookupGlobalProperty(s, property))
+    {
+      return s;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+  int64_t DatabaseWrapper::CreateResource(const std::string& publicId,
+                                          ResourceType type)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)");
+    s.BindInt(0, type);
+    s.BindString(1, publicId);
+    s.Run();
+    int64_t id = db_.GetLastInsertRowId();
+
+    ChangeType changeType;
+    switch (type)
+    {
+    case ResourceType_Patient: 
+      changeType = ChangeType_NewPatient; 
+      break;
+
+    case ResourceType_Study: 
+      changeType = ChangeType_NewStudy; 
+      break;
+
+    case ResourceType_Series: 
+      changeType = ChangeType_NewSeries; 
+      break;
+
+    case ResourceType_Instance: 
+      changeType = ChangeType_NewInstance; 
+      break;
+
+    default:
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    LogChange(changeType, id, type);
+    return id;
+  }
+
+  bool DatabaseWrapper::LookupResource(const std::string& publicId,
+                                       int64_t& id,
+                                       ResourceType& type)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT internalId, resourceType FROM Resources WHERE publicId=?");
+    s.BindString(0, publicId);
+
+    if (!s.Step())
+    {
+      return false;
+    }
+    else
+    {
+      id = s.ColumnInt(0);
+      type = static_cast<ResourceType>(s.ColumnInt(1));
+
+      // Check whether there is a single resource with this public id
+      assert(!s.Step());
+
+      return true;
+    }
+  }
+
+  bool DatabaseWrapper::LookupParent(int64_t& parentId,
+                                     int64_t resourceId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT parentId FROM Resources WHERE internalId=?");
+    s.BindInt(0, resourceId);
+
+    if (!s.Step())
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    if (s.ColumnIsNull(0))
+    {
+      return false;
+    }
+    else
+    {
+      parentId = s.ColumnInt(0);
+      return true;
+    }
+  }
+
+  std::string DatabaseWrapper::GetPublicId(int64_t resourceId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT publicId FROM Resources WHERE internalId=?");
+    s.BindInt(0, resourceId);
+    
+    if (!s.Step())
+    { 
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    return s.ColumnString(0);
+  }
+
+  void DatabaseWrapper::AttachChild(int64_t parent,
+                                    int64_t child)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
+    s.BindInt(0, parent);
+    s.BindInt(1, child);
+    s.Run();
+  }
+
+  void DatabaseWrapper::GetChildren(Json::Value& childrenPublicIds,
+                                    int64_t id)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?");
+    s.BindInt(0, id);
+
+    childrenPublicIds = Json::arrayValue;
+    while (s.Step())
+    {
+      childrenPublicIds.append(s.ColumnString(0));
+    }
+  }
+
+
+  void DatabaseWrapper::DeleteResource(int64_t id)
+  {
+    signalRemainingAncestor_->Reset();
+
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?");
+    s.BindInt(0, id);
+    s.Run();
+
+    if (signalRemainingAncestor_->HasRemainingAncestor())
+    {
+      listener_.SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorType(),
+                                        signalRemainingAncestor_->GetRemainingAncestorId());
+    }
+  }
+
+  void DatabaseWrapper::SetMetadata(int64_t id,
+                                    MetadataType type,
+                                    const std::string& value)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)");
+    s.BindInt(0, id);
+    s.BindInt(1, type);
+    s.BindString(2, value);
+    s.Run();
+  }
+
+  bool DatabaseWrapper::LookupMetadata(std::string& target,
+                                       int64_t id,
+                                       MetadataType type)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT value FROM Metadata WHERE id=? AND type=?");
+    s.BindInt(0, id);
+    s.BindInt(1, type);
+
+    if (!s.Step())
+    {
+      return false;
+    }
+    else
+    {
+      target = s.ColumnString(0);
+      return true;
+    }
+  }
+
+  std::string DatabaseWrapper::GetMetadata(int64_t id,
+                                           MetadataType type,
+                                           const std::string& defaultValue)
+  {
+    std::string s;
+    if (LookupMetadata(s, id, type))
+    {
+      return s;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  bool DatabaseWrapper::GetMetadataAsInteger(int& result,
+                                             int64_t id,
+                                             MetadataType type)
+  {
+    std::string s = GetMetadata(id, type, "");
+    if (s.size() == 0)
+    {
+      return false;
+    }
+
+    try
+    {
+      result = boost::lexical_cast<int>(s);
+      return true;
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      return false;
+    }
+  }
+
+
+
+  void DatabaseWrapper::AddAttachment(int64_t id,
+                                      const FileInfo& attachment)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?)");
+    s.BindInt(0, id);
+    s.BindInt(1, attachment.GetContentType());
+    s.BindString(2, attachment.GetUuid());
+    s.BindInt(3, attachment.GetCompressedSize());
+    s.BindInt(4, attachment.GetUncompressedSize());
+    s.BindInt(5, attachment.GetCompressionType());
+    s.Run();
+  }
+
+  bool DatabaseWrapper::LookupAttachment(FileInfo& attachment,
+                                         int64_t id,
+                                         FileContentType contentType)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT uuid, uncompressedSize, compressionType, compressedSize FROM AttachedFiles WHERE id=? AND fileType=?");
+    s.BindInt(0, id);
+    s.BindInt(1, contentType);
+
+    if (!s.Step())
+    {
+      return false;
+    }
+    else
+    {
+      attachment = FileInfo(s.ColumnString(0),
+                            contentType,
+                            s.ColumnInt(1),
+                            static_cast<CompressionType>(s.ColumnInt(2)),
+                            s.ColumnInt(3));
+      return true;
+    }
+  }
+
+  void DatabaseWrapper::SetMainDicomTags(int64_t id,
+                                         const DicomMap& tags)
+  {
+    DicomArray flattened(tags);
+    for (size_t i = 0; i < flattened.GetSize(); i++)
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
+      s.BindInt(0, id);
+      s.BindInt(1, flattened.GetElement(i).GetTag().GetGroup());
+      s.BindInt(2, flattened.GetElement(i).GetTag().GetElement());
+      s.BindString(3, flattened.GetElement(i).GetValue().AsString());
+      s.Run();
+    }
+  }
+
+  void DatabaseWrapper::GetMainDicomTags(DicomMap& map,
+                                         int64_t id)
+  {
+    map.Clear();
+
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?");
+    s.BindInt(0, id);
+    while (s.Step())
+    {
+      map.SetValue(s.ColumnInt(1),
+                   s.ColumnInt(2),
+                   s.ColumnString(3));
+    }
+  }
+
+
+  bool DatabaseWrapper::GetParentPublicId(std::string& result,
+                                          int64_t id)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b "
+                        "WHERE a.internalId = b.parentId AND b.internalId = ?");     
+    s.BindInt(0, id);
+
+    if (s.Step())
+    {
+      result = s.ColumnString(0);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void DatabaseWrapper::GetChildrenPublicId(std::list<std::string>& result,
+                                            int64_t id)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b  "
+                        "WHERE a.parentId = b.internalId AND b.internalId = ?");     
+    s.BindInt(0, id);
+
+    result.clear();
+
+    while (s.Step())
+    {
+      result.push_back(s.ColumnString(0));
+    }
+  }
+
+
+  void DatabaseWrapper::GetChildrenInternalId(std::list<int64_t>& result,
+                                              int64_t id)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b  "
+                        "WHERE a.parentId = b.internalId AND b.internalId = ?");     
+    s.BindInt(0, id);
+
+    result.clear();
+
+    while (s.Step())
+    {
+      result.push_back(s.ColumnInt(0));
+    }
+  }
+
+
+  void DatabaseWrapper::LogChange(ChangeType changeType,
+                                  int64_t internalId,
+                                  ResourceType resourceType,
+                                  const boost::posix_time::ptime& date)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)");
+    s.BindInt(0, changeType);
+    s.BindInt(1, internalId);
+    s.BindInt(2, resourceType);
+    s.BindString(3, boost::posix_time::to_iso_string(date));
+    s.Run();      
+  }
+
+
+  void DatabaseWrapper::GetChangesInternal(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);
+      ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1));
+      int64_t internalId = s.ColumnInt(2);
+      ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3));
+      const std::string& date = s.ColumnString(4);
+      std::string publicId = GetPublicId(internalId);
+
+      Json::Value item = Json::objectValue;
+      item["Seq"] = static_cast<int>(seq);
+      item["ChangeType"] = ToString(changeType);
+      item["ResourceType"] = ToString(resourceType);
+      item["ID"] = publicId;
+      item["Path"] = GetBasePath(resourceType, publicId);
+      item["Date"] = date;
+      last = seq;
+
+      changes.append(item);
+    }
+
+    target = Json::objectValue;
+    target["Changes"] = changes;
+    target["Done"] = !(changes.size() == maxResults && s.Step());
+    target["Last"] = static_cast<int>(last);
+  }
+
+
+  void DatabaseWrapper::GetChanges(Json::Value& target,
+                                   int64_t since,
+                                   unsigned int maxResults)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?");
+    s.BindInt(0, since);
+    s.BindInt(1, maxResults + 1);
+    GetChangesInternal(target, s, since, maxResults);
+  }
+
+  void DatabaseWrapper::GetLastChange(Json::Value& target)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1");
+    GetChangesInternal(target, s, 0, 1);
+  }
+
+
+  void DatabaseWrapper::LogExportedResource(ResourceType resourceType,
+                                            const std::string& publicId,
+                                            const std::string& remoteModality,
+                                            const std::string& patientId,
+                                            const std::string& studyInstanceUid,
+                                            const std::string& seriesInstanceUid,
+                                            const std::string& sopInstanceUid,
+                                            const boost::posix_time::ptime& date)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
+
+    s.BindInt(0, resourceType);
+    s.BindString(1, publicId);
+    s.BindString(2, remoteModality);
+    s.BindString(3, patientId);
+    s.BindString(4, studyInstanceUid);
+    s.BindString(5, seriesInstanceUid);
+    s.BindString(6, sopInstanceUid);
+    s.BindString(7, boost::posix_time::to_iso_string(date));
+
+    s.Run();      
+  }
+
+
+  void DatabaseWrapper::GetExportedResources(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);
+      ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1));
+      std::string publicId = s.ColumnString(2);
+
+      Json::Value item = Json::objectValue;
+      item["Seq"] = static_cast<int>(seq);
+      item["ResourceType"] = ToString(resourceType);
+      item["ID"] = publicId;
+      item["Path"] = GetBasePath(resourceType, publicId);
+      item["RemoteModality"] = s.ColumnString(3);
+      item["Date"] = s.ColumnString(8);
+
+      // WARNING: Do not add "break" below and do not reorder the case items!
+      switch (resourceType)
+      {
+      case ResourceType_Instance:
+        item["SopInstanceUid"] = s.ColumnString(7);
+
+      case ResourceType_Series:
+        item["SeriesInstanceUid"] = s.ColumnString(6);
+
+      case ResourceType_Study:
+        item["StudyInstanceUid"] = s.ColumnString(5);
+
+      case ResourceType_Patient:
+        item["PatientId"] = s.ColumnString(4);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      last = seq;
+
+      changes.append(item);
+    }
+
+    target = Json::objectValue;
+    target["Exports"] = changes;
+    target["Done"] = !(changes.size() == maxResults && s.Step());
+    target["Last"] = static_cast<int>(last);
+  }
+
+
+  void DatabaseWrapper::GetExportedResources(Json::Value& target,
+                                             int64_t since,
+                                             unsigned int maxResults)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?");
+    s.BindInt(0, since);
+    s.BindInt(1, maxResults + 1);
+    GetExportedResources(target, s, since, maxResults);
+  }
+
+    
+  void DatabaseWrapper::GetLastExportedResource(Json::Value& target)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1");
+    GetExportedResources(target, s, 0, 1);
+  }
+
+
+    
+
+  int64_t DatabaseWrapper::GetTableRecordCount(const std::string& table)
+  {
+    char buf[128];
+    sprintf(buf, "SELECT COUNT(*) FROM %s", table.c_str());
+    SQLite::Statement s(db_, buf);
+
+    if (!s.Step())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    int64_t c = s.ColumnInt(0);
+    assert(!s.Step());
+
+    return c;
+  }
+
+    
+  uint64_t DatabaseWrapper::GetTotalCompressedSize()
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles");
+    s.Run();
+    return static_cast<uint64_t>(s.ColumnInt64(0));
+  }
+
+    
+  uint64_t DatabaseWrapper::GetTotalUncompressedSize()
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles");
+    s.Run();
+    return static_cast<uint64_t>(s.ColumnInt64(0));
+  }
+
+  void DatabaseWrapper::GetAllPublicIds(Json::Value& target,
+                                        ResourceType resourceType)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?");
+    s.BindInt(0, resourceType);
+
+    target = Json::arrayValue;
+    while (s.Step())
+    {
+      target.append(s.ColumnString(0));
+    }
+  }
+
+
+  DatabaseWrapper::DatabaseWrapper(const std::string& path,
+                                   IServerIndexListener& listener) :
+    listener_(listener)
+  {
+    db_.Open(path);
+    Open();
+  }
+
+  DatabaseWrapper::DatabaseWrapper(IServerIndexListener& listener) :
+    listener_(listener)
+  {
+    db_.OpenInMemory();
+    Open();
+  }
+
+  void DatabaseWrapper::Open()
+  {
+    if (!db_.DoesTableExist("GlobalProperties"))
+    {
+      LOG(INFO) << "Creating the database";
+      std::string query;
+      EmbeddedResources::GetFileResource(query, EmbeddedResources::PREPARE_DATABASE);
+      db_.Execute(query);
+    }
+
+    // Sanity check of the version of the database
+    std::string version = GetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "Unknown");
+    bool ok = false;
+    try
+    {
+      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); 
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+    }
+
+    if (!ok)
+    {
+      throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
+    }
+
+    signalRemainingAncestor_ = new Internals::SignalRemainingAncestor;
+    db_.Register(signalRemainingAncestor_);
+    db_.Register(new Internals::SignalFileDeleted(listener_));
+  }
+
+  uint64_t DatabaseWrapper::GetResourceCount(ResourceType resourceType)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT COUNT(*) FROM Resources WHERE resourceType=?");
+    s.BindInt(0, resourceType);
+    
+    if (!s.Step())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    int64_t c = s.ColumnInt(0);
+    assert(!s.Step());
+
+    return c;
+  }
+
+  bool DatabaseWrapper::SelectPatientToRecycle(int64_t& internalId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                        "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1");
+   
+    if (!s.Step())
+    {
+      // No patient remaining or all the patients are protected
+      return false;
+    }
+    else
+    {
+      internalId = s.ColumnInt(0);
+      return true;
+    }    
+  }
+
+  bool DatabaseWrapper::SelectPatientToRecycle(int64_t& internalId,
+                                               int64_t patientIdToAvoid)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                        "SELECT patientId FROM PatientRecyclingOrder "
+                        "WHERE patientId != ? ORDER BY seq ASC LIMIT 1");
+    s.BindInt(0, patientIdToAvoid);
+
+    if (!s.Step())
+    {
+      // No patient remaining or all the patients are protected
+      return false;
+    }
+    else
+    {
+      internalId = s.ColumnInt(0);
+      return true;
+    }   
+  }
+
+  bool DatabaseWrapper::IsProtectedPatient(int64_t internalId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                        "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?");
+    s.BindInt(0, internalId);
+    return !s.Step();
+  }
+
+  void DatabaseWrapper::SetProtectedPatient(int64_t internalId, 
+                                            bool isProtected)
+  {
+    if (isProtected)
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
+      s.BindInt(0, internalId);
+      s.Run();
+    }
+    else if (IsProtectedPatient(internalId))
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
+      s.BindInt(0, internalId);
+      s.Run();
+    }
+    else
+    {
+      // Nothing to do: The patient is already unprotected
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DatabaseWrapper.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,212 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Core/SQLite/Connection.h"
+#include "../Core/SQLite/Transaction.h"
+#include "../Core/DicomFormat/DicomInstanceHasher.h"
+#include "../Core/FileStorage/FileInfo.h"
+#include "IServerIndexListener.h"
+
+#include <list>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    class SignalRemainingAncestor;
+  }
+
+  /**
+   * This class manages an instance of the Orthanc SQLite database. It
+   * translates low-level requests into SQL statements. Mutual
+   * exclusion MUST be implemented at a higher level.
+   **/
+  class DatabaseWrapper
+  {
+  private:
+    IServerIndexListener& listener_;
+    SQLite::Connection db_;
+    Internals::SignalRemainingAncestor* signalRemainingAncestor_;
+
+    void Open();
+
+    void GetChangesInternal(Json::Value& target,
+                            SQLite::Statement& s,
+                            int64_t since,
+                            unsigned int maxResults);
+
+    void GetExportedResources(Json::Value& target,
+                              SQLite::Statement& s,
+                              int64_t since,
+                              unsigned int maxResults);
+
+  public:
+    void SetGlobalProperty(GlobalProperty property,
+                           const std::string& value);
+
+    bool LookupGlobalProperty(std::string& target,
+                              GlobalProperty property);
+
+    std::string GetGlobalProperty(GlobalProperty property,
+                                  const std::string& defaultValue = "");
+
+    int64_t CreateResource(const std::string& publicId,
+                           ResourceType type);
+
+    bool LookupResource(const std::string& publicId,
+                        int64_t& id,
+                        ResourceType& type);
+
+    bool LookupParent(int64_t& parentId,
+                      int64_t resourceId);
+
+    std::string GetPublicId(int64_t resourceId);
+
+    void AttachChild(int64_t parent,
+                     int64_t child);
+
+    void GetChildren(Json::Value& childrenPublicIds,
+                     int64_t id);
+
+    void DeleteResource(int64_t id);
+
+    void SetMetadata(int64_t id,
+                     MetadataType type,
+                     const std::string& value);
+
+    bool LookupMetadata(std::string& target,
+                        int64_t id,
+                        MetadataType type);
+
+    std::string GetMetadata(int64_t id,
+                            MetadataType type,
+                            const std::string& defaultValue = "");
+
+    bool GetMetadataAsInteger(int& result,
+                              int64_t id,
+                              MetadataType type);
+
+    void AddAttachment(int64_t id,
+                       const FileInfo& attachment);
+
+    bool LookupAttachment(FileInfo& attachment,
+                          int64_t id,
+                          FileContentType contentType);
+
+    void SetMainDicomTags(int64_t id,
+                          const DicomMap& tags);
+
+    void GetMainDicomTags(DicomMap& map,
+                          int64_t id);
+
+    bool GetParentPublicId(std::string& result,
+                           int64_t id);
+
+    void GetChildrenPublicId(std::list<std::string>& result,
+                             int64_t id);
+
+    void GetChildrenInternalId(std::list<int64_t>& result,
+                               int64_t id);
+
+    void LogChange(ChangeType changeType,
+                   int64_t internalId,
+                   ResourceType resourceType,
+                   const boost::posix_time::ptime& date = boost::posix_time::second_clock::local_time());
+
+    void GetChanges(Json::Value& target,
+                    int64_t since,
+                    unsigned int maxResults);
+
+    void GetLastChange(Json::Value& target);
+
+    void LogExportedResource(ResourceType resourceType,
+                             const std::string& publicId,
+                             const std::string& remoteModality,
+                             const std::string& patientId,
+                             const std::string& studyInstanceUid,
+                             const std::string& seriesInstanceUid,
+                             const std::string& sopInstanceUid,
+                             const boost::posix_time::ptime& date = 
+                             boost::posix_time::second_clock::local_time());
+    
+    void GetExportedResources(Json::Value& target,
+                              int64_t since,
+                              unsigned int maxResults);
+
+    void GetLastExportedResource(Json::Value& target);
+
+    // For unit testing only!
+    int64_t GetTableRecordCount(const std::string& table);
+    
+    uint64_t GetTotalCompressedSize();
+    
+    uint64_t GetTotalUncompressedSize();
+
+    uint64_t GetResourceCount(ResourceType resourceType);
+
+    void GetAllPublicIds(Json::Value& target,
+                         ResourceType resourceType);
+
+    bool SelectPatientToRecycle(int64_t& internalId);
+
+    bool SelectPatientToRecycle(int64_t& internalId,
+                                int64_t patientIdToAvoid);
+
+    bool IsProtectedPatient(int64_t internalId);
+
+    void SetProtectedPatient(int64_t internalId, 
+                             bool isProtected);
+
+    DatabaseWrapper(const std::string& path,
+                    IServerIndexListener& listener);
+
+    DatabaseWrapper(IServerIndexListener& listener);
+
+    SQLite::Transaction* StartTransaction()
+    {
+      return new SQLite::Transaction(db_);
+    }
+
+    const char* GetErrorMessage() const
+    {
+      return db_.GetErrorMessage();
+    }
+
+    void FlushToDisk()
+    {
+      db_.FlushToDisk();
+    }
+  };
+}
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/DicomProtocol/DicomServer.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/DicomServer.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -47,7 +59,7 @@
     /* make sure data dictionary is loaded */
     if (!dcmDataDict.isDictionaryLoaded())
     {
-      LOG(WARNING) << "no data dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE;
+      LOG(ERROR) << "no data dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE;
     }
 
     /* initialize network, i.e. create an instance of T_ASC_Network*. */
@@ -60,7 +72,7 @@
       throw OrthancException("Cannot create network");
     }
 
-    LOG(WARNING) << "DICOM server started";
+    LOG(INFO) << "DICOM server started";
 
     server->started_ = true;
 
@@ -83,7 +95,7 @@
       }
     }
 
-    LOG(WARNING) << "DICOM server stopping";
+    LOG(INFO) << "DICOM server stopping";
 
     /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */
     /* is the counterpart of ASC_initializeNetwork(...) which was called above. */
--- a/OrthancServer/DicomProtocol/DicomServer.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/DicomServer.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -24,8 +36,6 @@
 #include "../ToDcmtkBridge.h"
 #include "../FromDcmtkBridge.h"
 
-#include <dcmtk/dcmnet/assoc.h>
-#include <dcmtk/dcmnet/dimse.h>
 #include <dcmtk/dcmdata/dcistrmb.h>
 #include <dcmtk/dcmdata/dcistrmf.h>
 #include <dcmtk/dcmdata/dcfilefo.h>
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/FromDcmtkBridge.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -37,21 +49,207 @@
 
 #include <dcmtk/dcmdata/dcdicent.h>
 #include <dcmtk/dcmdata/dcdict.h>
-#include <dcmtk/dcmdata/dcelem.h>
 #include <dcmtk/dcmdata/dcfilefo.h>
 #include <dcmtk/dcmdata/dcistrmb.h>
-#include <dcmtk/dcmdata/dcsequen.h>
 #include <dcmtk/dcmdata/dcvrfd.h>
 #include <dcmtk/dcmdata/dcvrfl.h>
 #include <dcmtk/dcmdata/dcvrsl.h>
 #include <dcmtk/dcmdata/dcvrss.h>
 #include <dcmtk/dcmdata/dcvrul.h>
 #include <dcmtk/dcmdata/dcvrus.h>
+#include <dcmtk/dcmdata/dcuid.h>
 
 #include <boost/math/special_functions/round.hpp>
 
 namespace Orthanc
 {
+  ParsedDicomFile::ParsedDicomFile(const std::string& content)
+  {
+    DcmInputBufferStream is;
+    if (content.size() > 0)
+    {
+      is.setBuffer(&content[0], content.size());
+    }
+    is.setEos();
+
+    file_.reset(new DcmFileFormat);
+    if (!file_->read(is).good())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  static void SendPathValueForDictionary(RestApiOutput& output,
+                                       DcmItem& dicom)
+  {
+    Json::Value v = Json::arrayValue;
+
+    for (unsigned long i = 0; i < dicom.card(); i++)
+    {
+      DcmElement* element = dicom.getElement(i);
+      if (element)
+      {
+        char buf[16];
+        sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag());
+        v.append(buf);
+      }
+    }
+
+    output.AnswerJson(v);
+  }
+
+  static inline uint16_t GetCharValue(char c)
+  {
+    if (c >= '0' && c <= '9')
+      return c - '0';
+    else if (c >= 'a' && c <= 'f')
+      return c - 'a' + 10;
+    else if (c >= 'A' && c <= 'F')
+      return c - 'A' + 10;
+    else
+      return 0;
+  }
+
+  static inline uint16_t GetTagValue(const char* c)
+  {
+    return ((GetCharValue(c[0]) << 12) + 
+            (GetCharValue(c[1]) << 8) + 
+            (GetCharValue(c[2]) << 4) + 
+            GetCharValue(c[3]));
+  }
+
+  static bool ParseTagAndGroup(DcmTagKey& key,
+                               const std::string& tag)
+  {
+    if (tag.size() != 9 ||
+        !isxdigit(tag[0]) ||
+        !isxdigit(tag[1]) ||
+        !isxdigit(tag[2]) ||
+        !isxdigit(tag[3]) ||
+        tag[4] != '-' ||
+        !isxdigit(tag[5]) ||
+        !isxdigit(tag[6]) ||
+        !isxdigit(tag[7]) ||
+        !isxdigit(tag[8]))        
+    {
+      return false;
+    }
+
+    uint16_t group = GetTagValue(tag.c_str());
+    uint16_t element = GetTagValue(tag.c_str() + 5);
+
+    key = DcmTagKey(group, element);
+
+    return true;
+  }
+
+  static void SendPathValueForLeaf(RestApiOutput& output,
+                                   const std::string& tag,
+                                   DcmItem& dicom)
+  {
+    DcmTagKey k;
+    if (!ParseTagAndGroup(k, tag))
+    {
+      return;
+    }
+
+    DcmElement* element = NULL;
+    if (dicom.findAndGetElement(k, element).good() && element != NULL)
+    {
+      if (element->getVR() == EVR_SQ)
+      {
+        // This element is a sequence
+        Json::Value v = Json::arrayValue;
+        DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
+
+        for (unsigned long i = 0; i < sequence.card(); i++)
+        {
+          v.append(boost::lexical_cast<std::string>(i));
+        }
+
+        output.AnswerJson(v);
+      }
+      else
+      {
+        // This element is not a sequence
+        std::string buffer;
+        buffer.resize(65536);
+        Uint32 length = element->getLength();
+        Uint32 offset = 0;
+
+        output.GetLowLevelOutput().SendOkHeader("application/octet-stream", true, length, NULL);
+
+        while (offset < length)
+        {
+          Uint32 nbytes;
+          if (length - offset < buffer.size())
+          {
+            nbytes = length - offset;
+          }
+          else
+          {
+            nbytes = buffer.size();
+          }
+
+          if (element->getPartialValue(&buffer[0], offset, nbytes).good())
+          {
+            output.GetLowLevelOutput().Send(&buffer[0], nbytes);
+            offset += nbytes;
+          }
+          else
+          {
+            return;
+          }
+        }
+
+        output.MarkLowLevelOutputDone();
+      }
+    }
+  }
+
+  void ParsedDicomFile::SendPathValue(RestApiOutput& output,
+                                      const UriComponents& uri)
+  {
+    DcmItem* dicom = file_->getDataset();
+
+    // Go down in the tag hierarchy according to the URI
+    for (size_t pos = 0; pos < uri.size() / 2; pos++)
+    {
+      size_t index;
+      try
+      {
+        index = boost::lexical_cast<size_t>(uri[2 * pos + 1]);
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return;
+      }
+
+      DcmTagKey k;
+      DcmItem *child = NULL;
+      if (!ParseTagAndGroup(k, uri[2 * pos]) ||
+          !dicom->findAndGetSequenceItem(k, child, index).good() ||
+          child == NULL)
+      {
+        return;
+      }
+
+      dicom = child;
+    }
+
+    // We have reached the end of the URI
+    if (uri.size() % 2 == 0)
+    {
+      SendPathValueForDictionary(output, *dicom);
+    }
+    else
+    {
+      SendPathValueForLeaf(output, uri.back(), *dicom);
+    }
+  }
+
+
   void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset)
   {
     target.Clear();
@@ -604,4 +802,26 @@
       result[GetName(it->first)] = it->second->AsString();
     }
   }
+
+
+  std::string FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel level)
+  {
+    char uid[100];
+
+    switch (level)
+    {
+    case DicomRootLevel_Instance:
+      return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
+
+    case DicomRootLevel_Series:
+      return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT);
+
+    case DicomRootLevel_Study:
+      return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT);
+
+    default:
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
 }
--- a/OrthancServer/FromDcmtkBridge.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/FromDcmtkBridge.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -21,8 +33,13 @@
 #pragma once
 
 #include "../Core/DicomFormat/DicomMap.h"
+#include "../Core/RestApi/RestApiOutput.h"
+#include "../Core/Toolbox.h"
+
 #include <dcmtk/dcmdata/dcdatset.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
 #include <json/json.h>
+#include <memory>
 
 namespace Orthanc
 {
@@ -33,6 +50,30 @@
     ImageExtractionMode_UInt16
   };
 
+  enum DicomRootLevel
+  {
+    DicomRootLevel_Study,
+    DicomRootLevel_Series,
+    DicomRootLevel_Instance
+  };
+
+  class ParsedDicomFile : public IDynamicObject
+  {
+  private:
+    std::auto_ptr<DcmFileFormat> file_;
+
+  public:
+    ParsedDicomFile(const std::string& content);
+
+    DcmFileFormat& GetDicom()
+    {
+      return *file_;
+    }
+
+    void SendPathValue(RestApiOutput& output,
+                       const UriComponents& uri);
+  };
+
   class FromDcmtkBridge
   {
   public:
@@ -93,5 +134,7 @@
 
     static void ToJson(Json::Value& result,
                        const DicomMap& values);
+
+    static std::string GenerateUniqueIdentifier(DicomRootLevel level);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/IServerIndexListener.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include "ServerEnumerations.h"
+
+namespace Orthanc
+{
+  class IServerIndexListener
+  {
+  public:
+    virtual ~IServerIndexListener()
+    {
+    }
+
+    virtual void SignalRemainingAncestor(ResourceType parentType,
+                                         const std::string& publicId) = 0;
+
+    virtual void SignalFileDeleted(const FileInfo& info) = 0;
+  };
+}
--- a/OrthancServer/Internals/CommandDispatcher.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/Internals/CommandDispatcher.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/Internals/CommandDispatcher.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -23,7 +35,6 @@
 #include "../DicomProtocol/DicomServer.h"
 #include "../../Core/MultiThreading/IRunnableBySteps.h"
 
-#include <dcmtk/dcmnet/assoc.h>
 #include <dcmtk/dcmnet/dimse.h>
 
 namespace Orthanc
--- a/OrthancServer/Internals/FindScp.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/Internals/FindScp.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -24,11 +36,6 @@
 #include "../ToDcmtkBridge.h"
 #include "../../Core/OrthancException.h"
 
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <dcmtk/dcmdata/dcmetinf.h>
-#include <dcmtk/dcmdata/dcostrmb.h>
-#include <dcmtk/dcmdata/dcdeftag.h>
-#include <dcmtk/dcmnet/diutil.h>
 #include <glog/logging.h>
 
 
--- a/OrthancServer/Internals/FindScp.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/Internals/FindScp.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -22,7 +34,6 @@
 
 #include "../DicomProtocol/IFindRequestHandler.h"
 
-#include <dcmtk/dcmnet/assoc.h>
 #include <dcmtk/dcmnet/dimse.h>
 
 namespace Orthanc
--- a/OrthancServer/Internals/MoveScp.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/Internals/MoveScp.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -26,11 +38,6 @@
 #include "../ToDcmtkBridge.h"
 #include "../../Core/OrthancException.h"
 
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <dcmtk/dcmdata/dcmetinf.h>
-#include <dcmtk/dcmdata/dcostrmb.h>
-#include <dcmtk/dcmdata/dcdeftag.h>
-#include <dcmtk/dcmnet/diutil.h>
 #include <glog/logging.h>
 
 
--- a/OrthancServer/Internals/MoveScp.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/Internals/MoveScp.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -22,7 +34,6 @@
 
 #include "../DicomProtocol/IMoveRequestHandler.h"
 
-#include <dcmtk/dcmnet/assoc.h>
 #include <dcmtk/dcmnet/dimse.h>
 
 namespace Orthanc
--- a/OrthancServer/Internals/StoreScp.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -69,7 +81,7 @@
                                      /*opt_groupLength*/ EGL_recalcGL,
                                      /*opt_paddingType*/ EPD_withoutPadding);
 #else
-      OFCondition c = dataSet->write(ob, xfer, encodingType);
+      OFCondition c = dataSet->write(ob, xfer, encodingType, NULL);
 #endif
 
       dataSet->transferEnd();
--- a/OrthancServer/Internals/StoreScp.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/Internals/StoreScp.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -22,7 +34,6 @@
 
 #include "../DicomProtocol/IStoreRequestHandler.h"
 
-#include <dcmtk/dcmnet/assoc.h>
 #include <dcmtk/dcmnet/dimse.h>
 
 namespace Orthanc
--- a/OrthancServer/OrthancInitialization.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
--- a/OrthancServer/OrthancInitialization.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/OrthancInitialization.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -23,6 +35,7 @@
 #include <string>
 #include <set>
 #include <json/json.h>
+#include <stdint.h>
 #include "../Core/HttpServer/MongooseServer.h"
 
 namespace Orthanc
--- a/OrthancServer/OrthancRestApi.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/OrthancRestApi.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -20,157 +32,50 @@
 
 #include "OrthancRestApi.h"
 
-#include "OrthancInitialization.h"
+#include "../Core/HttpServer/FilesystemHttpSender.h"
+#include "../Core/Uuid.h"
+#include "../Core/Compression/HierarchicalZipWriter.h"
+#include "DicomProtocol/DicomUserConnection.h"
 #include "FromDcmtkBridge.h"
-#include "../Core/Uuid.h"
+#include "OrthancInitialization.h"
+#include "ServerToolbox.h"
 
 #include <dcmtk/dcmdata/dcistrmb.h>
 #include <dcmtk/dcmdata/dcfilefo.h>
 #include <boost/lexical_cast.hpp>
+#include <glog/logging.h>
+
+
+#define RETRIEVE_CONTEXT(call)                          \
+  OrthancRestApi& contextApi =                          \
+    dynamic_cast<OrthancRestApi&>(call.GetContext());   \
+  ServerContext& context = contextApi.GetContext()
+
+#define RETRIEVE_MODALITIES(call)                                       \
+  const OrthancRestApi::Modalities& modalities =                        \
+    dynamic_cast<OrthancRestApi&>(call.GetContext()).GetModalities();
+
+
 
 namespace Orthanc
 {
-  static void SendJson(HttpOutput& output,
-                       const Json::Value& value)
-  {
-    Json::StyledWriter writer;
-    std::string s = writer.write(value);
-    output.AnswerBufferWithContentType(s, "application/json");
-  }
-
-
-  static void SimplifyTagsRecursion(Json::Value& target,
-                                    const Json::Value& source)
-  {
-    assert(source.isObject());
-
-    target = Json::objectValue;
-    Json::Value::Members members = source.getMemberNames();
-
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      const Json::Value& v = source[members[i]];
-      const std::string& name = v["Name"].asString();
-      const std::string& type = v["Type"].asString();
-
-      if (type == "String")
-      {
-        target[name] = v["Value"].asString();
-      }
-      else if (type == "TooLong" ||
-               type == "Null")
-      {
-        target[name] = Json::nullValue;
-      }
-      else if (type == "Sequence")
-      {
-        const Json::Value& array = v["Value"];
-        assert(array.isArray());
-
-        Json::Value children = Json::arrayValue;
-        for (size_t i = 0; i < array.size(); i++)
-        {
-          Json::Value c;
-          SimplifyTagsRecursion(c, array[i]);
-          children.append(c);
-        }
-
-        target[name] = children;
-      }
-      else
-      {
-        assert(0);
-      }
-    }
-  }
-
-
-  static void SimplifyTags(Json::Value& target,
-                           const FileStorage& storage,
-                           const std::string& fileUuid)
-  {
-    std::string s;
-    storage.ReadFile(s, fileUuid);
+  // DICOM SCU ----------------------------------------------------------------
 
-    Json::Value source;
-    Json::Reader reader;
-    if (!reader.parse(s, source))
-    {
-      throw OrthancException("Corrupted JSON file");
-    }
-
-    SimplifyTagsRecursion(target, source);
-  }
-
-
-  bool OrthancRestApi::Store(Json::Value& result,
-                               const std::string& postData)
-  {
-    // Prepare an input stream for the memory buffer
-    DcmInputBufferStream is;
-    if (postData.size() > 0)
-    {
-      is.setBuffer(&postData[0], postData.size());
-    }
-    is.setEos();
-
-    //printf("[%d]\n", postData.size());
-
-    DcmFileFormat dicomFile;
-    if (dicomFile.read(is).good())
-    {
-      DicomMap dicomSummary;
-      FromDcmtkBridge::Convert(dicomSummary, *dicomFile.getDataset());
-          
-      Json::Value dicomJson;
-      FromDcmtkBridge::ToJson(dicomJson, *dicomFile.getDataset());
-      
-      std::string instanceUuid;
-      StoreStatus status = StoreStatus_Failure;
-      if (postData.size() > 0)
-      {
-        status = index_.Store
-          (instanceUuid, storage_, reinterpret_cast<const char*>(&postData[0]),
-           postData.size(), dicomSummary, dicomJson, "");
-      }
-
-      switch (status)
-      {
-      case StoreStatus_Success:
-        result["ID"] = instanceUuid;
-        result["Path"] = "/instances/" + instanceUuid;
-        result["Status"] = "Success";
-        return true;
-      
-      case StoreStatus_AlreadyStored:
-        result["ID"] = instanceUuid;
-        result["Path"] = "/instances/" + instanceUuid;
-        result["Status"] = "AlreadyStored";
-        return true;
-
-      default:
-        return false;
-      }
-    }
-
-    return false;
-  }
-
-  void OrthancRestApi::ConnectToModality(DicomUserConnection& c,
-                                           const std::string& name)
+  static void ConnectToModality(DicomUserConnection& connection,
+                                const std::string& name)
   {
     std::string aet, address;
     int port;
     GetDicomModality(name, aet, address, port);
-    c.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC"));
-    c.SetDistantApplicationEntityTitle(aet);
-    c.SetDistantHost(address);
-    c.SetDistantPort(port);
-    c.Open();
+    connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC"));
+    connection.SetDistantApplicationEntityTitle(aet);
+    connection.SetDistantHost(address);
+    connection.SetDistantPort(port);
+    connection.Open();
   }
 
-  bool OrthancRestApi::MergeQueryAndTemplate(DicomMap& result,
-                                               const std::string& postData)
+  static bool MergeQueryAndTemplate(DicomMap& result,
+                                    const std::string& postData)
   {
     Json::Value query;
     Json::Reader reader;
@@ -191,100 +96,110 @@
     return true;
   }
 
-  bool OrthancRestApi::DicomFindPatient(Json::Value& result,
-                                          DicomUserConnection& c,
-                                          const std::string& postData)
+  static void DicomFindPatient(RestApi::PostCall& call)
   {
     DicomMap m;
     DicomMap::SetupFindPatientTemplate(m);
-    if (!MergeQueryAndTemplate(m, postData))
+    if (!MergeQueryAndTemplate(m, call.GetPostBody()))
     {
-      return false;
+      return;
     }
 
+    DicomUserConnection connection;
+    ConnectToModality(connection, call.GetUriComponent("id", ""));
+
     DicomFindAnswers answers;
-    c.FindPatient(answers, m);
+    connection.FindPatient(answers, m);
+
+    Json::Value result;
     answers.ToJson(result);
-    return true;
+    call.GetOutput().AnswerJson(result);
   }
 
-  bool OrthancRestApi::DicomFindStudy(Json::Value& result,
-                                        DicomUserConnection& c,
-                                        const std::string& postData)
+  static void DicomFindStudy(RestApi::PostCall& call)
   {
     DicomMap m;
     DicomMap::SetupFindStudyTemplate(m);
-    if (!MergeQueryAndTemplate(m, postData))
+    if (!MergeQueryAndTemplate(m, call.GetPostBody()))
     {
-      return false;
+      return;
     }
 
     if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 &&
         m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2)
     {
-      return false;
+      return;
     }        
-        
+      
+    DicomUserConnection connection;
+    ConnectToModality(connection, call.GetUriComponent("id", ""));
+  
     DicomFindAnswers answers;
-    c.FindStudy(answers, m);
+    connection.FindStudy(answers, m);
+
+    Json::Value result;
     answers.ToJson(result);
-    return true;
+    call.GetOutput().AnswerJson(result);
   }
 
-  bool OrthancRestApi::DicomFindSeries(Json::Value& result,
-                                         DicomUserConnection& c,
-                                         const std::string& postData)
+  static void DicomFindSeries(RestApi::PostCall& call)
   {
     DicomMap m;
     DicomMap::SetupFindSeriesTemplate(m);
-    if (!MergeQueryAndTemplate(m, postData))
+    if (!MergeQueryAndTemplate(m, call.GetPostBody()))
     {
-      return false;
+      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 false;
+      return;
     }        
-        
+         
+    DicomUserConnection connection;
+    ConnectToModality(connection, call.GetUriComponent("id", ""));
+  
     DicomFindAnswers answers;
-    c.FindSeries(answers, m);
+    connection.FindSeries(answers, m);
+
+    Json::Value result;
     answers.ToJson(result);
-    return true;
+    call.GetOutput().AnswerJson(result);
   }
 
-  bool OrthancRestApi::DicomFind(Json::Value& result,
-                                   DicomUserConnection& c,
-                                   const std::string& postData)
+  static void DicomFind(RestApi::PostCall& call)
   {
     DicomMap m;
     DicomMap::SetupFindPatientTemplate(m);
-    if (!MergeQueryAndTemplate(m, postData))
+    if (!MergeQueryAndTemplate(m, call.GetPostBody()))
     {
-      return false;
+      return;
     }
-
+ 
+    DicomUserConnection connection;
+    ConnectToModality(connection, call.GetUriComponent("id", ""));
+  
     DicomFindAnswers patients;
-    c.FindPatient(patients, m);
+    connection.FindPatient(patients, m);
 
     // Loop over the found patients
-    result = Json::arrayValue;
+    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, postData))
+      if (!MergeQueryAndTemplate(m, call.GetPostBody()))
       {
-        return false;
+        return;
       }
       m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID);
 
       DicomFindAnswers studies;
-      c.FindStudy(studies, m);
+      connection.FindStudy(studies, m);
 
       patient["Studies"] = Json::arrayValue;
       
@@ -295,15 +210,15 @@
         FromDcmtkBridge::ToJson(study, studies.GetAnswer(j));
 
         DicomMap::SetupFindSeriesTemplate(m);
-        if (!MergeQueryAndTemplate(m, postData))
+        if (!MergeQueryAndTemplate(m, call.GetPostBody()))
         {
-          return false;
+          return;
         }
         m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID);
         m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID);
 
         DicomFindAnswers series;
-        c.FindSeries(series, m);
+        connection.FindSeries(series, m);
 
         // Loop over the found series
         study["Series"] = Json::arrayValue;
@@ -320,489 +235,690 @@
       result.append(patient);
     }
     
-    return true;
+    call.GetOutput().AnswerJson(result);
+  }
+
+
+  static void DicomStore(RestApi::PostCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+
+    std::string remote = call.GetUriComponent("id", "");
+    DicomUserConnection connection;
+    ConnectToModality(connection, remote);
+
+    const std::string& resourceId = call.GetPostBody();
+
+    Json::Value found;
+    if (context.GetIndex().LookupResource(found, resourceId, ResourceType_Series))
+    {
+      // The UUID corresponds to a series
+      context.GetIndex().LogExportedResource(resourceId, remote);
+
+      for (Json::Value::ArrayIndex i = 0; i < found["Instances"].size(); i++)
+      {
+        std::string instanceId = found["Instances"][i].asString();
+        std::string dicom;
+        context.ReadFile(dicom, instanceId, FileContentType_Dicom);
+        connection.Store(dicom);
+      }
+
+      call.GetOutput().AnswerBuffer("{}", "application/json");
+    }
+    else if (context.GetIndex().LookupResource(found, resourceId, ResourceType_Instance))
+    {
+      // The UUID corresponds to an instance
+      context.GetIndex().LogExportedResource(resourceId, remote);
+
+      std::string dicom;
+      context.ReadFile(dicom, resourceId, FileContentType_Dicom);
+      connection.Store(dicom);
+
+      call.GetOutput().AnswerBuffer("{}", "application/json");
+    }
+    else
+    {
+      // The POST body is not a known resource, assume that it
+      // contains a raw DICOM instance
+      connection.Store(resourceId);
+      call.GetOutput().AnswerBuffer("{}", "application/json");
+    }
   }
 
 
 
-  bool OrthancRestApi::DicomStore(Json::Value& result,
-                                    DicomUserConnection& c,
-                                    const std::string& postData)
+  // System information -------------------------------------------------------
+
+  static void ServeRoot(RestApi::GetCall& call)
   {
-    Json::Value found(Json::objectValue);
+    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);
+  }
 
-    if (!Toolbox::IsUuid(postData))
-    {
-      // This is not a UUID, assume this is a DICOM instance
-      c.Store(postData);
-    }
-    else if (index_.GetSeries(found, postData))
+  static void GetStatistics(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+    Json::Value result = Json::objectValue;
+    context.GetIndex().ComputeStatistics(result);
+    call.GetOutput().AnswerJson(result);
+  }
+
+
+  // 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))
     {
-      // The UUID corresponds to a series
-      for (size_t i = 0; i < found["Instances"].size(); i++)
+      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:
       {
-        std::string uuid = found["Instances"][i].asString();
-        Json::Value instance(Json::objectValue);
-        if (index_.GetInstance(instance, uuid))
-        {
-          std::string content;
-          storage_.ReadFile(content, instance["FileUuid"].asString());
-          c.Store(content);
-        }
-        else
+        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);
     }
-    else if (index_.GetInstance(found, postData))
-    {
-      // The UUID corresponds to an instance
-      std::string content;
-      storage_.ReadFile(content, found["FileUuid"].asString());
-      c.Store(content);
-    }
-    else
+
+    writer.OpenDirectory(GetDirectoryNameInArchive(parent, parentType).c_str());
+    return true;
+  }
+
+  static bool ArchiveInstance(HierarchicalZipWriter& writer,
+                              ServerContext& context,
+                              const std::string& instancePublicId)
+  {
+    Json::Value instance;
+    if (!context.GetIndex().LookupResource(instance, instancePublicId, ResourceType_Instance))
     {
       return false;
     }
 
+    std::string filename = instance["MainDicomTags"]["SOPInstanceUID"].asString() + ".dcm";
+    writer.OpenFile(filename.c_str());
+
+    std::string dicom;
+    context.ReadFile(dicom, instancePublicId, FileContentType_Dicom);
+    writer.Write(dicom);
+
     return true;
   }
 
-
-  OrthancRestApi::OrthancRestApi(ServerIndex& index,
-                                     const std::string& path) :
-    index_(index),
-    storage_(path)
+  static bool ArchiveInternal(HierarchicalZipWriter& writer,
+                              ServerContext& context,
+                              const std::string& publicId,
+                              ResourceType resourceType,
+                              bool isFirstLevel)
   {
-    GetListOfDicomModalities(modalities_);
-  }
-
-
-  void OrthancRestApi::Handle(
-    HttpOutput& output,
-    const std::string& method,
-    const UriComponents& uri,
-    const Arguments& headers,
-    const Arguments& arguments,
-    const std::string& postData)
-  {
-    if (uri.size() == 0)
+    Json::Value resource;
+    if (!context.GetIndex().LookupResource(resource, publicId, resourceType))
     {
-      if (method == "GET")
-      {
-        output.Redirect("app/explorer.html");
-      }
-      else
-      {
-        output.SendMethodNotAllowedError("GET");
-      }
-
-      return;
+      return false;
     }
 
-    bool existingResource = false;
-    Json::Value result(Json::objectValue);
-
-
-    // List all the instances ---------------------------------------------------
- 
-    if (uri.size() == 1 && uri[0] == "instances")
+    if (isFirstLevel && 
+        !CreateRootDirectoryInArchive(writer, context, resource, resourceType))
     {
-      if (method == "GET")
-      {
-        result = Json::Value(Json::arrayValue);
-        index_.GetAllUuids(result, "Instances");
-        existingResource = true;
-      }
-      else if (method == "POST")
-      {
-        // Add a new instance to the storage
-        if (Store(result, postData))
-        {
-          SendJson(output, result);
-          return;
-        }
-        else
-        {
-          output.SendHeader(Orthanc_HttpStatus_415_UnsupportedMediaType);
-          return;
-        }
-      }
-      else
-      {
-        output.SendMethodNotAllowedError("GET,POST");
-        return;
-      }
+      return false;
     }
 
+    writer.OpenDirectory(GetDirectoryNameInArchive(resource, resourceType).c_str());
 
-    // List all the patients, studies or series ---------------------------------
- 
-    if (uri.size() == 1 && 
-        (uri[0] == "series" ||
-         uri[0] == "studies" ||
-         uri[0] == "patients"))
+    switch (resourceType)
     {
-      if (method == "GET")
-      {
-        result = Json::Value(Json::arrayValue);
+      case ResourceType_Patient:
+        for (size_t 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;
 
-        if (uri[0] == "instances")
-          index_.GetAllUuids(result, "Instances");
-        else if (uri[0] == "series")
-          index_.GetAllUuids(result, "Series");
-        else if (uri[0] == "studies")
-          index_.GetAllUuids(result, "Studies");
-        else if (uri[0] == "patients")
-          index_.GetAllUuids(result, "Patients");
+      case ResourceType_Study:
+        for (size_t 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;
 
-        existingResource = true;
-      }
-      else
-      {
-        output.SendMethodNotAllowedError("GET");
-        return;
-      }
+      case ResourceType_Series:
+        for (size_t i = 0; i < resource["Instances"].size(); i++)
+        {
+          if (!ArchiveInstance(writer, context, resource["Instances"][i].asString()))
+          {
+            return false;
+          }
+        }
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
     }
 
+    writer.CloseDirectory();
+    return true;
+  }                                 
 
-    // Information about a single object ----------------------------------------
- 
-    else if (uri.size() == 2 && 
-             (uri[0] == "instances" ||
-              uri[0] == "series" ||
-              uri[0] == "studies" ||
-              uri[0] == "patients"))
+  template <enum ResourceType resourceType>
+  static void GetArchive(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+
+    // Create a RAII for the temporary file to manage the ZIP file
+    Toolbox::TemporaryFile tmp;
+    std::string id = call.GetUriComponent("id", "");
+
     {
-      if (method == "GET")
+      // Create a ZIP writer
+      HierarchicalZipWriter writer(tmp.GetPath().c_str());
+
+      // Store the requested resource into the ZIP
+      if (!ArchiveInternal(writer, context, id, resourceType, true))
       {
-        if (uri[0] == "patients")
-        {
-          existingResource = index_.GetPatient(result, uri[1]);
-        }
-        else if (uri[0] == "studies")
-        {
-          existingResource = index_.GetStudy(result, uri[1]);
-        }
-        else if (uri[0] == "series")
-        {
-          existingResource = index_.GetSeries(result, uri[1]);
-        }
-        else if (uri[0] == "instances")
-        {
-          existingResource = index_.GetInstance(result, uri[1]);
-        }
-      }
-      else if (method == "DELETE")
-      {
-        if (uri[0] == "patients")
-        {
-          existingResource = index_.DeletePatient(result, uri[1]);
-        }
-        else if (uri[0] == "studies")
-        {
-          existingResource = index_.DeleteStudy(result, uri[1]);
-        }
-        else if (uri[0] == "series")
-        {
-          existingResource = index_.DeleteSeries(result, uri[1]);
-        }
-        else if (uri[0] == "instances")
-        {
-          existingResource = index_.DeleteInstance(result, uri[1]);
-        }
-
-        if (existingResource)
-        {
-          result["Status"] = "Success";
-        }
-      }
-      else
-      {
-        output.SendMethodNotAllowedError("GET,DELETE");
         return;
       }
     }
 
+    // Prepare the sending of the ZIP file
+    FilesystemHttpSender sender(tmp.GetPath().c_str());
+    sender.SetContentType("application/zip");
+    sender.SetDownloadFilename(id + ".zip");
 
-    // Get the DICOM or the JSON file of one instance ---------------------------
+    // Send the ZIP
+    call.GetOutput().AnswerFile(sender);
+
+    // The temporary file is automatically removed thanks to the RAII
+  }
+
+
+  // Changes API --------------------------------------------------------------
  
-    else if (uri.size() == 3 &&
-             uri[0] == "instances" &&
-             (uri[2] == "file" || 
-              uri[2] == "tags" || 
-              uri[2] == "simplified-tags"))
+  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"))
     {
-      std::string fileUuid, contentType;
-      if (uri[2] == "file")
-      {
-        existingResource = index_.GetDicomFile(fileUuid, uri[1]);
-        contentType = "application/dicom";
-      }
-      else if (uri[2] == "tags" ||
-               uri[2] == "simplified-tags")
-      {
-        existingResource = index_.GetJsonFile(fileUuid, uri[1]);
-        contentType = "application/json";
-      }
+      last = true;
+      return;
+    }
+
+    last = false;
 
-      if (existingResource)
-      {
-        if (uri[2] == "simplified-tags")
-        {
-          Json::Value v;
-          SimplifyTags(v, storage_, fileUuid);
-          SendJson(output, v);
-          return;
-        }
-        else
-        {
-          output.AnswerFile(storage_, fileUuid, contentType);
-          return;
-        }
-      }
+    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;
+    }
+  }
 
-    else if (uri.size() == 3 &&
-             uri[0] == "instances" &&
-             uri[2] == "frames")
+  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)))
     {
-      Json::Value instance(Json::objectValue);
-      existingResource = index_.GetInstance(instance, uri[1]);
+      call.GetOutput().AnswerJson(result);
+    }
+  }
+
+
+  static void GetExports(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
 
-      if (existingResource)
-      {
-        result = Json::arrayValue;
+    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);
+    }
+  }
 
-        unsigned int numberOfFrames = 1;
-        try
-        {
-          Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"];
-          numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString());
-        }
-        catch (boost::bad_lexical_cast)
-        {
-        }
+  
+  // 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());
 
-        for (unsigned int i = 0; i < numberOfFrames; i++)
-        {
-          result.append(i);
-        }                
-      }
+    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);
+  }
 
 
-    else if (uri[0] == "instances" &&
-             ((uri.size() == 3 &&
-               (uri[2] == "preview" || 
-                uri[2] == "image-uint8" || 
-                uri[2] == "image-uint16")) ||
-              (uri.size() == 5 &&
-               uri[2] == "frames" &&
-               (uri[4] == "preview" || 
-                uri[4] == "image-uint8" || 
-                uri[4] == "image-uint16"))))
+  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)
     {
-      std::string uuid;
-      existingResource = index_.GetDicomFile(uuid, uri[1]);
+      Json::Value simplified;
+      SimplifyTags(simplified, full);
+      call.GetOutput().AnswerJson(simplified);
+    }
+    else
+    {
+      call.GetOutput().AnswerJson(full);
+    }
+  }
 
-      std::string action = uri[2];
+  
+  static void ListFrames(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
 
-      unsigned int frame = 0;
-      if (existingResource &&
-          uri.size() == 5)
+    Json::Value instance;
+    if (context.GetIndex().LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance))
+    {
+      unsigned int numberOfFrames = 1;
+
+      try
       {
-        // Access to multi-frame image
-        action = uri[4];
-        try
-        {
-          frame = boost::lexical_cast<unsigned int>(uri[3]);
-        }
-        catch (boost::bad_lexical_cast)
-        {
-          existingResource = false;
-        }
+        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);
       }
 
-      if (existingResource)
+      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)
       {
-        std::string dicomContent, png;
-        storage_.ReadFile(dicomContent, uuid);
-        try
+        // 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++)
         {
-          if (action == "preview")
-          {
-            FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, ImageExtractionMode_Preview);
-          }
-          else if (action == "image-uint8")
-          {
-            FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, ImageExtractionMode_UInt8);
-          }
-          else if (action == "image-uint16")
-          {
-            FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, ImageExtractionMode_UInt16);
-          }
-          else
-          {
-            throw OrthancException(ErrorCode_InternalError);
-          }
+          root += "../";
+        }
 
-          output.AnswerBufferWithContentType(png, "image/png");
-          return;
-        }
-        catch (OrthancException&)
-        {
-          std::string root = "";
-          for (size_t i = 1; i < uri.size(); i++)
-          {
-            root += "../";
-          }
-
-          output.Redirect(root + "app/images/Unsupported.png");
-          return;
-        }
+        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();
+
+    LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP";
+
+    // Prepare an input stream for the memory buffer
+    DcmInputBufferStream is;
+    if (postData.size() > 0)
+    {
+      is.setBuffer(&postData[0], postData.size());
+    }
+    is.setEos();
+
+    DcmFileFormat dicomFile;
+    if (!dicomFile.read(is).good())
+    {
+      call.GetOutput().SignalError(Orthanc_HttpStatus_415_UnsupportedMediaType);
+      return;
+    }
+
+    DicomMap dicomSummary;
+    FromDcmtkBridge::Convert(dicomSummary, *dicomFile.getDataset());
+
+    DicomInstanceHasher hasher(dicomSummary);
+
+    Json::Value dicomJson;
+    FromDcmtkBridge::ToJson(dicomJson, *dicomFile.getDataset());
+      
+    StoreStatus status = StoreStatus_Failure;
+    if (postData.size() > 0)
+    {
+      status = context.Store
+        (reinterpret_cast<const char*>(&postData[0]),
+         postData.size(), dicomSummary, dicomJson, "");
+    }
+
+    Json::Value result = Json::objectValue;
+
+    if (status != StoreStatus_Failure)
+    {
+      result["ID"] = hasher.HashInstance();
+      result["Path"] = GetBasePath(ResourceType_Instance, hasher.HashInstance());
+    }
+
+    result["Status"] = ToString(status);
+    call.GetOutput().AnswerJson(result);
+  }
+
+
+  // DICOM bridge -------------------------------------------------------------
+
+  static bool IsExistingModality(const OrthancRestApi::Modalities& modalities,
+                                 const std::string& id)
+  {
+    return modalities.find(id) != modalities.end();
+  }
+
+  static void ListModalities(RestApi::GetCall& call)
+  {
+    RETRIEVE_MODALITIES(call);
+
+    Json::Value result = Json::arrayValue;
+    for (OrthancRestApi::Modalities::const_iterator 
+           it = modalities.begin(); it != modalities.end(); it++)
+    {
+      result.append(*it);
+    }
+
+    call.GetOutput().AnswerJson(result);
+  }
+
+
+  static void ListModalityOperations(RestApi::GetCall& call)
+  {
+    RETRIEVE_MODALITIES(call);
+
+    std::string id = call.GetUriComponent("id", "");
+    if (IsExistingModality(modalities, id))
+    {
+      Json::Value result = Json::arrayValue;
+      result.append("find-patient");
+      result.append("find-study");
+      result.append("find-series");
+      result.append("find");
+      result.append("store");
+      call.GetOutput().AnswerJson(result);
+    }
+  }
 
 
 
-    // Changes API --------------------------------------------------------------
- 
-    if (uri.size() == 1 && uri[0] == "changes")
-    {
-      if (method == "GET")
-      {
-        const static unsigned int MAX_RESULTS = 100;
+  // Raw access to the DICOM tags of an instance ------------------------------
+
+  static void GetRawContent(RestApi::GetCall& call)
+  {
+    // TODO IMPROVE MULTITHREADING
+    static boost::mutex mutex_;
+    boost::mutex::scoped_lock lock(mutex_);
 
-        std::string filter = GetArgument(arguments, "filter", "");
-        int64_t since;
-        unsigned int limit;
-        try
-        {
-          since = boost::lexical_cast<int64_t>(GetArgument(arguments, "since", "0"));
-          limit = boost::lexical_cast<unsigned int>(GetArgument(arguments, "limit", "0"));
-        }
-        catch (boost::bad_lexical_cast)
-        {
-          output.SendHeader(Orthanc_HttpStatus_400_BadRequest);
-          return;
-        }
+    RETRIEVE_CONTEXT(call);
+    std::string id = call.GetUriComponent("id", "");
+    ParsedDicomFile& dicom = context.GetDicomFile(id);
+    dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri());
+  }
 
-        if (limit == 0 || limit > MAX_RESULTS)
-        {
-          limit = MAX_RESULTS;
-        }
 
-        if (!index_.GetChanges(result, since, filter, limit))
-        {
-          output.SendHeader(Orthanc_HttpStatus_400_BadRequest);
-          return;
-        }
-
-        existingResource = true;
-      }
-      else
-      {
-        output.SendMethodNotAllowedError("GET");
-        return;
-      }
-    }
 
 
-    // DICOM bridge -------------------------------------------------------------
+  // Registration of the various REST handlers --------------------------------
 
-    if (uri.size() == 1 &&
-        uri[0] == "modalities")
-    {
-      if (method == "GET")
-      {
-        result = Json::Value(Json::arrayValue);
-        existingResource = true;
+  OrthancRestApi::OrthancRestApi(ServerContext& context) : 
+    context_(context)
+  {
+    GetListOfDicomModalities(modalities_);
+
+    Register("/", ServeRoot);
+    Register("/system", GetSystemInformation);
+    Register("/statistics", GetStatistics);
+    Register("/changes", GetChanges);
+    Register("/exports", GetExports);
 
-        for (Modalities::const_iterator it = modalities_.begin(); 
-             it != modalities_.end(); it++)
-        {
-          result.append(*it);
-        }
-      }
-      else
-      {
-        output.SendMethodNotAllowedError("GET");
-        return;
-      }
-    }
+    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>);
 
-    if ((uri.size() == 2 ||
-         uri.size() == 3) && 
-        uri[0] == "modalities")
-    {
-      if (modalities_.find(uri[1]) == modalities_.end())
-      {
-        // Unknown modality
-      }
-      else if (uri.size() == 2)
-      {
-        if (method != "GET")
-        {
-          output.SendMethodNotAllowedError("POST");
-          return;
-        }
-        else
-        {
-          existingResource = true;
-          result = Json::arrayValue;
-          result.append("find-patient");
-          result.append("find-study");
-          result.append("find-series");
-          result.append("find");
-          result.append("store");
-        }
-      }
-      else if (uri.size() == 3)
-      {
-        if (uri[2] != "find-patient" &&
-            uri[2] != "find-study" &&
-            uri[2] != "find-series" &&
-            uri[2] != "find" &&
-            uri[2] != "store")
-        {
-          // Unknown request
-        }
-        else if (method != "POST")
-        {
-          output.SendMethodNotAllowedError("POST");
-          return;
-        }
-        else
-        {
-          DicomUserConnection connection;
-          ConnectToModality(connection, uri[1]);
-          existingResource = true;
-          
-          if ((uri[2] == "find-patient" && !DicomFindPatient(result, connection, postData)) ||
-              (uri[2] == "find-study" && !DicomFindStudy(result, connection, postData)) ||
-              (uri[2] == "find-series" && !DicomFindSeries(result, connection, postData)) ||
-              (uri[2] == "find" && !DicomFind(result, connection, postData)) ||
-              (uri[2] == "store" && !DicomStore(result, connection, postData)))
-          {
-            output.SendHeader(Orthanc_HttpStatus_400_BadRequest);
-            return;
-          }
-        }
-      }
-    }
+    Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>);
+    Register("/studies/{id}/archive", GetArchive<ResourceType_Study>);
+    Register("/series/{id}/archive", GetArchive<ResourceType_Series>);
+
+    Register("/patients/{id}/protected", IsProtectedPatient);
+    Register("/patients/{id}/protected", SetPatientProtection);
+    Register("/instances/{id}/file", GetInstanceFile);
+    Register("/instances/{id}/tags", GetInstanceTags<false>);
+    Register("/instances/{id}/simplified-tags", GetInstanceTags<true>);
+    Register("/instances/{id}/frames", ListFrames);
+    Register("/instances/{id}/content/*", GetRawContent);
 
- 
-    if (existingResource)
-    {
-      SendJson(output, result);
-    }
-    else
-    {
-      output.SendHeader(Orthanc_HttpStatus_404_NotFound);
-    }
+    Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>);
+    Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
+    Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
+    Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>);
+    Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
+    Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
+
+    Register("/modalities", ListModalities);
+    Register("/modalities/{id}", ListModalityOperations);
+    Register("/modalities/{id}/find-patient", DicomFindPatient);
+    Register("/modalities/{id}/find-study", DicomFindStudy);
+    Register("/modalities/{id}/find-series", DicomFindSeries);
+    Register("/modalities/{id}/find", DicomFind);
+    Register("/modalities/{id}/store", DicomStore);
+
+    // TODO : "content"
   }
 }
--- a/OrthancServer/OrthancRestApi.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/OrthancRestApi.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -20,68 +32,33 @@
 
 #pragma once
 
-#include "../Core/HttpServer/HttpHandler.h"
-#include "ServerIndex.h"
-#include "DicomProtocol/DicomUserConnection.h"
+#include "ServerContext.h"
+#include "../Core/RestApi/RestApi.h"
 
 #include <set>
 
-
 namespace Orthanc
 {
-  class OrthancRestApi : public HttpHandler
+  class OrthancRestApi : public RestApi
   {
-  private:
+  public:
     typedef std::set<std::string> Modalities;
 
-    ServerIndex& index_;
-    FileStorage storage_;
+  private:
+    ServerContext& context_;
     Modalities modalities_;
 
-    bool Store(Json::Value& result,
-               const std::string& postData);
-
-    void ConnectToModality(DicomUserConnection& c,
-                           const std::string& name);
-
-    bool MergeQueryAndTemplate(DicomMap& result,
-                               const std::string& postData);
-
-    bool DicomFindPatient(Json::Value& result,
-                          DicomUserConnection& c,
-                          const std::string& postData);
-
-    bool DicomFindStudy(Json::Value& result,
-                        DicomUserConnection& c,
-                        const std::string& postData);
+  public:
+    OrthancRestApi(ServerContext& context);
 
-    bool DicomFindSeries(Json::Value& result,
-                         DicomUserConnection& c,
-                         const std::string& postData);
-
-    bool DicomFind(Json::Value& result,
-                   DicomUserConnection& c,
-                   const std::string& postData);
-
-    bool DicomStore(Json::Value& result,
-                    DicomUserConnection& c,
-                    const std::string& postData);
-
-  public:
-    OrthancRestApi(ServerIndex& index,
-                     const std::string& path);
-
-    virtual bool IsServedUri(const UriComponents& uri)
+    ServerContext& GetContext()
     {
-      return true;
+      return context_;
     }
 
-    virtual void Handle(
-      HttpOutput& output,
-      const std::string& method,
-      const UriComponents& uri,
-      const Arguments& headers,
-      const Arguments& arguments,
-      const std::string& postData);
+    Modalities& GetModalities()
+    {
+      return modalities_;
+    }
   };
 }
--- a/OrthancServer/PrepareDatabase.sql	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/PrepareDatabase.sql	Fri Dec 14 11:24:24 2012 +0100
@@ -1,136 +1,106 @@
 CREATE TABLE GlobalProperties(
-       name TEXT PRIMARY KEY,
+       property INTEGER PRIMARY KEY,
        value TEXT
        );
 
 CREATE TABLE Resources(
-       uuid TEXT PRIMARY KEY,
-       resourceType INTEGER
-       );
-
-CREATE TABLE Patients(
-       uuid TEXT PRIMARY KEY,
-       dicomPatientId TEXT
-       );
-
-CREATE TABLE Studies(
-       uuid TEXT PRIMARY KEY,
-       parentPatient TEXT REFERENCES Patients(uuid) ON DELETE CASCADE,
-       dicomStudy TEXT
-       );
-
-CREATE TABLE Series(
-       uuid TEXT PRIMARY KEY,
-       parentStudy TEXT REFERENCES Studies(uuid) ON DELETE CASCADE,
-       dicomSeries TEXT,
-       expectedNumberOfInstances INTEGER
-       );
-
-CREATE TABLE Instances(
-       uuid TEXT PRIMARY KEY,
-       parentSeries TEXT REFERENCES Series(uuid) ON DELETE CASCADE,
-       dicomInstance TEXT,
-       fileUuid TEXT,
-       fileSize INTEGER,
-       jsonUuid TEXT,
-       distantAet TEXT,
-       indexInSeries INTEGER
+       internalId INTEGER PRIMARY KEY AUTOINCREMENT,
+       resourceType INTEGER,
+       publicId TEXT,
+       parentId INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE
        );
 
 CREATE TABLE MainDicomTags(
-       uuid TEXT,
+       id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,
        tagGroup INTEGER,
        tagElement INTEGER,
        value TEXT,
-       PRIMARY KEY(uuid, tagGroup, tagElement)
+       PRIMARY KEY(id, tagGroup, tagElement)
+       );
+
+CREATE TABLE Metadata(
+       id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,
+       type INTEGER,
+       value TEXT,
+       PRIMARY KEY(id, type)
        );
 
+CREATE TABLE AttachedFiles(
+       id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,
+       fileType INTEGER,
+       uuid TEXT,
+       compressedSize INTEGER,
+       uncompressedSize INTEGER,
+       compressionType INTEGER,
+       PRIMARY KEY(id, fileType)
+       );              
+
 CREATE TABLE Changes(
        seq INTEGER PRIMARY KEY AUTOINCREMENT,
-       basePath TEXT,
-       uuid TEXT
+       changeType INTEGER,
+       internalId INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,
+       resourceType INTEGER,
+       date TEXT
+       );
+
+CREATE TABLE ExportedResources(
+       seq INTEGER PRIMARY KEY AUTOINCREMENT,
+       resourceType INTEGER,
+       publicId TEXT,
+       remoteModality TEXT,
+       patientId TEXT,
+       studyInstanceUid TEXT,
+       seriesInstanceUid TEXT,
+       sopInstanceUid TEXT,
+       date TEXT
+       ); 
+
+CREATE TABLE PatientRecyclingOrder(
+       seq INTEGER PRIMARY KEY AUTOINCREMENT,
+       patientId INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE
        );
 
-
-CREATE INDEX PatientToStudies ON Studies(parentPatient);
-CREATE INDEX StudyToSeries ON Series(parentStudy);
-CREATE INDEX SeriesToInstances ON Instances(parentSeries);
-
-CREATE INDEX DicomPatientIndex ON Patients(dicomPatientId);
-CREATE INDEX DicomStudyIndex ON Studies(dicomStudy);
-CREATE INDEX DicomSeriesIndex ON Series(dicomSeries);
-CREATE INDEX DicomInstanceIndex ON Instances(dicomInstance);
+CREATE INDEX ChildrenIndex ON Resources(parentId);
+CREATE INDEX PublicIndex ON Resources(publicId);
+CREATE INDEX ResourceTypeIndex ON Resources(resourceType);
+CREATE INDEX PatientRecyclingIndex ON PatientRecyclingOrder(patientId);
 
-CREATE INDEX MainDicomTagsIndex ON MainDicomTags(uuid);
-CREATE INDEX MainDicomTagsGroupElement ON MainDicomTags(tagGroup, tagElement);
-CREATE INDEX MainDicomTagsValues ON MainDicomTags(value COLLATE BINARY);
+CREATE INDEX MainDicomTagsIndex1 ON MainDicomTags(id);
+CREATE INDEX MainDicomTagsIndex2 ON MainDicomTags(tagGroup, tagElement);
+CREATE INDEX MainDicomTagsIndexValues ON MainDicomTags(value COLLATE BINARY);
 
-CREATE INDEX ChangesIndex ON Changes(uuid);
+CREATE INDEX ChangesIndex ON Changes(internalId);
 
-CREATE TRIGGER InstanceRemoved
-AFTER DELETE ON Instances
-FOR EACH ROW BEGIN
-  DELETE FROM Resources WHERE uuid = old.uuid;
-  DELETE FROM MainDicomTags WHERE uuid = old.uuid;
-  DELETE FROM Changes WHERE uuid = old.uuid;
-  SELECT DeleteFromFileStorage(old.fileUuid);
-  SELECT DeleteFromFileStorage(old.jsonUuid);
-  SELECT SignalDeletedLevel(3, old.parentSeries);
+CREATE TRIGGER AttachedFileDeleted
+AFTER DELETE ON AttachedFiles
+BEGIN
+  SELECT SignalFileDeleted(old.uuid, old.fileType, old.uncompressedSize, 
+                           old.compressionType, old.compressedSize);
 END;
 
-CREATE TRIGGER SeriesRemoved
-AFTER DELETE ON Series
-FOR EACH ROW BEGIN
-  DELETE FROM Resources WHERE uuid = old.uuid;
-  DELETE FROM MainDicomTags WHERE uuid = old.uuid;
-  DELETE FROM Changes WHERE uuid = old.uuid;
-  SELECT SignalDeletedLevel(2, old.parentStudy);
+CREATE TRIGGER ResourceDeleted
+AFTER DELETE ON Resources
+BEGIN
+  SELECT SignalRemainingAncestor(parent.publicId, parent.resourceType) 
+    FROM Resources AS parent WHERE internalId = old.parentId;
 END;
 
-CREATE TRIGGER StudyRemoved
-AFTER DELETE ON Studies
-FOR EACH ROW BEGIN
-  DELETE FROM Resources WHERE uuid = old.uuid;
-  DELETE FROM MainDicomTags WHERE uuid = old.uuid;
-  DELETE FROM Changes WHERE uuid = old.uuid;
-  SELECT SignalDeletedLevel(1, old.parentPatient);
+-- Delete a parent resource when its unique child is deleted 
+CREATE TRIGGER ResourceDeletedParentCleaning
+AFTER DELETE ON Resources
+FOR EACH ROW WHEN (SELECT COUNT(*) FROM Resources WHERE parentId = old.parentId) = 0
+BEGIN
+  DELETE FROM Resources WHERE internalId = old.parentId;
 END;
 
-CREATE TRIGGER PatientRemoved
-AFTER DELETE ON Patients
-FOR EACH ROW BEGIN
-  DELETE FROM Resources WHERE uuid = old.uuid;
-  DELETE FROM MainDicomTags WHERE uuid = old.uuid;
-  DELETE FROM Changes WHERE uuid = old.uuid;
-  SELECT SignalDeletedLevel(0, "");
+CREATE TRIGGER PatientAdded
+AFTER INSERT ON Resources
+FOR EACH ROW WHEN new.resourceType = 1  -- "1" corresponds to "ResourceType_Patient" in C++
+BEGIN
+  INSERT INTO PatientRecyclingOrder VALUES (NULL, new.internalId);
 END;
 
 
-
-
-CREATE TRIGGER InstanceRemovedUpwardCleaning
-AFTER DELETE ON Instances
-FOR EACH ROW 
-  WHEN (SELECT COUNT(*) FROM Instances WHERE parentSeries = old.parentSeries) = 0
-  BEGIN
-    SELECT DeleteFromFileStorage("deleting parent series");  -- TODO REMOVE THIS
-    DELETE FROM Series WHERE uuid = old.parentSeries;
-  END;
-
-CREATE TRIGGER SeriesRemovedUpwardCleaning
-AFTER DELETE ON Series
-FOR EACH ROW 
-  WHEN (SELECT COUNT(*) FROM Series WHERE parentStudy = old.parentStudy) = 0
-  BEGIN
-    SELECT DeleteFromFileStorage("deleting parent study");  -- TODO REMOVE THIS
-    DELETE FROM Studies WHERE uuid = old.parentStudy;
-  END;
-
-CREATE TRIGGER StudyRemovedUpwardCleaning
-AFTER DELETE ON Studies
-FOR EACH ROW 
-  WHEN (SELECT COUNT(*) FROM Studies WHERE parentPatient = old.parentPatient) = 0
-  BEGIN
-    SELECT DeleteFromFileStorage("deleting parent patient");  -- TODO REMOVE THIS
-    DELETE FROM Patients WHERE uuid = old.parentPatient;
-  END;
+-- Set the version of the database schema
+-- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration
+INSERT INTO GlobalProperties VALUES (1, "3");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ServerContext.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,183 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "ServerContext.h"
+
+#include "../Core/HttpServer/FilesystemHttpSender.h"
+
+#include <glog/logging.h>
+
+
+static const size_t DICOM_CACHE_SIZE = 2;
+
+/**
+ * IMPORTANT: We make the assumption that the same instance of
+ * FileStorage can be accessed from multiple threads. This seems OK
+ * since the filesystem implements the required locking mechanisms,
+ * but maybe a read-writer lock on the "FileStorage" could be
+ * useful. Conversely, "ServerIndex" already implements mutex-based
+ * locking.
+ **/
+
+namespace Orthanc
+{
+  ServerContext::ServerContext(const boost::filesystem::path& path) :
+    storage_(path.string()),
+    index_(*this, path.string()),
+    accessor_(storage_),
+    provider_(*this),
+    dicomCache_(provider_, DICOM_CACHE_SIZE)
+  {
+  }
+
+  void ServerContext::SetCompressionEnabled(bool enabled)
+  {
+    if (enabled)
+      LOG(WARNING) << "Disk compression is enabled";
+    else
+      LOG(WARNING) << "Disk compression is disabled";
+
+    compressionEnabled_ = enabled;
+  }
+
+  void ServerContext::RemoveFile(const std::string& fileUuid)
+  {
+    storage_.Remove(fileUuid);
+  }
+
+  StoreStatus ServerContext::Store(const char* dicomFile,
+                                   size_t dicomSize,
+                                   const DicomMap& dicomSummary,
+                                   const Json::Value& dicomJson,
+                                   const std::string& remoteAet)
+  {
+    if (compressionEnabled_)
+    {
+      accessor_.SetCompressionForNextOperations(CompressionType_Zlib);
+    }
+    else
+    {
+      accessor_.SetCompressionForNextOperations(CompressionType_None);
+    }      
+
+    FileInfo dicomInfo = accessor_.Write(dicomFile, dicomSize, FileContentType_Dicom);
+    FileInfo jsonInfo = accessor_.Write(dicomJson.toStyledString(), FileContentType_Json);
+
+    ServerIndex::Attachments attachments;
+    attachments.push_back(dicomInfo);
+    attachments.push_back(jsonInfo);
+
+    StoreStatus status = index_.Store(dicomSummary, attachments, remoteAet);
+
+    if (status != StoreStatus_Success)
+    {
+      storage_.Remove(dicomInfo.GetUuid());
+      storage_.Remove(jsonInfo.GetUuid());
+    }
+
+    switch (status)
+    {
+    case StoreStatus_Success:
+      LOG(INFO) << "New instance stored";
+      break;
+
+    case StoreStatus_AlreadyStored:
+      LOG(INFO) << "Already stored";
+      break;
+
+    case StoreStatus_Failure:
+      LOG(ERROR) << "Store failure";
+      break;
+    }
+
+    return status;
+  }
+
+  
+  void ServerContext::AnswerFile(RestApiOutput& output,
+                                 const std::string& instancePublicId,
+                                 FileContentType content)
+  {
+    FileInfo attachment;
+    if (!index_.LookupAttachment(attachment, instancePublicId, content))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    accessor_.SetCompressionForNextOperations(attachment.GetCompressionType());
+    std::auto_ptr<HttpFileSender> sender(accessor_.ConstructHttpFileSender(attachment.GetUuid()));
+    output.AnswerFile(*sender);
+  }
+
+
+  void ServerContext::ReadJson(Json::Value& result,
+                               const std::string& instancePublicId)
+  {
+    std::string s;
+    ReadFile(s, instancePublicId, FileContentType_Json);
+
+    Json::Reader reader;
+    if (!reader.parse(s, result))
+    {
+      throw OrthancException("Corrupted JSON file");
+    }
+  }
+
+
+  void ServerContext::ReadFile(std::string& result,
+                               const std::string& instancePublicId,
+                               FileContentType content)
+  {
+    FileInfo attachment;
+    if (!index_.LookupAttachment(attachment, instancePublicId, content))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    accessor_.SetCompressionForNextOperations(attachment.GetCompressionType());
+    accessor_.Read(result, attachment.GetUuid());
+  }
+
+
+  IDynamicObject* ServerContext::DicomCacheProvider::Provide(const std::string& instancePublicId)
+  {
+    std::string content;
+    context_.ReadFile(content, instancePublicId, FileContentType_Dicom);
+    return new ParsedDicomFile(content);
+  }
+
+
+  ParsedDicomFile& ServerContext::GetDicomFile(const std::string& instancePublicId)
+  {
+    return dynamic_cast<ParsedDicomFile&>(dicomCache_.Access(instancePublicId));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ServerContext.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,111 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Core/Cache/MemoryCache.h"
+#include "../Core/FileStorage/CompressedFileStorageAccessor.h"
+#include "../Core/FileStorage/FileStorage.h"
+#include "../Core/RestApi/RestApiOutput.h"
+#include "ServerIndex.h"
+#include "FromDcmtkBridge.h"
+
+namespace Orthanc
+{
+  /**
+   * This class is responsible for maintaining the storage area on the
+   * filesystem (including compression), as well as the index of the
+   * DICOM store. It implements the required locking mechanisms.
+   **/
+  class ServerContext
+  {
+  private:
+    class DicomCacheProvider : public ICachePageProvider
+    {
+    private:
+      ServerContext& context_;
+
+    public:
+      DicomCacheProvider(ServerContext& context) : context_(context)
+      {
+      }
+      
+      virtual IDynamicObject* Provide(const std::string& id);
+    };
+
+    FileStorage storage_;
+    ServerIndex index_;
+    CompressedFileStorageAccessor accessor_;
+    bool compressionEnabled_;
+    
+    DicomCacheProvider provider_;
+    MemoryCache dicomCache_;
+
+  public:
+    ServerContext(const boost::filesystem::path& path);
+
+    ServerIndex& GetIndex()
+    {
+      return index_;
+    }
+
+    void SetCompressionEnabled(bool enabled);
+
+    bool IsCompressionEnabled() const
+    {
+      return compressionEnabled_;
+    }
+
+    void RemoveFile(const std::string& fileUuid);
+
+    StoreStatus Store(const char* dicomFile,
+                      size_t dicomSize,
+                      const DicomMap& dicomSummary,
+                      const Json::Value& dicomJson,
+                      const std::string& remoteAet);
+
+    void AnswerFile(RestApiOutput& output,
+                    const std::string& instancePublicId,
+                    FileContentType content);
+
+    void ReadJson(Json::Value& result,
+                  const std::string& instancePublicId);
+
+    // TODO CACHING MECHANISM AT THIS POINT
+    void ReadFile(std::string& result,
+                  const std::string& instancePublicId,
+                  FileContentType content);
+
+    // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD
+    ParsedDicomFile& GetDicomFile(const std::string& instancePublicId);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ServerEnumerations.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,182 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "ServerEnumerations.h"
+
+#include "../Core/OrthancException.h"
+
+namespace Orthanc
+{
+  const char* ToString(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return "Patient";
+
+      case ResourceType_Study:
+        return "Study";
+
+      case ResourceType_Series:
+        return "Series";
+
+      case ResourceType_Instance:
+        return "Instance";
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  std::string GetBasePath(ResourceType type,
+                          const std::string& publicId)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return "/patients/" + publicId;
+
+      case ResourceType_Study:
+        return "/studies/" + publicId;
+
+      case ResourceType_Series:
+        return "/series/" + publicId;
+
+      case ResourceType_Instance:
+        return "/instances/" + publicId;
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  const char* ToString(SeriesStatus status)
+  {
+    switch (status)
+    {
+      case SeriesStatus_Complete:
+        return "Complete";
+
+      case SeriesStatus_Missing:
+        return "Missing";
+
+      case SeriesStatus_Inconsistent:
+        return "Inconsistent";
+
+      case SeriesStatus_Unknown:
+        return "Unknown";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  const char* ToString(StoreStatus status)
+  {
+    switch (status)
+    {
+      case StoreStatus_Success:
+        return "Success";
+
+      case StoreStatus_AlreadyStored:
+        return "AlreadyStored";
+
+      case StoreStatus_Failure:
+        return "Failure";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* ToString(ChangeType type)
+  {
+    switch (type)
+    {
+      case ChangeType_CompletedSeries:
+        return "CompletedSeries";
+
+      case ChangeType_NewInstance:
+        return "NewInstance";
+
+      case ChangeType_NewPatient:
+        return "NewPatient";
+
+      case ChangeType_NewSeries:
+        return "NewSeries";
+
+      case ChangeType_NewStudy:
+        return "NewStudy";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  ResourceType GetParentResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Study:
+        return ResourceType_Patient;
+
+      case ResourceType_Series:
+        return ResourceType_Study;
+
+      case ResourceType_Instance:
+        return ResourceType_Series;
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  ResourceType GetChildResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return ResourceType_Study;
+
+      case ResourceType_Study:
+        return ResourceType_Series;
+
+      case ResourceType_Series:
+        return ResourceType_Instance;
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ServerEnumerations.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,105 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include <string>
+
+namespace Orthanc
+{
+  enum SeriesStatus
+  {
+    SeriesStatus_Complete,
+    SeriesStatus_Missing,
+    SeriesStatus_Inconsistent,
+    SeriesStatus_Unknown
+  };
+
+  enum StoreStatus
+  {
+    StoreStatus_Success,
+    StoreStatus_AlreadyStored,
+    StoreStatus_Failure
+  };
+
+
+  /**
+   * WARNING: Do not change the explicit values in the enumerations
+   * below this point. This would result in incompatible databases
+   * between versions of Orthanc!
+   **/
+
+  enum GlobalProperty
+  {
+    GlobalProperty_DatabaseSchemaVersion = 1,
+    GlobalProperty_FlushSleep = 2
+  };
+
+  enum ResourceType
+  {
+    ResourceType_Patient = 1,
+    ResourceType_Study = 2,
+    ResourceType_Series = 3,
+    ResourceType_Instance = 4
+  };
+
+  enum MetadataType
+  {
+    MetadataType_Instance_IndexInSeries = 1,
+    MetadataType_Instance_ReceptionDate = 2,
+    MetadataType_Instance_RemoteAet = 3,
+    MetadataType_Series_ExpectedNumberOfInstances = 4
+  };
+
+  enum ChangeType
+  {
+    ChangeType_CompletedSeries = 1,
+    ChangeType_NewInstance = 2,
+    ChangeType_NewPatient = 3,
+    ChangeType_NewSeries = 4,
+    ChangeType_NewStudy = 5
+  };
+
+  std::string GetBasePath(ResourceType type,
+                          const std::string& publicId);
+
+  const char* ToString(ResourceType type);
+
+  const char* ToString(SeriesStatus status);
+
+  const char* ToString(StoreStatus status);
+
+  const char* ToString(ChangeType type);
+
+  ResourceType GetParentResourceType(ResourceType type);
+
+  ResourceType GetChildResourceType(ResourceType type);
+}
--- a/OrthancServer/ServerIndex.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/ServerIndex.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -20,8 +32,6 @@
 
 #include "ServerIndex.h"
 
-using namespace Orthanc;
-
 #ifndef NOMINMAX
 #define NOMINMAX
 #endif
@@ -32,6 +42,7 @@
 #include "../Core/DicomFormat/DicomArray.h"
 #include "../Core/SQLite/Transaction.h"
 #include "FromDcmtkBridge.h"
+#include "ServerContext.h"
 
 #include <boost/lexical_cast.hpp>
 #include <stdio.h>
@@ -41,437 +52,166 @@
 {
   namespace Internals
   {
-    class DeleteFromFileStorageFunction : public SQLite::IScalarFunction
+    class ServerIndexListener : public IServerIndexListener
     {
     private:
-      FileStorage fileStorage_;
+      ServerContext& context_;
+      bool hasRemainingLevel_;
+      ResourceType remainingType_;
+      std::string remainingPublicId_;
+      std::list<std::string> pendingFilesToRemove_;
+      uint64_t sizeOfFilesToRemove_;
 
     public:
-      DeleteFromFileStorageFunction(const std::string& path) :
-        fileStorage_(path)
+      ServerIndexListener(ServerContext& context) : 
+        context_(context)
       {
+        Reset();
+        assert(ResourceType_Patient < ResourceType_Study &&
+               ResourceType_Study < ResourceType_Series &&
+               ResourceType_Series < ResourceType_Instance);
       }
 
-      virtual const char* GetName() const
+      void Reset()
       {
-        return "DeleteFromFileStorage";
+        sizeOfFilesToRemove_ = 0;
+        hasRemainingLevel_ = false;
+        pendingFilesToRemove_.clear();
       }
 
-      virtual unsigned int GetCardinality() const
+      uint64_t GetSizeOfFilesToRemove()
       {
-        return 1;
+        return sizeOfFilesToRemove_;
       }
 
-      virtual void Compute(SQLite::FunctionContext& context)
+      void CommitFilesToRemove()
       {
-        std::string fileUuid = context.GetStringValue(0);
-        LOG(INFO) << "Removing file [" << fileUuid << "]";
-        if (Toolbox::IsUuid(fileUuid))
+        for (std::list<std::string>::iterator 
+               it = pendingFilesToRemove_.begin();
+             it != pendingFilesToRemove_.end(); it++)
         {
-          fileStorage_.Remove(fileUuid);
+          context_.RemoveFile(*it);
         }
       }
-    };
 
+      virtual void SignalRemainingAncestor(ResourceType parentType,
+                                           const std::string& publicId)
+      {
+        LOG(INFO) << "Remaining ancestor \"" << publicId << "\" (" << parentType << ")";
 
-    class SignalDeletedLevelFunction : public SQLite::IScalarFunction
-    {
-    private:
-      int remainingLevel_;
-      std::string remainingLevelUuid_;
+        if (hasRemainingLevel_)
+        {
+          if (parentType < remainingType_)
+          {
+            remainingType_ = parentType;
+            remainingPublicId_ = publicId;
+          }
+        }
+        else
+        {
+          hasRemainingLevel_ = true;
+          remainingType_ = parentType;
+          remainingPublicId_ = publicId;
+        }        
+      }
 
-    public:
-      void Clear()
+      virtual void SignalFileDeleted(const FileInfo& info)
       {
-        remainingLevel_ = std::numeric_limits<int>::max();
+        assert(Toolbox::IsUuid(info.GetUuid()));
+        pendingFilesToRemove_.push_back(info.GetUuid());
+        sizeOfFilesToRemove_ += info.GetCompressedSize();
       }
 
       bool HasRemainingLevel() const
       {
-        return (remainingLevel_ != 0 && 
-                remainingLevel_ !=  std::numeric_limits<int>::max());
+        return hasRemainingLevel_;
       }
 
-      const std::string& GetRemainingLevelUuid() const
-      {
-        assert(HasRemainingLevel());
-        return remainingLevelUuid_;
-      }
-
-      const char* GetRemainingLevelType() const
+      ResourceType GetRemainingType() const
       {
         assert(HasRemainingLevel());
-        switch (remainingLevel_)
-        {
-        case 1:
-          return "patient";
-        case 2:
-          return "study";
-        case 3:
-          return "series";
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-        }
+        return remainingType_;
       }
 
-      virtual const char* GetName() const
-      {
-        return "SignalDeletedLevel";
-      }
-
-      virtual unsigned int GetCardinality() const
+      const std::string& GetRemainingPublicId() const
       {
-        return 2;
-      }
-
-      virtual void Compute(SQLite::FunctionContext& context)
-      {
-        int level = context.GetIntValue(0);
-        if (level < remainingLevel_)
-        {
-          remainingLevel_ = level;
-          remainingLevelUuid_ = context.GetStringValue(1);
-        }
-
-        //printf("deleted level [%d] [%s]\n", level, context.GetStringValue(1).c_str());
-      }
+        assert(HasRemainingLevel());
+        return remainingPublicId_;
+      }                                 
     };
   }
 
 
-  void ServerIndex::StoreMainDicomTags(const std::string& uuid,
-                                       const DicomMap& map)
+  class ServerIndex::Transaction
   {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
+  private:
+    ServerIndex& index_;
+    std::auto_ptr<SQLite::Transaction> transaction_;
+    bool isCommitted_;
 
-    DicomArray flattened(map);
-    for (size_t i = 0; i < flattened.GetSize(); i++)
+  public:
+    Transaction(ServerIndex& index) : 
+      index_(index),
+      isCommitted_(false)
     {
-      s.Reset();
-      s.BindString(0, uuid);
-      s.BindInt(1, flattened.GetElement(i).GetTag().GetGroup());
-      s.BindInt(2, flattened.GetElement(i).GetTag().GetElement());
-      s.BindString(3, flattened.GetElement(i).GetValue().AsString());
-      s.Run();
-    }
-  }
+      assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize());
 
-  bool ServerIndex::GetMainDicomStringTag(std::string& result,
-                                          const std::string& uuid,
-                                          const DicomTag& tag)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT * FROM MainDicomTags WHERE uuid=? AND tagGroup=? AND tagElement=?");
-    s.BindString(0, uuid);
-    s.BindInt(1, tag.GetGroup());
-    s.BindInt(2, tag.GetElement());
-    if (!s.Step())
-    {
-      return false;
+      index_.listener_->Reset();
+      transaction_.reset(index_.db_->StartTransaction());
+      transaction_->Begin();
     }
 
-    result = s.ColumnString(0);
-    return true;
-  }
+    void Commit(uint64_t sizeOfAddedFiles)
+    {
+      if (!isCommitted_)
+      {
+        transaction_->Commit();
 
-  bool ServerIndex::GetMainDicomIntTag(int& result,
-                                       const std::string& uuid,
-                                       const DicomTag& tag)
-  {
-    std::string s;
-    if (!GetMainDicomStringTag(s, uuid, tag))
-    {
-      return false;
-    }
+        // We can remove the files once the SQLite transaction has
+        // been successfully committed. Some files might have to be
+        // deleted because of recycling.
+        index_.listener_->CommitFilesToRemove();
+
+        index_.currentStorageSize_ += sizeOfAddedFiles;
 
-    try
-    {
-      result = boost::lexical_cast<int>(s);
-      return true;
+        assert(index_.currentStorageSize_ >= index_.listener_->GetSizeOfFilesToRemove());
+        index_.currentStorageSize_ -= index_.listener_->GetSizeOfFilesToRemove();
+
+        assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize());
+
+        isCommitted_ = true;
+      }
     }
-    catch (boost::bad_lexical_cast)
-    {
-      return false;
-    }
-  }
-
-  bool ServerIndex::HasInstance(std::string& instanceUuid,
-                                const std::string& dicomInstance)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Instances WHERE dicomInstance=?");
-    s.BindString(0, dicomInstance);
-    if (s.Step())
-    {
-      instanceUuid = s.ColumnString(0);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
+  };
 
 
-  void ServerIndex::RecordChange(const std::string& resourceType,
-                                 const std::string& uuid)
+  bool ServerIndex::DeleteResource(Json::Value& target,
+                                   const std::string& uuid,
+                                   ResourceType expectedType)
   {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?)");
-    s.BindString(0, resourceType);
-    s.BindString(1, uuid);
-    s.Run();
-  }
-
-
-  std::string ServerIndex::CreateInstance(const std::string& parentSeriesUuid,
-                                          const std::string& dicomInstance,
-                                          const DicomMap& dicomSummary,
-                                          const std::string& fileUuid,
-                                          uint64_t fileSize,
-                                          const std::string& jsonUuid, 
-                                          const std::string& distantAet)
-  {
-    std::string instanceUuid = Toolbox::GenerateUuid();
-
-    SQLite::Statement s2(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(?, ?)");
-    s2.BindString(0, instanceUuid);
-    s2.BindInt(1, ResourceType_Instance);
-    s2.Run();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Instances VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
-    s.BindString(0, instanceUuid);
-    s.BindString(1, parentSeriesUuid);
-    s.BindString(2, dicomInstance);
-    s.BindString(3, fileUuid);
-    s.BindInt64(4, fileSize);
-    s.BindString(5, jsonUuid);
-    s.BindString(6, distantAet);
+    boost::mutex::scoped_lock lock(mutex_);
+    listener_->Reset();
 
-    const DicomValue* indexInSeries;
-    if ((indexInSeries = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
-        (indexInSeries = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
-    {
-      s.BindInt(7, boost::lexical_cast<unsigned int>(indexInSeries->AsString()));
-    }
-    else
-    {
-      s.BindNull(7);
-    }
-
-    s.Run();
-
-    RecordChange("instances", instanceUuid);
-
-    DicomMap dicom;
-    dicomSummary.ExtractInstanceInformation(dicom);
-    StoreMainDicomTags(instanceUuid, dicom);
+    Transaction t(*this);
 
-    return instanceUuid;
-  }
-
-  void ServerIndex::RemoveInstance(const std::string& uuid)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Instances WHERE uuid=?");
-    s.BindString(0, uuid);
-    s.Run();
-  }
-
-  bool ServerIndex::HasSeries(std::string& seriesUuid,
-                              const std::string& dicomSeries)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Series WHERE dicomSeries=?");
-    s.BindString(0, dicomSeries);
-    if (s.Step())
-    {
-      seriesUuid = s.ColumnString(0);
-      return true;
-    }
-    else
+    int64_t id;
+    ResourceType type;
+    if (!db_->LookupResource(uuid, id, type) ||
+        expectedType != type)
     {
       return false;
     }
-  }
-
-  std::string ServerIndex::CreateSeries(const std::string& parentStudyUuid,
-                                        const std::string& dicomSeries,
-                                        const DicomMap& dicomSummary)
-  {
-    std::string seriesUuid = Toolbox::GenerateUuid();
-
-    SQLite::Statement s2(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(?, ?)");
-    s2.BindString(0, seriesUuid);
-    s2.BindInt(1, ResourceType_Series);
-    s2.Run();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Series VALUES(?, ?, ?, ?)");
-    s.BindString(0, seriesUuid);
-    s.BindString(1, parentStudyUuid);
-    s.BindString(2, dicomSeries);
-
-    const DicomValue* expectedNumberOfInstances;
-    if (//(expectedNumberOfInstances = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_FRAMES)) != NULL ||
-        (expectedNumberOfInstances = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL ||
-        //(expectedNumberOfInstances = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL ||
-        (expectedNumberOfInstances = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL)
-    {
-      s.BindInt(3, boost::lexical_cast<unsigned int>(expectedNumberOfInstances->AsString()));
-    }
-    else
-    {
-      s.BindNull(3);
-    }
-
-    s.Run();
-
-    RecordChange("series", seriesUuid);
-
-    DicomMap dicom;
-    dicomSummary.ExtractSeriesInformation(dicom);
-    StoreMainDicomTags(seriesUuid, dicom);
-
-    return seriesUuid;
-  }
-
-  bool ServerIndex::HasStudy(std::string& studyUuid,
-                             const std::string& dicomStudy)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Studies WHERE dicomStudy=?");
-    s.BindString(0, dicomStudy);
-    if (s.Step())
-    {
-      studyUuid = s.ColumnString(0);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  std::string ServerIndex::CreateStudy(const std::string& parentPatientUuid,
-                                       const std::string& dicomStudy,
-                                       const DicomMap& dicomSummary)
-  {
-    std::string studyUuid = Toolbox::GenerateUuid();
-
-    SQLite::Statement s2(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(?, ?)");
-    s2.BindString(0, studyUuid);
-    s2.BindInt(1, ResourceType_Study);
-    s2.Run();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Studies VALUES(?, ?, ?)");
-    s.BindString(0, studyUuid);
-    s.BindString(1, parentPatientUuid);
-    s.BindString(2, dicomStudy);
-    s.Run();
-
-    RecordChange("studies", studyUuid);
-
-    DicomMap dicom;
-    dicomSummary.ExtractStudyInformation(dicom);
-    StoreMainDicomTags(studyUuid, dicom);
-
-    return studyUuid;
-  }
-
-
+      
+    db_->DeleteResource(id);
 
-  bool ServerIndex::HasPatient(std::string& patientUuid,
-                               const std::string& dicomPatientId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Patients WHERE dicomPatientId=?");
-    s.BindString(0, dicomPatientId);
-    if (s.Step())
-    {
-      patientUuid = s.ColumnString(0);
-      return true;
-    }
-    else
+    if (listener_->HasRemainingLevel())
     {
-      return false;
-    }
-  }
-
-  std::string ServerIndex::CreatePatient(const std::string& patientId,
-                                         const DicomMap& dicomSummary)
-  {
-    std::string patientUuid = Toolbox::GenerateUuid();
-    std::string dicomPatientId = dicomSummary.GetValue(DICOM_TAG_PATIENT_ID).AsString();
-
-    SQLite::Statement s2(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(?, ?)");
-    s2.BindString(0, patientUuid);
-    s2.BindInt(1, ResourceType_Patient);
-    s2.Run();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Patients VALUES(?, ?)");
-    s.BindString(0, patientUuid);
-    s.BindString(1, dicomPatientId);
-    s.Run();
-
-    RecordChange("patients", patientUuid);
-
-    DicomMap dicom;
-    dicomSummary.ExtractPatientInformation(dicom);
-    StoreMainDicomTags(patientUuid, dicom);
-
-    return patientUuid;
-  }
-
-
-  void ServerIndex::GetMainDicomTags(DicomMap& map,
-                                     const std::string& uuid)
-  {
-    map.Clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE uuid=?");
-    s.BindString(0, uuid);
-    while (s.Step())
-    {
-      map.SetValue(s.ColumnInt(1),
-                   s.ColumnInt(2),
-                   s.ColumnString(3));
-    }
-  }
-
-  void ServerIndex::MainDicomTagsToJson(Json::Value& target,
-                                        const std::string& uuid)
-  {
-    DicomMap map;
-    GetMainDicomTags(map, uuid);
-    target["MainDicomTags"] = Json::objectValue;
-    FromDcmtkBridge::ToJson(target["MainDicomTags"], map);
-  }
-
-
-  bool ServerIndex::DeleteInternal(Json::Value& target,
-                                   const std::string& uuid,
-                                   const std::string& tableName)
-  {
-    boost::mutex::scoped_lock scoped_lock(mutex_);
-
-    deletedLevels_->Clear();
-
-    SQLite::Statement s(db_, "DELETE FROM " + tableName + " WHERE uuid=?");
-    s.BindString(0, uuid);
-
-    if (!s.Run())
-    {
-      return false;
-    }
-
-    if (db_.GetLastChangeCount() == 0)
-    {
-      // Nothing was deleted, inexistent UUID
-      return false;
-    }
-    
-    if (deletedLevels_->HasRemainingLevel())
-    {
-      std::string type(deletedLevels_->GetRemainingLevelType());
-      const std::string& uuid = deletedLevels_->GetRemainingLevelUuid();
+      ResourceType type = listener_->GetRemainingType();
+      const std::string& uuid = listener_->GetRemainingPublicId();
 
       target["RemainingAncestor"] = Json::Value(Json::objectValue);
-      target["RemainingAncestor"]["Path"] = "/" + type + "/" + uuid;
-      target["RemainingAncestor"]["Type"] = type;
+      target["RemainingAncestor"]["Path"] = GetBasePath(type, uuid);
+      target["RemainingAncestor"]["Type"] = ToString(type);
       target["RemainingAncestor"]["ID"] = uuid;
     }
     else
@@ -479,177 +219,280 @@
       target["RemainingAncestor"] = Json::nullValue;
     }
 
+    t.Commit(0);
+
     return true;
   }
 
 
-  ServerIndex::ServerIndex(const std::string& storagePath)
+  static void FlushThread(DatabaseWrapper* db,
+                          boost::mutex* mutex,
+                          unsigned int sleep)
   {
-    boost::filesystem::path p = storagePath;
-
-    try
-    {
-      boost::filesystem::create_directories(storagePath);
-    }
-    catch (boost::filesystem::filesystem_error)
-    {
-    }
+    LOG(INFO) << "Starting the database flushing thread (sleep = " << sleep << ")";
 
-    p /= "index";
-    db_.Open(p.string());
-    db_.Register(new Internals::DeleteFromFileStorageFunction(storagePath));
-    deletedLevels_ = (Internals::SignalDeletedLevelFunction*) 
-      db_.Register(new Internals::SignalDeletedLevelFunction);
-
-    if (!db_.DoesTableExist("GlobalProperties"))
+    while (1)
     {
-      LOG(INFO) << "Creating the database";
-      std::string query;
-      EmbeddedResources::GetFileResource(query, EmbeddedResources::PREPARE_DATABASE);
-      db_.Execute(query);
+      boost::this_thread::sleep(boost::posix_time::seconds(sleep));
+      boost::mutex::scoped_lock lock(*mutex);
+      db->FlushToDisk();
     }
   }
 
 
-  StoreStatus ServerIndex::Store(std::string& instanceUuid,
-                                 const DicomMap& dicomSummary,
-                                 const std::string& fileUuid,
-                                 uint64_t uncompressedFileSize,
-                                 const std::string& jsonUuid,
-                                 const std::string& distantAet)
+  ServerIndex::ServerIndex(ServerContext& context,
+                           const std::string& dbPath) : 
+    maximumStorageSize_(0),
+    maximumPatients_(0)
   {
-    boost::mutex::scoped_lock scoped_lock(mutex_);
+    listener_.reset(new Internals::ServerIndexListener(context));
+
+    if (dbPath == ":memory:")
+    {
+      db_.reset(new DatabaseWrapper(*listener_));
+    }
+    else
+    {
+      boost::filesystem::path p = dbPath;
+
+      try
+      {
+        boost::filesystem::create_directories(p);
+      }
+      catch (boost::filesystem::filesystem_error)
+      {
+      }
+
+      db_.reset(new DatabaseWrapper(p.string() + "/index", *listener_));
+    }
+
+    currentStorageSize_ = db_->GetTotalCompressedSize();
+
+    // Initial recycling if the parameters have changed since the last
+    // execution of Orthanc
+    StandaloneRecycling();
 
-    std::string dicomPatientId = dicomSummary.GetValue(DICOM_TAG_PATIENT_ID).AsString();
-    std::string dicomInstance = dicomSummary.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
-    std::string dicomSeries = dicomSummary.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
-    std::string dicomStudy = dicomSummary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
+    unsigned int sleep;
+    try
+    {
+      std::string sleepString = db_->GetGlobalProperty(GlobalProperty_FlushSleep);
+      sleep = boost::lexical_cast<unsigned int>(sleepString);
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      // By default, wait for 10 seconds before flushing
+      sleep = 10;
+    }
+
+    flushThread_ = boost::thread(FlushThread, db_.get(), &mutex_, sleep);
+  }
+
+
+  ServerIndex::~ServerIndex()
+  {
+    LOG(INFO) << "Stopping the database flushing thread";
+    /*flushThread_.terminate();
+      flushThread_.join();*/
+  }
+
+
+  StoreStatus ServerIndex::Store(const DicomMap& dicomSummary,
+                                 const Attachments& attachments,
+                                 const std::string& remoteAet)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    listener_->Reset();
+
+    DicomInstanceHasher hasher(dicomSummary);
 
     try
     {
-      SQLite::Transaction t(db_);
-      t.Begin();
+      Transaction t(*this);
+
+      int64_t patient, study, series, instance;
+      ResourceType type;
+      bool isNewSeries = false;
 
-      if (HasInstance(instanceUuid, dicomInstance))
+      // Do nothing if the instance already exists
+      if (db_->LookupResource(hasher.HashInstance(), patient, type))
       {
+        assert(type == ResourceType_Instance);
         return StoreStatus_AlreadyStored;
-        // TODO: Check consistency?
+      }
+
+      // Ensure there is enough room in the storage for the new instance
+      uint64_t instanceSize = 0;
+      for (Attachments::const_iterator it = attachments.begin();
+           it != attachments.end(); it++)
+      {
+        instanceSize += it->GetCompressedSize();
       }
 
-      std::string patientUuid;
-      if (HasPatient(patientUuid, dicomPatientId))
+      Recycle(instanceSize, hasher.HashPatient());
+
+      // Create the instance
+      instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance);
+
+      DicomMap dicom;
+      dicomSummary.ExtractInstanceInformation(dicom);
+      db_->SetMainDicomTags(instance, dicom);
+
+      // Create the patient/study/series/instance hierarchy
+      if (!db_->LookupResource(hasher.HashSeries(), series, type))
       {
-        // TODO: Check consistency?
+        // This is a new series
+        isNewSeries = true;
+        series = db_->CreateResource(hasher.HashSeries(), ResourceType_Series);
+        dicomSummary.ExtractSeriesInformation(dicom);
+        db_->SetMainDicomTags(series, dicom);
+        db_->AttachChild(series, instance);
+
+        if (!db_->LookupResource(hasher.HashStudy(), study, type))
+        {
+          // This is a new study
+          study = db_->CreateResource(hasher.HashStudy(), ResourceType_Study);
+          dicomSummary.ExtractStudyInformation(dicom);
+          db_->SetMainDicomTags(study, dicom);
+          db_->AttachChild(study, series);
+
+          if (!db_->LookupResource(hasher.HashPatient(), patient, type))
+          {
+            // This is a new patient
+            patient = db_->CreateResource(hasher.HashPatient(), ResourceType_Patient);
+            dicomSummary.ExtractPatientInformation(dicom);
+            db_->SetMainDicomTags(patient, dicom);
+            db_->AttachChild(patient, study);
+          }
+          else
+          {
+            assert(type == ResourceType_Patient);
+            db_->AttachChild(patient, study);
+          }
+        }
+        else
+        {
+          assert(type == ResourceType_Study);
+          db_->AttachChild(study, series);
+        }
       }
       else
       {
-        patientUuid = CreatePatient(dicomPatientId, dicomSummary);
+        assert(type == ResourceType_Series);
+        db_->AttachChild(series, instance);
       }
 
-      std::string studyUuid;
-      if (HasStudy(studyUuid, dicomStudy))
+      // Attach the files to the newly created instance
+      for (Attachments::const_iterator it = attachments.begin();
+           it != attachments.end(); it++)
       {
-        // TODO: Check consistency?
-      }
-      else
-      {
-        studyUuid = CreateStudy(patientUuid, dicomStudy, dicomSummary);
+        db_->AddAttachment(instance, *it);
       }
 
-      std::string seriesUuid;
-      if (HasSeries(seriesUuid, dicomSeries))
+      // Attach the metadata
+      db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, Toolbox::GetNowIsoString());
+      db_->SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet);
+
+      const DicomValue* value;
+      if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
+          (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
       {
-        // TODO: Check consistency?
-      }
-      else
-      {
-        seriesUuid = CreateSeries(studyUuid, dicomSeries, dicomSummary);
+        db_->SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString());
       }
 
-      instanceUuid = CreateInstance(seriesUuid, dicomInstance, dicomSummary, fileUuid, 
-                                    uncompressedFileSize, jsonUuid, distantAet);
-      
-      t.Commit();
+      if (isNewSeries)
+      {
+        if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL ||
+            (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL ||
+            (value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL)
+        {
+          db_->SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->AsString());
+        }
+      }
+
+      // Check whether the series of this new instance is now completed
+      SeriesStatus seriesStatus = GetSeriesStatus(series);
+      if (seriesStatus == SeriesStatus_Complete)
+      {
+        db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series);
+      }
+
+      t.Commit(instanceSize);
+
       return StoreStatus_Success;
-      //t.Rollback();
     }
     catch (OrthancException& e)
     {
-      LOG(ERROR) << "EXCEPTION [" << e.What() << "]" << " " << db_.GetErrorMessage();  
+      LOG(ERROR) << "EXCEPTION [" << e.What() << "]" 
+                 << " (SQLite status: " << db_->GetErrorMessage() << ")";
     }
 
     return StoreStatus_Failure;
   }
 
 
-  StoreStatus ServerIndex::Store(std::string& instanceUuid,
-                                 FileStorage& storage,
-                                 const char* dicomFile,
-                                 size_t dicomSize,
-                                 const DicomMap& dicomSummary,
-                                 const Json::Value& dicomJson,
-                                 const std::string& distantAet)
+  void ServerIndex::ComputeStatistics(Json::Value& target)
   {
-    std::string fileUuid = storage.Create(dicomFile, dicomSize);
-    std::string jsonUuid = storage.Create(dicomJson.toStyledString());
-    StoreStatus status = Store(instanceUuid, dicomSummary, fileUuid, 
-                               dicomSize, jsonUuid, distantAet);
+    static const uint64_t MB = 1024 * 1024;
 
-    if (status != StoreStatus_Success)
-    {
-      storage.Remove(fileUuid);
-      storage.Remove(jsonUuid);
-    }
+    boost::mutex::scoped_lock lock(mutex_);
+    target = Json::objectValue;
 
-    switch (status)
-    {
-    case StoreStatus_Success:
-      LOG(INFO) << "New instance stored: " << GetTotalSize() << " bytes";
-      break;
-
-    case StoreStatus_AlreadyStored:
-      LOG(INFO) << "Already stored";
-      break;
+    uint64_t cs = currentStorageSize_;
+    assert(cs == db_->GetTotalCompressedSize());
+    uint64_t us = db_->GetTotalUncompressedSize();
+    target["TotalDiskSpace"] = boost::lexical_cast<std::string>(cs);
+    target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us);
+    target["TotalDiskSpaceMB"] = boost::lexical_cast<unsigned int>(cs / MB);
+    target["TotalUncompressedSizeMB"] = boost::lexical_cast<unsigned int>(us / MB);
 
-    case StoreStatus_Failure:
-      LOG(INFO) << "Store failure";
-      break;
-    }
-
-    return status;
-  }
-
-  uint64_t ServerIndex::GetTotalSize()
-  {
-    boost::mutex::scoped_lock scoped_lock(mutex_);
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(fileSize) FROM Instances");
-    s.Run();
-    return s.ColumnInt64(0);
-  }
+    target["CountPatients"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Patient));
+    target["CountStudies"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Study));
+    target["CountSeries"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Series));
+    target["CountInstances"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Instance));
+  }          
 
 
-  SeriesStatus ServerIndex::GetSeriesStatus(const std::string& seriesUuid)
+
+  SeriesStatus ServerIndex::GetSeriesStatus(int id)
   {
-    SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT expectedNumberOfInstances FROM Series WHERE uuid=?");
-    s1.BindString(0, seriesUuid);
-    if (!s1.Step() || s1.ColumnIsNull(0))
+    // Get the expected number of instances in this series (from the metadata)
+    std::string s = db_->GetMetadata(id, MetadataType_Series_ExpectedNumberOfInstances);
+
+    size_t expected;
+    try
+    {
+      expected = boost::lexical_cast<size_t>(s);
+      if (expected < 0)
+      {
+        return SeriesStatus_Unknown;
+      }
+    }
+    catch (boost::bad_lexical_cast&)
     {
       return SeriesStatus_Unknown;
     }
 
-    int numberOfInstances = s1.ColumnInt(0);
-    if (numberOfInstances < 0)
-    {
-      return SeriesStatus_Unknown;
-    }
+    // Loop over the instances of this series
+    std::list<int64_t> children;
+    db_->GetChildrenInternalId(children, id);
 
-    std::set<int> instances;
-    SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT indexInSeries FROM Instances WHERE parentSeries=?");
-    s2.BindString(0, seriesUuid);
-    while (s2.Step())
+    std::set<size_t> instances;
+    for (std::list<int64_t>::const_iterator 
+           it = children.begin(); it != children.end(); it++)
     {
-      int index = s2.ColumnInt(0);
-      if (index <= 0 || index > numberOfInstances)
+      // Get the index of this instance in the series
+      s = db_->GetMetadata(*it, MetadataType_Instance_IndexInSeries);
+      size_t index;
+      try
+      {
+        index = boost::lexical_cast<size_t>(s);
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return SeriesStatus_Unknown;
+      }
+
+      if (index <= 0 || index > expected)
       {
         // Out-of-range instance index
         return SeriesStatus_Inconsistent;
@@ -664,202 +507,183 @@
       instances.insert(index);
     }
 
-    for (int i = 1; i <= numberOfInstances; i++)
+    if (instances.size() == expected)
     {
-      if (instances.find(i) == instances.end())
-      {
-        return SeriesStatus_Missing;
-      }
-    }
-
-    return SeriesStatus_Complete;
-  }
-
-
-
-  bool ServerIndex::GetInstance(Json::Value& result,
-                                const std::string& instanceUuid)
-  {
-    assert(result.type() == Json::objectValue);
-    boost::mutex::scoped_lock scoped_lock(mutex_);
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT parentSeries, dicomInstance, fileSize, fileUuid, indexInSeries FROM Instances WHERE uuid=?");
-    s.BindString(0, instanceUuid);
-    if (!s.Step())
-    {
-      return false;
+      return SeriesStatus_Complete;
     }
     else
     {
-      result["ID"] = instanceUuid;
-      result["ParentSeries"] = s.ColumnString(0);
-      result["FileSize"] = s.ColumnInt(2);   // TODO switch to 64bit with JsonCpp 0.6?
-      result["FileUuid"] = s.ColumnString(3);
-      MainDicomTagsToJson(result, instanceUuid);
-      
-      if (s.ColumnIsNull(4))
-      {
-        result["IndexInSeries"] = Json::nullValue;
-      }
-      else
-      {
-        result["IndexInSeries"] = s.ColumnInt(4);
-      }
-
-      return true;
+      return SeriesStatus_Missing;
     }
   }
 
 
-  bool ServerIndex::GetSeries(Json::Value& result,
-                              const std::string& seriesUuid)
+
+  void ServerIndex::MainDicomTagsToJson(Json::Value& target,
+                                        int64_t resourceId)
   {
-    assert(result.type() == Json::objectValue);
-    boost::mutex::scoped_lock scoped_lock(mutex_);
+    DicomMap tags;
+    db_->GetMainDicomTags(tags, resourceId);
+    target["MainDicomTags"] = Json::objectValue;
+    FromDcmtkBridge::ToJson(target["MainDicomTags"], tags);
+  }
 
-    SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT parentStudy, dicomSeries, expectedNumberOfInstances FROM Series WHERE uuid=?");
-    s1.BindString(0, seriesUuid);
-    if (!s1.Step())
+  bool ServerIndex::LookupResource(Json::Value& result,
+                                   const std::string& publicId,
+                                   ResourceType expectedType)
+  {
+    result = Json::objectValue;
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    // Lookup for the requested resource
+    int64_t id;
+    ResourceType type;
+    if (!db_->LookupResource(publicId, id, type) ||
+        type != expectedType)
     {
       return false;
     }
 
-    result["ID"] = seriesUuid;
-    result["ParentStudy"] = s1.ColumnString(0);
-    MainDicomTagsToJson(result, seriesUuid);
-
-    Json::Value instances(Json::arrayValue);
-    SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Instances WHERE parentSeries=?");
-    s2.BindString(0, seriesUuid);
-    while (s2.Step())
+    // Find the parent resource (if it exists)
+    if (type != ResourceType_Patient)
     {
-      instances.append(s2.ColumnString(0));
-    }
-      
-    result["Instances"] = instances;
+      int64_t parentId;
+      if (!db_->LookupParent(parentId, id))
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      std::string parent = db_->GetPublicId(parentId);
 
-    if (s1.ColumnIsNull(2))
-    {
-      result["ExpectedNumberOfInstances"] = Json::nullValue;
-    }
-    else
-    {
-      result["ExpectedNumberOfInstances"] = s1.ColumnInt(2);
+      switch (type)
+      {
+        case ResourceType_Study:
+          result["ParentPatient"] = parent;
+          break;
+
+        case ResourceType_Series:
+          result["ParentStudy"] = parent;
+          break;
+
+        case ResourceType_Instance:
+          result["ParentSeries"] = parent;
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
     }
 
-    SeriesStatus status = GetSeriesStatus(seriesUuid);
+    // List the children resources
+    std::list<std::string> children;
+    db_->GetChildrenPublicId(children, id);
 
-    switch (status)
+    if (type != ResourceType_Instance)
     {
-    case SeriesStatus_Complete:
-      result["Status"] = "Complete";
-      break;
+      Json::Value c = Json::arrayValue;
+
+      for (std::list<std::string>::const_iterator
+             it = children.begin(); it != children.end(); it++)
+      {
+        c.append(*it);
+      }
+
+      switch (type)
+      {
+        case ResourceType_Patient:
+          result["Studies"] = c;
+          break;
+
+        case ResourceType_Study:
+          result["Series"] = c;
+          break;
+
+        case ResourceType_Series:
+          result["Instances"] = c;
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    // Set the resource type
+    switch (type)
+    {
+      case ResourceType_Patient:
+        result["Type"] = "Patient";
+        break;
+
+      case ResourceType_Study:
+        result["Type"] = "Study";
+        break;
 
-    case SeriesStatus_Missing:
-      result["Status"] = "Missing";
-      break;
+      case ResourceType_Series:
+      {
+        result["Type"] = "Series";
+        result["Status"] = ToString(GetSeriesStatus(id));
+
+        int i;
+        if (db_->GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances))
+          result["ExpectedNumberOfInstances"] = i;
+        else
+          result["ExpectedNumberOfInstances"] = Json::nullValue;
+
+        break;
+      }
+
+      case ResourceType_Instance:
+      {
+        result["Type"] = "Instance";
 
-    case SeriesStatus_Inconsistent:
-      result["Status"] = "Inconsistent";
-      break;
+        FileInfo attachment;
+        if (!db_->LookupAttachment(attachment, id, FileContentType_Dicom))
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize());
+        result["FileUuid"] = attachment.GetUuid();
 
-    default:
-    case SeriesStatus_Unknown:
-      result["Status"] = "Unknown";
-      break;
+        int i;
+        if (db_->GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries))
+          result["IndexInSeries"] = i;
+        else
+          result["IndexInSeries"] = Json::nullValue;
+
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
     }
 
+    // Record the remaining information
+    result["ID"] = publicId;
+    MainDicomTagsToJson(result, id);
+
     return true;
   }
 
 
-  bool ServerIndex::GetStudy(Json::Value& result,
-                             const std::string& studyUuid)
+  bool ServerIndex::LookupAttachment(FileInfo& attachment,
+                                     const std::string& instanceUuid,
+                                     FileContentType contentType)
   {
-    assert(result.type() == Json::objectValue);
-    boost::mutex::scoped_lock scoped_lock(mutex_);
-
-    SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT parentPatient, dicomStudy FROM Studies WHERE uuid=?");
-    s1.BindString(0, studyUuid);
-    if (!s1.Step())
-    {
-      return false;
-    }
-
-    result["ID"] = studyUuid;
-    result["ParentPatient"] = s1.ColumnString(0);
-    MainDicomTagsToJson(result, studyUuid);
+    boost::mutex::scoped_lock lock(mutex_);
 
-    Json::Value series(Json::arrayValue);
-    SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Series WHERE parentStudy=?");
-    s2.BindString(0, studyUuid);
-    while (s2.Step())
+    int64_t id;
+    ResourceType type;
+    if (!db_->LookupResource(instanceUuid, id, type) ||
+        type != ResourceType_Instance)
     {
-      series.append(s2.ColumnString(0));
-    }
-      
-    result["Series"] = series;
-    return true;
-  }
-
-
-  bool ServerIndex::GetPatient(Json::Value& result,
-                               const std::string& patientUuid)
-  {
-    assert(result.type() == Json::objectValue);
-    boost::mutex::scoped_lock scoped_lock(mutex_);
-
-    SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT dicomPatientId FROM Patients WHERE uuid=?");
-    s1.BindString(0, patientUuid);
-    if (!s1.Step())
-    {
-      return false;
+      throw OrthancException(ErrorCode_InternalError);
     }
 
-    result["ID"] = patientUuid;
-    MainDicomTagsToJson(result, patientUuid);
-
-    Json::Value studies(Json::arrayValue);
-    SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Studies WHERE parentPatient=?");
-    s2.BindString(0, patientUuid);
-    while (s2.Step())
+    if (db_->LookupAttachment(attachment, id, contentType))
     {
-      studies.append(s2.ColumnString(0));
-    }
-      
-    result["Studies"] = studies;
-    return true;
-  }
-
-
-  bool ServerIndex::GetJsonFile(std::string& fileUuid,
-                                const std::string& instanceUuid)
-  {
-    boost::mutex::scoped_lock scoped_lock(mutex_);
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT jsonUuid FROM Instances WHERE uuid=?");
-    s.BindString(0, instanceUuid);
-    if (s.Step())
-    {
-      fileUuid = s.ColumnString(0);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  bool ServerIndex::GetDicomFile(std::string& fileUuid,
-                                 const std::string& instanceUuid)
-  {
-    boost::mutex::scoped_lock scoped_lock(mutex_);
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT fileUuid FROM Instances WHERE uuid=?");
-    s.BindString(0, instanceUuid);
-    if (s.Step())
-    {
-      fileUuid = s.ColumnString(0);
+      assert(attachment.GetContentType() == contentType);
       return true;
     }
     else
@@ -869,83 +693,273 @@
   }
 
 
+
   void ServerIndex::GetAllUuids(Json::Value& target,
-                                const std::string& tableName)
+                                ResourceType resourceType)
   {
-    assert(target.type() == Json::arrayValue);
-    boost::mutex::scoped_lock scoped_lock(mutex_);
-
-    std::string query = "SELECT uuid FROM " + tableName;
-    SQLite::Statement s(db_, query);
-    while (s.Step())
-    {
-      target.append(s.ColumnString(0));
-    }
+    boost::mutex::scoped_lock lock(mutex_);
+    db_->GetAllPublicIds(target, resourceType);
   }
 
 
   bool ServerIndex::GetChanges(Json::Value& target,
-                               int64_t since,
-                               const std::string& filter,
+                               int64_t since,                               
                                unsigned int maxResults)
   {
-    assert(target.type() == Json::objectValue);
-    boost::mutex::scoped_lock scoped_lock(mutex_);
+    boost::mutex::scoped_lock lock(mutex_);
+    db_->GetChanges(target, since, maxResults);
+    return true;
+  }
+
+  bool ServerIndex::GetLastChange(Json::Value& target)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    db_->GetLastChange(target);
+    return true;
+  }
+
+  void ServerIndex::LogExportedResource(const std::string& publicId,
+                                        const std::string& remoteModality)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    int64_t id;
+    ResourceType type;
+    if (!db_->LookupResource(publicId, id, type))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    std::string patientId;
+    std::string studyInstanceUid;
+    std::string seriesInstanceUid;
+    std::string sopInstanceUid;
+
+    int64_t currentId = id;
+    ResourceType currentType = type;
 
-    if (filter.size() != 0 &&
-        filter != "instances" &&
-        filter != "series" &&
-        filter != "studies" &&
-        filter != "patients")
+    // Iteratively go up inside the patient/study/series/instance hierarchy
+    bool done = false;
+    while (!done)
     {
-      return false;
+      DicomMap map;
+      db_->GetMainDicomTags(map, currentId);
+
+      switch (currentType)
+      {
+        case ResourceType_Patient:
+          patientId = map.GetValue(DICOM_TAG_PATIENT_ID).AsString();
+          done = true;
+          break;
+
+        case ResourceType_Study:
+          studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
+          currentType = ResourceType_Patient;
+          break;
+
+        case ResourceType_Series:
+          seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
+          currentType = ResourceType_Study;
+          break;
+
+        case ResourceType_Instance:
+          sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
+          currentType = ResourceType_Series;
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      // If we have not reached the Patient level, find the parent of
+      // the current resource
+      if (!done)
+      {
+        assert(db_->LookupParent(currentId, currentId));
+      }
     }
 
-    std::auto_ptr<SQLite::Statement> s;
-    if (filter.size() == 0)
-    {    
-      s.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? "
-                                    "ORDER BY seq LIMIT ?"));
-      s->BindInt64(0, since);
-      s->BindInt(1, maxResults);
+    // No need for a SQLite::Transaction here, as we only insert 1 record
+    db_->LogExportedResource(type,
+                             publicId,
+                             remoteModality,
+                             patientId,
+                             studyInstanceUid,
+                             seriesInstanceUid,
+                             sopInstanceUid);
+  }
+
+
+  bool ServerIndex::GetExportedResources(Json::Value& target,
+                                         int64_t since,
+                                         unsigned int maxResults)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    db_->GetExportedResources(target, since, maxResults);
+    return true;
+  }
+
+  bool ServerIndex::GetLastExportedResource(Json::Value& target)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    db_->GetLastExportedResource(target);
+    return true;
+  }
+
+
+  bool ServerIndex::IsRecyclingNeeded(uint64_t instanceSize)
+  {
+    if (maximumStorageSize_ != 0)
+    {
+      uint64_t currentSize = currentStorageSize_ - listener_->GetSizeOfFilesToRemove();
+      assert(db_->GetTotalCompressedSize() == currentSize);
+
+      if (currentSize + instanceSize > maximumStorageSize_)
+      {
+        return true;
+      }
+    }
+
+    if (maximumPatients_ != 0)
+    {
+      uint64_t patientCount = db_->GetResourceCount(ResourceType_Patient);
+      if (patientCount > maximumPatients_)
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  
+  void ServerIndex::Recycle(uint64_t instanceSize,
+                            const std::string& newPatientId)
+  {
+    if (!IsRecyclingNeeded(instanceSize))
+    {
+      return;
+    }
+
+    // Check whether other DICOM instances from this patient are
+    // already stored
+    int64_t patientToAvoid;
+    ResourceType type;
+    bool hasPatientToAvoid = db_->LookupResource(newPatientId, patientToAvoid, type);
+
+    if (hasPatientToAvoid && type != ResourceType_Patient)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Iteratively select patient to remove until there is enough
+    // space in the DICOM store
+    int64_t patientToRecycle;
+    while (true)
+    {
+      // If other instances of this patient are already in the store,
+      // we must avoid to recycle them
+      bool ok = hasPatientToAvoid ?
+        db_->SelectPatientToRecycle(patientToRecycle, patientToAvoid) :
+        db_->SelectPatientToRecycle(patientToRecycle);
+        
+      if (!ok)
+      {
+        throw OrthancException(ErrorCode_FullStorage);
+      }
+      
+      LOG(INFO) << "Recycling one patient";
+      db_->DeleteResource(patientToRecycle);
+
+      if (!IsRecyclingNeeded(instanceSize))
+      {
+        // OK, we're done
+        break;
+      }
+    }
+  }  
+
+  void ServerIndex::SetMaximumPatientCount(unsigned int count) 
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    maximumPatients_ = count;
+
+    if (count == 0)
+    {
+      LOG(WARNING) << "No limit on the number of stored patients";
     }
     else
     {
-      s.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? "
-                                    "AND basePath=? ORDER BY seq LIMIT ?"));
-      s->BindInt64(0, since);
-      s->BindString(1, filter);
-      s->BindInt(2, maxResults);
+      LOG(WARNING) << "At most " << count << " patients will be stored";
     }
-    
-    int64_t lastSeq = 0;
-    Json::Value results(Json::arrayValue);
-    while (s->Step())
-    {
-      int64_t seq = s->ColumnInt64(0);
-      std::string basePath = s->ColumnString(1);
-      std::string uuid = s->ColumnString(2);
+
+    StandaloneRecycling();
+  }
 
-      if (filter.size() == 0 ||
-          filter == basePath)
-      {
-        Json::Value change(Json::objectValue);
-        change["Seq"] = static_cast<int>(seq);   // TODO JsonCpp in 64bit
-        change["BasePath"] = basePath;
-        change["ID"] = uuid;
-        results.append(change);
-      }
+  void ServerIndex::SetMaximumStorageSize(uint64_t size) 
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    maximumStorageSize_ = size;
 
-      if (seq > lastSeq)
-      {
-        lastSeq = seq;
-      }
+    if (size == 0)
+    {
+      LOG(WARNING) << "No limit on the size of the storage area";
+    }
+    else
+    {
+      LOG(WARNING) << "At most " << (size / (1024 * 1024)) << "MB will be used for the storage area";
     }
 
-    target["Results"] = results;
-    target["LastSeq"] = static_cast<int>(lastSeq);   // TODO JsonCpp in 64bit
+    StandaloneRecycling();
+  }
+
+  void ServerIndex::StandaloneRecycling()
+  {
+    // WARNING: No mutex here, do not include this as a public method
+    Transaction t(*this);
+    Recycle(0, "");
+    t.Commit(0);
+  }
+
+
+  bool ServerIndex::IsProtectedPatient(const std::string& publicId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    // Lookup for the requested resource
+    int64_t id;
+    ResourceType type;
+    if (!db_->LookupResource(publicId, id, type) ||
+        type != ResourceType_Patient)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
 
-    return true;
+    return db_->IsProtectedPatient(id);
+  }
+     
+
+  void ServerIndex::SetProtectedPatient(const std::string& publicId,
+                                        bool isProtected)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    // Lookup for the requested resource
+    int64_t id;
+    ResourceType type;
+    if (!db_->LookupResource(publicId, id, type) ||
+        type != ResourceType_Patient)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    // No need for a SQLite::Transaction here, as we only make 1 write to the DB
+    db_->SetProtectedPatient(id, isProtected);
+
+    if (isProtected)
+      LOG(INFO) << "Patient " << publicId << " has been protected";
+    else
+      LOG(INFO) << "Patient " << publicId << " has been unprotected";
   }
 
 }
--- a/OrthancServer/ServerIndex.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/ServerIndex.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -21,187 +33,114 @@
 #pragma once
 
 #include <boost/thread.hpp>
+#include <boost/noncopyable.hpp>
 #include "../Core/SQLite/Connection.h"
 #include "../Core/DicomFormat/DicomMap.h"
-#include "../Core/FileStorage.h"
+#include "../Core/DicomFormat/DicomInstanceHasher.h"
+#include "ServerEnumerations.h"
+
+#include "DatabaseWrapper.h"
 
 
 namespace Orthanc
 {
-  enum SeriesStatus
-  {
-    SeriesStatus_Complete,
-    SeriesStatus_Missing,
-    SeriesStatus_Inconsistent,
-    SeriesStatus_Unknown
-  };
-
-
-  enum StoreStatus
-  {
-    StoreStatus_Success,
-    StoreStatus_AlreadyStored,
-    StoreStatus_Failure
-  };
-
-
-  enum ResourceType
-  {
-    ResourceType_Patient = 1,
-    ResourceType_Study = 2,
-    ResourceType_Series = 3,
-    ResourceType_Instance = 4,
-    ResourceType_File = 5
-  };
-
+  class ServerContext;
 
   namespace Internals
   {
-    class SignalDeletedLevelFunction;
+    class ServerIndexListener;
   }
 
-
-  class ServerIndex
+  class ServerIndex : public boost::noncopyable
   {
   private:
-    SQLite::Connection db_;
-    boost::mutex mutex_;
-
-    // DO NOT delete the following one, SQLite::Connection will do it automatically
-    Internals::SignalDeletedLevelFunction* deletedLevels_;
+    class Transaction;
 
-    void StoreMainDicomTags(const std::string& uuid,
-                            const DicomMap& map);
-
-    bool GetMainDicomStringTag(std::string& result,
-                               const std::string& uuid,
-                               const DicomTag& tag);
+    boost::mutex mutex_;
+    boost::thread flushThread_;
 
-    bool GetMainDicomIntTag(int& result,
-                            const std::string& uuid,
-                            const DicomTag& tag);
-
-    bool HasInstance(std::string& instanceUuid,
-                     const std::string& dicomInstance);
+    std::auto_ptr<Internals::ServerIndexListener> listener_;
+    std::auto_ptr<DatabaseWrapper> db_;
 
-    void RecordChange(const std::string& resourceType,
-                      const std::string& uuid);
-
-    std::string CreateInstance(const std::string& parentSeriesUuid,
-                               const std::string& dicomInstance,
-                               const DicomMap& dicomSummary,
-                               const std::string& fileUuid,
-                               uint64_t fileSize,
-                               const std::string& jsonUuid, 
-                               const std::string& distantAet);
+    uint64_t currentStorageSize_;
+    uint64_t maximumStorageSize_;
+    unsigned int maximumPatients_;
 
-    void RemoveInstance(const std::string& uuid);
-
-    bool HasSeries(std::string& seriesUuid,
-                   const std::string& dicomSeries);
+    void MainDicomTagsToJson(Json::Value& result,
+                             int64_t resourceId);
 
-    std::string CreateSeries(const std::string& parentStudyUuid,
-                             const std::string& dicomSeries,
-                             const DicomMap& dicomSummary);
-
-    bool HasStudy(std::string& studyUuid,
-                  const std::string& dicomStudy);
-
-    std::string CreateStudy(const std::string& parentPatientUuid,
-                            const std::string& dicomStudy,
-                            const DicomMap& dicomSummary);
+    SeriesStatus GetSeriesStatus(int id);
 
-    bool HasPatient(std::string& patientUuid,
-                    const std::string& dicomPatientId);
-
-    std::string CreatePatient(const std::string& patientId,
-                              const DicomMap& dicomSummary);
+    bool IsRecyclingNeeded(uint64_t instanceSize);
 
-    void GetMainDicomTags(DicomMap& map,
-                          const std::string& uuid);
+    void Recycle(uint64_t instanceSize,
+                 const std::string& newPatientId);
 
-    void MainDicomTagsToJson(Json::Value& target,
-                             const std::string& uuid);
-
-    bool DeleteInternal(Json::Value& target,
-                        const std::string& uuid,
-                        const std::string& tableName);
+    void StandaloneRecycling();
 
   public:
-    ServerIndex(const std::string& storagePath);
+    typedef std::list<FileInfo> Attachments;
 
-    StoreStatus Store(std::string& instanceUuid,
-                      const DicomMap& dicomSummary,
-                      const std::string& fileUuid,
-                      uint64_t uncompressedFileSize,
-                      const std::string& jsonUuid,
-                      const std::string& distantAet);
+    ServerIndex(ServerContext& context,
+                const std::string& dbPath);
+
+    ~ServerIndex();
 
-    StoreStatus Store(std::string& instanceUuid,
-                      FileStorage& storage,
-                      const char* dicomFile,
-                      size_t dicomSize,
-                      const DicomMap& dicomSummary,
-                      const Json::Value& dicomJson,
-                      const std::string& distantAet);
+    uint64_t GetMaximumStorageSize() const
+    {
+      return maximumStorageSize_;
+    }
 
-    uint64_t GetTotalSize();
+    uint64_t GetMaximumPatientCount() const
+    {
+      return maximumPatients_;
+    }
 
-    SeriesStatus GetSeriesStatus(const std::string& seriesUuid);
-
+    // "size == 0" means no limit on the storage size
+    void SetMaximumStorageSize(uint64_t size);
 
-    bool GetInstance(Json::Value& result,
-                     const std::string& instanceUuid);
+    // "count == 0" means no limit on the number of patients
+    void SetMaximumPatientCount(unsigned int count);
 
-    bool GetSeries(Json::Value& result,
-                   const std::string& seriesUuid);
+    StoreStatus Store(const DicomMap& dicomSummary,
+                      const Attachments& attachments,
+                      const std::string& remoteAet);
 
-    bool GetStudy(Json::Value& result,
-                  const std::string& studyUuid);
-
-    bool GetPatient(Json::Value& result,
-                    const std::string& patientUuid);
+    void ComputeStatistics(Json::Value& target);                        
 
-    bool GetJsonFile(std::string& fileUuid,
-                     const std::string& instanceUuid);
+    bool LookupResource(Json::Value& result,
+                        const std::string& publicId,
+                        ResourceType expectedType);
 
-    bool GetDicomFile(std::string& fileUuid,
-                      const std::string& instanceUuid);
+    bool LookupAttachment(FileInfo& attachment,
+                          const std::string& instanceUuid,
+                          FileContentType contentType);
 
     void GetAllUuids(Json::Value& target,
-                     const std::string& tableName);
-
-    bool DeletePatient(Json::Value& target,
-                       const std::string& patientUuid)
-    {
-      return DeleteInternal(target, patientUuid, "Patients");
-    }
+                     ResourceType resourceType);
 
-    bool DeleteStudy(Json::Value& target,
-                     const std::string& studyUuid)
-    {
-      return DeleteInternal(target, studyUuid, "Studies");
-    }
-
-    bool DeleteSeries(Json::Value& target,
-                      const std::string& seriesUuid)
-    {
-      return DeleteInternal(target, seriesUuid, "Series");
-    }
-
-    bool DeleteInstance(Json::Value& target,
-                        const std::string& instanceUuid)
-    {
-      return DeleteInternal(target, instanceUuid, "Instances");
-    }
+    bool DeleteResource(Json::Value& target,
+                        const std::string& uuid,
+                        ResourceType expectedType);
 
     bool GetChanges(Json::Value& target,
                     int64_t since,
-                    const std::string& filter,
                     unsigned int maxResults);
 
-    /*bool GetAllInstances(std::list<std::string>& instancesUuid,
-                         const std::string& uuid,
-                         bool clear = true);*/
+    bool GetLastChange(Json::Value& target);
+
+    void LogExportedResource(const std::string& publicId,
+                             const std::string& remoteModality);
+
+    bool GetExportedResources(Json::Value& target,
+                              int64_t since,
+                              unsigned int maxResults);
+
+    bool GetLastExportedResource(Json::Value& target);
+
+    bool IsProtectedPatient(const std::string& publicId);
+
+    void SetProtectedPatient(const std::string& publicId,
+                             bool isProtected);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ServerToolbox.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,85 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 "ServerToolbox.h"
+
+#include "../Core/OrthancException.h"
+
+#include <cassert>
+
+namespace Orthanc
+{
+  void SimplifyTags(Json::Value& target,
+                    const Json::Value& source)
+  {
+    assert(source.isObject());
+
+    target = Json::objectValue;
+    Json::Value::Members members = source.getMemberNames();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& v = source[members[i]];
+      const std::string& name = v["Name"].asString();
+      const std::string& type = v["Type"].asString();
+
+      if (type == "String")
+      {
+        target[name] = v["Value"].asString();
+      }
+      else if (type == "TooLong" ||
+               type == "Null")
+      {
+        target[name] = Json::nullValue;
+      }
+      else if (type == "Sequence")
+      {
+        const Json::Value& array = v["Value"];
+        assert(array.isArray());
+
+        Json::Value children = Json::arrayValue;
+        for (Json::Value::ArrayIndex i = 0; i < array.size(); i++)
+        {
+          Json::Value c;
+          SimplifyTags(c, array[i]);
+          children.append(c);
+        }
+
+        target[name] = children;
+      }
+      else
+      {
+        assert(0);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ServerToolbox.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,41 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 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 <json/json.h>
+
+namespace Orthanc
+{
+  void SimplifyTags(Json::Value& target,
+                    const Json::Value& source);
+}
--- a/OrthancServer/ToDcmtkBridge.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/ToDcmtkBridge.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -21,7 +33,6 @@
 #include "ToDcmtkBridge.h"
 
 #include <memory>
-#include <dcmtk/dcmdata/dcelem.h>
 #include <dcmtk/dcmnet/diutil.h>
 
 
--- a/OrthancServer/ToDcmtkBridge.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/ToDcmtkBridge.h	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -23,7 +35,6 @@
 #include "../Core/DicomFormat/DicomMap.h"
 #include <dcmtk/dcmdata/dcdatset.h>
 
-
 namespace Orthanc
 {
   class ToDcmtkBridge
--- a/OrthancServer/main.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/OrthancServer/main.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,18 @@
  * 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
@@ -20,7 +32,7 @@
 
 #include "OrthancRestApi.h"
 
-#include <stdio.h>
+#include <fstream>
 #include <glog/logging.h>
 #include <boost/algorithm/string/predicate.hpp>
 
@@ -29,22 +41,20 @@
 #include "../Core/HttpServer/MongooseServer.h"
 #include "DicomProtocol/DicomServer.h"
 #include "OrthancInitialization.h"
-
+#include "ServerContext.h"
 
 using namespace Orthanc;
 
 
+
 class MyDicomStore : public IStoreRequestHandler
 {
 private:
-  ServerIndex& index_;
-  FileStorage storage_;
+  ServerContext& context_;
 
 public:
-  MyDicomStore(ServerIndex& index,
-               const std::string& path) :
-    index_(index),
-    storage_(path)
+  MyDicomStore(ServerContext& context) :
+    context_(context)
   {
   }
 
@@ -53,12 +63,10 @@
                       const Json::Value& dicomJson,
                       const std::string& remoteAet)
   {
-    std::string instanceUuid;
     if (dicomFile.size() > 0)
     {
-      index_.Store(instanceUuid, storage_, 
-                   reinterpret_cast<const char*>(&dicomFile[0]), dicomFile.size(),
-                   dicomSummary, dicomJson, remoteAet);
+      context_.Store(reinterpret_cast<const char*>(&dicomFile[0]), dicomFile.size(),
+                     dicomSummary, dicomJson, remoteAet);
     }
   }
 };
@@ -67,20 +75,16 @@
 class MyDicomStoreFactory : public IStoreRequestHandlerFactory
 {
 private:
-  ServerIndex& index_;
-  std::string path_;
+  ServerContext& context_;
 
 public:
-  MyDicomStoreFactory(ServerIndex& index,
-                      const std::string& path) :
-    index_(index),
-    path_(path)
+  MyDicomStoreFactory(ServerContext& context) : context_(context)
   {
   }
 
   virtual IStoreRequestHandler* ConstructStoreRequestHandler()
   {
-    return new MyDicomStore(index_, path_);
+    return new MyDicomStore(context_);
   }
 
   void Done()
@@ -90,21 +94,95 @@
 };
 
 
+void PrintHelp(char* path)
+{
+  std::cout 
+    << "Usage: " << path << " [OPTION]... [CONFIGURATION]" << std::endl
+    << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." << std::endl
+    << std::endl
+    << "If no configuration file is given on the command line, a set of default " << std::endl
+    << "parameters is used. Please refer to the Orthanc homepage for the full " << std::endl
+    << "instructions about how to use Orthanc " << std::endl
+    << "<https://code.google.com/p/orthanc/wiki/OrthancCookbook>." << std::endl
+    << std::endl
+    << "Command-line options:" << std::endl
+    << "  --help\t\tdisplay this help and exit" << std::endl
+    << "  --logdir=[dir]\tdirectory where to store the log files" << std::endl
+    << "\t\t\t(if not used, the logs are dumped to stderr)" << std::endl
+    << "  --config=[file]\tcreate a sample configuration file and exit" << std::endl
+    << "  --trace\t\thighest verbosity in logs (for debug)" << std::endl
+    << "  --verbose\t\tbe verbose in logs" << std::endl
+    << "  --version\t\toutput version information and exit" << std::endl
+    << std::endl
+    << "Exit status:" << std::endl
+    << " 0  if OK," << std::endl
+    << " -1  if error (have a look at the logs)." << std::endl
+    << std::endl;
+}
 
 
+void PrintVersion(char* path)
+{
+  std::cout
+    << path << " " << ORTHANC_VERSION << std::endl
+    << "Copyright (C) 2012 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
+    << std::endl
+    << "Written by Sebastien Jodogne <s.jodogne@gmail.com>" << std::endl;
+}
+
 
 int main(int argc, char* argv[]) 
 {
   // Initialize Google's logging library.
   FLAGS_logtostderr = true;
-  
+  FLAGS_minloglevel = 1;
+  FLAGS_v = 0;
+
   for (int i = 1; i < argc; i++)
   {
+    if (std::string(argv[i]) == "--help")
+    {
+      PrintHelp(argv[0]);
+      return 0;
+    }
+
+    if (std::string(argv[i]) == "--version")
+    {
+      PrintVersion(argv[0]);
+      return 0;
+    }
+
+    if (std::string(argv[i]) == "--verbose")
+    {
+      FLAGS_minloglevel = 0;
+    }
+
+    if (std::string(argv[i]) == "--trace")
+    {
+      FLAGS_minloglevel = 0;
+      FLAGS_v = 1;
+    }
+
     if (boost::starts_with(argv[i], "--logdir="))
     {
       FLAGS_logtostderr = false;
       FLAGS_log_dir = std::string(argv[i]).substr(9);
     }
+
+    if (boost::starts_with(argv[i], "--config="))
+    {
+      std::string configurationSample;
+      GetFileResource(configurationSample, EmbeddedResources::CONFIGURATION_SAMPLE);
+
+      std::string target = std::string(argv[i]).substr(9);
+      std::ofstream f(target.c_str());
+      f << configurationSample;
+      f.close();
+      return 0;
+    }
   }
 
   google::InitGoogleLogging("Orthanc");
@@ -132,9 +210,30 @@
       OrthancInitialize();
     }
 
-    std::string storageDirectory = GetGlobalStringParameter("StorageDirectory", "OrthancStorage");
-    ServerIndex index(storageDirectory);
-    MyDicomStoreFactory storeScp(index, storageDirectory);
+    boost::filesystem::path storageDirectory = GetGlobalStringParameter("StorageDirectory", "OrthancStorage");
+    ServerContext context(storageDirectory);
+    context.SetCompressionEnabled(GetGlobalBoolParameter("StorageCompression", false));
+
+    try
+    {
+      context.GetIndex().SetMaximumPatientCount(GetGlobalIntegerParameter("MaximumPatientCount", 0));
+    }
+    catch (...)
+    {
+      context.GetIndex().SetMaximumPatientCount(0);
+    }
+
+    try
+    {
+      uint64_t size = GetGlobalIntegerParameter("MaximumStorageSize", 0);
+      context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024);
+    }
+    catch (...)
+    {
+      context.GetIndex().SetMaximumStorageSize(0);
+    }
+
+    MyDicomStoreFactory storeScp(context);
 
     {
       // DICOM server
@@ -146,7 +245,7 @@
 
       // HTTP server
       MongooseServer httpServer;
-      httpServer.SetPort(GetGlobalIntegerParameter("HttpPort", 8000));
+      httpServer.SetPortNumber(GetGlobalIntegerParameter("HttpPort", 8042));
       httpServer.SetRemoteAccessAllowed(GetGlobalBoolParameter("RemoteAccessAllowed", false));
 
       httpServer.SetAuthenticationEnabled(GetGlobalBoolParameter("AuthenticationEnabled", false));
@@ -163,23 +262,26 @@
         httpServer.SetSslEnabled(false);
       }
 
+      LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber();
+      LOG(WARNING) << "HTTP server listening on port: " << httpServer.GetPortNumber();
+
 #if ORTHANC_STANDALONE == 1
       httpServer.RegisterHandler(new EmbeddedResourceHttpHandler("/app", EmbeddedResources::ORTHANC_EXPLORER));
 #else
       httpServer.RegisterHandler(new FilesystemHttpHandler("/app", ORTHANC_PATH "/OrthancExplorer"));
 #endif
 
-      httpServer.RegisterHandler(new OrthancRestApi(index, storageDirectory));
+      httpServer.RegisterHandler(new OrthancRestApi(context));
 
       // GO !!!
       httpServer.Start();
       dicomServer.Start();
 
-      LOG(INFO) << "The server has started";
+      LOG(WARNING) << "Orthanc has started";
       Toolbox::ServerBarrier();
 
       // Stop
-      LOG(INFO) << "The server is stopping";
+      LOG(WARNING) << "Orthanc is stopping";
     }
 
     storeScp.Done();
@@ -187,6 +289,7 @@
   catch (OrthancException& e)
   {
     LOG(ERROR) << "EXCEPTION [" << e.What() << "]";
+return -1;
   }
 
   OrthancFinalize();
--- a/README	Thu Oct 04 15:09:56 2012 +0200
+++ b/README	Fri Dec 14 11:24:24 2012 +0100
@@ -37,10 +37,13 @@
 Licensing
 ---------
 
-Orthanc is licensed under the GPLv3 license. Because Orthanc uses the
-Software-as-a-Service paradigm, commercial products are allowed to
-access the Orthanc REST services and to bundle Orthanc in an
-commercial aggregate.
+Orthanc is licensed under the GPLv3 license, with the OpenSSL
+exception:
+http://people.gnome.org/~markmc/openssl-and-the-gpl.html
+
+Because Orthanc uses the Software-as-a-Service paradigm, commercial
+products are allowed to access the Orthanc REST services and to bundle
+Orthanc in an commercial aggregate.
 
 We also kindly require scientific works and clinical studies that make
 use of Orthanc to cite Orthanc in their associated publications.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Archives/MessageWithDestination.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,171 @@
+#include "../Core/IDynamicObject.h"
+
+#include "../Core/OrthancException.h"
+
+#include <stdint.h>
+#include <memory>
+#include <map>
+#include <gtest/gtest.h>
+#include <string>
+#include <boost/thread.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+namespace Orthanc
+{
+  class SharedMessageQueue
+  {
+  private:
+    typedef std::list<IDynamicObject*>  Queue;
+
+    unsigned int maxSize_;
+    Queue queue_;
+    boost::mutex mutex_;
+    boost::condition_variable elementAvailable_;
+
+  public:
+    SharedMessageQueue(unsigned int maxSize = 0)
+    {
+      maxSize_ = maxSize;
+    }
+
+    ~SharedMessageQueue()
+    {
+      for (Queue::iterator it = queue_.begin(); it != queue_.end(); it++)
+      {
+        delete *it;
+      }
+    }
+
+    void Enqueue(IDynamicObject* message)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (maxSize_ != 0 && queue_.size() > maxSize_)
+      {
+        // Too many elements in the queue: First remove the oldest
+        delete queue_.front();
+        queue_.pop_front();
+      }
+
+      queue_.push_back(message);
+      elementAvailable_.notify_one();
+    }
+
+    IDynamicObject* Dequeue(int32_t timeout)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      // Wait for a message to arrive in the FIFO queue
+      while (queue_.empty())
+      {
+        if (timeout == 0)
+        {
+          elementAvailable_.wait(lock);
+        }
+        else
+        {
+          bool success = elementAvailable_.timed_wait
+            (lock, boost::posix_time::milliseconds(timeout));
+          if (!success)
+          {
+            throw OrthancException(ErrorCode_Timeout);
+          }
+        }
+      }
+
+      std::auto_ptr<IDynamicObject> message(queue_.front());
+      queue_.pop_front();
+
+      return message.release();
+    }
+
+    IDynamicObject* Dequeue()
+    {
+      return Dequeue(0);
+    }
+  };
+
+
+  /**
+   * This class represents a message that is to be sent to some destination.
+   **/
+  class MessageToDispatch : public boost::noncopyable
+  {
+  private:
+    IDynamicObject* message_;
+    std::string destination_;
+
+  public:
+    /**
+     * Create a new message with a destination.
+     * \param message The content of the message (takes the ownership)
+     * \param destination The destination of the message
+     **/
+    MessageToDispatch(IDynamicObject* message,
+                      const char* destination)
+    {
+      message_ = message;
+      destination_ = destination;
+    }
+
+    ~MessageToDispatch()
+    {
+      if (message_)
+      {
+        delete message_;
+      }
+    }
+  };
+
+
+  class IDestinationContext : public IDynamicObject
+  {
+  public:
+    virtual void Handle(const IDynamicObject& message) = 0;
+  };
+
+
+  class IDestinationContextFactory : public IDynamicObject
+  {
+  public:
+    virtual IDestinationContext* Construct(const char* destination) = 0;
+  };
+
+
+  class MessageDispatcher
+  {
+  private:
+    typedef std::map<std::string, IDestinationContext*>  ActiveContexts;
+
+    std::auto_ptr<IDestinationContextFactory> factory_;
+    ActiveContexts activeContexts_;
+    SharedMessageQueue queue_;
+
+  public:
+    MessageDispatcher(IDestinationContextFactory* factory)  // takes the ownership
+    {
+      factory_.reset(factory);
+    }
+
+    ~MessageDispatcher()
+    {
+      for (ActiveContexts::iterator it = activeContexts_.begin(); 
+           it != activeContexts_.end(); it++)
+      {
+        delete it->second;
+      }
+    }
+  };
+}
+
+
+
+#include "../Core/DicomFormat/DicomString.h"
+
+using namespace Orthanc;
+
+TEST(MessageToDispatch, A)
+{
+  MessageToDispatch a(new DicomString("coucou"), "pukkaj");
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Archives/PrepareDatabase-v1.sql	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,136 @@
+CREATE TABLE GlobalProperties(
+       name TEXT PRIMARY KEY,
+       value TEXT
+       );
+
+CREATE TABLE Resources(
+       uuid TEXT PRIMARY KEY,
+       resourceType INTEGER
+       );
+
+CREATE TABLE Patients(
+       uuid TEXT PRIMARY KEY,
+       dicomPatientId TEXT
+       );
+
+CREATE TABLE Studies(
+       uuid TEXT PRIMARY KEY,
+       parentPatient TEXT REFERENCES Patients(uuid) ON DELETE CASCADE,
+       dicomStudy TEXT
+       );
+
+CREATE TABLE Series(
+       uuid TEXT PRIMARY KEY,
+       parentStudy TEXT REFERENCES Studies(uuid) ON DELETE CASCADE,
+       dicomSeries TEXT,
+       expectedNumberOfInstances INTEGER
+       );
+
+CREATE TABLE Instances(
+       uuid TEXT PRIMARY KEY,
+       parentSeries TEXT REFERENCES Series(uuid) ON DELETE CASCADE,
+       dicomInstance TEXT,
+       fileUuid TEXT,
+       fileSize INTEGER,
+       jsonUuid TEXT,
+       distantAet TEXT,
+       indexInSeries INTEGER
+       );
+
+CREATE TABLE MainDicomTags(
+       uuid TEXT,
+       tagGroup INTEGER,
+       tagElement INTEGER,
+       value TEXT,
+       PRIMARY KEY(uuid, tagGroup, tagElement)
+       );
+
+CREATE TABLE Changes(
+       seq INTEGER PRIMARY KEY AUTOINCREMENT,
+       basePath TEXT,
+       uuid TEXT
+       );
+
+
+CREATE INDEX PatientToStudies ON Studies(parentPatient);
+CREATE INDEX StudyToSeries ON Series(parentStudy);
+CREATE INDEX SeriesToInstances ON Instances(parentSeries);
+
+CREATE INDEX DicomPatientIndex ON Patients(dicomPatientId);
+CREATE INDEX DicomStudyIndex ON Studies(dicomStudy);
+CREATE INDEX DicomSeriesIndex ON Series(dicomSeries);
+CREATE INDEX DicomInstanceIndex ON Instances(dicomInstance);
+
+CREATE INDEX MainDicomTagsIndex ON MainDicomTags(uuid);
+CREATE INDEX MainDicomTagsGroupElement ON MainDicomTags(tagGroup, tagElement);
+CREATE INDEX MainDicomTagsValues ON MainDicomTags(value COLLATE BINARY);
+
+CREATE INDEX ChangesIndex ON Changes(uuid);
+
+CREATE TRIGGER InstanceRemoved
+AFTER DELETE ON Instances
+FOR EACH ROW BEGIN
+  DELETE FROM Resources WHERE uuid = old.uuid;
+  DELETE FROM MainDicomTags WHERE uuid = old.uuid;
+  DELETE FROM Changes WHERE uuid = old.uuid;
+  SELECT DeleteFromFileStorage(old.fileUuid);
+  SELECT DeleteFromFileStorage(old.jsonUuid);
+  SELECT SignalDeletedLevel(3, old.parentSeries);
+END;
+
+CREATE TRIGGER SeriesRemoved
+AFTER DELETE ON Series
+FOR EACH ROW BEGIN
+  DELETE FROM Resources WHERE uuid = old.uuid;
+  DELETE FROM MainDicomTags WHERE uuid = old.uuid;
+  DELETE FROM Changes WHERE uuid = old.uuid;
+  SELECT SignalDeletedLevel(2, old.parentStudy);
+END;
+
+CREATE TRIGGER StudyRemoved
+AFTER DELETE ON Studies
+FOR EACH ROW BEGIN
+  DELETE FROM Resources WHERE uuid = old.uuid;
+  DELETE FROM MainDicomTags WHERE uuid = old.uuid;
+  DELETE FROM Changes WHERE uuid = old.uuid;
+  SELECT SignalDeletedLevel(1, old.parentPatient);
+END;
+
+CREATE TRIGGER PatientRemoved
+AFTER DELETE ON Patients
+FOR EACH ROW BEGIN
+  DELETE FROM Resources WHERE uuid = old.uuid;
+  DELETE FROM MainDicomTags WHERE uuid = old.uuid;
+  DELETE FROM Changes WHERE uuid = old.uuid;
+  SELECT SignalDeletedLevel(0, "");
+END;
+
+
+
+
+CREATE TRIGGER InstanceRemovedUpwardCleaning
+AFTER DELETE ON Instances
+FOR EACH ROW 
+  WHEN (SELECT COUNT(*) FROM Instances WHERE parentSeries = old.parentSeries) = 0
+  BEGIN
+    SELECT DeleteFromFileStorage("deleting parent series");  -- TODO REMOVE THIS
+    DELETE FROM Series WHERE uuid = old.parentSeries;
+  END;
+
+CREATE TRIGGER SeriesRemovedUpwardCleaning
+AFTER DELETE ON Series
+FOR EACH ROW 
+  WHEN (SELECT COUNT(*) FROM Series WHERE parentStudy = old.parentStudy) = 0
+  BEGIN
+    SELECT DeleteFromFileStorage("deleting parent study");  -- TODO REMOVE THIS
+    DELETE FROM Studies WHERE uuid = old.parentStudy;
+  END;
+
+CREATE TRIGGER StudyRemovedUpwardCleaning
+AFTER DELETE ON Studies
+FOR EACH ROW 
+  WHEN (SELECT COUNT(*) FROM Studies WHERE parentPatient = old.parentPatient) = 0
+  BEGIN
+    SELECT DeleteFromFileStorage("deleting parent patient");  -- TODO REMOVE THIS
+    DELETE FROM Patients WHERE uuid = old.parentPatient;
+  END;
--- a/Resources/CMake/BoostConfiguration.cmake	Thu Oct 04 15:09:56 2012 +0200
+++ b/Resources/CMake/BoostConfiguration.cmake	Fri Dec 14 11:24:24 2012 +0100
@@ -8,7 +8,7 @@
   #set(Boost_USE_STATIC_LIBS ON)
 
   find_package(Boost
-    COMPONENTS filesystem thread system)
+    COMPONENTS filesystem thread system date_time)
 
   if (NOT Boost_FOUND)
     message(FATAL_ERROR "Unable to locate Boost on this system")
@@ -52,6 +52,11 @@
     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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/Compiler.cmake	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,94 @@
+# This file sets all the compiler-related flags
+
+if (${CMAKE_COMPILER_IS_GNUCXX})
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long -Wno-implicit-function-declaration")  
+  # --std=c99 makes libcurl not to compile
+  # -pedantic gives a lot of warnings on OpenSSL 
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wno-long-long -Wno-variadic-macros")
+elseif (${MSVC})
+  # http://stackoverflow.com/a/6510446
+  foreach(flag_var
+    CMAKE_C_FLAGS_DEBUG
+    CMAKE_CXX_FLAGS_DEBUG
+    CMAKE_C_FLAGS_RELEASE 
+    CMAKE_CXX_FLAGS_RELEASE
+    CMAKE_C_FLAGS_MINSIZEREL 
+    CMAKE_CXX_FLAGS_MINSIZEREL 
+    CMAKE_C_FLAGS_RELWITHDEBINFO 
+    CMAKE_CXX_FLAGS_RELWITHDEBINFO) 
+    string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+    string(REGEX REPLACE "/MDd" "/MTd" ${flag_var} "${${flag_var}}")
+  endforeach(flag_var)
+  add_definitions(
+    -D_CRT_SECURE_NO_WARNINGS=1
+    -D_CRT_SECURE_NO_DEPRECATE=1
+    )
+  include_directories(${CMAKE_SOURCE_DIR}/Resources/VisualStudio)
+  link_libraries(netapi32)
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+  if (DEBIAN_FORCE_HARDENING)
+    execute_process(
+      COMMAND dpkg-buildflags --get CPPFLAGS 
+      OUTPUT_VARIABLE DEBIAN_CPP_FLAGS
+      OUTPUT_STRIP_TRAILING_WHITESPACE)
+    execute_process(
+      COMMAND dpkg-buildflags --get CFLAGS 
+      OUTPUT_VARIABLE DEBIAN_C_FLAGS
+      OUTPUT_STRIP_TRAILING_WHITESPACE)
+    execute_process(
+      COMMAND dpkg-buildflags --get CXXFLAGS 
+      OUTPUT_VARIABLE DEBIAN_CXX_FLAGS
+      OUTPUT_STRIP_TRAILING_WHITESPACE)
+    execute_process(
+      COMMAND dpkg-buildflags --get LDFLAGS 
+      OUTPUT_VARIABLE DEBIAN_LD_FLAGS
+      OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DEBIAN_C_FLAGS} ${DEBIAN_CPP_FLAGS}")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEBIAN_CXX_FLAGS} ${DEBIAN_CPP_FLAGS}")
+  endif()
+
+  if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive")
+  endif()
+
+  add_definitions(
+    -D_LARGEFILE64_SOURCE=1 
+    -D_FILE_OFFSET_BITS=64
+    )
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed ${DEBIAN_LD_FLAGS}")
+  set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined ${DEBIAN_LD_FLAGS}")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined ${DEBIAN_LD_FLAGS}")
+
+  # Remove the "-rdynamic" option
+  # http://www.mail-archive.com/cmake@cmake.org/msg08837.html
+  set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
+  link_libraries(uuid pthread rt)
+
+elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  add_definitions(
+    -DWINVER=0x0501
+    -D_CRT_SECURE_NO_WARNINGS=1
+    )
+  link_libraries(rpcrt4 ws2_32)
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  CHECK_INCLUDE_FILES(rpc.h HAVE_UUID_H)
+else()
+  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
+endif()
+
+if (NOT HAVE_UUID_H)
+  message(FATAL_ERROR "Please install the uuid-dev package")
+endif()
+
+if (${STATIC_BUILD})
+  add_definitions(-DORTHANC_STATIC=1)
+else()
+  add_definitions(-DORTHANC_STATIC=0)
+endif()
--- a/Resources/CMake/DcmtkConfiguration.cmake	Thu Oct 04 15:09:56 2012 +0200
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Fri Dec 14 11:24:24 2012 +0100
@@ -1,6 +1,3 @@
-# We always statically link against DCMTK 3.6.0, as there are many
-# differences wrt. DCMTK 3.5.x.
-
 if (${STATIC_BUILD})
   SET(DCMTK_VERSION_NUMBER 360)
   SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0)
@@ -12,27 +9,42 @@
   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)
-  CONFIGURE_FILE(${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in
+
+  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 THIRD_PARTY_SOURCES)
-  AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmdata/libsrc THIRD_PARTY_SOURCES)
-  AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/ofstd/libsrc THIRD_PARTY_SOURCES)
+  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 THIRD_PARTY_SOURCES)
+  AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES)
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
-    list(REMOVE_ITEM THIRD_PARTY_SOURCES 
+    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 THIRD_PARTY_SOURCES 
+    list(REMOVE_ITEM DCMTK_SOURCES 
       ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc
       )
   endif()
 
-  list(REMOVE_ITEM THIRD_PARTY_SOURCES 
+  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
@@ -59,37 +71,39 @@
   set(DCMTK_BUNDLES_LOG4CPLUS 1)
 
 else()
-  #include(FindDCMTK)
-  set(DCMTK_DIR /usr/include/dcmtk)
-  set(DCMTK_INCLUDE_DIR ${DCMTK_DIR})
-
-  #message(${DCMTK_LIBRARIES})
+  # 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(dcmdata dcmnet wrap ofstd)
+  link_libraries(${DCMTK_LIBRARIES})
 
   add_definitions(
     -DHAVE_CONFIG_H=1
     )
 
-  if (NOT EXISTS "${DCMTK_DIR}/config/cfunix.h")
+  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_DIR}/config/cfunix.h" 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})
-
+  file(STRINGS
+    "${DCMTK_CONFIGURATION_FILE}" 
+    DCMTK_VERSION_NUMBER1 REGEX
+    ".*PACKAGE_VERSION .*")    
 
-  IF (EXISTS "${DCMTK_DIR}/oflog")
-    set(DCMTK_BUNDLES_LOG4CPLUS 1)
-    link_libraries(oflog)
-  else()
-    set(DCMTK_BUNDLES_LOG4CPLUS 0)
-  endif()
+  string(REGEX REPLACE
+    ".*PACKAGE_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$"
+    "\\1\\2\\3" 
+    DCMTK_VERSION_NUMBER 
+    ${DCMTK_VERSION_NUMBER1})
 endif()
 
 add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER})
 message("DCMTK version: ${DCMTK_VERSION_NUMBER}")
-message("Does DCMTK includes its own copy of Log4Cplus: ${DCMTK_BUNDLES_LOG4CPLUS}")
--- a/Resources/CMake/GoogleLogConfiguration.cmake	Thu Oct 04 15:09:56 2012 +0200
+++ b/Resources/CMake/GoogleLogConfiguration.cmake	Fri Dec 14 11:24:24 2012 +0100
@@ -1,7 +1,46 @@
-if (${STATIC_BUILD})
+if (STATIC_BUILD OR NOT USE_DYNAMIC_GOOGLE_LOG)
   SET(GOOGLE_LOG_SOURCES_DIR ${CMAKE_BINARY_DIR}/glog-0.3.2)
   DownloadPackage("http://google-glog.googlecode.com/files/glog-0.3.2.tar.gz" "${GOOGLE_LOG_SOURCES_DIR}" "" "")
 
+  set(GOOGLE_LOG_HEADERS
+    ${GOOGLE_LOG_SOURCES_DIR}/src/glog/logging.h
+    ${GOOGLE_LOG_SOURCES_DIR}/src/glog/raw_logging.h
+    ${GOOGLE_LOG_SOURCES_DIR}/src/glog/stl_logging.h
+    ${GOOGLE_LOG_SOURCES_DIR}/src/glog/vlog_is_on.h
+    )
+
+  set(ac_google_namespace google)
+  set(ac_google_start_namespace "namespace google {")
+  set(ac_google_end_namespace "}")
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+    set(ac_cv_have_unistd_h 1)
+    set(ac_cv_have_stdint_h 1)
+    set(ac_cv_have_systypes_h 0)
+    set(ac_cv_have_inttypes_h 0)
+    set(ac_cv_have_libgflags 0)
+    set(ac_cv_have_uint16_t 1)
+    set(ac_cv_have_u_int16_t 0)
+    set(ac_cv_have___uint16 0)
+    set(ac_cv_cxx_using_operator 1)
+    set(ac_cv_have___builtin_expect 1)
+  else()
+    set(ac_cv_have_unistd_h 0)
+    set(ac_cv_have_stdint_h 0)
+    set(ac_cv_have_systypes_h 0)
+    set(ac_cv_have_inttypes_h 0)
+    set(ac_cv_have_libgflags 0)
+    set(ac_cv_have_uint16_t 0)
+    set(ac_cv_have_u_int16_t 0)
+    set(ac_cv_have___uint16 1)
+    set(ac_cv_cxx_using_operator 1)
+    set(ac_cv_have___builtin_expect 0)
+  endif()
+
+  foreach (f ${GOOGLE_LOG_HEADERS})
+    configure_file(${f}.in ${f})
+  endforeach()
+
   include_directories(
     ${GOOGLE_LOG_SOURCES_DIR}/src
     )
--- a/Resources/CMake/GoogleLogConfiguration.h	Thu Oct 04 15:09:56 2012 +0200
+++ b/Resources/CMake/GoogleLogConfiguration.h	Fri Dec 14 11:24:24 2012 +0100
@@ -138,7 +138,16 @@
 #define PACKAGE_VERSION "0.3.2"
 
 /* How to access the PC from a struct ucontext */
+/*#include <ucontext.h>
+#include <sys/ucontext.h>
+#ifdef REG_RIP
 #define PC_FROM_UCONTEXT uc_mcontext.gregs[REG_RIP]
+#else
+#undef PC_FROM_UCONTEXT
+#endif*/
+
+// This is required for older versions of Linux
+#undef PC_FROM_UCONTEXT
 
 /* Define to necessary symbol if this constant uses a non-standard name on
    your system. */
--- a/Resources/CMake/GoogleTestConfiguration.cmake	Thu Oct 04 15:09:56 2012 +0200
+++ b/Resources/CMake/GoogleTestConfiguration.cmake	Fri Dec 14 11:24:24 2012 +0100
@@ -1,4 +1,13 @@
-if (${STATIC_BUILD})
+if (DEBIAN_USE_GTEST_SOURCE_PACKAGE)
+  set(GTEST_SOURCES /usr/src/gtest/src/gtest-all.cc)
+  include_directories(/usr/src/gtest)
+
+  if (NOT EXISTS /usr/include/gtest/gtest.h OR
+      NOT EXISTS ${GTEST_SOURCES})
+    message(FATAL_ERROR "Please install the libgtest-dev package")
+  endif()
+
+elseif (STATIC_BUILD OR NOT USE_DYNAMIC_GOOGLE_TEST)
   SET(GTEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.6.0)
   DownloadPackage("http://googletest.googlecode.com/files/gtest-1.6.0.zip" "${GTEST_SOURCES_DIR}" "" "")
 
@@ -13,6 +22,10 @@
 
 else()
   include(FindGTest)
+  if (NOT GTEST_FOUND)
+    message(FATAL_ERROR "Unable to find GoogleTest")
+  endif()
+
   include_directories(${GTEST_INCLUDE_DIRS})
   link_libraries(${GTEST_LIBRARIES})
 endif()
--- a/Resources/CMake/JsonCppConfiguration.cmake	Thu Oct 04 15:09:56 2012 +0200
+++ b/Resources/CMake/JsonCppConfiguration.cmake	Fri Dec 14 11:24:24 2012 +0100
@@ -1,14 +1,26 @@
-SET(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.5.0)
-DownloadPackage("http://downloads.sourceforge.net/project/jsoncpp/jsoncpp/0.5.0/jsoncpp-src-0.5.0.tar.gz" "${JSONCPP_SOURCES_DIR}" "" "")
+if (USE_DYNAMIC_JSONCPP)
+  CHECK_INCLUDE_FILE_CXX(jsoncpp/json/reader.h HAVE_JSONCPP_H)
+  if (NOT HAVE_JSONCPP_H)
+    message(FATAL_ERROR "Please install the libjsoncpp-dev package")
+  endif()
+
+  include_directories(/usr/include/jsoncpp)
+  link_libraries(jsoncpp)
+
+else()
+  SET(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.5.0)
+  DownloadPackage("http://downloads.sourceforge.net/project/jsoncpp/jsoncpp/0.5.0/jsoncpp-src-0.5.0.tar.gz" "${JSONCPP_SOURCES_DIR}" "" "")
 
-list(APPEND THIRD_PARTY_SOURCES
-  ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp
-  ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp
-  ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp
-  )
+  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
-  )
+  include_directories(
+    ${JSONCPP_SOURCES_DIR}/include
+    )
 
-source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*)
+  source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*)
+endif()
+
--- a/Resources/CMake/SQLiteConfiguration.cmake	Thu Oct 04 15:09:56 2012 +0200
+++ b/Resources/CMake/SQLiteConfiguration.cmake	Fri Dec 14 11:24:24 2012 +0100
@@ -1,19 +1,39 @@
-SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3071300)
-DownloadPackage("http://www.sqlite.org/sqlite-amalgamation-3071300.zip" "${SQLITE_SOURCES_DIR}" "" "")
+if (STATIC_BUILD OR NOT USE_DYNAMIC_SQLITE)
+  SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3071300)
+  DownloadPackage("http://www.sqlite.org/sqlite-amalgamation-3071300.zip" "${SQLITE_SOURCES_DIR}" "" "")
+
+  list(APPEND THIRD_PARTY_SOURCES
+    ${SQLITE_SOURCES_DIR}/sqlite3.c
+    )
 
-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}
+    )
 
-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
-  )
+  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()
 
-include_directories(
-  ${SQLITE_SOURCES_DIR}
-  )
+  # 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}")
 
-source_group(ThirdParty\\SQLite REGULAR_EXPRESSION ${SQLITE_SOURCES_DIR}/.*)
\ No newline at end of file
+  IF (${SQLITE_VERSION_NUMBER} LESS 3007000)
+    # "sqlite3_create_function_v2" is not defined in SQLite < 3.7.0
+    message(FATAL_ERROR "SQLite version must be above 3.7.0. Please set the CMake variable USE_DYNAMIC_SQLITE to OFF.")
+  ENDIF()
+
+  link_libraries(sqlite3)
+endif()
--- a/Resources/Configuration.json	Thu Oct 04 15:09:56 2012 +0200
+++ b/Resources/Configuration.json	Fri Dec 14 11:24:24 2012 +0100
@@ -3,9 +3,24 @@
      * 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 database
     "StorageDirectory" : "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,
 
 
     /**
@@ -13,7 +28,7 @@
      **/
 
     // HTTP port for the REST services and for the GUI
-    "HttpPort" : 8000,
+    "HttpPort" : 8042,
 
 
 
@@ -37,7 +52,7 @@
      **/
 
     // Whether remote hosts can connect to the HTTP server
-    "RemoteAccessAllowed" : true,
+    "RemoteAccessAllowed" : false,
 
     // Whether or not SSL is enabled
     "SslEnabled" : false,
@@ -62,7 +77,12 @@
 
     // The list of the known DICOM modalities
     "DicomModalities" : {
-        // "sample" : [ "SAMPLESCP", "192.168.100.42", 104 ]
+      /**
+       * Uncommenting the following line would enable Orthanc to
+       * connect to an instance of the "storescp" open-source DICOM
+       * store started by the command line "storescp 2000".
+       **/
+      // "sample" : [ "STORESCP", "localhost", 2000 ]
     },
 
     // The list of the known Orthanc peers (currently unused)
--- a/Resources/EmbedResources.py	Thu Oct 04 15:09:56 2012 +0200
+++ b/Resources/EmbedResources.py	Fri Dec 14 11:24:24 2012 +0100
@@ -1,7 +1,38 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012 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/>.
+
+
 import sys
 import os
 import os.path
 import pprint
+import re
 
 if len(sys.argv) < 2 or len(sys.argv) % 2 != 0:
     print ('Usage:')
@@ -20,6 +51,10 @@
 ## Read each resource file
 #####################################################################
 
+def CheckNoUpcase(s):
+    if re.search('[A-Z]', s) != None:
+        raise Exception("Path in a directory with an upcase letter: %s" % s)
+
 resources = {}
 
 counter = 0
@@ -42,10 +77,11 @@
             for f in files:
                 if f.find('~') == -1:  # Ignore Emacs backup files
                     if base == '.':
-                        r = f.lower()
+                        r = f
                     else:
-                        r = os.path.join(base, f).lower()
+                        r = os.path.join(base, f)
 
+                    CheckNoUpcase(r)
                     r = '/' + r.replace('\\', '/')
                     if r in content:
                         raise Exception("Twice the same filename (check case): " + r)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/LinuxStandardBaseToolchain.cmake	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,41 @@
+INCLUDE(CMakeForceCompiler)
+
+SET(LSB_PATH $ENV{LSB_PATH})
+SET(LSB_TARGET_VERSION "4.0")
+
+IF ("${LSB_PATH}" STREQUAL "")
+  SET(LSB_PATH "/opt/lsb")
+ENDIF()
+
+message("Using the following Linux Standard Base: ${LSB_PATH}")
+
+IF (EXISTS ${LSB_PATH}/lib64)
+  SET(LSB_TARGET_PROCESSOR "x86_64")
+ELSEIF (EXISTS ${LSB_PATH}/lib)
+  SET(LSB_TARGET_PROCESSOR "x86")
+ELSE()
+  MESSAGE(FATAL_ERROR "Unable to detect the target processor architecture. Check the LSB_PATH environment variable.")
+ENDIF()
+
+SET(LSB_CPPPATH ${LSB_PATH}/include)
+SET(LSB_LIBPATH ${LSB_PATH}/lib-${LSB_TARGET_VERSION})
+SET(PKG_CONFIG_PATH ${LSB_LIBPATH}/pkgconfig/)
+
+# the name of the target operating system
+SET(CMAKE_SYSTEM_NAME Linux)
+SET(CMAKE_SYSTEM_VERSION LinuxStandardBase)
+SET(CMAKE_SYSTEM_PROCESSOR ${LSB_TARGET_PROCESSOR})
+
+# which compilers to use for C and C++
+SET(CMAKE_C_COMPILER ${LSB_PATH}/bin/lsbcc)
+CMAKE_FORCE_CXX_COMPILER(${LSB_PATH}/bin/lsbc++ GNU)
+
+# here is the target environment located
+SET(CMAKE_FIND_ROOT_PATH ${LSB_PATH})
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,61 @@
+#!/usr/bin/python
+
+import os
+import sys
+import os.path
+import httplib2
+
+if len(sys.argv) != 4:
+    print("""
+Sample script to recursively import in Orthanc all the DICOM files
+that are stored in some path. Please make sure that Orthanc is running
+before starting this script. The files are uploaded through the REST
+API.
+
+Usage: %s [hostname] [HTTP port] [path]
+For instance: %s localhost 8042 .
+""" % (sys.argv[0], sys.argv[0]))
+    exit(-1)
+
+URL = 'http://%s:%d/instances' % (sys.argv[1], int(sys.argv[2]))
+
+success = 0
+
+
+# This function will upload a single file to Orthanc through the REST API
+def UploadFile(path):
+    global success
+
+    f = open(path, "r")
+    content = f.read()
+    f.close()
+
+    try:
+        sys.stdout.write("Importing %s" % path)
+
+        h = httplib2.Http()
+        resp, content = h.request(URL, 'POST', 
+                                  body = content,
+                                  headers = { 'content-type' : 'application/dicom' })
+
+        if resp.status == 200:
+            sys.stdout.write(" => success\n")
+            success += 1
+        else:
+            sys.stdout.write(" => failure (is it a DICOM file?)\n")
+
+    except:
+        sys.stdout.write(" => unable to connect\n")
+
+
+if os.path.isfile(sys.argv[3]):
+    # Upload a single file
+    UploadFile(sys.argv[3])
+else:
+    # Recursively upload a directory
+    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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/RestApi/CMakeLists.txt	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,62 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(RestApiSample)
+
+include(ExternalProject)
+
+# Send the toolchain information to the Orthanc 
+if (CMAKE_TOOLCHAIN_FILE)
+  set(TOOLCHAIN "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}")
+endif()
+
+ExternalProject_Add(
+  ORTHANC_CORE
+
+  # We use the Orthanc-0.3.1 branch for this sample
+  DOWNLOAD_COMMAND hg clone https://code.google.com/p/orthanc/ -r Orthanc-0.3.1
+
+  # Optional step, to reuse the third-party downloads
+  PATCH_COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/../../../ThirdPartyDownloads ThirdPartyDownloads
+
+  PREFIX ${CMAKE_BINARY_DIR}/Orthanc/
+  UPDATE_COMMAND ""
+  SOURCE_DIR ${CMAKE_BINARY_DIR}/Orthanc/src/orthanc/
+  CMAKE_COMMAND ${CMAKE_COMMAND}
+  CMAKE_ARGS -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DUSE_DYNAMIC_GOOGLE_LOG=OFF -DUSE_DYNAMIC_SQLITE=OFF -DONLY_CORE_LIBRARY=ON -DENABLE_SSL=OFF ${TOOLCHAIN}
+  BUILD_COMMAND $(MAKE)
+  INSTALL_COMMAND ""
+  BUILD_IN_SOURCE 0
+  )
+
+ExternalProject_Get_Property(ORTHANC_CORE source_dir)
+include_directories(${source_dir})
+
+ExternalProject_Get_Property(ORTHANC_CORE binary_dir)
+link_directories(${binary_dir})
+include_directories(${binary_dir}/jsoncpp-src-0.5.0/include)
+include_directories(${binary_dir}/glog-0.3.2/src)
+include_directories(${binary_dir}/boost_1_49_0)
+
+
+add_executable(RestApiSample
+  Sample.cpp
+  )
+
+add_dependencies(RestApiSample ORTHANC_CORE)
+
+target_link_libraries(RestApiSample 
+  # From Orthanc
+  CoreLibrary
+  GoogleLog
+
+  # These two libraries are not necessary
+  #OpenSSL
+  #Curl
+  )
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+  target_link_libraries(RestApiSample pthread)
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  add_definitions(-DGOOGLE_GLOG_DLL_DECL=)
+  target_link_libraries(RestApiSample wsock32)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/RestApi/Sample.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,105 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <Core/HttpServer/MongooseServer.h>
+#include <Core/RestApi/RestApi.h>
+#include <Core/Toolbox.h>
+#include <glog/logging.h>
+#include <stdio.h>
+
+
+/**
+ * This is a demo program that shows how to setup a REST server with
+ * the Orthanc Core API. Once the server is running, here are some 
+ * sample command lines to interact with it:
+ * 
+ *  # curl http://localhost:8042
+ *  # curl 'http://localhost:8042?name=Hide'
+ *  # curl http://localhost:8042 -X DELETE
+ *  # curl http://localhost:8042 -X PUT -d "PutBody"
+ *  # curl http://localhost:8042 -X POST -d "PostBody"
+ **/
+
+static void GetRoot(Orthanc::RestApi::GetCall& call)
+{
+  std::string answer = "Hello world\n";
+  answer += "Glad to meet you, Mr. " + call.GetArgument("name", "Nobody") + "\n";
+  call.GetOutput().AnswerBuffer(answer, "text/plain");
+}
+ 
+static void DeleteRoot(Orthanc::RestApi::DeleteCall& call)
+{
+  call.GetOutput().AnswerBuffer("Hey, you have just deleted the server!\n",
+                                "text/plain");
+}
+ 
+static void PostRoot(Orthanc::RestApi::PostCall& call)
+{
+  call.GetOutput().AnswerBuffer("I have received a POST with body: [" +
+                                call.GetPostBody() + "]\n", "text/plain");
+}
+ 
+static void PutRoot(Orthanc::RestApi::PutCall& call)
+{
+  call.GetOutput().AnswerBuffer("I have received a PUT with body: [" +
+                                call.GetPutBody() + "]\n", "text/plain");
+}
+ 
+int main()
+{
+  // Initialize the logging mechanism
+  google::InitGoogleLogging("Orthanc");
+  FLAGS_logtostderr = true;
+  FLAGS_minloglevel = 0;                      // Use the verbose mode
+  FLAGS_v = 0;
+  
+  // Define the callbacks of the REST API
+  std::auto_ptr<Orthanc::RestApi> rest(new Orthanc::RestApi);
+  rest->Register("/", GetRoot);
+  rest->Register("/", PostRoot);
+  rest->Register("/", PutRoot);
+  rest->Register("/", DeleteRoot);
+
+  // Setup the embedded HTTP server
+  Orthanc::MongooseServer httpServer;
+  httpServer.SetPortNumber(8042);             // Use TCP port 8042
+  httpServer.SetRemoteAccessAllowed(true);    // Do not block remote requests
+  httpServer.RegisterHandler(rest.release()); // The REST API is the handler
+
+  // Start the server and wait for the user to hit "Ctrl-C"
+  httpServer.Start();
+  LOG(WARNING) << "REST server has started";
+  Orthanc::Toolbox::ServerBarrier();
+  LOG(WARNING) << "REST server has stopped";
+
+  return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/sha1/Makefile	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,41 @@
+#
+#  Makefile
+#  
+#  Copyright (C) 1998, 2009
+#  Paul E. Jones <paulej@packetizer.com>
+#  All Rights Reserved.
+#
+#############################################################################
+#  $Id: Makefile 12 2009-06-22 19:34:25Z paulej $
+#############################################################################
+#
+#  Description:
+#	This is a makefile for UNIX to build the programs sha, shacmp, and
+#	shatest
+#
+#
+
+CC	= g++
+
+CFLAGS	= -c -O2 -Wall -D_FILE_OFFSET_BITS=64
+
+LIBS	=
+
+OBJS	= sha1.o
+
+all: sha shacmp shatest
+
+sha: sha.o $(OBJS)
+	$(CC) -o $@ sha.o $(OBJS) $(LIBS)
+
+shacmp: shacmp.o $(OBJS)
+	$(CC) -o $@ shacmp.o $(OBJS) $(LIBS)
+
+shatest: shatest.o $(OBJS)
+	$(CC) -o $@ shatest.o $(OBJS) $(LIBS)
+
+%.o: %.cpp
+	$(CC) $(CFLAGS) -o $@ $<
+
+clean:
+	$(RM) *.o sha shacmp shatest
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/sha1/Makefile.nt	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,48 @@
+#
+#  Makefile.nt
+#  
+#  Copyright (C) 1998, 2009
+#  Paul E. Jones <paulej@packetizer.com>
+#  All Rights Reserved.
+#
+#############################################################################
+#  $Id: Makefile.nt 13 2009-06-22 20:20:32Z paulej $
+#############################################################################
+#
+#  Description:
+#	This is a makefile for Win32 to build the programs sha, shacmp, and
+#	shatest
+#
+#  Portability Issues:
+#	Designed to work with Visual C++
+#
+#
+
+.silent:
+
+!include <win32.mak>
+
+RM	= del /q
+
+LIBS	= $(conlibs) setargv.obj
+
+CFLAGS	= -D _CRT_SECURE_NO_WARNINGS /EHsc /O2 /W3
+
+OBJS	= sha1.obj
+
+all: sha.exe shacmp.exe shatest.exe
+
+sha.exe: sha.obj $(OBJS)
+	$(link) $(conflags) -out:$@ sha.obj $(OBJS) $(LIBS)
+
+shacmp.exe: shacmp.obj $(OBJS)
+	$(link) $(conflags) -out:$@ shacmp.obj $(OBJS) $(LIBS)
+
+shatest.exe: shatest.obj $(OBJS)
+	$(link) $(conflags) -out:$@ shatest.obj $(OBJS) $(LIBS)
+
+.cpp.obj:
+	$(cc) $(CFLAGS) $(cflags) $(cvars) $<
+
+clean:
+	$(RM) *.obj sha.exe shacmp.exe shatest.exe
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/sha1/license.txt	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,14 @@
+Copyright (C) 1998, 2009
+Paul E. Jones <paulej@packetizer.com>
+
+Freeware Public License (FPL)
+
+This software is licensed as "freeware."  Permission to distribute
+this software in source and binary forms, including incorporation 
+into other products, is hereby granted without a fee.  THIS SOFTWARE 
+IS PROVIDED 'AS IS' AND WITHOUT ANY EXPRESSED OR IMPLIED WARRANTIES, 
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
+AND FITNESS FOR A PARTICULAR PURPOSE.  THE AUTHOR SHALL NOT BE HELD 
+LIABLE FOR ANY DAMAGES RESULTING FROM THE USE OF THIS SOFTWARE, EITHER 
+DIRECTLY OR INDIRECTLY, INCLUDING, BUT NOT LIMITED TO, LOSS OF DATA 
+OR DATA BEING RENDERED INACCURATE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/sha1/sha.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,176 @@
+/*
+ *  sha.cpp
+ *
+ *  Copyright (C) 1998, 2009
+ *  Paul E. Jones <paulej@packetizer.com>
+ *  All Rights Reserved
+ *
+ *****************************************************************************
+ *  $Id: sha.cpp 13 2009-06-22 20:20:32Z paulej $
+ *****************************************************************************
+ *
+ *  Description:
+ *      This utility will display the message digest (fingerprint) for
+ *      the specified file(s).
+ *
+ *  Portability Issues:
+ *      None.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#ifdef WIN32
+#include <io.h>
+#endif
+#include <fcntl.h>
+#include "sha1.h"
+
+/*
+ *  Function prototype
+ */
+void usage();
+
+
+/*  
+ *  main
+ *
+ *  Description:
+ *      This is the entry point for the program
+ *
+ *  Parameters:
+ *      argc: [in]
+ *          This is the count of arguments in the argv array
+ *      argv: [in]
+ *          This is an array of filenames for which to compute message digests
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+int main(int argc, char *argv[])
+{
+    SHA1        sha;                        // SHA-1 class
+    FILE        *fp;                        // File pointer for reading files
+    char        c;                          // Character read from file
+    unsigned    message_digest[5];          // Message digest from "sha"
+    int         i;                          // Counter
+    bool        reading_stdin;              // Are we reading standard in?
+    bool        read_stdin = false;         // Have we read stdin?
+
+    /*
+     *  Check the program arguments and print usage information if -?
+     *  or --help is passed as the first argument.
+     */
+    if (argc > 1 && (!strcmp(argv[1],"-?") || !strcmp(argv[1],"--help")))
+    {
+        usage();
+        return 1;
+    }
+
+    /*
+     *  For each filename passed in on the command line, calculate the
+     *  SHA-1 value and display it.
+     */
+    for(i = 0; i < argc; i++)
+    {
+        /*
+         *  We start the counter at 0 to guarantee entry into the for loop.
+         *  So if 'i' is zero, we will increment it now.  If there is no
+         *  argv[1], we will use STDIN below.
+         */
+        if (i == 0)
+        {
+            i++;
+        }
+
+        if (argc == 1 || !strcmp(argv[i],"-"))
+        {
+#ifdef WIN32
+            _setmode(_fileno(stdin), _O_BINARY);
+#endif
+            fp = stdin;
+            reading_stdin = true;
+        }
+        else
+        {
+            if (!(fp = fopen(argv[i],"rb")))
+            {
+                fprintf(stderr, "sha: unable to open file %s\n", argv[i]);
+                return 2;
+            }
+            reading_stdin = false;
+        }
+
+        /*
+         *  We do not want to read STDIN multiple times
+         */
+        if (reading_stdin)
+        {
+            if (read_stdin)
+            {
+                continue;
+            }
+
+            read_stdin = true;
+        }
+
+        /*
+         *  Reset the SHA1 object and process input
+         */
+        sha.Reset();
+
+        c = fgetc(fp);
+        while(!feof(fp))
+        {
+            sha.Input(c);
+            c = fgetc(fp);
+        }
+
+        if (!reading_stdin)
+        {
+            fclose(fp);
+        }
+
+        if (!sha.Result(message_digest))
+        {
+            fprintf(stderr,"sha: could not compute message digest for %s\n",
+                    reading_stdin?"STDIN":argv[i]);
+        }
+        else
+        {
+            printf( "%08X %08X %08X %08X %08X - %s\n",
+                    message_digest[0],
+                    message_digest[1],
+                    message_digest[2],
+                    message_digest[3],
+                    message_digest[4],
+                    reading_stdin?"STDIN":argv[i]);
+        }
+    }
+
+    return 0;
+}
+
+/*  
+ *  usage
+ *
+ *  Description:
+ *      This function will display program usage information to the user.
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void usage()
+{
+    printf("usage: sha <file> [<file> ...]\n");
+    printf("\tThis program will display the message digest (fingerprint)\n");
+    printf("\tfor files using the Secure Hashing Algorithm (SHA-1).\n");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/sha1/sha1.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,589 @@
+/*
+ *  sha1.cpp
+ *
+ *  Copyright (C) 1998, 2009
+ *  Paul E. Jones <paulej@packetizer.com>
+ *  All Rights Reserved.
+ *
+ *****************************************************************************
+ *  $Id: sha1.cpp 12 2009-06-22 19:34:25Z paulej $
+ *****************************************************************************
+ *
+ *  Description:
+ *      This class implements the Secure Hashing Standard as defined
+ *      in FIPS PUB 180-1 published April 17, 1995.
+ *
+ *      The Secure Hashing Standard, which uses the Secure Hashing
+ *      Algorithm (SHA), produces a 160-bit message digest for a
+ *      given data stream.  In theory, it is highly improbable that
+ *      two messages will produce the same message digest.  Therefore,
+ *      this algorithm can serve as a means of providing a "fingerprint"
+ *      for a message.
+ *
+ *  Portability Issues:
+ *      SHA-1 is defined in terms of 32-bit "words".  This code was
+ *      written with the expectation that the processor has at least
+ *      a 32-bit machine word size.  If the machine word size is larger,
+ *      the code should still function properly.  One caveat to that
+ *      is that the input functions taking characters and character arrays
+ *      assume that only 8 bits of information are stored in each character.
+ *
+ *  Caveats:
+ *      SHA-1 is designed to work with messages less than 2^64 bits long.
+ *      Although SHA-1 allows a message digest to be generated for
+ *      messages of any number of bits less than 2^64, this implementation
+ *      only works with messages with a length that is a multiple of 8
+ *      bits.
+ *
+ */
+
+
+#include "sha1.h"
+
+/*  
+ *  SHA1
+ *
+ *  Description:
+ *      This is the constructor for the sha1 class.
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+SHA1::SHA1()
+{
+    Reset();
+}
+
+/*  
+ *  ~SHA1
+ *
+ *  Description:
+ *      This is the destructor for the sha1 class
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+SHA1::~SHA1()
+{
+    // The destructor does nothing
+}
+
+/*  
+ *  Reset
+ *
+ *  Description:
+ *      This function will initialize the sha1 class member variables
+ *      in preparation for computing a new message digest.
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::Reset()
+{
+    Length_Low          = 0;
+    Length_High         = 0;
+    Message_Block_Index = 0;
+
+    H[0]        = 0x67452301;
+    H[1]        = 0xEFCDAB89;
+    H[2]        = 0x98BADCFE;
+    H[3]        = 0x10325476;
+    H[4]        = 0xC3D2E1F0;
+
+    Computed    = false;
+    Corrupted   = false;
+}
+
+/*  
+ *  Result
+ *
+ *  Description:
+ *      This function will return the 160-bit message digest into the
+ *      array provided.
+ *
+ *  Parameters:
+ *      message_digest_array: [out]
+ *          This is an array of five unsigned integers which will be filled
+ *          with the message digest that has been computed.
+ *
+ *  Returns:
+ *      True if successful, false if it failed.
+ *
+ *  Comments:
+ *
+ */
+bool SHA1::Result(unsigned *message_digest_array)
+{
+    int i;                                  // Counter
+
+    if (Corrupted)
+    {
+        return false;
+    }
+
+    if (!Computed)
+    {
+        PadMessage();
+        Computed = true;
+    }
+
+    for(i = 0; i < 5; i++)
+    {
+        message_digest_array[i] = H[i];
+    }
+
+    return true;
+}
+
+/*  
+ *  Input
+ *
+ *  Description:
+ *      This function accepts an array of octets as the next portion of
+ *      the message.
+ *
+ *  Parameters:
+ *      message_array: [in]
+ *          An array of characters representing the next portion of the
+ *          message.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::Input(   const unsigned char *message_array,
+                    unsigned            length)
+{
+    if (!length)
+    {
+        return;
+    }
+
+    if (Computed || Corrupted)
+    {
+        Corrupted = true;
+        return;
+    }
+
+    while(length-- && !Corrupted)
+    {
+        Message_Block[Message_Block_Index++] = (*message_array & 0xFF);
+
+        Length_Low += 8;
+        Length_Low &= 0xFFFFFFFF;               // Force it to 32 bits
+        if (Length_Low == 0)
+        {
+            Length_High++;
+            Length_High &= 0xFFFFFFFF;          // Force it to 32 bits
+            if (Length_High == 0)
+            {
+                Corrupted = true;               // Message is too long
+            }
+        }
+
+        if (Message_Block_Index == 64)
+        {
+            ProcessMessageBlock();
+        }
+
+        message_array++;
+    }
+}
+
+/*  
+ *  Input
+ *
+ *  Description:
+ *      This function accepts an array of octets as the next portion of
+ *      the message.
+ *
+ *  Parameters:
+ *      message_array: [in]
+ *          An array of characters representing the next portion of the
+ *          message.
+ *      length: [in]
+ *          The length of the message_array
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::Input(   const char  *message_array,
+                    unsigned    length)
+{
+    Input((unsigned char *) message_array, length);
+}
+
+/*  
+ *  Input
+ *
+ *  Description:
+ *      This function accepts a single octets as the next message element.
+ *
+ *  Parameters:
+ *      message_element: [in]
+ *          The next octet in the message.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::Input(unsigned char message_element)
+{
+    Input(&message_element, 1);
+}
+
+/*  
+ *  Input
+ *
+ *  Description:
+ *      This function accepts a single octet as the next message element.
+ *
+ *  Parameters:
+ *      message_element: [in]
+ *          The next octet in the message.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::Input(char message_element)
+{
+    Input((unsigned char *) &message_element, 1);
+}
+
+/*  
+ *  operator<<
+ *
+ *  Description:
+ *      This operator makes it convenient to provide character strings to
+ *      the SHA1 object for processing.
+ *
+ *  Parameters:
+ *      message_array: [in]
+ *          The character array to take as input.
+ *
+ *  Returns:
+ *      A reference to the SHA1 object.
+ *
+ *  Comments:
+ *      Each character is assumed to hold 8 bits of information.
+ *
+ */
+SHA1& SHA1::operator<<(const char *message_array)
+{
+    const char *p = message_array;
+
+    while(*p)
+    {
+        Input(*p);
+        p++;
+    }
+
+    return *this;
+}
+
+/*  
+ *  operator<<
+ *
+ *  Description:
+ *      This operator makes it convenient to provide character strings to
+ *      the SHA1 object for processing.
+ *
+ *  Parameters:
+ *      message_array: [in]
+ *          The character array to take as input.
+ *
+ *  Returns:
+ *      A reference to the SHA1 object.
+ *
+ *  Comments:
+ *      Each character is assumed to hold 8 bits of information.
+ *
+ */
+SHA1& SHA1::operator<<(const unsigned char *message_array)
+{
+    const unsigned char *p = message_array;
+
+    while(*p)
+    {
+        Input(*p);
+        p++;
+    }
+
+    return *this;
+}
+
+/*  
+ *  operator<<
+ *
+ *  Description:
+ *      This function provides the next octet in the message.
+ *
+ *  Parameters:
+ *      message_element: [in]
+ *          The next octet in the message
+ *
+ *  Returns:
+ *      A reference to the SHA1 object.
+ *
+ *  Comments:
+ *      The character is assumed to hold 8 bits of information.
+ *
+ */
+SHA1& SHA1::operator<<(const char message_element)
+{
+    Input((unsigned char *) &message_element, 1);
+
+    return *this;
+}
+
+/*  
+ *  operator<<
+ *
+ *  Description:
+ *      This function provides the next octet in the message.
+ *
+ *  Parameters:
+ *      message_element: [in]
+ *          The next octet in the message
+ *
+ *  Returns:
+ *      A reference to the SHA1 object.
+ *
+ *  Comments:
+ *      The character is assumed to hold 8 bits of information.
+ *
+ */
+SHA1& SHA1::operator<<(const unsigned char message_element)
+{
+    Input(&message_element, 1);
+
+    return *this;
+}
+
+/*  
+ *  ProcessMessageBlock
+ *
+ *  Description:
+ *      This function will process the next 512 bits of the message
+ *      stored in the Message_Block array.
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *      Many of the variable names in this function, especially the single
+ *      character names, were used because those were the names used
+ *      in the publication.
+ *
+ */
+void SHA1::ProcessMessageBlock()
+{
+    const unsigned K[] =    {               // Constants defined for SHA-1
+                                0x5A827999,
+                                0x6ED9EBA1,
+                                0x8F1BBCDC,
+                                0xCA62C1D6
+                            };
+    int         t;                          // Loop counter
+    unsigned    temp;                       // Temporary word value
+    unsigned    W[80];                      // Word sequence
+    unsigned    A, B, C, D, E;              // Word buffers
+
+    /*
+     *  Initialize the first 16 words in the array W
+     */
+    for(t = 0; t < 16; t++)
+    {
+        W[t] = ((unsigned) Message_Block[t * 4]) << 24;
+        W[t] |= ((unsigned) Message_Block[t * 4 + 1]) << 16;
+        W[t] |= ((unsigned) Message_Block[t * 4 + 2]) << 8;
+        W[t] |= ((unsigned) Message_Block[t * 4 + 3]);
+    }
+
+    for(t = 16; t < 80; t++)
+    {
+       W[t] = CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
+    }
+
+    A = H[0];
+    B = H[1];
+    C = H[2];
+    D = H[3];
+    E = H[4];
+
+    for(t = 0; t < 20; t++)
+    {
+        temp = CircularShift(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    for(t = 20; t < 40; t++)
+    {
+        temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    for(t = 40; t < 60; t++)
+    {
+        temp = CircularShift(5,A) +
+               ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    for(t = 60; t < 80; t++)
+    {
+        temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    H[0] = (H[0] + A) & 0xFFFFFFFF;
+    H[1] = (H[1] + B) & 0xFFFFFFFF;
+    H[2] = (H[2] + C) & 0xFFFFFFFF;
+    H[3] = (H[3] + D) & 0xFFFFFFFF;
+    H[4] = (H[4] + E) & 0xFFFFFFFF;
+
+    Message_Block_Index = 0;
+}
+
+/*  
+ *  PadMessage
+ *
+ *  Description:
+ *      According to the standard, the message must be padded to an even
+ *      512 bits.  The first padding bit must be a '1'.  The last 64 bits
+ *      represent the length of the original message.  All bits in between
+ *      should be 0.  This function will pad the message according to those
+ *      rules by filling the message_block array accordingly.  It will also
+ *      call ProcessMessageBlock() appropriately.  When it returns, it
+ *      can be assumed that the message digest has been computed.
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1::PadMessage()
+{
+    /*
+     *  Check to see if the current message block is too small to hold
+     *  the initial padding bits and length.  If so, we will pad the
+     *  block, process it, and then continue padding into a second block.
+     */
+    if (Message_Block_Index > 55)
+    {
+        Message_Block[Message_Block_Index++] = 0x80;
+        while(Message_Block_Index < 64)
+        {
+            Message_Block[Message_Block_Index++] = 0;
+        }
+
+        ProcessMessageBlock();
+
+        while(Message_Block_Index < 56)
+        {
+            Message_Block[Message_Block_Index++] = 0;
+        }
+    }
+    else
+    {
+        Message_Block[Message_Block_Index++] = 0x80;
+        while(Message_Block_Index < 56)
+        {
+            Message_Block[Message_Block_Index++] = 0;
+        }
+
+    }
+
+    /*
+     *  Store the message length as the last 8 octets
+     */
+    Message_Block[56] = (Length_High >> 24) & 0xFF;
+    Message_Block[57] = (Length_High >> 16) & 0xFF;
+    Message_Block[58] = (Length_High >> 8) & 0xFF;
+    Message_Block[59] = (Length_High) & 0xFF;
+    Message_Block[60] = (Length_Low >> 24) & 0xFF;
+    Message_Block[61] = (Length_Low >> 16) & 0xFF;
+    Message_Block[62] = (Length_Low >> 8) & 0xFF;
+    Message_Block[63] = (Length_Low) & 0xFF;
+
+    ProcessMessageBlock();
+}
+
+
+/*  
+ *  CircularShift
+ *
+ *  Description:
+ *      This member function will perform a circular shifting operation.
+ *
+ *  Parameters:
+ *      bits: [in]
+ *          The number of bits to shift (1-31)
+ *      word: [in]
+ *          The value to shift (assumes a 32-bit integer)
+ *
+ *  Returns:
+ *      The shifted value.
+ *
+ *  Comments:
+ *
+ */
+unsigned SHA1::CircularShift(int bits, unsigned word)
+{
+    return ((word << bits) & 0xFFFFFFFF) | ((word & 0xFFFFFFFF) >> (32-bits));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/sha1/sha1.h	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,89 @@
+/*
+ *  sha1.h
+ *
+ *  Copyright (C) 1998, 2009
+ *  Paul E. Jones <paulej@packetizer.com>
+ *  All Rights Reserved.
+ *
+ *****************************************************************************
+ *  $Id: sha1.h 12 2009-06-22 19:34:25Z paulej $
+ *****************************************************************************
+ *
+ *  Description:
+ *      This class implements the Secure Hashing Standard as defined
+ *      in FIPS PUB 180-1 published April 17, 1995.
+ *
+ *      Many of the variable names in this class, especially the single
+ *      character names, were used because those were the names used
+ *      in the publication.
+ *
+ *      Please read the file sha1.cpp for more information.
+ *
+ */
+
+#ifndef _SHA1_H_
+#define _SHA1_H_
+
+class SHA1
+{
+
+    public:
+
+        SHA1();
+        virtual ~SHA1();
+
+        /*
+         *  Re-initialize the class
+         */
+        void Reset();
+
+        /*
+         *  Returns the message digest
+         */
+        bool Result(unsigned *message_digest_array);
+
+        /*
+         *  Provide input to SHA1
+         */
+        void Input( const unsigned char *message_array,
+                    unsigned            length);
+        void Input( const char  *message_array,
+                    unsigned    length);
+        void Input(unsigned char message_element);
+        void Input(char message_element);
+        SHA1& operator<<(const char *message_array);
+        SHA1& operator<<(const unsigned char *message_array);
+        SHA1& operator<<(const char message_element);
+        SHA1& operator<<(const unsigned char message_element);
+
+    private:
+
+        /*
+         *  Process the next 512 bits of the message
+         */
+        void ProcessMessageBlock();
+
+        /*
+         *  Pads the current message block to 512 bits
+         */
+        void PadMessage();
+
+        /*
+         *  Performs a circular left shift operation
+         */
+        inline unsigned CircularShift(int bits, unsigned word);
+
+        unsigned H[5];                      // Message digest buffers
+
+        unsigned Length_Low;                // Message length in bits
+        unsigned Length_High;               // Message length in bits
+
+        unsigned char Message_Block[64];    // 512-bit message blocks
+        int Message_Block_Index;            // Index into message block array
+
+        bool Computed;                      // Is the digest computed?
+        bool Corrupted;                     // Is the message digest corruped?
+    
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/sha1/shacmp.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,169 @@
+/*
+ *  shacmp.cpp
+ *
+ *  Copyright (C) 1998, 2009
+ *  Paul E. Jones <paulej@packetizer.com>
+ *  All Rights Reserved
+ *
+ *****************************************************************************
+ *  $Id: shacmp.cpp 12 2009-06-22 19:34:25Z paulej $
+ *****************************************************************************
+ *
+ *  Description:
+ *      This utility will compare two files by producing a message digest
+ *      for each file using the Secure Hashing Algorithm and comparing
+ *      the message digests.  This function will return 0 if they
+ *      compare or 1 if they do not or if there is an error.
+ *      Errors result in a return code higher than 1.
+ *
+ *  Portability Issues:
+ *      none.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "sha1.h"
+
+/*
+ *  Return codes
+ */
+#define SHA1_COMPARE        0
+#define SHA1_NO_COMPARE     1
+#define SHA1_USAGE_ERROR    2
+#define SHA1_FILE_ERROR     3
+
+/*
+ *  Function prototype
+ */
+void usage();
+
+/*  
+ *  main
+ *
+ *  Description:
+ *      This is the entry point for the program
+ *
+ *  Parameters:
+ *      argc: [in]
+ *          This is the count of arguments in the argv array
+ *      argv: [in]
+ *          This is an array of filenames for which to compute message digests
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+int main(int argc, char *argv[])
+{
+    SHA1        sha;                        // SHA-1 class
+    FILE        *fp;                        // File pointer for reading files
+    char        c;                          // Character read from file
+    unsigned    message_digest[2][5];       // Message digest for files
+    int         i;                          // Counter
+    bool        message_match;              // Message digest match flag
+    int         returncode;
+
+    /*
+     *  If we have two arguments, we will assume they are filenames.  If
+     *  we do not have to arguments, call usage() and exit.
+     */
+    if (argc != 3)
+    {
+        usage();
+        return SHA1_USAGE_ERROR;
+    }
+
+    /*
+     *  Get the message digests for each file
+     */
+    for(i = 1; i <= 2; i++)
+    {
+        sha.Reset();
+
+        if (!(fp = fopen(argv[i],"rb")))
+        {
+            fprintf(stderr, "sha: unable to open file %s\n", argv[i]);
+            return SHA1_FILE_ERROR;
+        }
+
+        c = fgetc(fp);
+        while(!feof(fp))
+        {
+            sha.Input(c);
+            c = fgetc(fp);
+        }
+
+        fclose(fp);
+
+        if (!sha.Result(message_digest[i-1]))
+        {
+            fprintf(stderr,"shacmp: could not compute message digest for %s\n",
+                    argv[i]);
+            return SHA1_FILE_ERROR;
+        }
+    }
+
+    /*
+     *  Compare the message digest values
+     */
+    message_match = true;
+    for(i = 0; i < 5; i++)
+    {
+        if (message_digest[0][i] != message_digest[1][i])
+        {
+            message_match = false;
+            break;
+        }
+    }
+
+    if (message_match)
+    {
+        printf("Fingerprints match:\n");
+        returncode = SHA1_COMPARE;
+    }
+    else
+    {
+        printf("Fingerprints do not match:\n");
+        returncode = SHA1_NO_COMPARE;
+    }
+
+    printf( "\t%08X %08X %08X %08X %08X\n",
+            message_digest[0][0],
+            message_digest[0][1],
+            message_digest[0][2],
+            message_digest[0][3],
+            message_digest[0][4]);
+    printf( "\t%08X %08X %08X %08X %08X\n",
+            message_digest[1][0],
+            message_digest[1][1],
+            message_digest[1][2],
+            message_digest[1][3],
+            message_digest[1][4]);
+
+    return returncode;
+}
+
+/*  
+ *  usage
+ *
+ *  Description:
+ *      This function will display program usage information to the user.
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void usage()
+{
+    printf("usage: shacmp <file> <file>\n");
+    printf("\tThis program will compare the message digests (fingerprints)\n");
+    printf("\tfor two files using the Secure Hashing Algorithm (SHA-1).\n");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/sha1/shatest.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,149 @@
+/*
+ *  shatest.cpp
+ *
+ *  Copyright (C) 1998, 2009
+ *  Paul E. Jones <paulej@packetizer.com>
+ *  All Rights Reserved
+ *
+ *****************************************************************************
+ *  $Id: shatest.cpp 12 2009-06-22 19:34:25Z paulej $
+ *****************************************************************************
+ *
+ *  Description:
+ *      This file will exercise the SHA1 class and perform the three
+ *      tests documented in FIPS PUB 180-1.
+ *
+ *  Portability Issues:
+ *      None.
+ *
+ */
+
+#include <iostream>
+#include "sha1.h"
+
+using namespace std;
+
+/*
+ *  Define patterns for testing
+ */
+#define TESTA   "abc"
+#define TESTB   "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+
+/*
+ *  Function prototype
+ */
+void DisplayMessageDigest(unsigned *message_digest);
+
+/*  
+ *  main
+ *
+ *  Description:
+ *      This is the entry point for the program
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+int main()
+{
+    SHA1        sha;
+    unsigned    message_digest[5];
+
+    /*
+     *  Perform test A
+     */
+    cout << endl << "Test A: 'abc'" << endl;
+
+    sha.Reset();
+    sha << TESTA;
+
+    if (!sha.Result(message_digest))
+    {
+        cerr << "ERROR-- could not compute message digest" << endl;
+    }
+    else
+    {
+        DisplayMessageDigest(message_digest);
+        cout << "Should match:" << endl;
+        cout << '\t' << "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D" << endl;
+    }
+
+    /*
+     *  Perform test B
+     */
+    cout << endl << "Test B: " << TESTB << endl;
+
+    sha.Reset();
+    sha << TESTB;
+
+    if (!sha.Result(message_digest))
+    {
+        cerr << "ERROR-- could not compute message digest" << endl;
+    }
+    else
+    {
+        DisplayMessageDigest(message_digest);
+        cout << "Should match:" << endl;
+        cout << '\t' << "84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1" << endl;
+    }
+
+    /*
+     *  Perform test C
+     */
+    cout << endl << "Test C: One million 'a' characters" << endl;
+
+    sha.Reset();
+    for(int i = 1; i <= 1000000; i++) sha.Input('a');
+
+    if (!sha.Result(message_digest))
+    {
+        cerr << "ERROR-- could not compute message digest" << endl;
+    }
+    else
+    {
+        DisplayMessageDigest(message_digest);
+        cout << "Should match:" << endl;
+        cout << '\t' << "34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F" << endl;
+    }
+
+    return 0;
+}
+
+/*  
+ *  DisplayMessageDigest
+ *
+ *  Description:
+ *      Display Message Digest array
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void DisplayMessageDigest(unsigned *message_digest)
+{
+    ios::fmtflags   flags;
+
+    cout << '\t';
+
+    flags = cout.setf(ios::hex|ios::uppercase,ios::basefield);
+    cout.setf(ios::uppercase);
+
+    for(int i = 0; i < 5 ; i++)
+    {
+        cout << message_digest[i] << ' ';
+    }
+
+    cout << endl;
+
+    cout.setf(flags);
+}
--- a/THANKS	Thu Oct 04 15:09:56 2012 +0200
+++ b/THANKS	Fri Dec 14 11:24:24 2012 +0100
@@ -8,7 +8,26 @@
 complete and exempt or errors.
 
 
-Contributors
-------------
+Code contributors
+-----------------
+
+* Cyril Paulus (cyril.paulus@student.ulg.ac.be), for the build process
+  and suggestions about the REST API.
+
+
+Artwork
+-------
+
+https://code.google.com/p/orthanc/wiki/Logos
 
-* Cyril Paulus (cyril.paulus@student.ulg.ac.be), for the build process.
+* Benjamin Golinvaux (golinvauxb@gmail.com), for creating the official logo.
+* Jean-François Degbomont (jfdegbo@gmail.com), for submitting a logo.
+* Martin Jolly (martin.jolly@gmail.com), for submitting a logo.
+* Philippe Sepers (sepers.philippe@gmail.com), for submitting a logo.
+
+
+Debian
+------
+
+* Mathieu Malaterre (mathieu.malaterre@gmail.com), for sponsoring Orthanc.
+* Andreas Tille (andreas@an3as.eu), for help about Debian packaging. 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTests/FileStorage.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,149 @@
+#include "gtest/gtest.h"
+
+#include <ctype.h>
+#include <glog/logging.h>
+
+#include "../Core/FileStorage/FileStorage.h"
+#include "../OrthancServer/ServerIndex.h"
+#include "../Core/Toolbox.h"
+#include "../Core/OrthancException.h"
+#include "../Core/Uuid.h"
+#include "../Core/HttpServer/FilesystemHttpSender.h"
+#include "../Core/HttpServer/BufferHttpSender.h"
+#include "../Core/FileStorage/FileStorageAccessor.h"
+#include "../Core/FileStorage/CompressedFileStorageAccessor.h"
+
+using namespace Orthanc;
+
+TEST(FileStorage, Basic)
+{
+  FileStorage s("FileStorageUnitTests");
+
+  std::string data = Toolbox::GenerateUuid();
+  std::string uid = s.Create(data);
+  std::string d;
+  s.ReadFile(d, uid);
+  ASSERT_EQ(d.size(), data.size());
+  ASSERT_FALSE(memcmp(&d[0], &data[0], data.size()));
+}
+
+TEST(FileStorage, EndToEnd)
+{
+  FileStorage s("FileStorageUnitTests");
+  s.Clear();
+
+  std::list<std::string> u;
+  for (unsigned int i = 0; i < 10; i++)
+  {
+    u.push_back(s.Create(Toolbox::GenerateUuid()));
+  }
+
+  std::set<std::string> ss;
+  s.ListAllFiles(ss);
+  ASSERT_EQ(10u, ss.size());
+  
+  unsigned int c = 0;
+  for (std::list<std::string>::iterator
+         i = u.begin(); i != u.end(); i++, c++)
+  {
+    ASSERT_TRUE(ss.find(*i) != ss.end());
+    if (c < 5)
+      s.Remove(*i);
+  }
+
+  s.ListAllFiles(ss);
+  ASSERT_EQ(5u, ss.size());
+
+  s.Clear();
+  s.ListAllFiles(ss);
+  ASSERT_EQ(0u, ss.size());
+}
+
+
+TEST(FileStorageAccessor, Simple)
+{
+  FileStorage s("FileStorageUnitTests");
+  FileStorageAccessor accessor(s);
+
+  std::string data = "Hello world";
+  FileInfo info = accessor.Write(data, FileContentType_Dicom);
+  
+  std::string r;
+  accessor.Read(r, info.GetUuid());
+
+  ASSERT_EQ(data, r);
+  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
+  ASSERT_EQ(11u, info.GetUncompressedSize());
+  ASSERT_EQ(11u, info.GetCompressedSize());
+  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
+}
+
+
+TEST(FileStorageAccessor, NoCompression)
+{
+  FileStorage s("FileStorageUnitTests");
+  CompressedFileStorageAccessor accessor(s);
+
+  accessor.SetCompressionForNextOperations(CompressionType_None);
+  std::string data = "Hello world";
+  FileInfo info = accessor.Write(data, FileContentType_Dicom);
+  
+  std::string r;
+  accessor.Read(r, info.GetUuid());
+
+  ASSERT_EQ(data, r);
+  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
+  ASSERT_EQ(11u, info.GetUncompressedSize());
+  ASSERT_EQ(11u, info.GetCompressedSize());
+  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
+}
+
+
+TEST(FileStorageAccessor, Compression)
+{
+  FileStorage s("FileStorageUnitTests");
+  CompressedFileStorageAccessor accessor(s);
+
+  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
+  std::string data = "Hello world";
+  FileInfo info = accessor.Write(data, FileContentType_Dicom);
+  
+  std::string r;
+  accessor.Read(r, info.GetUuid());
+
+  ASSERT_EQ(data, r);
+  ASSERT_EQ(CompressionType_Zlib, info.GetCompressionType());
+  ASSERT_EQ(11u, info.GetUncompressedSize());
+  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
+}
+
+
+TEST(FileStorageAccessor, Mix)
+{
+  FileStorage s("FileStorageUnitTests");
+  CompressedFileStorageAccessor accessor(s);
+
+  std::string r;
+  std::string compressedData = "Hello";
+  std::string uncompressedData = "HelloWorld";
+
+  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
+  FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom);
+  
+  accessor.SetCompressionForNextOperations(CompressionType_None);
+  FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom);
+  
+  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
+  accessor.Read(r, compressedInfo.GetUuid());
+  ASSERT_EQ(compressedData, r);
+
+  accessor.SetCompressionForNextOperations(CompressionType_None);
+  accessor.Read(r, compressedInfo.GetUuid());
+  ASSERT_NE(compressedData, r);
+
+  /*
+  // This test is too slow on Windows
+  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
+  ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid()), OrthancException);
+  */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTests/MemoryCache.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,128 @@
+#include "gtest/gtest.h"
+
+#include <glog/logging.h>
+#include <memory>
+#include <boost/thread.hpp>
+#include <boost/lexical_cast.hpp>
+#include "../Core/IDynamicObject.h"
+#include "../Core/Cache/MemoryCache.h"
+
+
+TEST(CacheIndex, Basic)
+{
+  Orthanc::CacheIndex<std::string> r;
+  
+  r.Add("d");
+  r.Add("a");
+  r.Add("c");
+  r.Add("b");
+
+  r.TagAsMostRecent("a");
+  r.TagAsMostRecent("d");
+  r.TagAsMostRecent("b");
+  r.TagAsMostRecent("c");
+  r.TagAsMostRecent("d");
+  r.TagAsMostRecent("c");
+
+  ASSERT_EQ("a", r.RemoveOldest());
+  ASSERT_EQ("b", r.RemoveOldest());
+  ASSERT_EQ("d", r.RemoveOldest());
+  ASSERT_EQ("c", r.RemoveOldest());
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
+TEST(CacheIndex, Payload)
+{
+  Orthanc::CacheIndex<std::string, int> r;
+  
+  r.Add("a", 420);
+  r.Add("b", 421);
+  r.Add("c", 422);
+  r.Add("d", 423);
+
+  r.TagAsMostRecent("a");
+  r.TagAsMostRecent("d");
+  r.TagAsMostRecent("b");
+  r.TagAsMostRecent("c");
+  r.TagAsMostRecent("d");
+  r.TagAsMostRecent("c");
+
+  ASSERT_TRUE(r.Contains("b"));
+  ASSERT_EQ(421, r.Invalidate("b"));
+  ASSERT_FALSE(r.Contains("b"));
+
+  int p;
+  ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p);
+  ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p);
+  ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p);
+
+  ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p);
+  ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p);
+  ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p);
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
+
+
+namespace
+{
+  class Integer : public Orthanc::IDynamicObject
+  {
+  private:
+    std::string& log_;
+    int value_;
+
+  public:
+    Integer(std::string& log, int v) : log_(log), value_(v)
+    {
+    }
+
+    virtual ~Integer()
+    {
+      LOG(INFO) << "Removing cache entry for " << value_;
+      log_ += boost::lexical_cast<std::string>(value_) + " ";
+    }
+
+    int GetValue() const 
+    {
+      return value_;
+    }
+  };
+
+  class IntegerProvider : public Orthanc::ICachePageProvider
+  {
+  public:
+    std::string log_;
+
+    Orthanc::IDynamicObject* Provide(const std::string& s)
+    {
+      LOG(INFO) << "Providing " << s;
+      return new Integer(log_, boost::lexical_cast<int>(s));
+    }
+  };
+}
+
+
+TEST(MemoryCache, Basic)
+{
+  IntegerProvider provider;
+
+  {
+    Orthanc::MemoryCache cache(provider, 3);
+    cache.Access("42");  // 42 -> exit
+    cache.Access("43");  // 43, 42 -> exit
+    cache.Access("45");  // 45, 43, 42 -> exit
+    cache.Access("42");  // 42, 45, 43 -> exit
+    cache.Access("43");  // 43, 42, 45 -> exit
+    cache.Access("47");  // 45 is removed; 47, 43, 42 -> exit 
+    cache.Access("44");  // 42 is removed; 44, 47, 43 -> exit
+    cache.Access("42");  // 43 is removed; 42, 44, 47 -> exit
+    // Closing the cache: 47, 44, 42 are successively removed
+  }
+
+  ASSERT_EQ("45 42 43 47 44 42 ", provider.log_);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTests/RestApi.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,52 @@
+#include "gtest/gtest.h"
+
+#include <ctype.h>
+#include <glog/logging.h>
+
+#include "../Core/RestApi/RestApi.h"
+#include "../Core/Uuid.h"
+#include "../Core/OrthancException.h"
+#include "../Core/Compression/ZlibCompressor.h"
+
+using namespace Orthanc;
+
+TEST(RestApi, RestApiPath)
+{
+  RestApiPath::Components args;
+  UriComponents trail;
+
+  {
+    RestApiPath uri("/coucou/{abc}/d/*");
+    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
+    ASSERT_EQ(1u, args.size());
+    ASSERT_EQ(3u, trail.size());
+    ASSERT_EQ("moi", args["abc"]);
+    ASSERT_EQ("e", trail[0]);
+    ASSERT_EQ("f", trail[1]);
+    ASSERT_EQ("g", trail[2]);
+
+    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f"));
+    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/"));
+    ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d"));
+    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi"));
+  }
+
+  {
+    RestApiPath uri("/coucou/{abc}/d");
+    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
+    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d"));
+    ASSERT_EQ(1u, args.size());
+    ASSERT_EQ(0u, trail.size());
+    ASSERT_EQ("moi", args["abc"]);
+  }
+
+  {
+    RestApiPath uri("/*");
+    ASSERT_TRUE(uri.Match(args, trail, "/a/b/c"));
+    ASSERT_EQ(0u, args.size());
+    ASSERT_EQ(3u, trail.size());
+    ASSERT_EQ("a", trail[0]);
+    ASSERT_EQ("b", trail[1]);
+    ASSERT_EQ("c", trail[2]);
+  }
+}
--- a/UnitTests/SQLite.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/UnitTests/SQLite.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -3,6 +3,7 @@
 #include "../Core/Toolbox.h"
 #include "../Core/SQLite/Connection.h"
 #include "../Core/SQLite/Statement.h"
+#include "../Core/SQLite/Transaction.h"
 
 #include <sqlite3.h>
 
@@ -202,3 +203,36 @@
   ASSERT_TRUE(func->deleted_.find(4300) != func->deleted_.end());
   ASSERT_TRUE(func->deleted_.find(4301) != func->deleted_.end());
 }
+
+
+TEST(SQLite, EmptyTransactions)
+{
+  try
+  {
+    SQLite::Connection c;
+    c.OpenInMemory();
+
+    c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY);");
+    c.Execute("INSERT INTO a VALUES(NULL)");
+      
+    {
+      SQLite::Transaction t(c);
+      t.Begin();
+      {
+        SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a");
+        s.Step();
+      }
+      //t.Commit();
+    }
+
+    {
+      SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a");
+      s.Step();
+    }
+  }
+  catch (OrthancException& e)
+  {
+    fprintf(stderr, "Exception: [%s]\n", e.What());
+    throw e;
+  }
+}
--- a/UnitTests/SQLiteChromium.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/UnitTests/SQLiteChromium.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,8 +7,9 @@
 
 #include <sqlite3.h>
 
+
 using namespace Orthanc;
-
+using namespace Orthanc::SQLite;
 
 
 /********************************************************************
@@ -37,13 +38,13 @@
     db_.Close();
   }
 
-  SQLite::Connection& db() 
+  Connection& db() 
   { 
     return db_; 
   }
 
 private:
-  SQLite::Connection db_;
+  Connection db_;
 };
 
 
@@ -67,17 +68,17 @@
             db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE"));
   ASSERT_EQ(SQLITE_ERROR,
             db().ExecuteAndReturnErrorCode(
-                "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)"));
+              "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)"));
 }
 
 TEST_F(SQLConnectionTest, CachedStatement) {
-  SQLite::StatementId id1("foo", 12);
+  StatementId id1("foo", 12);
   ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
   ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)"));
 
   // Create a new cached statement.
   {
-    SQLite::Statement s(db(), id1, "SELECT a FROM foo");
+    Statement s(db(), id1, "SELECT a FROM foo");
     ASSERT_TRUE(s.Step());
     EXPECT_EQ(12, s.ColumnInt(0));
   }
@@ -88,7 +89,7 @@
   {
     // Get the same statement using different SQL. This should ignore our
     // SQL and use the cached one (so it will be valid).
-    SQLite::Statement s(db(), id1, "something invalid(");
+    Statement s(db(), id1, "something invalid(");
     ASSERT_TRUE(s.Step());
     EXPECT_EQ(12, s.ColumnInt(0));
   }
@@ -132,7 +133,7 @@
   EXPECT_LT(0, row);
 
   // It should be the primary key of the row we just inserted.
-  SQLite::Statement s(db(), "SELECT value FROM foo WHERE id=?");
+  Statement s(db(), "SELECT value FROM foo WHERE id=?");
   s.BindInt64(0, row);
   ASSERT_TRUE(s.Step());
   EXPECT_EQ(12, s.ColumnInt(0));
@@ -155,65 +156,70 @@
  ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/statement_unittest.cc
  ********************************************************************/
 
-class SQLStatementTest : public SQLConnectionTest
+namespace Orthanc
 {
-};
-
+  namespace SQLite
+  {
+    class SQLStatementTest : public SQLConnectionTest
+    {
+    };
 
-TEST_F(SQLStatementTest, Run) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
+    TEST_F(SQLStatementTest, Run) {
+      ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+      ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
 
-  SQLite::Statement s(db(), "SELECT b FROM foo WHERE a=?");
-  // Stepping it won't work since we haven't bound the value.
-  EXPECT_FALSE(s.Step());
+      Statement s(db(), "SELECT b FROM foo WHERE a=?");
+      // Stepping it won't work since we haven't bound the value.
+      EXPECT_FALSE(s.Step());
 
-  // Run should fail since this produces output, and we should use Step(). This
-  // gets a bit wonky since sqlite says this is OK so succeeded is set.
-  s.Reset(true);
-  s.BindInt(0, 3);
-  EXPECT_FALSE(s.Run());
-  EXPECT_EQ(SQLITE_ROW, db().GetErrorCode());
+      // Run should fail since this produces output, and we should use Step(). This
+      // gets a bit wonky since sqlite says this is OK so succeeded is set.
+      s.Reset(true);
+      s.BindInt(0, 3);
+      EXPECT_FALSE(s.Run());
+      EXPECT_EQ(SQLITE_ROW, db().GetErrorCode());
 
-  // Resetting it should put it back to the previous state (not runnable).
-  s.Reset(true);
+      // Resetting it should put it back to the previous state (not runnable).
+      s.Reset(true);
 
-  // Binding and stepping should produce one row.
-  s.BindInt(0, 3);
-  EXPECT_TRUE(s.Step());
-  EXPECT_EQ(12, s.ColumnInt(0));
-  EXPECT_FALSE(s.Step());
-}
+      // Binding and stepping should produce one row.
+      s.BindInt(0, 3);
+      EXPECT_TRUE(s.Step());
+      EXPECT_EQ(12, s.ColumnInt(0));
+      EXPECT_FALSE(s.Step());
+    }
 
-TEST_F(SQLStatementTest, BasicErrorCallback) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)"));
-  // Insert in the foo table the primary key. It is an error to insert
-  // something other than an number. This error causes the error callback
-  // handler to be called with SQLITE_MISMATCH as error code.
-  SQLite::Statement s(db(), "INSERT INTO foo (a) VALUES (?)");
-  s.BindCString(0, "bad bad");
-  EXPECT_THROW(s.Run(), OrthancException);
-}
+    TEST_F(SQLStatementTest, BasicErrorCallback) {
+      ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)"));
+      // Insert in the foo table the primary key. It is an error to insert
+      // something other than an number. This error causes the error callback
+      // handler to be called with SQLITE_MISMATCH as error code.
+      Statement s(db(), "INSERT INTO foo (a) VALUES (?)");
+      s.BindCString(0, "bad bad");
+      EXPECT_THROW(s.Run(), OrthancException);
+    }
 
-TEST_F(SQLStatementTest, Reset) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)"));
+    TEST_F(SQLStatementTest, Reset) {
+      ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+      ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
+      ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)"));
 
-  SQLite::Statement s(db(), "SELECT b FROM foo WHERE a = ? ");
-  s.BindInt(0, 3);
-  ASSERT_TRUE(s.Step());
-  EXPECT_EQ(12, s.ColumnInt(0));
-  ASSERT_FALSE(s.Step());
+      Statement s(db(), "SELECT b FROM foo WHERE a = ? ");
+      s.BindInt(0, 3);
+      ASSERT_TRUE(s.Step());
+      EXPECT_EQ(12, s.ColumnInt(0));
+      ASSERT_FALSE(s.Step());
 
-  s.Reset(false);
-  // Verify that we can get all rows again.
-  ASSERT_TRUE(s.Step());
-  EXPECT_EQ(12, s.ColumnInt(0));
-  EXPECT_FALSE(s.Step());
+      s.Reset(false);
+      // Verify that we can get all rows again.
+      ASSERT_TRUE(s.Step());
+      EXPECT_EQ(12, s.ColumnInt(0));
+      EXPECT_FALSE(s.Step());
 
-  s.Reset(true);
-  ASSERT_FALSE(s.Step());
+      s.Reset(true);
+      ASSERT_FALSE(s.Step());
+    }
+  }
 }
 
 
@@ -221,7 +227,6 @@
 
 
 
-
 /********************************************************************
  ** Tests from
  ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc
@@ -239,7 +244,7 @@
   // Returns the number of rows in table "foo".
   int CountFoo() 
   {
-    SQLite::Statement count(db(), "SELECT count(*) FROM foo");
+    Statement count(db(), "SELECT count(*) FROM foo");
     count.Step();
     return count.ColumnInt(0);
   }
@@ -248,7 +253,7 @@
 
 TEST_F(SQLTransactionTest, Commit) {
   {
-    SQLite::Transaction t(db());
+    Transaction t(db());
     EXPECT_FALSE(t.IsOpen());
     t.Begin();
     EXPECT_TRUE(t.IsOpen());
@@ -266,7 +271,7 @@
   // Test some basic initialization, and that rollback runs when you exit the
   // scope.
   {
-    SQLite::Transaction t(db());
+    Transaction t(db());
     EXPECT_FALSE(t.IsOpen());
     t.Begin();
     EXPECT_TRUE(t.IsOpen());
@@ -278,7 +283,7 @@
   EXPECT_EQ(0, CountFoo());
 
   // Test explicit rollback.
-  SQLite::Transaction t2(db());
+  Transaction t2(db());
   EXPECT_FALSE(t2.IsOpen());
   t2.Begin();
 
@@ -296,13 +301,13 @@
 
   // Outermost transaction.
   {
-    SQLite::Transaction outer(db());
+    Transaction outer(db());
     outer.Begin();
     EXPECT_EQ(1, db().GetTransactionNesting());
 
     // The first inner one gets committed.
     {
-      SQLite::Transaction inner1(db());
+      Transaction inner1(db());
       inner1.Begin();
       EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
       EXPECT_EQ(2, db().GetTransactionNesting());
@@ -316,7 +321,7 @@
 
     // The second inner one gets rolled back.
     {
-      SQLite::Transaction inner2(db());
+      Transaction inner2(db());
       inner2.Begin();
       EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
       EXPECT_EQ(2, db().GetTransactionNesting());
@@ -329,7 +334,7 @@
     // back.
     EXPECT_EQ(1, db().GetTransactionNesting());
     {
-      SQLite::Transaction inner3(db());
+      Transaction inner3(db());
       EXPECT_THROW(inner3.Begin(), OrthancException);
       EXPECT_EQ(1, db().GetTransactionNesting());
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTests/ServerIndex.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -0,0 +1,398 @@
+#include "gtest/gtest.h"
+
+#include "../OrthancServer/DatabaseWrapper.h"
+#include "../Core/Uuid.h"
+
+#include <ctype.h>
+#include <glog/logging.h>
+
+using namespace Orthanc;
+
+namespace
+{
+  class ServerIndexListener : public IServerIndexListener
+  {
+  public:
+    std::vector<std::string> deletedFiles_;
+    std::string ancestorId_;
+    ResourceType ancestorType_;
+
+    void Reset()
+    {
+      ancestorId_ = "";
+      deletedFiles_.clear();
+    }
+
+    virtual void SignalRemainingAncestor(ResourceType type,
+                                         const std::string& publicId) 
+    {
+      ancestorId_ = publicId;
+      ancestorType_ = type;
+    }
+
+    virtual void SignalFileDeleted(const FileInfo& info)
+    {
+      const std::string fileUuid = info.GetUuid();
+      deletedFiles_.push_back(fileUuid);
+      LOG(INFO) << "A file must be removed: " << fileUuid;
+    }                                
+  };
+}
+
+
+TEST(DatabaseWrapper, Simple)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  int64_t a[] = {
+    index.CreateResource("a", ResourceType_Patient),   // 0
+    index.CreateResource("b", ResourceType_Study),     // 1
+    index.CreateResource("c", ResourceType_Series),    // 2
+    index.CreateResource("d", ResourceType_Instance),  // 3
+    index.CreateResource("e", ResourceType_Instance),  // 4
+    index.CreateResource("f", ResourceType_Instance),  // 5
+    index.CreateResource("g", ResourceType_Study)      // 6
+  };
+
+  ASSERT_EQ("a", index.GetPublicId(a[0]));
+  ASSERT_EQ("b", index.GetPublicId(a[1]));
+  ASSERT_EQ("c", index.GetPublicId(a[2]));
+  ASSERT_EQ("d", index.GetPublicId(a[3]));
+  ASSERT_EQ("e", index.GetPublicId(a[4]));
+  ASSERT_EQ("f", index.GetPublicId(a[5]));
+  ASSERT_EQ("g", index.GetPublicId(a[6]));
+
+  {
+    Json::Value t;
+    index.GetAllPublicIds(t, ResourceType_Patient);
+
+    ASSERT_EQ(1u, t.size());
+    ASSERT_EQ("a", t[0u].asString());
+
+    index.GetAllPublicIds(t, ResourceType_Series);
+    ASSERT_EQ(1u, t.size());
+    ASSERT_EQ("c", t[0u].asString());
+
+    index.GetAllPublicIds(t, ResourceType_Study);
+    ASSERT_EQ(2u, t.size());
+
+    index.GetAllPublicIds(t, ResourceType_Instance);
+    ASSERT_EQ(3u, t.size());
+  }
+
+  index.SetGlobalProperty(GlobalProperty_FlushSleep, "World");
+
+  index.AttachChild(a[0], a[1]);
+  index.AttachChild(a[1], a[2]);
+  index.AttachChild(a[2], a[3]);
+  index.AttachChild(a[2], a[4]);
+  index.AttachChild(a[6], a[5]);
+
+  int64_t parent;
+  ASSERT_FALSE(index.LookupParent(parent, a[0]));
+  ASSERT_TRUE(index.LookupParent(parent, a[1])); ASSERT_EQ(a[0], parent);
+  ASSERT_TRUE(index.LookupParent(parent, a[2])); ASSERT_EQ(a[1], parent);
+  ASSERT_TRUE(index.LookupParent(parent, a[3])); ASSERT_EQ(a[2], parent);
+  ASSERT_TRUE(index.LookupParent(parent, a[4])); ASSERT_EQ(a[2], parent);
+  ASSERT_TRUE(index.LookupParent(parent, a[5])); ASSERT_EQ(a[6], parent);
+  ASSERT_FALSE(index.LookupParent(parent, a[6]));
+
+  std::string s;
+  
+  ASSERT_FALSE(index.GetParentPublicId(s, a[0]));
+  ASSERT_FALSE(index.GetParentPublicId(s, a[6]));
+  ASSERT_TRUE(index.GetParentPublicId(s, a[1])); ASSERT_EQ("a", s);
+  ASSERT_TRUE(index.GetParentPublicId(s, a[2])); ASSERT_EQ("b", s);
+  ASSERT_TRUE(index.GetParentPublicId(s, a[3])); ASSERT_EQ("c", s);
+  ASSERT_TRUE(index.GetParentPublicId(s, a[4])); ASSERT_EQ("c", s);
+  ASSERT_TRUE(index.GetParentPublicId(s, a[5])); ASSERT_EQ("g", s);
+
+  std::list<std::string> l;
+  index.GetChildrenPublicId(l, a[0]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("b", l.front());
+  index.GetChildrenPublicId(l, a[1]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("c", l.front());
+  index.GetChildrenPublicId(l, a[3]); ASSERT_EQ(0u, l.size()); 
+  index.GetChildrenPublicId(l, a[4]); ASSERT_EQ(0u, l.size()); 
+  index.GetChildrenPublicId(l, a[5]); ASSERT_EQ(0u, l.size()); 
+  index.GetChildrenPublicId(l, a[6]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("f", l.front());
+
+  index.GetChildrenPublicId(l, a[2]); ASSERT_EQ(2u, l.size()); 
+  if (l.front() == "d")
+  {
+    ASSERT_EQ("e", l.back());
+  }
+  else
+  {
+    ASSERT_EQ("d", l.back());
+    ASSERT_EQ("e", l.front());
+  }
+
+  index.AddAttachment(a[4], FileInfo("my json file", FileContentType_Json, 42, CompressionType_Zlib, 21));
+  index.AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42));
+  index.AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44));
+  index.SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE");
+
+  ASSERT_EQ(21u + 42u + 44u, index.GetTotalCompressedSize());
+  ASSERT_EQ(42u + 42u + 44u, index.GetTotalUncompressedSize());
+
+  DicomMap m;
+  m.SetValue(0x0010, 0x0010, "PatientName");
+  index.SetMainDicomTags(a[3], m);
+
+  int64_t b;
+  ResourceType t;
+  ASSERT_TRUE(index.LookupResource("g", b, t));
+  ASSERT_EQ(7, b);
+  ASSERT_EQ(ResourceType_Study, t);
+
+  ASSERT_TRUE(index.LookupMetadata(s, a[4], MetadataType_Instance_RemoteAet));
+  ASSERT_FALSE(index.LookupMetadata(s, a[4], MetadataType_Instance_IndexInSeries));
+  ASSERT_EQ("PINNACLE", s);
+  ASSERT_EQ("PINNACLE", index.GetMetadata(a[4], MetadataType_Instance_RemoteAet));
+  ASSERT_EQ("None", index.GetMetadata(a[4], MetadataType_Instance_IndexInSeries, "None"));
+
+  ASSERT_TRUE(index.LookupGlobalProperty(s, GlobalProperty_FlushSleep));
+  ASSERT_FALSE(index.LookupGlobalProperty(s, static_cast<GlobalProperty>(42)));
+  ASSERT_EQ("World", s);
+  ASSERT_EQ("World", index.GetGlobalProperty(GlobalProperty_FlushSleep));
+  ASSERT_EQ("None", index.GetGlobalProperty(static_cast<GlobalProperty>(42), "None"));
+
+  FileInfo att;
+  ASSERT_TRUE(index.LookupAttachment(att, a[4], FileContentType_Json));
+  ASSERT_EQ("my json file", att.GetUuid());
+  ASSERT_EQ(21u, att.GetCompressedSize());
+  ASSERT_EQ(42u, att.GetUncompressedSize());
+  ASSERT_EQ(CompressionType_Zlib, att.GetCompressionType());
+
+  ASSERT_EQ(0u, listener.deletedFiles_.size());
+  ASSERT_EQ(7u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(3u, index.GetTableRecordCount("AttachedFiles"));
+  ASSERT_EQ(1u, index.GetTableRecordCount("Metadata"));
+  ASSERT_EQ(1u, index.GetTableRecordCount("MainDicomTags"));
+  index.DeleteResource(a[0]);
+
+  ASSERT_EQ(2u, listener.deletedFiles_.size());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "my json file") == listener.deletedFiles_.end());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "my dicom file") == listener.deletedFiles_.end());
+
+  ASSERT_EQ(2u, index.GetTableRecordCount("Resources"));
+  ASSERT_EQ(0u, index.GetTableRecordCount("Metadata"));
+  ASSERT_EQ(1u, index.GetTableRecordCount("AttachedFiles"));
+  ASSERT_EQ(0u, index.GetTableRecordCount("MainDicomTags"));
+  index.DeleteResource(a[5]);
+  ASSERT_EQ(0u, index.GetTableRecordCount("Resources"));
+  ASSERT_EQ(0u, index.GetTableRecordCount("AttachedFiles"));
+  ASSERT_EQ(2u, index.GetTableRecordCount("GlobalProperties"));
+
+  ASSERT_EQ(3u, listener.deletedFiles_.size());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "world") == listener.deletedFiles_.end());
+}
+
+
+
+
+TEST(DatabaseWrapper, Upward)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  int64_t a[] = {
+    index.CreateResource("a", ResourceType_Patient),   // 0
+    index.CreateResource("b", ResourceType_Study),     // 1
+    index.CreateResource("c", ResourceType_Series),    // 2
+    index.CreateResource("d", ResourceType_Instance),  // 3
+    index.CreateResource("e", ResourceType_Instance),  // 4
+    index.CreateResource("f", ResourceType_Study),     // 5
+    index.CreateResource("g", ResourceType_Series),    // 6
+    index.CreateResource("h", ResourceType_Series)     // 7
+  };
+
+  index.AttachChild(a[0], a[1]);
+  index.AttachChild(a[1], a[2]);
+  index.AttachChild(a[2], a[3]);
+  index.AttachChild(a[2], a[4]);
+  index.AttachChild(a[1], a[6]);
+  index.AttachChild(a[0], a[5]);
+  index.AttachChild(a[5], a[7]);
+
+  {
+    Json::Value j;
+    index.GetChildren(j, a[0]);
+    ASSERT_EQ(2u, j.size());
+    ASSERT_TRUE((j[0u] == "b" && j[1u] == "f") ||
+                (j[1u] == "b" && j[0u] == "f"));
+
+    index.GetChildren(j, a[1]);
+    ASSERT_EQ(2u, j.size());
+    ASSERT_TRUE((j[0u] == "c" && j[1u] == "g") ||
+                (j[1u] == "c" && j[0u] == "g"));
+
+    index.GetChildren(j, a[2]);
+    ASSERT_EQ(2u, j.size());
+    ASSERT_TRUE((j[0u] == "d" && j[1u] == "e") ||
+                (j[1u] == "d" && j[0u] == "e"));
+
+    index.GetChildren(j, a[3]); ASSERT_EQ(0u, j.size());
+    index.GetChildren(j, a[4]); ASSERT_EQ(0u, j.size());
+    index.GetChildren(j, a[5]); ASSERT_EQ(1u, j.size()); ASSERT_EQ("h", j[0u].asString());
+    index.GetChildren(j, a[6]); ASSERT_EQ(0u, j.size());
+    index.GetChildren(j, a[7]); ASSERT_EQ(0u, j.size());
+  }
+
+  listener.Reset();
+  index.DeleteResource(a[3]);
+  ASSERT_EQ("c", listener.ancestorId_);
+  ASSERT_EQ(ResourceType_Series, listener.ancestorType_);
+
+  listener.Reset();
+  index.DeleteResource(a[4]);
+  ASSERT_EQ("b", listener.ancestorId_);
+  ASSERT_EQ(ResourceType_Study, listener.ancestorType_);
+
+  listener.Reset();
+  index.DeleteResource(a[7]);
+  ASSERT_EQ("a", listener.ancestorId_);
+  ASSERT_EQ(ResourceType_Patient, listener.ancestorType_);
+
+  listener.Reset();
+  index.DeleteResource(a[6]);
+  ASSERT_EQ("", listener.ancestorId_);  // No more ancestor
+}
+
+
+TEST(DatabaseWrapper, PatientRecycling)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  std::vector<int64_t> patients;
+  for (int i = 0; i < 10; i++)
+  {
+    std::string p = "Patient " + boost::lexical_cast<std::string>(i);
+    patients.push_back(index.CreateResource(p, ResourceType_Patient));
+    index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10));
+    ASSERT_FALSE(index.IsProtectedPatient(patients[i]));
+  }
+
+  ASSERT_EQ(10u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(10u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  listener.Reset();
+
+  index.DeleteResource(patients[5]);
+  index.DeleteResource(patients[0]);
+  ASSERT_EQ(8u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(8u, index.GetTableRecordCount("PatientRecyclingOrder"));
+
+  ASSERT_EQ(2u, listener.deletedFiles_.size());
+  ASSERT_EQ("Patient 5", listener.deletedFiles_[0]);
+  ASSERT_EQ("Patient 0", listener.deletedFiles_[1]);
+
+  int64_t p;
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]);
+  index.DeleteResource(p);
+  index.DeleteResource(patients[8]);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]);
+  index.DeleteResource(p);
+  ASSERT_FALSE(index.SelectPatientToRecycle(p));
+
+  ASSERT_EQ(10u, listener.deletedFiles_.size());
+  ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+}
+
+
+TEST(DatabaseWrapper, PatientProtection)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  std::vector<int64_t> patients;
+  for (int i = 0; i < 5; i++)
+  {
+    std::string p = "Patient " + boost::lexical_cast<std::string>(i);
+    patients.push_back(index.CreateResource(p, ResourceType_Patient));
+    index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10));
+    ASSERT_FALSE(index.IsProtectedPatient(patients[i]));
+  }
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder"));
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder"));
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[3], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[3]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, listener.deletedFiles_.size());
+
+  // Unprotecting a patient puts it at the last position in the recycling queue
+  int64_t p;
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[1])); ASSERT_EQ(p, patients[4]);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
+  index.DeleteResource(p);
+  ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[2]));
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
+  index.DeleteResource(p);
+  // "patients[3]" is still protected
+  ASSERT_FALSE(index.SelectPatientToRecycle(p));
+
+  ASSERT_EQ(4u, listener.deletedFiles_.size());
+  ASSERT_EQ(1u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  index.SetProtectedPatient(patients[3], false);
+  ASSERT_EQ(1u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[3]));
+  ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[2]));
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
+  index.DeleteResource(p);
+
+  ASSERT_EQ(5u, listener.deletedFiles_.size());
+  ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+}
--- a/UnitTests/Versions.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/UnitTests/Versions.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -7,6 +7,7 @@
 #include <zlib.h>
 #include <curl/curl.h>
 #include <boost/version.hpp>
+#include <sqlite3.h>
 
 
 TEST(Versions, Zlib)
@@ -26,6 +27,18 @@
             png_access_version_number());
 }
 
+TEST(Versions, SQLite)
+{
+  // http://www.sqlite.org/capi3ref.html#sqlite3_libversion
+  assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER );
+  assert(strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) == 0);
+  assert(strcmp(sqlite3_libversion(), SQLITE_VERSION) == 0);
+
+  // Ensure that the SQLite version is above 3.7.0.
+  // "sqlite3_create_function_v2" is not defined in previous versions.
+  ASSERT_GE(SQLITE_VERSION_NUMBER, 3007000);
+}
+
 
 #if ORTHANC_STATIC == 1
 TEST(Versions, ZlibStatic)
--- a/UnitTests/Zip.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/UnitTests/Zip.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -2,14 +2,18 @@
 
 #include "../Core/OrthancException.h"
 #include "../Core/Compression/ZipWriter.h"
+#include "../Core/Compression/HierarchicalZipWriter.h"
+#include "../Core/Toolbox.h"
 
 
+using namespace Orthanc;
+
 TEST(ZipWriter, Basic)
 {
   Orthanc::ZipWriter w;
   w.SetOutputPath("hello.zip");
   w.Open();
-  w.CreateFileInZip("world/hello");
+  w.OpenFile("world/hello");
   w.Write("Hello world");
 }
 
@@ -22,3 +26,97 @@
   w.Open();
   ASSERT_THROW(w.Write("hello world"), Orthanc::OrthancException);
 }
+
+
+
+
+
+namespace Orthanc
+{
+  // The namespace is necessary
+  // http://code.google.com/p/googletest/wiki/AdvancedGuide#Private_Class_Members
+
+  TEST(HierarchicalZipWriter, Index)
+  {
+    HierarchicalZipWriter::Index i;
+    ASSERT_EQ("hello", i.OpenFile("hello"));
+    ASSERT_EQ("hello-2", i.OpenFile("hello"));
+    ASSERT_EQ("coucou", i.OpenFile("coucou"));
+    ASSERT_EQ("hello-3", i.OpenFile("hello"));
+
+    i.OpenDirectory("coucou");
+
+    ASSERT_EQ("coucou-2/world", i.OpenFile("world"));
+    ASSERT_EQ("coucou-2/world-2", i.OpenFile("world"));
+
+    i.OpenDirectory("world");
+  
+    ASSERT_EQ("coucou-2/world-3/hello", i.OpenFile("hello"));
+    ASSERT_EQ("coucou-2/world-3/hello-2", i.OpenFile("hello"));
+
+    i.CloseDirectory();
+
+    ASSERT_EQ("coucou-2/world-4", i.OpenFile("world"));
+
+    i.CloseDirectory();
+
+    ASSERT_EQ("coucou-3", i.OpenFile("coucou"));
+
+    ASSERT_THROW(i.CloseDirectory(), OrthancException);
+  }
+
+
+  TEST(HierarchicalZipWriter, Filenames)
+  {
+    ASSERT_EQ("trE hell", HierarchicalZipWriter::Index::KeepAlphanumeric("    ÊtrE hellô  "));
+
+    // The "^" character is considered as a space in DICOM
+    ASSERT_EQ("Hel lo world", HierarchicalZipWriter::Index::KeepAlphanumeric("    Hel^^  ^\r\n\t^^lo  \t  <world>  "));
+  }
+}
+
+
+TEST(HierarchicalZipWriter, Basic)
+{
+  static const std::string SPACES = "                             ";
+
+  HierarchicalZipWriter w("hello2.zip");
+
+  w.SetCompressionLevel(0);
+
+  // Inside "/"
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello\n");
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello-2\n");
+  w.OpenDirectory("hello");
+
+  // Inside "/hello-3"
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello\n");
+  w.OpenDirectory("hello");
+
+  w.SetCompressionLevel(9);
+
+  // Inside "/hello-3/hello-2"
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello\n");
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello-2\n");
+  w.CloseDirectory();
+
+  // Inside "/hello-3"
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello-3\n");
+
+  /**
+
+     TO CHECK THE CONTENT OF THE "hello2.zip" FILE:
+
+     # unzip -v hello2.zip 
+
+     => There must be 6 files. The first 3 files must have a negative
+     compression ratio.
+
+  **/
+}
--- a/UnitTests/main.cpp	Thu Oct 04 15:09:56 2012 +0200
+++ b/UnitTests/main.cpp	Fri Dec 14 11:24:24 2012 +0100
@@ -4,7 +4,6 @@
 
 #include "../Core/Compression/ZlibCompressor.h"
 #include "../Core/DicomFormat/DicomTag.h"
-#include "../Core/FileStorage.h"
 #include "../OrthancCppClient/HttpClient.h"
 #include "../Core/HttpServer/HttpHandler.h"
 #include "../Core/OrthancException.h"
@@ -98,51 +97,6 @@
   ASSERT_EQ(a["aaa"], "");
 }
 
-TEST(FileStorage, Basic)
-{
-  FileStorage s("FileStorageUnitTests");
-
-  std::string data = Toolbox::GenerateUuid();
-  std::string uid = s.Create(data);
-  std::string d;
-  s.ReadFile(d, uid);
-  ASSERT_EQ(d.size(), data.size());
-  ASSERT_FALSE(memcmp(&d[0], &data[0], data.size()));
-}
-
-TEST(FileStorage, EndToEnd)
-{
-  FileStorage s("FileStorageUnitTests");
-  s.Clear();
-
-  std::list<std::string> u;
-  for (unsigned int i = 0; i < 10; i++)
-  {
-    u.push_back(s.Create(Toolbox::GenerateUuid()));
-  }
-
-  std::set<std::string> ss;
-  s.ListAllFiles(ss);
-  ASSERT_EQ(10u, ss.size());
-  
-  unsigned int c = 0;
-  for (std::list<std::string>::iterator
-         i = u.begin(); i != u.end(); i++, c++)
-  {
-    ASSERT_TRUE(ss.find(*i) != ss.end());
-    if (c < 5)
-      s.Remove(*i);
-  }
-
-  s.ListAllFiles(ss);
-  ASSERT_EQ(5u, ss.size());
-
-  s.Clear();
-  s.ListAllFiles(ss);
-  ASSERT_EQ(0u, ss.size());
-}
-
-
 TEST(DicomFormat, Tag)
 {
   ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010)));
@@ -188,6 +142,7 @@
 
   ASSERT_THROW(Toolbox::SplitUriComponents(c, ""), OrthancException);
   ASSERT_THROW(Toolbox::SplitUriComponents(c, "a"), OrthancException);
+  ASSERT_THROW(Toolbox::SplitUriComponents(c, "/coucou//coucou"), OrthancException);
 }
 
 
@@ -266,6 +221,17 @@
   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(""));
@@ -279,6 +245,13 @@
   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>
@@ -302,7 +275,7 @@
 
   // Open in Emacs, then save with UTF-8 encoding, then "hexdump -C"
   std::string utf8 = Toolbox::ConvertToUtf8(s, "ISO-8859-1");
-  ASSERT_EQ(15, utf8.size());
+  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]));
@@ -325,6 +298,11 @@
 {
   // Initialize Google's logging library.
   FLAGS_logtostderr = true;
+  FLAGS_minloglevel = 0;
+
+  // Go to trace-level verbosity
+  //FLAGS_v = 1;
+
   google::InitGoogleLogging("Orthanc");
 
   OrthancInitialize();