changeset 556:b26a7c397c34 find-move-scp

merge
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 18 Sep 2013 15:27:07 +0200
parents f6e61e329bbf (current diff) 11fee43c5861 (diff)
children e0cfb413c86b
files Core/Cache/CacheIndex.h Core/PngWriter.cpp Core/PngWriter.h OrthancCppClient/CMakeLists.txt OrthancCppClient/HttpClient.cpp OrthancCppClient/HttpClient.h OrthancCppClient/HttpEnumerations.h OrthancCppClient/HttpException.cpp OrthancCppClient/HttpException.h OrthancCppClient/main.cpp UnitTests/PngWriter.cpp
diffstat 208 files changed, 8863 insertions(+), 2246 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Mon Apr 29 12:48:10 2013 +0200
+++ b/CMakeLists.txt	Wed Sep 18 15:27:07 2013 +0200
@@ -9,27 +9,25 @@
 
 # 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(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)")
 SET(ENABLE_SSL ON CACHE BOOL "Include support for SSL")
 SET(BUILD_UNIT_TESTS ON CACHE BOOL "Build the unit tests")
+SET(DCMTK_DICTIONARY_DIR "/usr/share/dcmtk" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (ignored in standalone builds)") 
 
-# Advanced parameters (for Debian packaging)
-SET(USE_DYNAMIC_JSONCPP OFF CACHE BOOL "Use the dynamic version of JsonCpp (only for Debian sid)")
+# Advanced parameters to fine-tune linking against system libraries
+SET(USE_DYNAMIC_JSONCPP OFF CACHE BOOL "Use the dynamic version of JsonCpp")
 SET(USE_DYNAMIC_GOOGLE_LOG ON CACHE BOOL "Use the dynamic version of Google Log")
-SET(USE_DYNAMIC_GOOGLE_TEST ON CACHE BOOL "Use the dynamic version of Google Test (not for Debian sid)")
+SET(USE_DYNAMIC_GOOGLE_TEST ON CACHE BOOL "Use the dynamic version of Google Test")
 SET(USE_DYNAMIC_SQLITE ON CACHE BOOL "Use the dynamic version of SQLite")
 SET(USE_DYNAMIC_MONGOOSE OFF CACHE BOOL "Use the dynamic version of Mongoose")
-SET(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")
+SET(USE_DYNAMIC_LUA OFF CACHE BOOL "Use the dynamic version of Lua")
+SET(DEBIAN_USE_GTEST_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
 
 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)
+mark_as_advanced(DEBIAN_USE_GTEST_SOURCE_PACKAGE)
 
 # Some basic inclusions
 include(CheckIncludeFiles)
@@ -62,23 +60,21 @@
 endif()
 
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/BoostConfiguration.cmake)
-
-if(NOT ONLY_CORE_LIBRARY)
-  include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake)
-endif()
-
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/MongooseConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/ZlibConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/SQLiteConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/JsonCppConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibCurlConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/LuaConfiguration.cmake)
 
 
 # Prepare the embedded files
 set(EMBEDDED_FILES
   PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql
   CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json
+  LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua
   )
 
 if (${STANDALONE_BUILD})
@@ -119,10 +115,12 @@
   Core/DicomFormat/DicomTag.cpp
   Core/DicomFormat/DicomIntegerPixelAccessor.cpp
   Core/DicomFormat/DicomInstanceHasher.cpp
+  Core/Enumerations.cpp
   Core/FileStorage/FileStorage.cpp
   Core/FileStorage/StorageAccessor.cpp
   Core/FileStorage/CompressedFileStorageAccessor.cpp
   Core/FileStorage/FileStorageAccessor.cpp
+  Core/HttpClient.cpp
   Core/HttpServer/EmbeddedResourceHttpHandler.cpp
   Core/HttpServer/FilesystemHttpHandler.cpp
   Core/HttpServer/HttpHandler.cpp
@@ -134,7 +132,11 @@
   Core/RestApi/RestApiOutput.cpp
   Core/RestApi/RestApi.cpp
   Core/MultiThreading/BagOfRunnablesBySteps.cpp
-  Core/PngWriter.cpp
+  Core/MultiThreading/SharedMessageQueue.cpp
+  Core/MultiThreading/ThreadedCommandProcessor.cpp
+  Core/MultiThreading/ArrayFilledByThreads.cpp
+  Core/FileFormats/PngReader.cpp
+  Core/FileFormats/PngWriter.cpp
   Core/SQLite/Connection.cpp
   Core/SQLite/FunctionContext.cpp
   Core/SQLite/Statement.cpp
@@ -143,68 +145,71 @@
   Core/SQLite/Transaction.cpp
   Core/Toolbox.cpp
   Core/Uuid.cpp
+  Core/Lua/LuaContext.cpp
+  Core/Lua/LuaFunctionCall.cpp
 
-  OrthancCppClient/HttpClient.cpp
-  OrthancCppClient/HttpException.cpp
+  OrthancCppClient/OrthancConnection.cpp
+  OrthancCppClient/Study.cpp
+  OrthancCppClient/Series.cpp
+  OrthancCppClient/Instance.cpp
+  OrthancCppClient/Patient.cpp
   )  
 
 
+add_library(ServerLibrary
+  STATIC
+  ${DCMTK_SOURCES}
+  OrthancServer/DicomProtocol/DicomFindAnswers.cpp
+  OrthancServer/DicomProtocol/DicomServer.cpp
+  OrthancServer/DicomProtocol/DicomUserConnection.cpp
+  OrthancServer/FromDcmtkBridge.cpp
+  OrthancServer/Internals/CommandDispatcher.cpp
+  OrthancServer/Internals/FindScp.cpp
+  OrthancServer/Internals/MoveScp.cpp
+  OrthancServer/Internals/StoreScp.cpp
+  OrthancServer/OrthancInitialization.cpp
+  OrthancServer/OrthancRestApi.cpp
+  OrthancServer/ServerIndex.cpp
+  OrthancServer/ToDcmtkBridge.cpp
+  OrthancServer/DatabaseWrapper.cpp
+  OrthancServer/ServerContext.cpp
+  OrthancServer/ServerEnumerations.cpp
+  OrthancServer/ServerToolbox.cpp
+  )
 
-if(NOT ONLY_CORE_LIBRARY)
-  add_library(ServerLibrary
-    STATIC
-    ${DCMTK_SOURCES}
-    OrthancServer/DicomProtocol/DicomFindAnswers.cpp
-    OrthancServer/DicomProtocol/DicomServer.cpp
-    OrthancServer/DicomProtocol/DicomUserConnection.cpp
-    OrthancServer/FromDcmtkBridge.cpp
-    OrthancServer/Internals/CommandDispatcher.cpp
-    OrthancServer/Internals/FindScp.cpp
-    OrthancServer/Internals/MoveScp.cpp
-    OrthancServer/Internals/StoreScp.cpp
-    OrthancServer/OrthancInitialization.cpp
-    OrthancServer/OrthancRestApi.cpp
-    OrthancServer/ServerIndex.cpp
-    OrthancServer/ToDcmtkBridge.cpp
-    OrthancServer/DatabaseWrapper.cpp
-    OrthancServer/ServerContext.cpp
-    OrthancServer/ServerEnumerations.cpp
-    OrthancServer/ServerToolbox.cpp
-    )
-
-  # Ensure autogenerated code is built before building ServerLibrary
-  add_dependencies(ServerLibrary CoreLibrary)
+# Ensure autogenerated code is built before building ServerLibrary
+add_dependencies(ServerLibrary CoreLibrary)
 
-  add_executable(Orthanc
-    OrthancServer/main.cpp
-    )
+add_executable(Orthanc
+  OrthancServer/main.cpp
+  )
 
-  target_link_libraries(Orthanc ServerLibrary CoreLibrary)
+target_link_libraries(Orthanc ServerLibrary CoreLibrary)
 
-  install(
-    TARGETS Orthanc
-    RUNTIME DESTINATION bin
-    )
+install(
+  TARGETS Orthanc
+  RUNTIME DESTINATION bin
+  )
 
-  # Build the unit tests if required
-  if (BUILD_UNIT_TESTS)
-    add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1)
-    include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake)
-    add_executable(UnitTests
-      ${GTEST_SOURCES}
-      UnitTests/FileStorage.cpp
-      UnitTests/MemoryCache.cpp
-      UnitTests/PngWriter.cpp
-      UnitTests/RestApi.cpp
-      UnitTests/SQLite.cpp
-      UnitTests/SQLiteChromium.cpp
-      UnitTests/ServerIndex.cpp
-      UnitTests/Versions.cpp
-      UnitTests/Zip.cpp
-      UnitTests/main.cpp
-      )
-    target_link_libraries(UnitTests ServerLibrary CoreLibrary)
-  endif()
+# Build the unit tests if required
+if (BUILD_UNIT_TESTS)
+  add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1)
+  include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake)
+  add_executable(UnitTests
+    ${GTEST_SOURCES}
+    UnitTests/FileStorage.cpp
+    UnitTests/MemoryCache.cpp
+    UnitTests/Png.cpp
+    UnitTests/RestApi.cpp
+    UnitTests/SQLite.cpp
+    UnitTests/SQLiteChromium.cpp
+    UnitTests/ServerIndex.cpp
+    UnitTests/Versions.cpp
+    UnitTests/Zip.cpp
+    UnitTests/Lua.cpp
+    UnitTests/main.cpp
+    )
+  target_link_libraries(UnitTests ServerLibrary CoreLibrary)
 endif()
 
 
--- a/Core/Cache/CacheIndex.h	Mon Apr 29 12:48:10 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,250 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <list>
-#include <map>
-#include <boost/noncopyable.hpp>
-#include <cassert>
-
-#include "../OrthancException.h"
-#include "../Toolbox.h"
-
-namespace Orthanc
-{
-  /**
-   * This class implements the index of a cache with least recently
-   * used (LRU) recycling policy. All the items of the cache index
-   * can be associated with a payload.
-   * Reference: http://stackoverflow.com/a/2504317
-   **/
-  template <typename T, typename Payload = NullType>
-  class CacheIndex : public boost::noncopyable
-  {
-  private:
-    typedef std::list< std::pair<T, Payload> >  Queue;
-    typedef std::map<T, typename Queue::iterator>  Index;
-
-    Index  index_;
-    Queue  queue_;
-
-    /**
-     * Internal method for debug builds to check whether the internal
-     * data structures are not corrupted.
-     **/
-    void CheckInvariants() const;
-
-  public:
-    /**
-     * Add a new element to the cache index, and make it the most
-     * recent element.
-     * \param id The ID of the element.
-     * \param payload The payload of the element.
-     **/
-    void Add(T id, Payload payload = Payload());
-
-    /**
-     * When accessing an element of the cache, this method tags the
-     * element as the most recently used.
-     * \param id The most recently accessed item.
-     **/
-    void TagAsMostRecent(T id);
-
-    /**
-     * Remove an element from the cache index.
-     * \param id The item to remove.
-     **/
-    Payload Invalidate(T id);
-
-    /**
-     * Get the oldest element in the cache and remove it.
-     * \return The oldest item.
-     **/
-    T RemoveOldest()
-    {
-      Payload p;
-      return RemoveOldest(p);
-    }
-
-    /**
-     * Get the oldest element in the cache, remove it and return the
-     * associated payload.
-     * \param payload Where to store the associated payload.
-     * \return The oldest item.
-     **/
-    T RemoveOldest(Payload& payload);
-
-    /**
-     * Check whether an element is contained in the cache.
-     * \param id The item.
-     * \return \c true iff the item is indexed by the cache.
-     **/
-    bool Contains(T id) const
-    {
-      return index_.find(id) != index_.end();
-    }
-
-    bool Contains(T id, Payload& payload) const
-    {
-      typename Index::const_iterator it = index_.find(id);
-      if (it == index_.end())
-      {
-        return false;
-      }
-      else
-      {
-        payload = it->second->second;
-        return true;
-      }
-    }
-
-    /**
-     * Return the number of elements in the cache.
-     * \return The number of elements.
-     **/
-    size_t GetSize() const
-    {
-      assert(index_.size() == queue_.size());
-      return queue_.size();
-    }
-
-    /**
-     * Check whether the cache index is empty.
-     * \return \c true iff the cache is empty.
-     **/
-    bool IsEmpty() const
-    {
-      return index_.empty();
-    }
-  };
-
-
-
-
-  /******************************************************************
-   ** Implementation of the template
-   ******************************************************************/
-
-  template <typename T, typename Payload>
-  void CacheIndex<T, Payload>::CheckInvariants() const
-  {
-#ifndef NDEBUG
-    assert(index_.size() == queue_.size());
-
-    for (typename Index::const_iterator 
-           it = index_.begin(); it != index_.end(); it++)
-    {
-      assert(it->second != queue_.end());
-      assert(it->second->first == it->first);
-    }
-#endif
-  }
-
-
-  template <typename T, typename Payload>
-  void CacheIndex<T, Payload>::Add(T id, Payload payload)
-  {
-    if (Contains(id))
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-
-    queue_.push_front(std::make_pair(id, payload));
-    index_[id] = queue_.begin();
-
-    CheckInvariants();
-  }
-
-
-  template <typename T, typename Payload>
-  void CacheIndex<T, Payload>::TagAsMostRecent(T id)
-  {
-    if (!Contains(id))
-    {
-      throw OrthancException(ErrorCode_InexistentItem);
-    }
-
-    typename Index::iterator it = index_.find(id);
-    assert(it != index_.end());
-
-    std::pair<T, Payload> item = *(it->second);
-    
-    queue_.erase(it->second);
-    queue_.push_front(item);
-    index_[id] = queue_.begin();
-
-    CheckInvariants();
-  }
-
-
-  template <typename T, typename Payload>
-  Payload CacheIndex<T, Payload>::Invalidate(T id)
-  {
-    if (!Contains(id))
-    {
-      throw OrthancException(ErrorCode_InexistentItem);
-    }
-
-    typename Index::iterator it = index_.find(id);
-    assert(it != index_.end());
-
-    Payload payload = it->second->second;
-    queue_.erase(it->second);
-    index_.erase(it);
-
-    CheckInvariants();
-    return payload;
-  }
-
-
-  template <typename T, typename Payload>
-  T CacheIndex<T, Payload>::RemoveOldest(Payload& payload)
-  {
-    if (IsEmpty())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-
-    std::pair<T, Payload> item = queue_.back();
-    T oldest = item.first;
-    payload = item.second;
-
-    queue_.pop_back();
-    assert(index_.find(oldest) != index_.end());
-    index_.erase(oldest);
-
-    CheckInvariants();
-
-    return oldest;
-  }
-}
--- a/Core/Cache/ICachePageProvider.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Cache/ICachePageProvider.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/LeastRecentlyUsedIndex.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,346 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <list>
+#include <map>
+#include <boost/noncopyable.hpp>
+#include <cassert>
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  /**
+   * This class implements the index of a cache with least recently
+   * used (LRU) recycling policy. All the items of the cache index
+   * can be associated with a payload.
+   * Reference: http://stackoverflow.com/a/2504317
+   **/
+  template <typename T, typename Payload = NullType>
+  class LeastRecentlyUsedIndex : public boost::noncopyable
+  {
+  private:
+    typedef std::list< std::pair<T, Payload> >  Queue;
+    typedef std::map<T, typename Queue::iterator>  Index;
+
+    Index  index_;
+    Queue  queue_;
+
+    /**
+     * Internal method for debug builds to check whether the internal
+     * data structures are not corrupted.
+     **/
+    void CheckInvariants() const;
+
+  public:
+    /**
+     * Add a new element to the cache index, and make it the most
+     * recent element.
+     * \param id The ID of the element.
+     * \param payload The payload of the element.
+     **/
+    void Add(T id, Payload payload = Payload());
+
+    void AddOrMakeMostRecent(T id, Payload payload = Payload());
+
+    /**
+     * When accessing an element of the cache, this method tags the
+     * element as the most recently used.
+     * \param id The most recently accessed item.
+     **/
+    void MakeMostRecent(T id);
+
+    void MakeMostRecent(T id, Payload updatedPayload);
+
+    /**
+     * Remove an element from the cache index.
+     * \param id The item to remove.
+     **/
+    Payload Invalidate(T id);
+
+    /**
+     * Get the oldest element in the cache and remove it.
+     * \return The oldest item.
+     **/
+    T RemoveOldest();
+
+    /**
+     * Get the oldest element in the cache, remove it and return the
+     * associated payload.
+     * \param payload Where to store the associated payload.
+     * \return The oldest item.
+     **/
+    T RemoveOldest(Payload& payload);
+
+    /**
+     * Check whether an element is contained in the cache.
+     * \param id The item.
+     * \return \c true iff the item is indexed by the cache.
+     **/
+    bool Contains(T id) const
+    {
+      return index_.find(id) != index_.end();
+    }
+
+    bool Contains(T id, Payload& payload) const
+    {
+      typename Index::const_iterator it = index_.find(id);
+      if (it == index_.end())
+      {
+        return false;
+      }
+      else
+      {
+        payload = it->second->second;
+        return true;
+      }
+    }
+
+    /**
+     * Return the number of elements in the cache.
+     * \return The number of elements.
+     **/
+    size_t GetSize() const
+    {
+      assert(index_.size() == queue_.size());
+      return queue_.size();
+    }
+
+    /**
+     * Check whether the cache index is empty.
+     * \return \c true iff the cache is empty.
+     **/
+    bool IsEmpty() const
+    {
+      return index_.empty();
+    }
+
+    const T& GetOldest() const;
+    
+    const Payload& GetOldestPayload() const;
+  };
+
+
+
+
+  /******************************************************************
+   ** Implementation of the template
+   ******************************************************************/
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::CheckInvariants() const
+  {
+#ifndef NDEBUG
+    assert(index_.size() == queue_.size());
+
+    for (typename Index::const_iterator 
+           it = index_.begin(); it != index_.end(); it++)
+    {
+      assert(it->second != queue_.end());
+      assert(it->second->first == it->first);
+    }
+#endif
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::Add(T id, Payload payload)
+  {
+    if (Contains(id))
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    queue_.push_front(std::make_pair(id, payload));
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::AddOrMakeMostRecent(T id, Payload payload)
+  {
+    typename Index::iterator it = index_.find(id);
+
+    if (it != index_.end())
+    {
+      // Already existing. Make it most recent.
+      std::pair<T, Payload> item = *(it->second);
+      item.second = payload;
+      queue_.erase(it->second);
+      queue_.push_front(item);
+    }
+    else
+    {
+      // New item
+      queue_.push_front(std::make_pair(id, payload));
+    }
+
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id, Payload updatedPayload)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    item.second = updatedPayload;
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  Payload LeastRecentlyUsedIndex<T, Payload>::Invalidate(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    Payload payload = it->second->second;
+    queue_.erase(it->second);
+    index_.erase(it);
+
+    CheckInvariants();
+    return payload;
+  }
+
+
+  template <typename T, typename Payload>
+  T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest(Payload& payload)
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+    payload = item.second;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+
+
+  template <typename T, typename Payload>
+  T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest()
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+
+
+  template <typename T, typename Payload>
+  const T& LeastRecentlyUsedIndex<T, Payload>::GetOldest() const
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return queue_.back().first;
+  }
+
+
+  template <typename T, typename Payload>
+  const Payload& LeastRecentlyUsedIndex<T, Payload>::GetOldestPayload() const
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return queue_.back().second;
+  }
+}
--- a/Core/Cache/MemoryCache.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Cache/MemoryCache.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -46,7 +46,7 @@
     {
       VLOG(1) << "Reusing a cache page";
       assert(p != NULL);
-      index_.TagAsMostRecent(id);
+      index_.MakeMostRecent(id);
       return *p;
     }
 
--- a/Core/Cache/MemoryCache.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Cache/MemoryCache.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -33,7 +33,7 @@
 #pragma once
 
 #include <memory>
-#include "CacheIndex.h"
+#include "LeastRecentlyUsedIndex.h"
 #include "ICachePageProvider.h"
 
 namespace Orthanc
@@ -52,7 +52,7 @@
 
     ICachePageProvider& provider_;
     size_t cacheSize_;
-    CacheIndex<std::string, Page*>  index_;
+    LeastRecentlyUsedIndex<std::string, Page*>  index_;
 
     Page& Load(const std::string& id);
 
--- a/Core/ChunkedBuffer.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/ChunkedBuffer.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/ChunkedBuffer.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/ChunkedBuffer.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/BufferCompressor.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Compression/BufferCompressor.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/BufferCompressor.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Compression/BufferCompressor.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/HierarchicalZipWriter.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Compression/HierarchicalZipWriter.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/HierarchicalZipWriter.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Compression/HierarchicalZipWriter.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZipWriter.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Compression/ZipWriter.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZipWriter.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Compression/ZipWriter.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZlibCompressor.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Compression/ZlibCompressor.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZlibCompressor.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Compression/ZlibCompressor.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomArray.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomArray.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomArray.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomArray.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomElement.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomElement.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomInstanceHasher.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomInstanceHasher.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomInstanceHasher.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomInstanceHasher.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -231,25 +231,28 @@
       pixel += channel * frameOffset_ / samplesPerPixel_ + x * bytesPerPixel_;
     }
 
-    int32_t v;
+    uint32_t v;
     v = pixel[0];
     if (bytesPerPixel_ >= 2)
-      v = v + (static_cast<int32_t>(pixel[1]) << 8);
+      v = v + (static_cast<uint32_t>(pixel[1]) << 8);
     if (bytesPerPixel_ >= 3)
-      v = v + (static_cast<int32_t>(pixel[2]) << 16);
+      v = v + (static_cast<uint32_t>(pixel[2]) << 16);
     if (bytesPerPixel_ >= 4)
-      v = v + (static_cast<int32_t>(pixel[3]) << 24);
+      v = v + (static_cast<uint32_t>(pixel[3]) << 24);
 
-    v = (v >> shift_) & mask_;
+    v = v >> shift_;
 
     if (v & signMask_)
     {
-      // Signed value: Not implemented yet
-      //throw OrthancException(ErrorCode_NotImplemented);
-      v = 0;
+      // Signed value
+      // http://en.wikipedia.org/wiki/Two%27s_complement#Subtraction_from_2N
+      return -static_cast<int32_t>(mask_) + static_cast<int32_t>(v & mask_) - 1;
     }
-
-    return v;
+    else
+    {
+      // Unsigned value
+      return static_cast<int32_t>(v & mask_);
+    }
   }
 
 
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomMap.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomMap.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -76,9 +76,11 @@
     DicomTag(0x0018, 0x0024),   // SequenceName
     DicomTag(0x0018, 0x1030),   // ProtocolName
     DicomTag(0x0020, 0x0011),   // SeriesNumber
-    //DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES,
+    DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES,
     DICOM_TAG_IMAGES_IN_ACQUISITION,
+    DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS,
     DICOM_TAG_NUMBER_OF_SLICES,
+    DICOM_TAG_NUMBER_OF_TIME_SLICES,
     DICOM_TAG_SERIES_INSTANCE_UID
   };
 
@@ -90,6 +92,7 @@
     DICOM_TAG_IMAGE_INDEX,
     DICOM_TAG_INSTANCE_NUMBER,
     DICOM_TAG_NUMBER_OF_FRAMES,
+    DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER,
     DICOM_TAG_SOP_INSTANCE_UID
   };
 
--- a/Core/DicomFormat/DicomMap.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomMap.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomNullValue.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomNullValue.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomString.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomString.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomTag.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomTag.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomTag.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomTag.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -95,6 +95,7 @@
   static const DicomTag DICOM_TAG_INSTANCE_NUMBER(0x0020, 0x0013);
 
   static const DicomTag DICOM_TAG_NUMBER_OF_SLICES(0x0054, 0x0081);
+  static const DicomTag DICOM_TAG_NUMBER_OF_TIME_SLICES(0x0054, 0x0101);
   static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
   static const DicomTag DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES(0x0018, 0x1090);
   static const DicomTag DICOM_TAG_IMAGES_IN_ACQUISITION(0x0020, 0x1002);
@@ -105,4 +106,8 @@
   static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
   static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID(0x0002, 0x0002);
   static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID(0x0002, 0x0003);
+
+  // DICOM tags used for fMRI (thanks to Will Ryder)
+  static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105);
+  static const DicomTag DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER(0x0020, 0x0100);
 }
--- a/Core/DicomFormat/DicomValue.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/DicomFormat/DicomValue.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/EnumerationDictionary.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,122 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+#include <string>
+#include <map>
+
+namespace Orthanc
+{
+  namespace Toolbox
+  {
+    template <typename Enumeration>
+    class EnumerationDictionary
+    {
+    private:
+      typedef std::map<Enumeration, std::string>  EnumerationToString;
+      typedef std::map<std::string, Enumeration>  StringToEnumeration;
+
+      EnumerationToString enumerationToString_;
+      StringToEnumeration stringToEnumeration_;
+
+    public:
+      void Add(Enumeration value, const std::string& str)
+      {
+        // Check if these values are free
+        if (enumerationToString_.find(value) != enumerationToString_.end() ||
+            stringToEnumeration_.find(str) != stringToEnumeration_.end())
+        {
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+
+        // Prevent the registration of a number
+        try
+        {
+          boost::lexical_cast<int>(str);
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+        catch (boost::bad_lexical_cast)
+        {
+          // OK, the string is not a number
+        }
+
+        enumerationToString_[value] = str;
+        stringToEnumeration_[str] = value;
+        stringToEnumeration_[boost::lexical_cast<std::string>(static_cast<int>(value))] = value;
+      }
+
+      Enumeration Translate(const std::string& str) const
+      {
+        try
+        {
+          int value = boost::lexical_cast<int>(str);
+          return static_cast<Enumeration>(value);
+        }
+        catch (boost::bad_lexical_cast)
+        {
+        }
+
+        typename StringToEnumeration::const_iterator
+          found = stringToEnumeration_.find(str);
+
+        if (found == stringToEnumeration_.end())
+        {
+          throw OrthancException(ErrorCode_InexistentItem);
+        }
+        else
+        {
+          return found->second;
+        }
+      }
+
+      std::string Translate(Enumeration e) const
+      {
+        typename EnumerationToString::const_iterator
+          found = enumerationToString_.find(e);
+
+        if (found == enumerationToString_.end())
+        {
+          // No name for this item
+          return boost::lexical_cast<std::string>(static_cast<int>(e));
+        }
+        else
+        {
+          return found->second;
+        }
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Enumerations.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,225 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Enumerations.h"
+
+#include "OrthancException.h"
+
+namespace Orthanc
+{
+  const char* EnumerationToString(HttpMethod method)
+  {
+    switch (method)
+    {
+      case HttpMethod_Get:
+        return "GET";
+
+      case HttpMethod_Post:
+        return "POST";
+
+      case HttpMethod_Delete:
+        return "DELETE";
+
+      case HttpMethod_Put:
+        return "PUT";
+
+      default:
+        return "?";
+    }
+  }
+
+
+  const char* EnumerationToString(HttpStatus status)
+  {
+    switch (status)
+    {
+    case HttpStatus_100_Continue:
+      return "Continue";
+
+    case HttpStatus_101_SwitchingProtocols:
+      return "Switching Protocols";
+
+    case HttpStatus_102_Processing:
+      return "Processing";
+
+    case HttpStatus_200_Ok:
+      return "OK";
+
+    case HttpStatus_201_Created:
+      return "Created";
+
+    case HttpStatus_202_Accepted:
+      return "Accepted";
+
+    case HttpStatus_203_NonAuthoritativeInformation:
+      return "Non-Authoritative Information";
+
+    case HttpStatus_204_NoContent:
+      return "No Content";
+
+    case HttpStatus_205_ResetContent:
+      return "Reset Content";
+
+    case HttpStatus_206_PartialContent:
+      return "Partial Content";
+
+    case HttpStatus_207_MultiStatus:
+      return "Multi-Status";
+
+    case HttpStatus_208_AlreadyReported:
+      return "Already Reported";
+
+    case HttpStatus_226_IMUsed:
+      return "IM Used";
+
+    case HttpStatus_300_MultipleChoices:
+      return "Multiple Choices";
+
+    case HttpStatus_301_MovedPermanently:
+      return "Moved Permanently";
+
+    case HttpStatus_302_Found:
+      return "Found";
+
+    case HttpStatus_303_SeeOther:
+      return "See Other";
+
+    case HttpStatus_304_NotModified:
+      return "Not Modified";
+
+    case HttpStatus_305_UseProxy:
+      return "Use Proxy";
+
+    case HttpStatus_307_TemporaryRedirect:
+      return "Temporary Redirect";
+
+    case HttpStatus_400_BadRequest:
+      return "Bad Request";
+
+    case HttpStatus_401_Unauthorized:
+      return "Unauthorized";
+
+    case HttpStatus_402_PaymentRequired:
+      return "Payment Required";
+
+    case HttpStatus_403_Forbidden:
+      return "Forbidden";
+
+    case HttpStatus_404_NotFound:
+      return "Not Found";
+
+    case HttpStatus_405_MethodNotAllowed:
+      return "Method Not Allowed";
+
+    case HttpStatus_406_NotAcceptable:
+      return "Not Acceptable";
+
+    case HttpStatus_407_ProxyAuthenticationRequired:
+      return "Proxy Authentication Required";
+
+    case HttpStatus_408_RequestTimeout:
+      return "Request Timeout";
+
+    case HttpStatus_409_Conflict:
+      return "Conflict";
+
+    case HttpStatus_410_Gone:
+      return "Gone";
+
+    case HttpStatus_411_LengthRequired:
+      return "Length Required";
+
+    case HttpStatus_412_PreconditionFailed:
+      return "Precondition Failed";
+
+    case HttpStatus_413_RequestEntityTooLarge:
+      return "Request Entity Too Large";
+
+    case HttpStatus_414_RequestUriTooLong:
+      return "Request-URI Too Long";
+
+    case HttpStatus_415_UnsupportedMediaType:
+      return "Unsupported Media Type";
+
+    case HttpStatus_416_RequestedRangeNotSatisfiable:
+      return "Requested Range Not Satisfiable";
+
+    case HttpStatus_417_ExpectationFailed:
+      return "Expectation Failed";
+
+    case HttpStatus_422_UnprocessableEntity:
+      return "Unprocessable Entity";
+
+    case HttpStatus_423_Locked:
+      return "Locked";
+
+    case HttpStatus_424_FailedDependency:
+      return "Failed Dependency";
+
+    case HttpStatus_426_UpgradeRequired:
+      return "Upgrade Required";
+
+    case HttpStatus_500_InternalServerError:
+      return "Internal Server Error";
+
+    case HttpStatus_501_NotImplemented:
+      return "Not Implemented";
+
+    case HttpStatus_502_BadGateway:
+      return "Bad Gateway";
+
+    case HttpStatus_503_ServiceUnavailable:
+      return "Service Unavailable";
+
+    case HttpStatus_504_GatewayTimeout:
+      return "Gateway Timeout";
+
+    case HttpStatus_505_HttpVersionNotSupported:
+      return "HTTP Version Not Supported";
+
+    case HttpStatus_506_VariantAlsoNegotiates:
+      return "Variant Also Negotiates";
+
+    case HttpStatus_507_InsufficientStorage:
+      return "Insufficient Storage";
+
+    case HttpStatus_509_BandwidthLimitExceeded:
+      return "Bandwidth Limit Exceeded";
+
+    case HttpStatus_510_NotExtended:
+      return "Not Extended";
+
+    default:
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- a/Core/Enumerations.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Enumerations.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -32,10 +32,15 @@
 
 #pragma once
 
-#include "../OrthancCppClient/HttpEnumerations.h"
-
 namespace Orthanc
 {
+  enum Endianness
+  {
+    Endianness_Unknown,
+    Endianness_Big,
+    Endianness_Little
+  };
+
   enum ErrorCode
   {
     // Generic error codes
@@ -49,6 +54,7 @@
     ErrorCode_BadSequenceOfCalls,
     ErrorCode_InexistentItem,
     ErrorCode_BadRequest,
+    ErrorCode_NetworkProtocol,
 
     // Specific error codes
     ErrorCode_UriSyntax,
@@ -65,7 +71,97 @@
   {
     PixelFormat_RGB24,
     PixelFormat_Grayscale8,
-    PixelFormat_Grayscale16
+    PixelFormat_Grayscale16,
+    PixelFormat_SignedGrayscale16
+  };
+
+  enum ImageExtractionMode
+  {
+    ImageExtractionMode_Preview,
+    ImageExtractionMode_UInt8,
+    ImageExtractionMode_UInt16,
+    ImageExtractionMode_Int16
+  };
+
+
+  /**
+   * Most common, non-joke and non-experimental HTTP status codes
+   * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+   **/
+  enum HttpStatus
+  {
+    HttpStatus_None = -1,
+
+    // 1xx Informational
+    HttpStatus_100_Continue = 100,
+    HttpStatus_101_SwitchingProtocols = 101,
+    HttpStatus_102_Processing = 102,
+
+    // 2xx Success
+    HttpStatus_200_Ok = 200,
+    HttpStatus_201_Created = 201,
+    HttpStatus_202_Accepted = 202,
+    HttpStatus_203_NonAuthoritativeInformation = 203,
+    HttpStatus_204_NoContent = 204,
+    HttpStatus_205_ResetContent = 205,
+    HttpStatus_206_PartialContent = 206,
+    HttpStatus_207_MultiStatus = 207,
+    HttpStatus_208_AlreadyReported = 208,
+    HttpStatus_226_IMUsed = 226,
+
+    // 3xx Redirection
+    HttpStatus_300_MultipleChoices = 300,
+    HttpStatus_301_MovedPermanently = 301,
+    HttpStatus_302_Found = 302,
+    HttpStatus_303_SeeOther = 303,
+    HttpStatus_304_NotModified = 304,
+    HttpStatus_305_UseProxy = 305,
+    HttpStatus_307_TemporaryRedirect = 307,
+
+    // 4xx Client Error
+    HttpStatus_400_BadRequest = 400,
+    HttpStatus_401_Unauthorized = 401,
+    HttpStatus_402_PaymentRequired = 402,
+    HttpStatus_403_Forbidden = 403,
+    HttpStatus_404_NotFound = 404,
+    HttpStatus_405_MethodNotAllowed = 405,
+    HttpStatus_406_NotAcceptable = 406,
+    HttpStatus_407_ProxyAuthenticationRequired = 407,
+    HttpStatus_408_RequestTimeout = 408,
+    HttpStatus_409_Conflict = 409,
+    HttpStatus_410_Gone = 410,
+    HttpStatus_411_LengthRequired = 411,
+    HttpStatus_412_PreconditionFailed = 412,
+    HttpStatus_413_RequestEntityTooLarge = 413,
+    HttpStatus_414_RequestUriTooLong = 414,
+    HttpStatus_415_UnsupportedMediaType = 415,
+    HttpStatus_416_RequestedRangeNotSatisfiable = 416,
+    HttpStatus_417_ExpectationFailed = 417,
+    HttpStatus_422_UnprocessableEntity = 422,
+    HttpStatus_423_Locked = 423,
+    HttpStatus_424_FailedDependency = 424,
+    HttpStatus_426_UpgradeRequired = 426,
+
+    // 5xx Server Error
+    HttpStatus_500_InternalServerError = 500,
+    HttpStatus_501_NotImplemented = 501,
+    HttpStatus_502_BadGateway = 502,
+    HttpStatus_503_ServiceUnavailable = 503,
+    HttpStatus_504_GatewayTimeout = 504,
+    HttpStatus_505_HttpVersionNotSupported = 505,
+    HttpStatus_506_VariantAlsoNegotiates = 506,
+    HttpStatus_507_InsufficientStorage = 507,
+    HttpStatus_509_BandwidthLimitExceeded = 509,
+    HttpStatus_510_NotExtended = 510
+  };
+
+
+  enum HttpMethod
+  {
+    HttpMethod_Get = 0,
+    HttpMethod_Post = 1,
+    HttpMethod_Delete = 2,
+    HttpMethod_Put = 3
   };
 
 
@@ -86,4 +182,10 @@
     FileContentType_Dicom = 1,
     FileContentType_Json = 2
   };
+
+
+
+  const char* EnumerationToString(HttpMethod method);
+
+  const char* EnumerationToString(HttpStatus status);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileFormats/PngReader.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,305 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PngReader.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+#include <png.h>
+#include <string.h>  // For memcpy()
+
+namespace Orthanc
+{
+  namespace 
+  {
+    struct FileRabi
+    {
+      FILE* fp_;
+
+      FileRabi(const char* filename)
+      {
+        fp_ = fopen(filename, "rb");
+        if (!fp_)
+        {
+          throw OrthancException(ErrorCode_InexistentFile);
+        }
+      }
+
+      ~FileRabi()
+      {
+        if (fp_)
+          fclose(fp_);
+      }
+    };
+  }
+
+
+  struct PngReader::PngRabi
+  {
+    png_structp png_;
+    png_infop info_;
+    png_infop endInfo_;
+
+    void Destruct()
+    {
+      if (png_)
+      {
+        png_destroy_read_struct(&png_, &info_, &endInfo_);
+
+        png_ = NULL;
+        info_ = NULL;
+        endInfo_ = NULL;
+      }
+    }
+
+    PngRabi()
+    {
+      png_ = NULL;
+      info_ = NULL;
+      endInfo_ = NULL;
+
+      png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+      if (!png_)
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+
+      info_ = png_create_info_struct(png_);
+      if (!info_)
+      {
+        png_destroy_read_struct(&png_, NULL, NULL);
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+
+      endInfo_ = png_create_info_struct(png_);
+      if (!info_)
+      {
+        png_destroy_read_struct(&png_, &info_, NULL);
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+    }
+
+    ~PngRabi()
+    {
+      Destruct();
+    }
+
+    static void MemoryCallback(png_structp png_ptr, 
+                               png_bytep data, 
+                               png_size_t size);
+  };
+
+
+  void PngReader::CheckHeader(const void* header)
+  {
+    int is_png = !png_sig_cmp((png_bytep) header, 0, 8);
+    if (!is_png)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+  PngReader::PngReader()
+  {
+    width_ = 0;
+    height_ = 0;
+    pitch_ = 0;
+    format_ = PixelFormat_Grayscale8;
+  }
+
+  void PngReader::Read(PngRabi& rabi)
+  {
+    png_set_sig_bytes(rabi.png_, 8);
+
+    png_read_info(rabi.png_, rabi.info_);
+
+    png_uint_32 width, height;
+    int bit_depth, color_type, interlace_type;
+    int compression_type, filter_method;
+    // get size and bit-depth of the PNG-image
+    png_get_IHDR(rabi.png_, rabi.info_,
+                 &width, &height,
+                 &bit_depth, &color_type, &interlace_type,
+                 &compression_type, &filter_method);
+
+    width_ = width;
+    height_ = height;
+
+    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8)
+    {
+      format_ = PixelFormat_Grayscale8;
+      pitch_ = width_;
+    }
+    else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16)
+    {
+      format_ = PixelFormat_Grayscale16;
+      pitch_ = 2 * width_;
+
+      if (Toolbox::DetectEndianness() == Endianness_Little)
+      {
+        png_set_swap(rabi.png_);
+      }
+    }
+    else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8)
+    {
+      format_ = PixelFormat_Grayscale8;
+      pitch_ = 3 * width_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    buffer_.resize(height_ * pitch_);
+
+    if (height_ == 0 || width_ == 0)
+    {
+      // Empty image, we are done
+      return;
+    }
+
+    png_read_update_info(rabi.png_, rabi.info_);
+
+    std::vector<png_bytep> rows(height_);
+    for (size_t i = 0; i < height_; i++)
+    {
+      rows[i] = &buffer_[0] + i * pitch_;
+    }
+
+    png_read_image(rabi.png_, &rows[0]);
+  }
+
+  void PngReader::ReadFromFile(const char* filename)
+  {
+    FileRabi f(filename);
+
+    char header[8];
+    if (fread(header, 1, 8, f.fp_) != 8)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    CheckHeader(header);
+
+    PngRabi rabi;
+
+    if (setjmp(png_jmpbuf(rabi.png_)))
+    {
+      rabi.Destruct();
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    png_init_io(rabi.png_, f.fp_);
+
+    Read(rabi);
+  }
+
+
+
+  namespace
+  {
+    struct MemoryBuffer
+    {
+      const uint8_t* buffer_;
+      size_t size_;
+      size_t pos_;
+      bool ok_;
+    };
+  }
+
+
+  void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, 
+                                          png_bytep outBytes, 
+                                          png_size_t byteCountToRead)
+  {
+    MemoryBuffer* from = (MemoryBuffer*) png_get_io_ptr(png_ptr);
+
+    if (!from->ok_)
+    {
+      return;
+    }
+
+    if (from->pos_ + byteCountToRead > from->size_)
+    {
+      from->ok_ = false;
+      return;
+    }
+
+    memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead);
+
+    from->pos_ += byteCountToRead;
+  }
+
+
+  void PngReader::ReadFromMemory(const void* buffer,
+                                 size_t size)
+  {
+    if (size < 8)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    CheckHeader(buffer);
+
+    PngRabi rabi;
+
+    if (setjmp(png_jmpbuf(rabi.png_)))
+    {
+      rabi.Destruct();
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    MemoryBuffer tmp;
+    tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8;  // We skip the header
+    tmp.size_ = size - 8;
+    tmp.pos_ = 0;
+    tmp.ok_ = true;
+
+    png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback);
+
+    Read(rabi);
+
+    if (!tmp.ok_)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+  void PngReader::ReadFromMemory(const std::string& buffer)
+  {
+    if (buffer.size() != 0)
+      ReadFromMemory(&buffer[0], buffer.size());
+    else
+      ReadFromMemory(NULL, 0);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileFormats/PngReader.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,104 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+
+#include <vector>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+
+namespace Orthanc
+{
+  class PngReader
+  {
+  private:
+    struct PngRabi;
+
+    PixelFormat format_;
+    unsigned int width_;
+    unsigned int height_;
+    unsigned int pitch_;
+    std::vector<uint8_t> buffer_;
+
+    void CheckHeader(const void* header);
+
+    void Read(PngRabi& rabi);
+
+  public:
+    PngReader();
+
+    PixelFormat GetFormat() const
+    {
+      return format_;
+    }
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetPitch() const
+    {
+      return pitch_;
+    }
+
+    const void* GetBuffer() const
+    {
+      if (buffer_.size() > 0)
+        return &buffer_[0];
+      else
+        return NULL;
+    }
+
+    const void* GetBuffer(unsigned int y) const
+    {
+      if (buffer_.size() > 0)
+        return &buffer_[y * pitch_];
+      else
+        return NULL;
+    }
+
+    void ReadFromFile(const char* filename);
+
+    void ReadFromMemory(const void* buffer,
+                        size_t size);
+
+    void ReadFromMemory(const std::string& buffer);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileFormats/PngWriter.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,260 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PngWriter.h"
+
+#include <vector>
+#include <stdint.h>
+#include <png.h>
+#include "../OrthancException.h"
+#include "../ChunkedBuffer.h"
+#include "../Toolbox.h"
+
+
+// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4
+// http://zarb.org/~gc/html/libpng.html
+/*
+  void write_row_callback(png_ptr, png_uint_32 row, int pass)
+  {
+  }*/
+
+
+
+
+/*  bool isError_;
+
+// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2
+
+static void ErrorHandler(png_structp png, png_const_charp message)
+{
+printf("** [%s]\n", message);
+
+PngWriter* that = (PngWriter*) png_get_error_ptr(png);
+that->isError_ = true;
+printf("** %d\n", (int)that);
+
+//((PngWriter*) payload)->isError_ = true;
+}
+
+static void WarningHandler(png_structp png, png_const_charp message)
+{
+  printf("++ %d\n", (int)message);
+}*/
+
+
+namespace Orthanc
+{
+  struct PngWriter::PImpl
+  {
+    png_structp png_;
+    png_infop info_;
+
+    // Filled by Prepare()
+    std::vector<uint8_t*> rows_;
+    int bitDepth_;
+    int colorType_;
+  };
+
+
+
+  PngWriter::PngWriter() : pimpl_(new PImpl)
+  {
+    pimpl_->png_ = NULL;
+    pimpl_->info_ = NULL;
+
+    pimpl_->png_ = png_create_write_struct
+      (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler);
+    if (!pimpl_->png_)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    pimpl_->info_ = png_create_info_struct(pimpl_->png_);
+    if (!pimpl_->info_)
+    {
+      png_destroy_write_struct(&pimpl_->png_, NULL);
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+  }
+
+  PngWriter::~PngWriter()
+  {
+    if (pimpl_->info_)
+    {
+      png_destroy_info_struct(pimpl_->png_, &pimpl_->info_);
+    }
+
+    if (pimpl_->png_)
+    {
+      png_destroy_write_struct(&pimpl_->png_, NULL);
+    }
+  }
+
+
+
+  void PngWriter::Prepare(unsigned int width,
+                          unsigned int height,
+                          unsigned int pitch,
+                          PixelFormat format,
+                          const void* buffer)
+  {
+    pimpl_->rows_.resize(height);
+    for (unsigned int y = 0; y < height; y++)
+    {
+      pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch;
+    }
+
+    switch (format)
+    {
+    case PixelFormat_RGB24:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_RGB;
+      break;
+
+    case PixelFormat_Grayscale8:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
+      break;
+
+    case PixelFormat_Grayscale16:
+    case PixelFormat_SignedGrayscale16:
+      pimpl_->bitDepth_ = 16;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
+      break;
+
+    default:
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void PngWriter::Compress(unsigned int width,
+                           unsigned int height,
+                           unsigned int pitch,
+                           PixelFormat format)
+  {
+    png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height,
+                 pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE,
+                 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+    png_write_info(pimpl_->png_, pimpl_->info_);
+
+    if (height > 0)
+    {
+      switch (format)
+      {
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+        png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]);
+
+        if (Toolbox::DetectEndianness() == Endianness_Little)
+        {
+          // Must swap the endianness!!
+          png_write_png(pimpl_->png_, pimpl_->info_, PNG_TRANSFORM_SWAP_ENDIAN, NULL);
+        }
+
+        break;
+
+      default:
+        png_write_image(pimpl_->png_, &pimpl_->rows_[0]);
+      }
+    }
+
+    png_write_end(pimpl_->png_, NULL);
+  }
+
+
+  void PngWriter::WriteToFile(const char* filename,
+                              unsigned int width,
+                              unsigned int height,
+                              unsigned int pitch,
+                              PixelFormat format,
+                              const void* buffer)
+  {
+    Prepare(width, height, pitch, format, buffer);
+
+    FILE* fp = fopen(filename, "wb");
+    if (!fp)
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }    
+
+    png_init_io(pimpl_->png_, fp);
+
+    if (setjmp(png_jmpbuf(pimpl_->png_)))
+    {
+      // Error during writing PNG
+      throw OrthancException(ErrorCode_CannotWriteFile);      
+    }
+
+    Compress(width, height, pitch, format);
+
+    fclose(fp);
+  }
+
+
+
+
+  static void MemoryCallback(png_structp png_ptr, 
+                             png_bytep data, 
+                             png_size_t size)
+  {
+    ChunkedBuffer* buffer = (ChunkedBuffer*) png_get_io_ptr(png_ptr);
+    buffer->AddChunk(reinterpret_cast<const char*>(data), size);
+  }
+
+
+
+  void PngWriter::WriteToMemory(std::string& png,
+                                unsigned int width,
+                                unsigned int height,
+                                unsigned int pitch,
+                                PixelFormat format,
+                                const void* buffer)
+  {
+    ChunkedBuffer chunks;
+
+    Prepare(width, height, pitch, format, buffer);
+
+    if (setjmp(png_jmpbuf(pimpl_->png_)))
+    {
+      // Error during writing PNG
+      throw OrthancException(ErrorCode_InternalError);      
+    }
+
+    png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL);
+
+    Compress(width, height, pitch, format);
+
+    chunks.Flatten(png);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileFormats/PngWriter.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,78 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace Orthanc
+{
+  class PngWriter
+  {
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    void Compress(unsigned int width,
+                  unsigned int height,
+                  unsigned int pitch,
+                  PixelFormat format);
+
+    void Prepare(unsigned int width,
+                 unsigned int height,
+                 unsigned int pitch,
+                 PixelFormat format,
+                 const void* buffer);
+
+  public:
+    PngWriter();
+
+    ~PngWriter();
+
+    void WriteToFile(const char* filename,
+                     unsigned int width,
+                     unsigned int height,
+                     unsigned int pitch,
+                     PixelFormat format,
+                     const void* buffer);
+
+    void WriteToMemory(std::string& png,
+                       unsigned int width,
+                       unsigned int height,
+                       unsigned int pitch,
+                       PixelFormat format,
+                       const void* buffer);
+  };
+}
--- a/Core/FileStorage/CompressedFileStorageAccessor.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/CompressedFileStorageAccessor.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/FileStorage/CompressedFileStorageAccessor.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileInfo.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/FileStorage/FileInfo.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorage.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/FileStorage/FileStorage.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorage.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/FileStorage/FileStorage.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorageAccessor.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/FileStorage/FileStorageAccessor.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorageAccessor.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/FileStorage/FileStorageAccessor.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/StorageAccessor.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/FileStorage/StorageAccessor.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/StorageAccessor.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/FileStorage/StorageAccessor.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpClient.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,262 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "HttpClient.h"
+
+#include "../Core/Toolbox.h"
+#include "../Core/OrthancException.h"
+
+#include <string.h>
+#include <curl/curl.h>
+
+
+namespace Orthanc
+{
+  struct HttpClient::PImpl
+  {
+    CURL* curl_;
+    struct curl_slist *postHeaders_;
+  };
+
+
+  static CURLcode CheckCode(CURLcode code)
+  {
+    if (code != CURLE_OK)
+    {
+      throw OrthancException("libCURL error: " + std::string(curl_easy_strerror(code)));
+    }
+
+    return code;
+  }
+
+
+  static size_t CurlCallback(void *buffer, size_t size, size_t nmemb, void *payload)
+  {
+    std::string& target = *(static_cast<std::string*>(payload));
+
+    size_t length = size * nmemb;
+    if (length == 0)
+      return 0;
+
+    size_t pos = target.size();
+
+    target.resize(pos + length);
+    memcpy(&target.at(pos), buffer, length);
+
+    return length;
+  }
+
+
+  void HttpClient::Setup()
+  {
+    pimpl_->postHeaders_ = NULL;
+    if ((pimpl_->postHeaders_ = curl_slist_append(pimpl_->postHeaders_, "Expect:")) == NULL)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    pimpl_->curl_ = curl_easy_init();
+    if (!pimpl_->curl_)
+    {
+      curl_slist_free_all(pimpl_->postHeaders_);
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlCallback));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1));
+
+#if ORTHANC_SSL_ENABLED == 1
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); 
+#endif
+
+    // This fixes the "longjmp causes uninitialized stack frame" crash
+    // that happens on modern Linux versions.
+    // http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOSIGNAL, 1));
+
+    url_ = "";
+    method_ = HttpMethod_Get;
+    lastStatus_ = HttpStatus_200_Ok;
+    isVerbose_ = false;
+  }
+
+
+  HttpClient::HttpClient() : pimpl_(new PImpl)
+  {
+    Setup();
+  }
+
+
+  HttpClient::HttpClient(const HttpClient& other) : pimpl_(new PImpl)
+  {
+    Setup();
+
+    if (other.IsVerbose())
+    {
+      SetVerbose(true);
+    }
+
+    if (other.credentials_.size() != 0)
+    {
+      credentials_ = other.credentials_;
+    }
+  }
+
+
+  HttpClient::~HttpClient()
+  {
+    curl_easy_cleanup(pimpl_->curl_);
+    curl_slist_free_all(pimpl_->postHeaders_);
+  }
+
+
+  void HttpClient::SetVerbose(bool isVerbose)
+  {
+    isVerbose_ = isVerbose;
+
+    if (isVerbose_)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1));
+    }
+    else
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 0));
+    }
+  }
+
+
+  bool HttpClient::Apply(std::string& answer)
+  {
+    answer.clear();
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str()));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, NULL));
+
+    if (credentials_.size() != 0)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, credentials_.c_str()));
+    }
+
+    switch (method_)
+    {
+    case HttpMethod_Get:
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L));
+      break;
+
+    case HttpMethod_Post:
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->postHeaders_));
+
+      if (postData_.size() > 0)
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, postData_.c_str()));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, postData_.size()));
+      }
+      else
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0));
+      }
+
+      break;
+
+    case HttpMethod_Delete:
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE"));
+      break;
+
+    case HttpMethod_Put:
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L));
+      break;
+
+    default:
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Do the actual request
+    CheckCode(curl_easy_perform(pimpl_->curl_));
+
+    long status;
+    CheckCode(curl_easy_getinfo(pimpl_->curl_, CURLINFO_RESPONSE_CODE, &status));
+
+    if (status == 0)
+    {
+      // This corresponds to a call to an inexistent host
+      lastStatus_ = HttpStatus_500_InternalServerError;
+    }
+    else
+    {
+      lastStatus_ = static_cast<HttpStatus>(status);
+    }
+
+    return (status >= 200 && status < 300);
+  }
+
+
+  bool HttpClient::Apply(Json::Value& answer)
+  {
+    std::string s;
+    if (Apply(s))
+    {
+      Json::Reader reader;
+      return reader.parse(s, answer);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void HttpClient::SetCredentials(const char* username,
+                                  const char* password)
+  {
+    credentials_ = std::string(username) + ":" + std::string(password);
+  }
+
+  
+  void HttpClient::GlobalInitialize()
+  {
+    CheckCode(curl_global_init(CURL_GLOBAL_DEFAULT));
+  }
+  
+  void HttpClient::GlobalFinalize()
+  {
+    curl_global_cleanup();
+  }
+
+  const char* HttpClient::GetLastStatusText() const
+  {
+    return EnumerationToString(lastStatus_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpClient.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,127 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Core/Enumerations.h"
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class HttpClient
+  {
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    std::string url_;
+    std::string credentials_;
+    HttpMethod method_;
+    HttpStatus lastStatus_;
+    std::string postData_;
+    bool isVerbose_;
+
+    void Setup();
+
+    void operator= (const HttpClient&);  // Forbidden
+
+  public:
+    HttpClient(const HttpClient& base);
+
+    HttpClient();
+
+    ~HttpClient();
+
+    void SetUrl(const char* url)
+    {
+      url_ = std::string(url);
+    }
+
+    void SetUrl(const std::string& url)
+    {
+      url_ = url;
+    }
+
+    const std::string& GetUrl() const
+    {
+      return url_;
+    }
+
+    void SetMethod(HttpMethod method)
+    {
+      method_ = method;
+    }
+
+    HttpMethod GetMethod() const
+    {
+      return method_;
+    }
+
+    std::string& AccessPostData()
+    {
+      return postData_;
+    }
+
+    const std::string& AccessPostData() const
+    {
+      return postData_;
+    }
+
+    void SetVerbose(bool isVerbose);
+
+    bool IsVerbose() const
+    {
+      return isVerbose_;
+    }
+
+    bool Apply(std::string& answer);
+
+    bool Apply(Json::Value& answer);
+
+    HttpStatus GetLastStatus() const
+    {
+      return lastStatus_;
+    }
+
+    const char* GetLastStatusText() const;
+
+    void SetCredentials(const char* username,
+                        const char* password);
+
+    static void GlobalInitialize();
+  
+    static void GlobalFinalize();
+  };
+}
--- a/Core/HttpServer/BufferHttpSender.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/BufferHttpSender.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -58,13 +58,13 @@
 
   void EmbeddedResourceHttpHandler::Handle(
     HttpOutput& output,
-    Orthanc_HttpMethod method,
+    HttpMethod method,
     const UriComponents& uri,
     const Arguments& headers,
     const Arguments& arguments,
     const std::string&)
   {
-    if (method != Orthanc_HttpMethod_Get)
+    if (method != HttpMethod_Get)
     {
       output.SendMethodNotAllowedError("GET");
       return;
@@ -82,7 +82,7 @@
     catch (OrthancException& e)
     {
       LOG(WARNING) << "Unable to find HTTP resource: " << resourcePath;
-      output.SendHeader(Orthanc_HttpStatus_404_NotFound);
+      output.SendHeader(HttpStatus_404_NotFound);
     }
   } 
 }
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -54,7 +54,7 @@
 
     virtual void Handle(
       HttpOutput& output,
-      Orthanc_HttpMethod method,
+      HttpMethod method,
       const UriComponents& uri,
       const Arguments& headers,
       const Arguments& arguments,
--- a/Core/HttpServer/FilesystemHttpHandler.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -127,13 +127,13 @@
 
   void FilesystemHttpHandler::Handle(
     HttpOutput& output,
-    Orthanc_HttpMethod method,
+    HttpMethod method,
     const UriComponents& uri,
     const Arguments& headers,
     const Arguments& arguments,
     const std::string&)
   {
-    if (method != Orthanc_HttpMethod_Get)
+    if (method != HttpMethod_Get)
     {
       output.SendMethodNotAllowedError("GET");
       return;
@@ -161,7 +161,7 @@
     }
     else
     {
-      output.SendHeader(Orthanc_HttpStatus_404_NotFound);
+      output.SendHeader(HttpStatus_404_NotFound);
     }
   } 
 }
--- a/Core/HttpServer/FilesystemHttpHandler.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -56,7 +56,7 @@
 
     virtual void Handle(
       HttpOutput& output,
-      Orthanc_HttpMethod method,
+      HttpMethod method,
       const UriComponents& uri,
       const Arguments& headers,
       const Arguments& arguments,
--- a/Core/HttpServer/FilesystemHttpSender.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/FilesystemHttpSender.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpFileSender.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/HttpFileSender.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -47,7 +47,7 @@
 
     if (!SendData(output))
     {
-      output.SendHeader(Orthanc_HttpStatus_500_InternalServerError);
+      output.SendHeader(HttpStatus_500_InternalServerError);
     }
   }
 }
--- a/Core/HttpServer/HttpFileSender.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/HttpFileSender.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpHandler.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/HttpHandler.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpHandler.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/HttpHandler.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -36,7 +36,6 @@
 #include <vector>
 #include <stdint.h>
 #include "../Toolbox.h"
-#include "../../OrthancCppClient/HttpEnumerations.h"
 
 namespace Orthanc
 {
@@ -54,7 +53,7 @@
     virtual bool IsServedUri(const UriComponents& uri) = 0;
 
     virtual void Handle(HttpOutput& output,
-                        Orthanc_HttpMethod method,
+                        HttpMethod method,
                         const UriComponents& uri,
                         const Arguments& headers,
                         const Arguments& getArguments,
--- a/Core/HttpServer/HttpOutput.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/HttpOutput.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -38,7 +38,6 @@
 #include <boost/lexical_cast.hpp>
 #include "../OrthancException.h"
 #include "../Toolbox.h"
-#include "../../OrthancCppClient/HttpException.h"
 
 namespace Orthanc
 {
@@ -104,17 +103,17 @@
   void HttpOutput::SendMethodNotAllowedError(const std::string& allowed)
   {
     std::string s = 
-      "HTTP/1.1 405 " + std::string(HttpException::GetDescription(Orthanc_HttpStatus_405_MethodNotAllowed)) +
+      "HTTP/1.1 405 " + std::string(EnumerationToString(HttpStatus_405_MethodNotAllowed)) +
       "\r\nAllow: " + allowed + 
       "\r\n\r\n";
     Send(&s[0], s.size());
   }
 
 
-  void HttpOutput::SendHeader(Orthanc_HttpStatus status)
+  void HttpOutput::SendHeader(HttpStatus status)
   {
-    if (status == Orthanc_HttpStatus_200_Ok ||
-        status == Orthanc_HttpStatus_405_MethodNotAllowed)
+    if (status == HttpStatus_200_Ok ||
+        status == HttpStatus_405_MethodNotAllowed)
     {
       throw OrthancException("Please use the dedicated methods to this HTTP status code in HttpOutput");
     }
@@ -123,11 +122,11 @@
   }
 
 
-  void HttpOutput::SendHeaderInternal(Orthanc_HttpStatus status)
+  void HttpOutput::SendHeaderInternal(HttpStatus status)
   {
     std::string s = "HTTP/1.1 " + 
       boost::lexical_cast<std::string>(status) +
-      " " + std::string(HttpException::GetDescription(status)) +
+      " " + std::string(EnumerationToString(status)) +
       "\r\n\r\n";
     Send(&s[0], s.size());
   }
@@ -190,7 +189,7 @@
   void HttpOutput::Redirect(const std::string& path)
   {
     std::string s = 
-      "HTTP/1.1 301 " + std::string(HttpException::GetDescription(Orthanc_HttpStatus_301_MovedPermanently)) + 
+      "HTTP/1.1 301 " + std::string(EnumerationToString(HttpStatus_301_MovedPermanently)) + 
       "\r\nLocation: " + path +
       "\r\n\r\n";
     Send(&s[0], s.size());  
--- a/Core/HttpServer/HttpOutput.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/HttpOutput.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -45,7 +45,7 @@
   private:
     typedef std::list< std::pair<std::string, std::string> >  Header;
 
-    void SendHeaderInternal(Orthanc_HttpStatus status);
+    void SendHeaderInternal(HttpStatus status);
 
     void PrepareOkHeader(Header& header,
                          const char* contentType,
@@ -74,7 +74,7 @@
 
     void SendMethodNotAllowedError(const std::string& allowed);
 
-    void SendHeader(Orthanc_HttpStatus status);
+    void SendHeader(HttpStatus status);
 
     void Redirect(const std::string& path);
 
--- a/Core/HttpServer/MongooseServer.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/MongooseServer.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -268,9 +268,9 @@
 
 
 
-  static PostDataStatus ReadPostData(std::string& postData,
-                                     struct mg_connection *connection,
-                                     const HttpHandler::Arguments& headers)
+  static PostDataStatus ReadBody(std::string& postData,
+                                 struct mg_connection *connection,
+                                 const HttpHandler::Arguments& headers)
   {
     HttpHandler::Arguments::const_iterator cs = headers.find("content-length");
     if (cs == headers.end())
@@ -303,6 +303,7 @@
       {
         return PostDataStatus_Failure;
       }
+
       assert(r <= length);
       length -= r;
       pos += r;
@@ -322,7 +323,7 @@
     std::string boundary = "--" + contentType.substr(multipartLength);
 
     std::string postData;
-    PostDataStatus status = ReadPostData(postData, connection, headers);
+    PostDataStatus status = ReadBody(postData, connection, headers);
 
     if (status != PostDataStatus_Success)
     {
@@ -454,6 +455,114 @@
   }
 
 
+  static std::string GetAuthenticatedUsername(const HttpHandler::Arguments& headers)
+  {
+    HttpHandler::Arguments::const_iterator auth = headers.find("authorization");
+
+    if (auth == headers.end())
+    {
+      return "";
+    }
+
+    std::string s = auth->second;
+    if (s.substr(0, 6) != "Basic ")
+    {
+      return "";
+    }
+
+    std::string b64 = s.substr(6);
+    std::string decoded = Toolbox::DecodeBase64(b64);
+    size_t semicolons = decoded.find(':');
+
+    if (semicolons == std::string::npos)
+    {
+      // Bad-formatted request
+      return "";
+    }
+    else
+    {
+      return decoded.substr(0, semicolons);
+    }
+  }
+
+
+  static bool ExtractMethod(HttpMethod& method,
+                            const struct mg_request_info *request,
+                            const HttpHandler::Arguments& headers,
+                            const HttpHandler::Arguments& argumentsGET)
+  {
+    std::string overriden;
+
+    // Check whether some PUT/DELETE faking is done
+
+    // 1. Faking with Google's approach
+    HttpHandler::Arguments::const_iterator methodOverride =
+      headers.find("x-http-method-override");
+
+    if (methodOverride != headers.end())
+    {
+      overriden = methodOverride->second;
+    }
+    else if (!strcmp(request->request_method, "GET"))
+    {
+      // 2. Faking with Ruby on Rail's approach
+      // GET /my/resource?_method=delete <=> DELETE /my/resource
+      methodOverride = argumentsGET.find("_method");
+      if (methodOverride != argumentsGET.end())
+      {
+        overriden = methodOverride->second;
+      }
+    }
+
+    if (overriden.size() > 0)
+    {
+      // A faking has been done within this request
+      Toolbox::ToUpperCase(overriden);
+
+      LOG(INFO) << "HTTP method faking has been detected for " << overriden;
+
+      if (overriden == "PUT")
+      {
+        method = HttpMethod_Put;
+        return true;
+      }
+      else if (overriden == "DELETE")
+      {
+        method = HttpMethod_Delete;
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+    // No PUT/DELETE faking was present
+    if (!strcmp(request->request_method, "GET"))
+    {
+      method = HttpMethod_Get;
+    }
+    else if (!strcmp(request->request_method, "POST"))
+    {
+      method = HttpMethod_Post;
+    }
+    else if (!strcmp(request->request_method, "DELETE"))
+    {
+      method = HttpMethod_Delete;
+    }
+    else if (!strcmp(request->request_method, "PUT"))
+    {
+      method = HttpMethod_Put;
+    }
+    else
+    {
+      return false;
+    }    
+
+    return true;
+  }
+
+
 
   static void* Callback(enum mg_event event,
                         struct mg_connection *connection,
@@ -464,30 +573,7 @@
       MongooseServer* that = (MongooseServer*) (request->user_data);
       MongooseOutput output(connection);
 
-      // Compute the method
-      Orthanc_HttpMethod method;
-      if (!strcmp(request->request_method, "GET"))
-      {
-        method = Orthanc_HttpMethod_Get;
-      }
-      else if (!strcmp(request->request_method, "POST"))
-      {
-        method = Orthanc_HttpMethod_Post;
-      }
-      else if (!strcmp(request->request_method, "DELETE"))
-      {
-        method = Orthanc_HttpMethod_Delete;
-      }
-      else if (!strcmp(request->request_method, "PUT"))
-      {
-        method = Orthanc_HttpMethod_Put;
-      }
-      else
-      {
-        output.SendHeader(Orthanc_HttpStatus_405_MethodNotAllowed);
-        return (void*) "";
-      }      
-
+      // Check remote calls
       if (!that->IsRemoteAccessAllowed() &&
           request->remote_ip != LOCALHOST)
       {
@@ -495,8 +581,9 @@
         return (void*) "";
       }
 
-      HttpHandler::Arguments arguments, headers;
 
+      // Extract the HTTP headers
+      HttpHandler::Arguments headers;
       for (int i = 0; i < request->num_headers; i++)
       {
         std::string name = request->http_headers[i].name;
@@ -504,6 +591,24 @@
         headers.insert(std::make_pair(name, request->http_headers[i].value));
       }
 
+
+      // Extract the GET arguments
+      HttpHandler::Arguments argumentsGET;
+      if (!strcmp(request->request_method, "GET"))
+      {
+        HttpHandler::ParseGetQuery(argumentsGET, request->query_string);
+      }
+
+
+      // Compute the HTTP method, taking method faking into consideration
+      HttpMethod method;
+      if (!ExtractMethod(method, request, headers, argumentsGET))
+      {
+        output.SendHeader(HttpStatus_400_BadRequest);
+        return (void*) "";
+      }
+
+
       // Authenticate this connection
       if (that->IsAuthenticationEnabled() &&
           !Authorize(*that, headers, output))
@@ -511,83 +616,115 @@
         return (void*) "";
       }
 
-      std::string postData;
 
-      if (method == Orthanc_HttpMethod_Get)
+      // Apply the filter, if it is installed
+      const IIncomingHttpRequestFilter *filter = that->GetIncomingHttpRequestFilter();
+      if (filter != NULL)
       {
-        HttpHandler::ParseGetQuery(arguments, request->query_string);
+        std::string username = GetAuthenticatedUsername(headers);
+
+        char remoteIp[24];
+        sprintf(remoteIp, "%d.%d.%d.%d", 
+                reinterpret_cast<const uint8_t*>(&request->remote_ip) [3], 
+                reinterpret_cast<const uint8_t*>(&request->remote_ip) [2], 
+                reinterpret_cast<const uint8_t*>(&request->remote_ip) [1], 
+                reinterpret_cast<const uint8_t*>(&request->remote_ip) [0]);
+
+        if (!filter->IsAllowed(method, request->uri, remoteIp, username.c_str()))
+        {
+          SendUnauthorized(output);
+          return (void*) "";
+        }
       }
-      else if (method == Orthanc_HttpMethod_Post ||
-               method == Orthanc_HttpMethod_Put)
+
+
+      // Extract the body of the request for PUT and POST
+      std::string body;
+      if (method == HttpMethod_Post ||
+          method == HttpMethod_Put)
       {
+        PostDataStatus status;
+
         HttpHandler::Arguments::const_iterator ct = headers.find("content-type");
         if (ct == headers.end())
         {
-          output.SendHeader(Orthanc_HttpStatus_400_BadRequest);
-          return (void*) "";
-        }
-
-        PostDataStatus status;
-      
-        std::string contentType = ct->second;
-        if (contentType.size() >= multipartLength &&
-            !memcmp(contentType.c_str(), multipart, multipartLength))
-        {
-          status = ParseMultipartPost(postData, connection, headers, contentType, that->GetChunkStore());
+          // No content-type specified. Assume no multi-part content occurs at this point.
+          status = ReadBody(body, connection, headers);          
         }
         else
         {
-          status = ReadPostData(postData, connection, headers);
+          std::string contentType = ct->second;
+          if (contentType.size() >= multipartLength &&
+              !memcmp(contentType.c_str(), multipart, multipartLength))
+          {
+            status = ParseMultipartPost(body, connection, headers, contentType, that->GetChunkStore());
+          }
+          else
+          {
+            status = ReadBody(body, connection, headers);
+          }
         }
 
         switch (status)
         {
-        case PostDataStatus_NoLength:
-          output.SendHeader(Orthanc_HttpStatus_411_LengthRequired);
-          return (void*) "";
+          case PostDataStatus_NoLength:
+            output.SendHeader(HttpStatus_411_LengthRequired);
+            return (void*) "";
 
-        case PostDataStatus_Failure:
-          output.SendHeader(Orthanc_HttpStatus_400_BadRequest);
-          return (void*) "";
+          case PostDataStatus_Failure:
+            output.SendHeader(HttpStatus_400_BadRequest);
+            return (void*) "";
 
-        case PostDataStatus_Pending:
-          output.AnswerBufferWithContentType(NULL, 0, "");
-          return (void*) "";
+          case PostDataStatus_Pending:
+            output.AnswerBufferWithContentType(NULL, 0, "");
+            return (void*) "";
 
-        default:
-          break;
+          default:
+            break;
         }
       }
 
+
+      // Call the proper handler for this URI
       UriComponents uri;
-      Toolbox::SplitUriComponents(uri, request->uri);
+      try
+      {
+        Toolbox::SplitUriComponents(uri, request->uri);
+      }
+      catch (OrthancException)
+      {
+        output.SendHeader(HttpStatus_400_BadRequest);
+        return (void*) "";
+      }
+
 
       HttpHandler* handler = that->FindHandler(uri);
       if (handler)
       {
         try
         {
-          handler->Handle(output, method, uri, headers, arguments, postData);
+          LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri);
+          handler->Handle(output, method, uri, headers, argumentsGET, body);
         }
         catch (OrthancException& e)
         {
           LOG(ERROR) << "MongooseServer Exception [" << e.What() << "]";
-          output.SendHeader(Orthanc_HttpStatus_500_InternalServerError);        
+          output.SendHeader(HttpStatus_500_InternalServerError);        
         }
         catch (boost::bad_lexical_cast&)
         {
           LOG(ERROR) << "MongooseServer Exception: Bad lexical cast";
-          output.SendHeader(Orthanc_HttpStatus_400_BadRequest);
+          output.SendHeader(HttpStatus_400_BadRequest);
         }
         catch (std::runtime_error&)
         {
           LOG(ERROR) << "MongooseServer Exception: Presumably a bad JSON request";
-          output.SendHeader(Orthanc_HttpStatus_400_BadRequest);
+          output.SendHeader(HttpStatus_400_BadRequest);
         }
       }
       else
       {
-        output.SendHeader(Orthanc_HttpStatus_404_NotFound);
+        output.SendHeader(HttpStatus_404_NotFound);
       }
 
       // Mark as processed
@@ -613,6 +750,7 @@
     authentication_ = false;
     ssl_ = false;
     port_ = 8000;
+    filter_ = NULL;
   }
 
 
@@ -737,6 +875,11 @@
     remoteAllowed_ = allowed;
   }
 
+  void MongooseServer::SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter)
+  {
+    Stop();
+    filter_ = &filter;
+  }
 
   bool MongooseServer::IsValidBasicHttpAuthentication(const std::string& basic) const
   {
--- a/Core/HttpServer/MongooseServer.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/HttpServer/MongooseServer.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -44,6 +44,19 @@
 {
   class ChunkStore;
 
+  class IIncomingHttpRequestFilter
+  {
+  public:
+    virtual ~IIncomingHttpRequestFilter()
+    {
+    }
+
+    virtual bool IsAllowed(HttpMethod method,
+                           const char* uri,
+                           const char* ip,
+                           const char* username) const = 0;
+  };
+
   class MongooseServer
   {
   private:
@@ -62,6 +75,7 @@
     bool ssl_;
     std::string certificate_;
     uint16_t port_;
+    IIncomingHttpRequestFilter* filter_;
   
     bool IsRunning() const;
 
@@ -116,6 +130,13 @@
 
     void SetRemoteAccessAllowed(bool allowed);
 
+    const IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const
+    {
+      return filter_;
+    }
+
+    void SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter);
+
     void ClearHandlers();
 
     // Can return NULL if no handler is associated to this URI
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/ICommand.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,48 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IDynamicObject.h"
+
+namespace Orthanc
+{
+  /**
+   * This class is the base class for the "Command" design pattern.
+   * http://en.wikipedia.org/wiki/Command_pattern
+   **/
+  class ICommand : public IDynamicObject
+  {
+  public:
+    virtual bool Execute() = 0;
+  };
+}
--- a/Core/IDynamicObject.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/IDynamicObject.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Lua/LuaContext.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,150 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LuaContext.h"
+
+#include <glog/logging.h>
+
+extern "C" 
+{
+#include <lualib.h>
+#include <lauxlib.h>
+}
+
+namespace Orthanc
+{
+  int LuaContext::PrintToLog(lua_State *state)
+  {
+    // Get the pointer to the "LuaContext" underlying object
+    lua_getglobal(state, "_LuaContext");
+    assert(lua_type(state, -1) == LUA_TLIGHTUSERDATA);
+    LuaContext* that = const_cast<LuaContext*>(reinterpret_cast<const LuaContext*>(lua_topointer(state, -1)));
+    assert(that != NULL);
+    lua_pop(state, 1);
+
+    // http://medek.wordpress.com/2009/02/03/wrapping-lua-errors-and-print-function/
+    int nArgs = lua_gettop(state);
+    lua_getglobal(state, "tostring");
+
+    // Make sure you start at 1 *NOT* 0 for arrays in Lua.
+    std::string result;
+
+    for (int i = 1; i <= nArgs; i++)
+    {
+      const char *s;
+      lua_pushvalue(state, -1);
+      lua_pushvalue(state, i);
+      lua_call(state, 1, 1);
+      s = lua_tostring(state, -1);
+
+      if (result.size() > 0)
+        result.append(", ");
+
+      if (s == NULL)
+        result.append("<No conversion to string>");
+      else
+        result.append(s);
+ 
+      lua_pop(state, 1);
+    }
+
+    LOG(INFO) << "Lua says: " << result;         
+    that->log_.append(result);
+    that->log_.append("\n");
+
+    return 0;
+  }
+
+
+  LuaContext::LuaContext()
+  {
+    lua_ = luaL_newstate();
+    if (!lua_)
+    {
+      throw LuaException("Unable to create the Lua context");
+    }
+
+    luaL_openlibs(lua_);
+    lua_register(lua_, "print", PrintToLog);
+    
+    lua_pushlightuserdata(lua_, this);
+    lua_setglobal(lua_, "_LuaContext");
+  }
+
+
+  LuaContext::~LuaContext()
+  {
+    lua_close(lua_);
+  }
+
+
+  void LuaContext::Execute(std::string* output,
+                           const std::string& command)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    log_.clear();
+    int error = (luaL_loadbuffer(lua_, command.c_str(), command.size(), "line") ||
+                 lua_pcall(lua_, 0, 0, 0));
+
+    if (error) 
+    {
+      assert(lua_gettop(lua_) >= 1);
+
+      std::string description(lua_tostring(lua_, -1));
+      lua_pop(lua_, 1); /* pop error message from the stack */
+      throw LuaException(description);
+    }
+
+    if (output != NULL)
+    {
+      *output = log_;
+    }
+  }
+
+
+  void LuaContext::Execute(EmbeddedResources::FileResourceId resource)
+  {
+    std::string command;
+    EmbeddedResources::GetFileResource(command, resource);
+    Execute(command);
+  }
+
+
+  bool LuaContext::IsExistingFunction(const char* name)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    lua_settop(lua_, 0);
+    lua_getglobal(lua_, name);
+    return lua_type(lua_, -1) == LUA_TFUNCTION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Lua/LuaContext.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "LuaException.h"
+
+#include <boost/thread.hpp>
+
+extern "C" 
+{
+#include <lua.h>
+}
+
+#include <EmbeddedResources.h>
+
+
+namespace Orthanc
+{
+  class LuaContext : public boost::noncopyable
+  {
+  private:
+    friend class LuaFunctionCall;
+
+    lua_State *lua_;
+    boost::mutex mutex_;
+    std::string log_;
+
+    static int PrintToLog(lua_State *L);
+
+    void Execute(std::string* output,
+                 const std::string& command);
+
+  public:
+    LuaContext();
+
+    ~LuaContext();
+
+    void Execute(const std::string& command)
+    {
+      Execute(NULL, command);
+    }
+
+    void Execute(std::string& output,
+                 const std::string& command)
+    {
+      Execute(&output, command);
+    }
+
+    void Execute(EmbeddedResources::FileResourceId resource);
+
+    bool IsExistingFunction(const char* name);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Lua/LuaException.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../OrthancException.h"
+
+namespace Orthanc
+{
+  class LuaException : public OrthancException
+  {
+  public:
+    LuaException(const char* explanation) : 
+      OrthancException(explanation)
+    {
+    }
+
+    LuaException(const std::string& explanation) : 
+      OrthancException(explanation)
+    {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Lua/LuaFunctionCall.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,192 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LuaFunctionCall.h"
+
+
+namespace Orthanc
+{
+  void LuaFunctionCall::CheckAlreadyExecuted()
+  {
+    if (isExecuted_)
+    {
+      throw LuaException("Arguments cannot be pushed after the function is executed");
+    }
+  }
+
+  LuaFunctionCall::LuaFunctionCall(LuaContext& context,
+                                   const char* functionName) : 
+    context_(context),
+    lock_(context.mutex_),
+    isExecuted_(false)
+  {
+    // Clear the stack to fulfill the invariant
+    lua_settop(context_.lua_, 0);
+    lua_getglobal(context_.lua_, functionName);
+  }
+
+  void LuaFunctionCall::PushString(const std::string& value)
+  {
+    CheckAlreadyExecuted();
+    lua_pushstring(context_.lua_, value.c_str());
+  }
+
+  void LuaFunctionCall::PushBoolean(bool value)
+  {
+    CheckAlreadyExecuted();
+    lua_pushboolean(context_.lua_, value);
+  }
+
+  void LuaFunctionCall::PushInteger(int value)
+  {
+    CheckAlreadyExecuted();
+    lua_pushinteger(context_.lua_, value);
+  }
+
+  void LuaFunctionCall::PushDouble(double value)
+  {
+    CheckAlreadyExecuted();
+    lua_pushnumber(context_.lua_, value);
+  }
+
+  void LuaFunctionCall::PushJSON(const Json::Value& value)
+  {
+    CheckAlreadyExecuted();
+
+    if (value.isString())
+    {
+      lua_pushstring(context_.lua_, value.asCString());
+    }
+    else if (value.isDouble())
+    {
+      lua_pushnumber(context_.lua_, value.asDouble());
+    }
+    else if (value.isInt())
+    {
+      lua_pushinteger(context_.lua_, value.asInt());
+    }
+    else if (value.isUInt())
+    {
+      lua_pushinteger(context_.lua_, value.asUInt());
+    }
+    else if (value.isBool())
+    {
+      lua_pushboolean(context_.lua_, value.asBool());
+    }
+    else if (value.isNull())
+    {
+      lua_pushnil(context_.lua_);
+    }
+    else if (value.isArray())
+    {
+      lua_newtable(context_.lua_);
+
+      // http://lua-users.org/wiki/SimpleLuaApiExample
+      for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
+      {
+        // Push the table index (note the "+1" because of Lua conventions)
+        lua_pushnumber(context_.lua_, i + 1);
+
+        // Push the value of the cell
+        PushJSON(value[i]);
+
+        // Stores the pair in the table
+        lua_rawset(context_.lua_, -3);
+      }
+    }
+    else if (value.isObject())
+    {
+      lua_newtable(context_.lua_);
+
+      Json::Value::Members members = value.getMemberNames();
+
+      for (Json::Value::Members::const_iterator 
+             it = members.begin(); it != members.end(); it++)
+      {
+        // Push the index of the cell
+        lua_pushstring(context_.lua_, it->c_str());
+
+        // Push the value of the cell
+        PushJSON(value[*it]);
+
+        // Stores the pair in the table
+        lua_rawset(context_.lua_, -3);
+      }
+    }
+    else
+    {
+      throw LuaException("Unsupported JSON conversion");
+    }
+  }
+
+  void LuaFunctionCall::Execute(int numOutputs)
+  {
+    CheckAlreadyExecuted();
+
+    assert(lua_gettop(context_.lua_) >= 1);
+    int nargs = lua_gettop(context_.lua_) - 1;
+    int error = lua_pcall(context_.lua_, nargs, numOutputs, 0);
+
+    if (error) 
+    {
+      assert(lua_gettop(context_.lua_) >= 1);
+          
+      std::string description(lua_tostring(context_.lua_, -1));
+      lua_pop(context_.lua_, 1); /* pop error message from the stack */
+      throw LuaException(description);
+    }
+
+    if (lua_gettop(context_.lua_) < numOutputs)
+    {
+      throw LuaException("The function does not give the expected number of outputs");
+    }
+
+    isExecuted_ = true;
+  }
+
+  bool LuaFunctionCall::ExecutePredicate()
+  {
+    Execute(1);
+        
+    if (lua_gettop(context_.lua_) == 0)
+    {
+      throw LuaException("No output was provided by the function");
+    }
+
+    if (!lua_isboolean(context_.lua_, 1))
+    {
+      throw LuaException("The function is not a predicate (only true/false outputs allowed)");
+    }
+
+    return lua_toboolean(context_.lua_, 1) != 0;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Lua/LuaFunctionCall.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,69 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "LuaContext.h"
+
+#include <json/json.h>
+
+
+namespace Orthanc
+{
+  class LuaFunctionCall : public boost::noncopyable
+  {
+  private:
+    LuaContext& context_;
+    boost::mutex::scoped_lock lock_;
+    bool isExecuted_;
+
+    void CheckAlreadyExecuted();
+
+  public:
+    LuaFunctionCall(LuaContext& context,
+                    const char* functionName);
+
+    void PushString(const std::string& value);
+
+    void PushBoolean(bool value);
+
+    void PushInteger(int value);
+
+    void PushDouble(double value);
+
+    void PushJSON(const Json::Value& value);
+
+    void Execute(int numOutputs = 0);
+
+    bool ExecutePredicate();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/MultiThreading/ArrayFilledByThreads.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,121 @@
+#include "ArrayFilledByThreads.h"
+
+#include "../MultiThreading/ThreadedCommandProcessor.h"
+#include "../OrthancException.h"
+
+namespace Orthanc
+{
+  class ArrayFilledByThreads::Command : public ICommand
+  {
+  private:
+    ArrayFilledByThreads&  that_;
+    size_t  index_;
+
+  public:
+    Command(ArrayFilledByThreads& that,
+            size_t index) :
+      that_(that),
+      index_(index)
+    {
+    }
+
+    virtual bool Execute()
+    {
+      std::auto_ptr<IDynamicObject> obj(that_.filler_.GetFillerItem(index_));
+      if (obj.get() == NULL)
+      {
+        return false;
+      }
+      else
+      {
+        boost::mutex::scoped_lock lock(that_.mutex_);
+        that_.array_[index_] = obj.release();
+        return true;
+      }
+    }
+  };
+
+  void ArrayFilledByThreads::Clear()
+  {
+    for (size_t i = 0; i < array_.size(); i++)
+    {
+      if (array_[i])
+        delete array_[i];
+    }
+
+    array_.clear();
+    filled_ = false;
+  }
+
+  void ArrayFilledByThreads::Update()
+  {
+    if (!filled_)
+    {
+      array_.resize(filler_.GetFillerSize());
+
+      Orthanc::ThreadedCommandProcessor processor(threadCount_);
+      for (size_t i = 0; i < array_.size(); i++)
+      {
+        processor.Post(new Command(*this, i));
+      }
+
+      processor.Join();
+      filled_ = true;
+    }
+  }
+
+
+  ArrayFilledByThreads::ArrayFilledByThreads(IFiller& filler) : filler_(filler)
+  {
+    filled_ = false;
+    threadCount_ = 4;
+  }
+
+
+  ArrayFilledByThreads::~ArrayFilledByThreads()
+  {
+    Clear();
+  }
+
+  
+  void ArrayFilledByThreads::Reload()
+  {
+    Clear();
+    Update();
+  }
+
+
+  void ArrayFilledByThreads::Invalidate()
+  {
+    Clear();
+  }
+
+
+  void ArrayFilledByThreads::SetThreadCount(unsigned int t)
+  {
+    if (t < 1)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    threadCount_ = t;
+  }
+
+
+  size_t ArrayFilledByThreads::GetSize()
+  {
+    Update();
+    return array_.size();
+  }
+
+
+  IDynamicObject& ArrayFilledByThreads::GetItem(size_t index)
+  {
+    if (index >= GetSize())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    return *array_[index];
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/MultiThreading/ArrayFilledByThreads.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <boost/thread.hpp>
+
+#include "../ICommand.h"
+
+namespace Orthanc
+{
+  class ArrayFilledByThreads
+  {
+  public:
+    class IFiller
+    {
+    public:
+      virtual size_t GetFillerSize() = 0;
+
+      virtual IDynamicObject* GetFillerItem(size_t index) = 0;
+    };
+
+  private:
+    IFiller& filler_;
+    boost::mutex  mutex_;
+    std::vector<IDynamicObject*>  array_;
+    bool filled_;
+    unsigned int threadCount_;
+
+    class Command;
+
+    void Clear();
+
+    void Update();
+
+  public:
+    ArrayFilledByThreads(IFiller& filler);
+
+    ~ArrayFilledByThreads();
+  
+    void Reload();
+
+    void Invalidate();
+
+    void SetThreadCount(unsigned int t);
+
+    unsigned int GetThreadCount() const
+    {
+      return threadCount_;
+    }
+
+    size_t GetSize();
+
+    IDynamicObject& GetItem(size_t index);
+  };
+}
+
--- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -102,7 +102,11 @@
         assert(t.get() != NULL);
         assert(bag->pimpl_->activeThreads_.find(r.get()) == bag->pimpl_->activeThreads_.end());
 
-        t->join();
+        if (t->joinable())
+        {
+          t->join();
+        }
+
         bag->pimpl_->oneThreadIsJoined_.notify_one();
       }
 
@@ -128,7 +132,11 @@
     // Stop the finish listener
     pimpl_->stopFinishListener_ = true;
     pimpl_->oneThreadIsStopped_.notify_one();  // Awakens the listener
-    pimpl_->finishListener_->join();
+
+    if (pimpl_->finishListener_->joinable())
+    {
+      pimpl_->finishListener_->join();
+    }
   }
 
 
--- a/Core/MultiThreading/BagOfRunnablesBySteps.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/MultiThreading/BagOfRunnablesBySteps.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/MultiThreading/IRunnableBySteps.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/MultiThreading/IRunnableBySteps.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/MultiThreading/SharedMessageQueue.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,122 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SharedMessageQueue.h"
+
+namespace Orthanc
+{
+  SharedMessageQueue::SharedMessageQueue(unsigned int maxSize)
+  {
+    maxSize_ = maxSize;
+  }
+
+
+  SharedMessageQueue::~SharedMessageQueue()
+  {
+    for (Queue::iterator it = queue_.begin(); it != queue_.end(); it++)
+    {
+      delete *it;
+    }
+  }
+
+
+  void SharedMessageQueue::Enqueue(IDynamicObject* message)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (maxSize_ != 0 && queue_.size() > maxSize_)
+    {
+      // Too many elements in the queue: First remove the oldest
+      delete queue_.front();
+      queue_.pop_front();
+    }
+
+    queue_.push_back(message);
+    elementAvailable_.notify_one();
+  }
+
+
+  IDynamicObject* SharedMessageQueue::Dequeue(int32_t millisecondsTimeout)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    // Wait for a message to arrive in the FIFO queue
+    while (queue_.empty())
+    {
+      if (millisecondsTimeout == 0)
+      {
+        elementAvailable_.wait(lock);
+      }
+      else
+      {
+        bool success = elementAvailable_.timed_wait
+          (lock, boost::posix_time::milliseconds(millisecondsTimeout));
+        if (!success)
+        {
+          return NULL;
+        }
+      }
+    }
+
+    std::auto_ptr<IDynamicObject> message(queue_.front());
+    queue_.pop_front();
+    emptied_.notify_all();
+
+    return message.release();
+  }
+
+
+
+  bool SharedMessageQueue::WaitEmpty(int32_t millisecondsTimeout)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    
+    // Wait for the queue to become empty
+    if (!queue_.empty())
+    {
+      if (millisecondsTimeout == 0)
+      {
+        emptied_.wait(lock);
+      }
+      else
+      {
+        if (!emptied_.timed_wait
+            (lock, boost::posix_time::milliseconds(millisecondsTimeout)))
+        {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/MultiThreading/SharedMessageQueue.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,67 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../IDynamicObject.h"
+
+#include <stdint.h>
+#include <list>
+#include <boost/thread.hpp>
+
+namespace Orthanc
+{
+  class SharedMessageQueue
+  {
+  private:
+    typedef std::list<IDynamicObject*>  Queue;
+
+    unsigned int maxSize_;
+    Queue queue_;
+    boost::mutex mutex_;
+    boost::condition_variable elementAvailable_;
+    boost::condition_variable emptied_;
+
+  public:
+    SharedMessageQueue(unsigned int maxSize = 0);
+
+    ~SharedMessageQueue();
+
+    // This transfers the ownership of the message
+    void Enqueue(IDynamicObject* message);
+
+    // The caller is responsible to delete the dequeud message!
+    IDynamicObject* Dequeue(int32_t millisecondsTimeout);
+
+    bool WaitEmpty(int32_t millisecondsTimeout);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/MultiThreading/ThreadedCommandProcessor.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,205 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ThreadedCommandProcessor.h"
+
+#include "../OrthancException.h"
+
+namespace Orthanc
+{
+  static const int32_t TIMEOUT = 10;
+
+
+  void ThreadedCommandProcessor::Processor(ThreadedCommandProcessor* that)
+  {
+    while (!that->done_)
+    {
+      std::auto_ptr<IDynamicObject> command(that->queue_.Dequeue(TIMEOUT));
+
+      if (command.get() != NULL)
+      {
+        bool success = false;
+
+        try
+        {
+          if (that->success_)
+          {
+            // No command has failed so far
+
+            if (that->cancel_)
+            {
+              // The commands have been canceled. Skip the execution
+              // of this command, yet mark it as succeeded.
+              success = true;
+            }
+            else
+            {
+              success = dynamic_cast<ICommand&>(*command).Execute();
+            }
+          }
+          else
+          {
+            // A command has already failed. Skip the execution of this command.
+          }
+        }
+        catch (OrthancException)
+        {
+        }
+
+        {
+          boost::mutex::scoped_lock lock(that->mutex_);
+          assert(that->remainingCommands_ > 0);
+          that->remainingCommands_--;
+
+          if (!success)
+          {
+            if (!that->cancel_ && that->listener_ && that->success_)
+            {
+              // This is the first command that fails
+              that->listener_->SignalFailure();
+            }
+
+            that->success_ = false;
+          }
+          else
+          {
+            if (!that->cancel_ && that->listener_)
+            {
+              if (that->remainingCommands_ == 0)
+              {
+                that->listener_->SignalSuccess(that->totalCommands_);
+              }
+              else
+              {
+                that->listener_->SignalProgress(that->totalCommands_ - that->remainingCommands_,
+                                                that->totalCommands_);
+              }
+            }
+          }
+
+          that->processedCommand_.notify_all();
+        }
+      }
+    }
+  }
+
+
+  ThreadedCommandProcessor::ThreadedCommandProcessor(unsigned int numThreads)
+  {
+    if (numThreads < 1)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    listener_ = NULL;
+    success_ = true;
+    done_ = false;
+    cancel_ = false;
+    threads_.resize(numThreads);
+    remainingCommands_ = 0;
+    totalCommands_ = 0;
+
+    for (unsigned int i = 0; i < numThreads; i++)
+    {
+      threads_[i] = new boost::thread(Processor, this);
+    }
+  }
+
+
+  ThreadedCommandProcessor::~ThreadedCommandProcessor()
+  {
+    done_ = true;
+      
+    for (unsigned int i = 0; i < threads_.size(); i++)
+    {
+      boost::thread* t = threads_[i];
+
+      if (t != NULL)
+      {
+        if (t->joinable())
+        {
+          t->join();
+        }
+
+        delete t;
+      }
+    }
+  }
+
+
+  void ThreadedCommandProcessor::Post(ICommand* command)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    queue_.Enqueue(command);
+    remainingCommands_++;
+    totalCommands_++;
+  }
+
+
+  bool ThreadedCommandProcessor::Join()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    while (!remainingCommands_ == 0)
+    {
+      processedCommand_.wait(lock);
+    }
+
+    if (cancel_ && listener_)
+    {
+      listener_->SignalCancel();
+    }
+
+    // Reset the sequence counters for subsequent commands
+    bool hasSucceeded = success_;
+    success_ = true;
+    totalCommands_ = 0;
+    cancel_ = false;
+
+    return hasSucceeded;
+  }
+
+
+  void ThreadedCommandProcessor::Cancel()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    cancel_ = true;
+  }
+
+
+  void ThreadedCommandProcessor::SetListener(IListener& listener)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    listener_ = &listener;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/MultiThreading/ThreadedCommandProcessor.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,94 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../ICommand.h"
+
+#include "SharedMessageQueue.h"
+
+namespace Orthanc
+{
+  class ThreadedCommandProcessor
+  {
+  public:
+    class IListener
+    {
+    public:
+      virtual ~IListener()
+      {
+      }
+
+      virtual void SignalProgress(unsigned int current,
+                                  unsigned int total) = 0;
+
+      virtual void SignalSuccess(unsigned int total) = 0;
+
+      virtual void SignalFailure() = 0;
+
+      virtual void SignalCancel() = 0;
+    };
+
+  private:
+    SharedMessageQueue  queue_;
+    bool done_;
+    bool cancel_;
+    std::vector<boost::thread*>  threads_;
+    IListener* listener_;
+
+    boost::mutex mutex_;
+    bool success_;
+    unsigned int remainingCommands_, totalCommands_;
+    boost::condition_variable processedCommand_;
+
+    static void Processor(ThreadedCommandProcessor* that);
+
+  public:
+    ThreadedCommandProcessor(unsigned int numThreads);
+
+    ~ThreadedCommandProcessor();
+
+    // This takes the ownership of the command
+    void Post(ICommand* command);
+
+    bool Join();
+
+    void Cancel();
+
+    void SetListener(IListener& listener);
+
+    IListener& GetListener() const
+    {
+      return *listener_;
+    }
+  };
+}
--- a/Core/OrthancException.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/OrthancException.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -102,6 +102,9 @@
       case ErrorCode_BadRequest:
         return "Bad request";
 
+      case ErrorCode_NetworkProtocol:
+        return "Error in the network protocol";
+
       case ErrorCode_Custom:
       default:
         return "???";
--- a/Core/OrthancException.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/OrthancException.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -39,13 +39,19 @@
 {
   class OrthancException
   {
-  private:
+  protected:
     ErrorCode error_;
     std::string custom_;
 
   public:
     static const char* GetDescription(ErrorCode error);
 
+    OrthancException(const char* custom)
+    {
+      error_ = ErrorCode_Custom;
+      custom_ = custom;
+    }
+
     OrthancException(const std::string& custom)
     {
       error_ = ErrorCode_Custom;
--- a/Core/PngWriter.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PngWriter.h"
-
-#include <vector>
-#include <stdint.h>
-#include <png.h>
-#include "OrthancException.h"
-#include "ChunkedBuffer.h"
-
-
-// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4
-// http://zarb.org/~gc/html/libpng.html
-/*
-  void write_row_callback(png_ptr, png_uint_32 row, int pass)
-  {
-  }*/
-
-
-
-
-/*  bool isError_;
-
-// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2
-
-static void ErrorHandler(png_structp png, png_const_charp message)
-{
-printf("** [%s]\n", message);
-
-PngWriter* that = (PngWriter*) png_get_error_ptr(png);
-that->isError_ = true;
-printf("** %d\n", (int)that);
-
-//((PngWriter*) payload)->isError_ = true;
-}
-
-static void WarningHandler(png_structp png, png_const_charp message)
-{
-  printf("++ %d\n", (int)message);
-}*/
-
-
-namespace Orthanc
-{
-  struct PngWriter::PImpl
-  {
-    png_structp png_;
-    png_infop info_;
-
-    // Filled by Prepare()
-    std::vector<uint8_t*> rows_;
-    int bitDepth_;
-    int colorType_;
-  };
-
-
-
-  PngWriter::PngWriter() : pimpl_(new PImpl)
-  {
-    pimpl_->png_ = NULL;
-    pimpl_->info_ = NULL;
-
-    pimpl_->png_ = png_create_write_struct
-      (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler);
-    if (!pimpl_->png_)
-    {
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
-
-    pimpl_->info_ = png_create_info_struct(pimpl_->png_);
-    if (!pimpl_->info_)
-    {
-      png_destroy_write_struct(&pimpl_->png_, NULL);
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
-  }
-
-  PngWriter::~PngWriter()
-  {
-    if (pimpl_->info_)
-    {
-      png_destroy_info_struct(pimpl_->png_, &pimpl_->info_);
-    }
-
-    if (pimpl_->png_)
-    {
-      png_destroy_write_struct(&pimpl_->png_, NULL);
-    }
-  }
-
-
-
-  void PngWriter::Prepare(unsigned int width,
-                          unsigned int height,
-                          unsigned int pitch,
-                          PixelFormat format,
-                          const void* buffer)
-  {
-    pimpl_->rows_.resize(height);
-    for (unsigned int y = 0; y < height; y++)
-    {
-      pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch;
-    }
-
-    switch (format)
-    {
-    case PixelFormat_RGB24:
-      pimpl_->bitDepth_ = 8;
-      pimpl_->colorType_ = PNG_COLOR_TYPE_RGB;
-      break;
-
-    case PixelFormat_Grayscale8:
-      pimpl_->bitDepth_ = 8;
-      pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
-      break;
-
-    case PixelFormat_Grayscale16:
-      pimpl_->bitDepth_ = 16;
-      pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
-      break;
-
-    default:
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  void PngWriter::Compress(unsigned int width,
-                           unsigned int height,
-                           unsigned int pitch,
-                           PixelFormat format)
-  {
-    png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height,
-                 pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE,
-                 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
-
-    png_write_info(pimpl_->png_, pimpl_->info_);
-
-    if (height > 0)
-    {
-      switch (format)
-      {
-      case PixelFormat_Grayscale16:
-        // Must swap the endianness!!
-        png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]);
-        png_write_png(pimpl_->png_, pimpl_->info_, PNG_TRANSFORM_SWAP_ENDIAN, NULL);
-        break;
-
-      default:
-        png_write_image(pimpl_->png_, &pimpl_->rows_[0]);
-      }
-    }
-
-    png_write_end(pimpl_->png_, NULL);
-  }
-
-
-  void PngWriter::WriteToFile(const char* filename,
-                              unsigned int width,
-                              unsigned int height,
-                              unsigned int pitch,
-                              PixelFormat format,
-                              const void* buffer)
-  {
-    Prepare(width, height, pitch, format, buffer);
-
-    FILE* fp = fopen(filename, "wb");
-    if (!fp)
-    {
-      throw OrthancException(ErrorCode_CannotWriteFile);
-    }    
-
-    png_init_io(pimpl_->png_, fp);
-
-    if (setjmp(png_jmpbuf(pimpl_->png_)))
-    {
-      // Error during writing PNG
-      throw OrthancException(ErrorCode_CannotWriteFile);      
-    }
-
-    Compress(width, height, pitch, format);
-
-    fclose(fp);
-  }
-
-
-
-
-  static void MemoryCallback(png_structp png_ptr, 
-                             png_bytep data, 
-                             png_size_t size)
-  {
-    ChunkedBuffer* buffer = (ChunkedBuffer*) png_get_io_ptr(png_ptr);
-    buffer->AddChunk(reinterpret_cast<const char*>(data), size);
-  }
-
-
-
-  void PngWriter::WriteToMemory(std::string& png,
-                                unsigned int width,
-                                unsigned int height,
-                                unsigned int pitch,
-                                PixelFormat format,
-                                const void* buffer)
-  {
-    ChunkedBuffer chunks;
-
-    Prepare(width, height, pitch, format, buffer);
-
-    if (setjmp(png_jmpbuf(pimpl_->png_)))
-    {
-      // Error during writing PNG
-      throw OrthancException(ErrorCode_InternalError);      
-    }
-
-    png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL);
-
-    Compress(width, height, pitch, format);
-
-    chunks.Flatten(png);
-  }
-}
--- a/Core/PngWriter.h	Mon Apr 29 12:48:10 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "Enumerations.h"
-
-#include <boost/shared_ptr.hpp>
-#include <string>
-
-namespace Orthanc
-{
-  class PngWriter
-  {
-  private:
-    struct PImpl;
-    boost::shared_ptr<PImpl> pimpl_;
-
-    void Compress(unsigned int width,
-                  unsigned int height,
-                  unsigned int pitch,
-                  PixelFormat format);
-
-    void Prepare(unsigned int width,
-                 unsigned int height,
-                 unsigned int pitch,
-                 PixelFormat format,
-                 const void* buffer);
-
-  public:
-    PngWriter();
-
-    ~PngWriter();
-
-    void WriteToFile(const char* filename,
-                     unsigned int width,
-                     unsigned int height,
-                     unsigned int pitch,
-                     PixelFormat format,
-                     const void* buffer);
-
-    void WriteToMemory(std::string& png,
-                       unsigned int width,
-                       unsigned int height,
-                       unsigned int pitch,
-                       PixelFormat format,
-                       const void* buffer);
-  };
-}
--- a/Core/RestApi/RestApi.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/RestApi/RestApi.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -180,7 +180,7 @@
   }
 
   void RestApi::Handle(HttpOutput& output,
-                       Orthanc_HttpMethod method,
+                       HttpMethod method,
                        const UriComponents& uri,
                        const Arguments& headers,
                        const Arguments& getArguments,
@@ -191,14 +191,14 @@
     RestApiPath::Components components;
     UriComponents trailing;
 
-    if (method == Orthanc_HttpMethod_Get)
+    if (method == HttpMethod_Get)
     {
       for (GetHandlers::const_iterator it = getHandlers_.begin();
            it != getHandlers_.end(); it++)
       {
         if (it->first->Match(components, trailing, uri))
         {
-          LOG(INFO) << "REST GET call on: " << Toolbox::FlattenUri(uri);
+          //LOG(INFO) << "REST GET call on: " << Toolbox::FlattenUri(uri);
           ok = true;
           GetCall call;
           call.output_ = &restOutput;
@@ -213,14 +213,14 @@
         }
       }
     }
-    else if (method == Orthanc_HttpMethod_Put)
+    else if (method == HttpMethod_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);
+          //LOG(INFO) << "REST PUT call on: " << Toolbox::FlattenUri(uri);
           ok = true;
           PutCall call;
           call.output_ = &restOutput;
@@ -235,14 +235,14 @@
         }
       }
     }
-    else if (method == Orthanc_HttpMethod_Post)
+    else if (method == HttpMethod_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);
+          //LOG(INFO) << "REST POST call on: " << Toolbox::FlattenUri(uri);
           ok = true;
           PostCall call;
           call.output_ = &restOutput;
@@ -257,14 +257,14 @@
         }
       }
     }
-    else if (method == Orthanc_HttpMethod_Delete)
+    else if (method == HttpMethod_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);
+          //LOG(INFO) << "REST DELETE call on: " << Toolbox::FlattenUri(uri);
           ok = true;
           DeleteCall call;
           call.output_ = &restOutput;
@@ -280,7 +280,8 @@
 
     if (!ok)
     {
-      LOG(INFO) << "REST method " << method << " not allowed on: " << Toolbox::FlattenUri(uri);
+      LOG(INFO) << "REST method " << EnumerationToString(method) 
+                << " not allowed on: " << Toolbox::FlattenUri(uri);
       output.SendMethodNotAllowedError(GetAcceptedMethods(uri));
     }
   }
--- a/Core/RestApi/RestApi.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/RestApi/RestApi.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -212,7 +212,7 @@
     virtual bool IsServedUri(const UriComponents& uri);
 
     virtual void Handle(HttpOutput& output,
-                        Orthanc_HttpMethod method,
+                        HttpMethod method,
                         const UriComponents& uri,
                         const Arguments& headers,
                         const Arguments& getArguments,
--- a/Core/RestApi/RestApiOutput.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/RestApi/RestApiOutput.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -48,7 +48,7 @@
   {
     if (!alreadySent_)
     {
-      output_.SendHeader(Orthanc_HttpStatus_400_BadRequest);
+      output_.SendHeader(HttpStatus_400_BadRequest);
     }
   }
   
@@ -100,10 +100,10 @@
     alreadySent_ = true;
   }
 
-  void RestApiOutput::SignalError(Orthanc_HttpStatus status)
+  void RestApiOutput::SignalError(HttpStatus status)
   {
-    if (status != Orthanc_HttpStatus_403_Forbidden &&
-        status != Orthanc_HttpStatus_415_UnsupportedMediaType)
+    if (status != HttpStatus_403_Forbidden &&
+        status != HttpStatus_415_UnsupportedMediaType)
     {
       throw OrthancException("This HTTP status is not allowed in a REST API");
     }
--- a/Core/RestApi/RestApiOutput.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/RestApi/RestApiOutput.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -74,7 +74,7 @@
                       size_t length,
                       const std::string& contentType);
 
-    void SignalError(Orthanc_HttpStatus status);
+    void SignalError(HttpStatus status);
 
     void Redirect(const std::string& path);
 
--- a/Core/RestApi/RestApiPath.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/RestApi/RestApiPath.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/RestApi/RestApiPath.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/RestApi/RestApiPath.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Core/SQLite/Connection.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/Connection.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Connection.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/Connection.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/FunctionContext.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/FunctionContext.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Redistribution and use in source and binary forms, with or without
--- a/Core/SQLite/FunctionContext.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/FunctionContext.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Redistribution and use in source and binary forms, with or without
--- a/Core/SQLite/IScalarFunction.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/IScalarFunction.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Redistribution and use in source and binary forms, with or without
--- a/Core/SQLite/Statement.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/Statement.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Statement.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/Statement.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementId.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/StatementId.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementId.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/StatementId.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementReference.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/StatementReference.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementReference.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/StatementReference.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Transaction.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/Transaction.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Transaction.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/SQLite/Transaction.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/Toolbox.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Toolbox.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -34,6 +34,7 @@
 
 #include "OrthancException.h"
 
+#include <stdint.h>
 #include <string.h>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
@@ -131,9 +132,9 @@
 #if defined(_WIN32)
   static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType)
   {
-	// http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx
-	finish = true;
-	return true;
+    // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx
+    finish = true;
+    return true;
   }
 #else
   static void SignalHandler(int)
@@ -168,7 +169,7 @@
   void Toolbox::ServerBarrier()
   {
 #if defined(_WIN32)
-	SetConsoleCtrlHandler(ConsoleControlHandler, true);
+    SetConsoleCtrlHandler(ConsoleControlHandler, true);
 #else
     signal(SIGINT, SignalHandler);
     signal(SIGQUIT, SignalHandler);
@@ -181,7 +182,7 @@
     }
 
 #if defined(_WIN32)
-	SetConsoleCtrlHandler(ConsoleControlHandler, false);
+    SetConsoleCtrlHandler(ConsoleControlHandler, false);
 #else
     signal(SIGINT, NULL);
     signal(SIGQUIT, NULL);
@@ -207,10 +208,10 @@
                          const std::string& path) 
   {
     boost::filesystem::ifstream f;
-    f.open(path, std::ifstream::in | std::ios::binary);
+    f.open(path, std::ifstream::in | std::ifstream::binary);
     if (!f.good())
     {
-      throw OrthancException("Unable to open a file");
+      throw OrthancException(ErrorCode_InexistentFile);
     }
 
     // http://www.cplusplus.com/reference/iostream/istream/tellg/
@@ -228,6 +229,26 @@
   }
 
 
+  void Toolbox::WriteFile(const std::string& content,
+                          const std::string& path)
+  {
+    boost::filesystem::ofstream f;
+    f.open(path, std::ofstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }
+
+    if (content.size() != 0)
+    {
+      f.write(content.c_str(), content.size());
+    }
+
+    f.close();
+  }
+
+
+
   void Toolbox::RemoveFile(const std::string& path)
   {
     if (boost::filesystem::exists(path))
@@ -565,6 +586,33 @@
     }
   }
 
+  bool Toolbox::IsSHA1(const std::string& str)
+  {
+    if (str.size() != 44)
+    {
+      return false;
+    }
+
+    for (unsigned int i = 0; i < 44; i++)
+    {
+      if (i == 8 ||
+          i == 17 ||
+          i == 26 ||
+          i == 35)
+      {
+        if (str[i] != '-')
+          return false;
+      }
+      else
+      {
+        if (!isalnum(str[i]))
+          return false;
+      }
+    }
+
+    return true;
+  }
+
   std::string Toolbox::GetNowIsoString()
   {
     boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
@@ -644,4 +692,29 @@
 
     s.resize(target);
   }
+
+
+  Endianness Toolbox::DetectEndianness()
+  {
+    // http://sourceforge.net/p/predef/wiki/Endianness/
+
+    uint8_t buffer[4];
+
+    buffer[0] = 0x00;
+    buffer[1] = 0x01;
+    buffer[2] = 0x02;
+    buffer[3] = 0x03;
+
+    switch (*((uint32_t *)buffer)) 
+    {
+      case 0x00010203: 
+        return Endianness_Big;
+
+      case 0x03020100: 
+        return Endianness_Little;
+        
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
 }
--- a/Core/Toolbox.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Toolbox.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -32,6 +32,8 @@
 
 #pragma once
 
+#include "Enumerations.h"
+
 #include <stdint.h>
 #include <vector>
 #include <string>
@@ -55,6 +57,9 @@
     void ReadFile(std::string& content,
                   const std::string& path);
 
+    void WriteFile(const std::string& content,
+                   const std::string& path);
+
     void Sleep(uint32_t seconds);
 
     void USleep(uint64_t microSeconds);
@@ -80,6 +85,8 @@
     void ComputeSHA1(std::string& result,
                      const std::string& data);
 
+    bool IsSHA1(const std::string& str);
+
     std::string DecodeBase64(const std::string& data);
 
     std::string EncodeBase64(const std::string& data);
@@ -99,5 +106,7 @@
 
     // In-place percent-decoding for URL
     void UrlDecode(std::string& s);
+
+    Endianness DetectEndianness();
   }
 }
--- a/Core/Uuid.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Uuid.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -96,6 +96,28 @@
     }
 
 
+    bool StartsWithUuid(const std::string& str)
+    {
+      if (str.size() < 36)
+      {
+        return false;
+      }
+
+      if (str.size() == 36)
+      {
+        return IsUuid(str);
+      }
+
+      assert(str.size() > 36);
+      if (!isspace(str[36]))
+      {
+        return false;
+      }
+
+      return IsUuid(str.substr(0, 36));
+    }
+
+
     static std::string CreateTemporaryPath(const char* extension)
     {
 #if BOOST_HAS_FILESYSTEM_V3 == 1
--- a/Core/Uuid.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/Core/Uuid.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -51,6 +51,8 @@
 
     bool IsUuid(const std::string& str);
 
+    bool StartsWithUuid(const std::string& str);
+
     class TemporaryFile
     {
     private:
--- a/NEWS	Mon Apr 29 12:48:10 2013 +0200
+++ b/NEWS	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,62 @@
 Pending changes in the mainline
 ===============================
 
+* Switch to Boost 1.54.0 (cf. issue #9)
+
+
+Version 0.6.1 (2013/09/16)
+==========================
+
+* Detection of stable patients/studies/series
+* C-Find SCU at the instance level
+* Link from modified to original resource in Orthanc Explorer
+* Fix of issue #8
+* Anonymization of the medical alerts tag (0010,2000)
+
+
+Version 0.6.0 (2013/07/16)
+==========================
+
+Major changes
+-------------
+
+* Introduction of the C++ client
+* Send DICOM resources to other Orthanc instances through HTTP
+* Access to signed images (instances/.../image-int16)
+  (Closes: Debian #716958)
+
+Minor changes
+-------------
+
+* Export of DICOM files to the host filesystem (instances/.../export)
+* Statistics about patients, studies, series and instances
+* Link from anonymized to original resource in Orthanc Explorer
+* Fixes for Red Hat and Debian packaging
+* Fixes for history in Orthanc Explorer
+* Fixes for boost::thread, as reported by Cyril Paulus
+* Fix licensing (Closes: Debian #712038)
+
+Metadata
+--------
+
+* Access to the metadata through the REST API (.../metadata)
+* Support of user-defined metadata
+* "LastUpdate" metadata for patients, studies and series
+* "/tools/now" to be used in combination with "LastUpdate"
+* Improved support of series with temporal positions
+
+
+Version 0.5.2 (2013/05/07)
+==========================
+
+* "Bulk" Store-SCU (send several DICOM instances with the same
+  DICOM connexion)
+* Store-SCU for patients and studies in Orthanc Explorer
+* Filtering of incoming DICOM instances (through Lua scripting)
+* Filtering of incoming HTTP requests (through Lua scripting)
+* Clearing of "/exports" and "/changes"
+* Check MD5 of third party downloads
+* Faking of the HTTP methods PUT and DELETE
 
 
 Version 0.5.1 (2013/04/17)
@@ -9,7 +65,7 @@
 * Support of RGB images
 * Fix of store SCU in release builds
 * Possibility to store the SQLite index at another place than the
-  DICOM instances
+  DICOM instances (for performance)
 
 
 Version 0.5.0 (2013/01/31)
--- a/OrthancCppClient/CMakeLists.txt	Mon Apr 29 12:48:10 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-# Mini-project to check whether "OrthancCppClient" can compile in a
-# standalone fashion
-
-cmake_minimum_required(VERSION 2.8)
-
-project(OrthancCppClientTest)
-
-SET(STATIC_BUILD OFF)
-
-include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DownloadPackage.cmake)
-include(${CMAKE_SOURCE_DIR}/../Resources/CMake/JsonCppConfiguration.cmake)
-include(${CMAKE_SOURCE_DIR}/../Resources/CMake/LibCurlConfiguration.cmake)
-
-if (${CMAKE_COMPILER_IS_GNUCXX})
-  set(CMAKE_C_FLAGS "-Wall -pedantic -Wno-implicit-function-declaration")  # --std=c99 makes libcurl not to compile
-  set(CMAKE_CXX_FLAGS "-Wall -pedantic -Wno-long-long -Wno-variadic-macros")
-  set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed")
-  set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined")
-elseif (${MSVC})
-  add_definitions(-D_CRT_SECURE_NO_WARNINGS=1)  
-endif()
-
-add_library(OrthancCppClient
-  SHARED
-
-  ${THIRD_PARTY_SOURCES}
-  HttpException.cpp
-  HttpClient.cpp
-  )
-
-add_executable(Test
-  main.cpp
-  )
-
-target_link_libraries(Test OrthancCppClient)
--- a/OrthancCppClient/HttpClient.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,208 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use, copy,
- * modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- **/
-
-
-#include "HttpClient.h"
-
-#include <string.h>
-#include <curl/curl.h>
-
-
-namespace Orthanc
-{
-  struct HttpClient::PImpl
-  {
-    CURL* curl_;
-    struct curl_slist *postHeaders_;
-  };
-
-
-  static CURLcode CheckCode(CURLcode code)
-  {
-    if (code != CURLE_OK)
-    {
-      printf("ICI: %s\n", curl_easy_strerror(code));
-      throw HttpException("CURL: " + std::string(curl_easy_strerror(code)));
-    }
-
-    return code;
-  }
-
-
-  static size_t CurlCallback(void *buffer, size_t size, size_t nmemb, void *payload)
-  {
-    std::string& target = *(static_cast<std::string*>(payload));
-
-    size_t length = size * nmemb;
-    if (length == 0)
-      return 0;
-
-    size_t pos = target.size();
-
-    target.resize(pos + length);
-    memcpy(&target.at(pos), buffer, length);
-
-    return length;
-  }
-
-
-  HttpClient::HttpClient() : pimpl_(new PImpl)
-  {
-    pimpl_->postHeaders_ = NULL;
-    if ((pimpl_->postHeaders_ = curl_slist_append(pimpl_->postHeaders_, "Expect:")) == NULL)
-    {
-      throw HttpException("HttpClient: Not enough memory");
-    }
-
-    pimpl_->curl_ = curl_easy_init();
-    if (!pimpl_->curl_)
-    {
-      curl_slist_free_all(pimpl_->postHeaders_);
-      throw HttpException("HttpClient: Not enough memory");
-    }
-
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlCallback));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1));
-
-#if ORTHANC_SSL_ENABLED == 1
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); 
-#endif
-
-    url_ = "";
-    method_ = Orthanc_HttpMethod_Get;
-    lastStatus_ = Orthanc_HttpStatus_200_Ok;
-    isVerbose_ = false;
-  }
-
-
-  HttpClient::~HttpClient()
-  {
-    curl_easy_cleanup(pimpl_->curl_);
-    curl_slist_free_all(pimpl_->postHeaders_);
-  }
-
-
-  void HttpClient::SetVerbose(bool isVerbose)
-  {
-    isVerbose_ = isVerbose;
-
-    if (isVerbose_)
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1));
-    }
-    else
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 0));
-    }
-  }
-
-
-  bool HttpClient::Apply(std::string& answer)
-  {
-    answer.clear();
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str()));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, NULL));
-
-    switch (method_)
-    {
-    case Orthanc_HttpMethod_Get:
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L));
-      break;
-
-    case Orthanc_HttpMethod_Post:
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L));
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->postHeaders_));
-
-      if (postData_.size() > 0)
-      {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, postData_.c_str()));
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, postData_.size()));
-      }
-      else
-      {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL));
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0));
-      }
-
-      break;
-
-    case Orthanc_HttpMethod_Delete:
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L));
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE"));
-      break;
-
-    case Orthanc_HttpMethod_Put:
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L));
-      break;
-
-    default:
-      throw HttpException("HttpClient: Internal error");
-    }
-
-    // Do the actual request
-    CheckCode(curl_easy_perform(pimpl_->curl_));
-
-    long status;
-    CheckCode(curl_easy_getinfo(pimpl_->curl_, CURLINFO_RESPONSE_CODE, &status));
-
-    if (status == 0)
-    {
-      // This corresponds to a call to an inexistent host
-      lastStatus_ = Orthanc_HttpStatus_500_InternalServerError;
-    }
-    else
-    {
-      lastStatus_ = static_cast<Orthanc_HttpStatus>(status);
-    }
-
-    return (status >= 200 && status < 300);
-  }
-
-
-  bool HttpClient::Apply(Json::Value& answer)
-  {
-    std::string s;
-    if (Apply(s))
-    {
-      Json::Reader reader;
-      return reader.parse(s, answer);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  void HttpClient::SetPassword(const char* username,
-			       const char* password)
-  {
-    std::string s = std::string(username) + ":" + std::string(password);
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, s.c_str()));
-  }
-}
--- a/OrthancCppClient/HttpClient.h	Mon Apr 29 12:48:10 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use, copy,
- * modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- **/
-
-
-#pragma once
-
-#include "HttpEnumerations.h"
-#include "HttpException.h"
-
-#include <string>
-#include <boost/shared_ptr.hpp>
-#include <json/json.h>
-
-namespace Orthanc
-{
-  class HttpClient
-  {
-  private:
-    struct PImpl;
-    boost::shared_ptr<PImpl> pimpl_;
-
-    std::string url_;
-    Orthanc_HttpMethod method_;
-    Orthanc_HttpStatus lastStatus_;
-    std::string postData_;
-    bool isVerbose_;
-
-  public:
-    HttpClient();
-
-    ~HttpClient();
-
-    void SetUrl(const char* url)
-    {
-      url_ = std::string(url);
-    }
-
-    void SetUrl(const std::string& url)
-    {
-      url_ = url;
-    }
-
-    const std::string& GetUrl() const
-    {
-      return url_;
-    }
-
-    void SetMethod(Orthanc_HttpMethod method)
-    {
-      method_ = method;
-    }
-
-    Orthanc_HttpMethod GetMethod() const
-    {
-      return method_;
-    }
-
-    std::string& AccessPostData()
-    {
-      return postData_;
-    }
-
-    const std::string& AccessPostData() const
-    {
-      return postData_;
-    }
-
-    void SetVerbose(bool isVerbose);
-
-    bool IsVerbose() const
-    {
-      return isVerbose_;
-    }
-
-    bool Apply(std::string& answer);
-
-    bool Apply(Json::Value& answer);
-
-    Orthanc_HttpStatus GetLastStatus() const
-    {
-      return lastStatus_;
-    }
-
-    const char* GetLastStatusText() const
-    {
-      return HttpException::GetDescription(lastStatus_);
-    }
-
-    void SetPassword(const char* username,
-                     const char* password);
-  };
-}
--- a/OrthancCppClient/HttpEnumerations.h	Mon Apr 29 12:48:10 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use, copy,
- * modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- **/
-
-
-#pragma once
-
-
-/**
- * This file contains the enumerations for the access to the Orthanc
- * REST API in C and C++. Namespaces are not used, in order to enable
- * the access in C.
- **/
-
-// Most common, non-joke and non-experimental HTTP status codes
-// http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-enum Orthanc_HttpStatus
-{
-  Orthanc_HttpStatus_None = -1,
-
-  // 1xx Informational
-  Orthanc_HttpStatus_100_Continue = 100,
-  Orthanc_HttpStatus_101_SwitchingProtocols = 101,
-  Orthanc_HttpStatus_102_Processing = 102,
-
-  // 2xx Success
-  Orthanc_HttpStatus_200_Ok = 200,
-  Orthanc_HttpStatus_201_Created = 201,
-  Orthanc_HttpStatus_202_Accepted = 202,
-  Orthanc_HttpStatus_203_NonAuthoritativeInformation = 203,
-  Orthanc_HttpStatus_204_NoContent = 204,
-  Orthanc_HttpStatus_205_ResetContent = 205,
-  Orthanc_HttpStatus_206_PartialContent = 206,
-  Orthanc_HttpStatus_207_MultiStatus = 207,
-  Orthanc_HttpStatus_208_AlreadyReported = 208,
-  Orthanc_HttpStatus_226_IMUsed = 226,
-
-  // 3xx Redirection
-  Orthanc_HttpStatus_300_MultipleChoices = 300,
-  Orthanc_HttpStatus_301_MovedPermanently = 301,
-  Orthanc_HttpStatus_302_Found = 302,
-  Orthanc_HttpStatus_303_SeeOther = 303,
-  Orthanc_HttpStatus_304_NotModified = 304,
-  Orthanc_HttpStatus_305_UseProxy = 305,
-  Orthanc_HttpStatus_307_TemporaryRedirect = 307,
-
-  // 4xx Client Error
-  Orthanc_HttpStatus_400_BadRequest = 400,
-  Orthanc_HttpStatus_401_Unauthorized = 401,
-  Orthanc_HttpStatus_402_PaymentRequired = 402,
-  Orthanc_HttpStatus_403_Forbidden = 403,
-  Orthanc_HttpStatus_404_NotFound = 404,
-  Orthanc_HttpStatus_405_MethodNotAllowed = 405,
-  Orthanc_HttpStatus_406_NotAcceptable = 406,
-  Orthanc_HttpStatus_407_ProxyAuthenticationRequired = 407,
-  Orthanc_HttpStatus_408_RequestTimeout = 408,
-  Orthanc_HttpStatus_409_Conflict = 409,
-  Orthanc_HttpStatus_410_Gone = 410,
-  Orthanc_HttpStatus_411_LengthRequired = 411,
-  Orthanc_HttpStatus_412_PreconditionFailed = 412,
-  Orthanc_HttpStatus_413_RequestEntityTooLarge = 413,
-  Orthanc_HttpStatus_414_RequestUriTooLong = 414,
-  Orthanc_HttpStatus_415_UnsupportedMediaType = 415,
-  Orthanc_HttpStatus_416_RequestedRangeNotSatisfiable = 416,
-  Orthanc_HttpStatus_417_ExpectationFailed = 417,
-  Orthanc_HttpStatus_422_UnprocessableEntity = 422,
-  Orthanc_HttpStatus_423_Locked = 423,
-  Orthanc_HttpStatus_424_FailedDependency = 424,
-  Orthanc_HttpStatus_426_UpgradeRequired = 426,
-
-  // 5xx Server Error
-  Orthanc_HttpStatus_500_InternalServerError = 500,
-  Orthanc_HttpStatus_501_NotImplemented = 501,
-  Orthanc_HttpStatus_502_BadGateway = 502,
-  Orthanc_HttpStatus_503_ServiceUnavailable = 503,
-  Orthanc_HttpStatus_504_GatewayTimeout = 504,
-  Orthanc_HttpStatus_505_HttpVersionNotSupported = 505,
-  Orthanc_HttpStatus_506_VariantAlsoNegotiates = 506,
-  Orthanc_HttpStatus_507_InsufficientStorage = 507,
-  Orthanc_HttpStatus_509_BandwidthLimitExceeded = 509,
-  Orthanc_HttpStatus_510_NotExtended = 510
-};
-
-
-enum Orthanc_HttpMethod
-{
-  Orthanc_HttpMethod_Get = 0,
-  Orthanc_HttpMethod_Post = 1,
-  Orthanc_HttpMethod_Delete = 2,
-  Orthanc_HttpMethod_Put = 3
-};
--- a/OrthancCppClient/HttpException.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,208 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use, copy,
- * modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- **/
-
-
-#include "HttpException.h"
-
-namespace Orthanc
-{
-  const char* HttpException::What() const
-  {
-    if (status_ == Orthanc_HttpStatus_None)
-    {
-      return custom_.c_str();
-    }
-    else
-    {
-      return GetDescription(status_);
-    }
-  }
-
-  const char* HttpException::GetDescription(Orthanc_HttpStatus status)
-  {
-    switch (status)
-    {
-    case Orthanc_HttpStatus_100_Continue:
-      return "Continue";
-
-    case Orthanc_HttpStatus_101_SwitchingProtocols:
-      return "Switching Protocols";
-
-    case Orthanc_HttpStatus_102_Processing:
-      return "Processing";
-
-    case Orthanc_HttpStatus_200_Ok:
-      return "OK";
-
-    case Orthanc_HttpStatus_201_Created:
-      return "Created";
-
-    case Orthanc_HttpStatus_202_Accepted:
-      return "Accepted";
-
-    case Orthanc_HttpStatus_203_NonAuthoritativeInformation:
-      return "Non-Authoritative Information";
-
-    case Orthanc_HttpStatus_204_NoContent:
-      return "No Content";
-
-    case Orthanc_HttpStatus_205_ResetContent:
-      return "Reset Content";
-
-    case Orthanc_HttpStatus_206_PartialContent:
-      return "Partial Content";
-
-    case Orthanc_HttpStatus_207_MultiStatus:
-      return "Multi-Status";
-
-    case Orthanc_HttpStatus_208_AlreadyReported:
-      return "Already Reported";
-
-    case Orthanc_HttpStatus_226_IMUsed:
-      return "IM Used";
-
-    case Orthanc_HttpStatus_300_MultipleChoices:
-      return "Multiple Choices";
-
-    case Orthanc_HttpStatus_301_MovedPermanently:
-      return "Moved Permanently";
-
-    case Orthanc_HttpStatus_302_Found:
-      return "Found";
-
-    case Orthanc_HttpStatus_303_SeeOther:
-      return "See Other";
-
-    case Orthanc_HttpStatus_304_NotModified:
-      return "Not Modified";
-
-    case Orthanc_HttpStatus_305_UseProxy:
-      return "Use Proxy";
-
-    case Orthanc_HttpStatus_307_TemporaryRedirect:
-      return "Temporary Redirect";
-
-    case Orthanc_HttpStatus_400_BadRequest:
-      return "Bad Request";
-
-    case Orthanc_HttpStatus_401_Unauthorized:
-      return "Unauthorized";
-
-    case Orthanc_HttpStatus_402_PaymentRequired:
-      return "Payment Required";
-
-    case Orthanc_HttpStatus_403_Forbidden:
-      return "Forbidden";
-
-    case Orthanc_HttpStatus_404_NotFound:
-      return "Not Found";
-
-    case Orthanc_HttpStatus_405_MethodNotAllowed:
-      return "Method Not Allowed";
-
-    case Orthanc_HttpStatus_406_NotAcceptable:
-      return "Not Acceptable";
-
-    case Orthanc_HttpStatus_407_ProxyAuthenticationRequired:
-      return "Proxy Authentication Required";
-
-    case Orthanc_HttpStatus_408_RequestTimeout:
-      return "Request Timeout";
-
-    case Orthanc_HttpStatus_409_Conflict:
-      return "Conflict";
-
-    case Orthanc_HttpStatus_410_Gone:
-      return "Gone";
-
-    case Orthanc_HttpStatus_411_LengthRequired:
-      return "Length Required";
-
-    case Orthanc_HttpStatus_412_PreconditionFailed:
-      return "Precondition Failed";
-
-    case Orthanc_HttpStatus_413_RequestEntityTooLarge:
-      return "Request Entity Too Large";
-
-    case Orthanc_HttpStatus_414_RequestUriTooLong:
-      return "Request-URI Too Long";
-
-    case Orthanc_HttpStatus_415_UnsupportedMediaType:
-      return "Unsupported Media Type";
-
-    case Orthanc_HttpStatus_416_RequestedRangeNotSatisfiable:
-      return "Requested Range Not Satisfiable";
-
-    case Orthanc_HttpStatus_417_ExpectationFailed:
-      return "Expectation Failed";
-
-    case Orthanc_HttpStatus_422_UnprocessableEntity:
-      return "Unprocessable Entity";
-
-    case Orthanc_HttpStatus_423_Locked:
-      return "Locked";
-
-    case Orthanc_HttpStatus_424_FailedDependency:
-      return "Failed Dependency";
-
-    case Orthanc_HttpStatus_426_UpgradeRequired:
-      return "Upgrade Required";
-
-    case Orthanc_HttpStatus_500_InternalServerError:
-      return "Internal Server Error";
-
-    case Orthanc_HttpStatus_501_NotImplemented:
-      return "Not Implemented";
-
-    case Orthanc_HttpStatus_502_BadGateway:
-      return "Bad Gateway";
-
-    case Orthanc_HttpStatus_503_ServiceUnavailable:
-      return "Service Unavailable";
-
-    case Orthanc_HttpStatus_504_GatewayTimeout:
-      return "Gateway Timeout";
-
-    case Orthanc_HttpStatus_505_HttpVersionNotSupported:
-      return "HTTP Version Not Supported";
-
-    case Orthanc_HttpStatus_506_VariantAlsoNegotiates:
-      return "Variant Also Negotiates";
-
-    case Orthanc_HttpStatus_507_InsufficientStorage:
-      return "Insufficient Storage";
-
-    case Orthanc_HttpStatus_509_BandwidthLimitExceeded:
-      return "Bandwidth Limit Exceeded";
-
-    case Orthanc_HttpStatus_510_NotExtended:
-      return "Not Extended";
-
-    default:
-      throw HttpException("Unknown HTTP status");
-    }
-  }
-}
--- a/OrthancCppClient/HttpException.h	Mon Apr 29 12:48:10 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use, copy,
- * modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- **/
-
-
-#pragma once
-
-#include "HttpEnumerations.h"
-
-#include <string>
-
-namespace Orthanc
-{
-  class HttpException
-  {
-  private:
-    Orthanc_HttpStatus status_;
-    std::string custom_;
-
-  public:
-    static const char* GetDescription(Orthanc_HttpStatus status);
-
-    HttpException(const std::string& custom)
-    {
-      status_ = Orthanc_HttpStatus_None;
-      custom_ = custom;
-    }
-
-    HttpException(Orthanc_HttpStatus status)
-    {
-      status_ = status;
-    }
-
-    Orthanc_HttpStatus GetHttpStatus() const
-    {
-      return status_;
-    }
-
-    const char* What() const;
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Instance.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,224 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Instance.h"
+
+#include "OrthancConnection.h"
+#include "../Core/OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancClient
+{
+  void Instance::DownloadImage()
+  {
+    if (reader_.get() == NULL)
+    {
+      const char* suffix;
+      switch (mode_)
+      {
+        case Orthanc::ImageExtractionMode_Preview:
+          suffix = "preview";
+          break;
+          
+        case Orthanc::ImageExtractionMode_UInt8:
+          suffix = "image-uint8";
+          break;
+          
+        case Orthanc::ImageExtractionMode_UInt16:
+          suffix = "image-uint16";
+          break;
+          
+        case Orthanc::ImageExtractionMode_Int16:
+          suffix = "image-int16";
+          break;
+          
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      Orthanc::HttpClient client(connection_.GetHttpClient());
+      client.SetUrl(connection_.GetOrthancUrl() +  "/instances/" + id_ + "/" + suffix);
+      std::string png;
+
+      if (!client.Apply(png))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+     
+      reader_.reset(new Orthanc::PngReader);
+      reader_->ReadFromMemory(png);
+    }
+  }
+
+  Instance::Instance(const OrthancConnection& connection,
+                     const std::string& id) :
+    connection_(connection),
+    id_(id),
+    mode_(Orthanc::ImageExtractionMode_Int16)
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+            
+    client.SetUrl(connection_.GetOrthancUrl() + "/instances/" + id_ + "/simplified-tags");
+    Json::Value v;
+    if (!client.Apply(tags_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  std::string Instance::GetTagAsString(const char* tag)
+  {
+    if (tags_.isMember(tag))
+    {
+      return tags_[tag].asString();
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
+    }
+  }
+
+  float Instance::GetTagAsFloat(const char* tag)
+  {
+    std::string value = GetTagAsString(tag);
+
+    try
+    {
+      return boost::lexical_cast<float>(value);
+    }
+    catch (boost::bad_lexical_cast)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+  int Instance::GetTagAsInt(const char* tag)
+  {
+    std::string value = GetTagAsString(tag);
+
+    try
+    {
+      return boost::lexical_cast<int>(value);
+    }
+    catch (boost::bad_lexical_cast)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+  unsigned int Instance::GetWidth()
+  {
+    DownloadImage();
+    return reader_->GetWidth();
+  }
+
+  unsigned int Instance::GetHeight() 
+  {
+    DownloadImage();
+    return reader_->GetHeight();
+  }
+
+  unsigned int Instance::GetPitch()
+  {
+    DownloadImage();
+    return reader_->GetPitch();
+  }
+
+  Orthanc::PixelFormat Instance::GetPixelFormat()
+  {
+    DownloadImage();
+    return reader_->GetFormat();
+  }
+
+  const void* Instance::GetBuffer()
+  {
+    DownloadImage();
+    return reader_->GetBuffer();
+  }
+
+  const void* Instance::GetBuffer(unsigned int y)
+  {
+    DownloadImage();
+    return reader_->GetBuffer(y);
+  }
+
+  void Instance::DiscardImage()
+  {
+    reader_.reset();
+  }
+
+
+  void Instance::SetImageExtractionMode(Orthanc::ImageExtractionMode mode)
+  {
+    if (mode_ == mode)
+    {
+      return;
+    }
+
+    DiscardImage();
+    mode_ = mode;
+  }
+
+
+  void Instance::SplitVectorOfFloats(std::vector<float>& target,
+                                     const char* tag)
+  {
+    const std::string value = GetTagAsString(tag);
+
+    target.clear();
+
+    try
+    {
+      std::string tmp;
+      for (size_t i = 0; i < value.size(); i++)
+      {
+        if (value[i] == '\\')
+        {
+          target.push_back(boost::lexical_cast<float>(tmp));
+          tmp.clear();
+        }
+        else
+        {
+          tmp.push_back(value[i]);
+        }
+      }
+
+      target.push_back(boost::lexical_cast<float>(tmp));
+    }
+    catch (boost::bad_lexical_cast)
+    {
+      // Unable to parse the Image Orientation Patient.
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Instance.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,95 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <json/value.h>
+
+#include "../Core/IDynamicObject.h"
+#include "../Core/FileFormats/PngReader.h"
+
+namespace OrthancClient
+{
+  class OrthancConnection;
+
+  class Instance : public Orthanc::IDynamicObject
+  {
+  private:
+    const OrthancConnection& connection_;
+    std::string id_;
+    Json::Value tags_;
+    std::auto_ptr<Orthanc::PngReader> reader_;
+    Orthanc::ImageExtractionMode mode_;
+
+    void DownloadImage();
+
+  public:
+    Instance(const OrthancConnection& connection,
+             const std::string& id);
+
+    const std::string& GetId() const
+    {
+      return id_;
+    }
+
+    void SetImageExtractionMode(Orthanc::ImageExtractionMode mode);
+
+    Orthanc::ImageExtractionMode GetImageExtractionMode() const
+    {
+      return mode_;
+    }
+
+    std::string GetTagAsString(const char* tag);
+
+    float GetTagAsFloat(const char* tag);
+
+    int GetTagAsInt(const char* tag);
+
+    unsigned int GetWidth();
+
+    unsigned int GetHeight();
+
+    unsigned int GetPitch();
+
+    Orthanc::PixelFormat GetPixelFormat();
+
+    const void* GetBuffer();
+
+    const void* GetBuffer(unsigned int y);
+
+    void DiscardImage();
+
+    void SplitVectorOfFloats(std::vector<float>& target,
+                             const char* tag);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/OrthancConnection.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancConnection.h"
+
+#include "../Core/OrthancException.h"
+
+namespace OrthancClient
+{
+  void OrthancConnection::ReadPatients()
+  {
+    client_.SetUrl(orthancUrl_ + "/patients");
+    Json::Value v;
+    if (!client_.Apply(content_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* OrthancConnection::GetFillerItem(size_t index)
+  {
+    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
+    return new Patient(*this, content_[tmp].asString());
+  }
+
+  Patient& OrthancConnection::GetPatient(unsigned int index)
+  {
+    return dynamic_cast<Patient&>(patients_.GetItem(index));
+  }
+
+  OrthancConnection::OrthancConnection(const char* orthancUrl) : 
+    orthancUrl_(orthancUrl), patients_(*this)
+  {
+    ReadPatients();
+  }
+  
+  OrthancConnection::OrthancConnection(const char* orthancUrl,
+                                       const char* username, 
+                                       const char* password) : 
+    orthancUrl_(orthancUrl), patients_(*this)
+  {
+    client_.SetCredentials(username, password);
+    ReadPatients();
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/OrthancConnection.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,99 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Core/HttpClient.h"
+
+#include "Patient.h"
+
+namespace OrthancClient
+{
+  class OrthancConnection : 
+    public boost::noncopyable,
+    private Orthanc::ArrayFilledByThreads::IFiller
+  {
+  private:
+    Orthanc::HttpClient client_;
+    std::string orthancUrl_;
+    Orthanc::ArrayFilledByThreads  patients_;
+    Json::Value content_;
+
+    void ReadPatients();
+
+    virtual size_t GetFillerSize()
+    {
+      return content_.size();
+    }
+
+    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
+
+  public:
+    OrthancConnection(const char* orthancUrl);
+
+    OrthancConnection(const char* orthancUrl,
+                      const char* username, 
+                      const char* password);
+
+    unsigned int GetThreadCount() const
+    {
+      return patients_.GetThreadCount();
+    }
+
+    void SetThreadCount(unsigned int threadCount)
+    {
+      patients_.SetThreadCount(threadCount);
+    }
+
+    void Reload()
+    {
+      patients_.Reload();
+    }
+
+    const Orthanc::HttpClient& GetHttpClient() const
+    {
+      return client_;
+    }
+
+    const std::string& GetOrthancUrl() const
+    {
+      return orthancUrl_;
+    }
+
+    unsigned int GetPatientCount()
+    {
+      return patients_.GetSize();
+    }
+
+    Patient& GetPatient(unsigned int index);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Patient.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,78 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Patient.h"
+
+#include "OrthancConnection.h"
+#include "../Core/OrthancException.h"
+
+namespace OrthancClient
+{
+  void Patient::ReadPatient()
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+    client.SetUrl(connection_.GetOrthancUrl() + "/patients/" + id_);
+    Json::Value v;
+    if (!client.Apply(patient_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* Patient::GetFillerItem(size_t index)
+  {
+    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
+    return new Study(connection_, patient_["Studies"][tmp].asString());
+  }
+
+  Patient::Patient(const OrthancConnection& connection,
+                   const std::string& id) :
+    connection_(connection),
+    id_(id),
+    studies_(*this)
+  {
+    studies_.SetThreadCount(connection.GetThreadCount());
+    ReadPatient();
+  }
+
+  std::string Patient::GetMainDicomTag(const char* tag, const char* defaultValue) const
+  {
+    if (patient_["MainDicomTags"].isMember(tag))
+    {
+      return patient_["MainDicomTags"][tag].asString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Patient.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,85 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Study.h"
+
+namespace OrthancClient
+{
+  class Patient : 
+    public Orthanc::IDynamicObject, 
+    private Orthanc::ArrayFilledByThreads::IFiller
+  {
+  private:
+    const OrthancConnection& connection_;
+    std::string id_;
+    Json::Value patient_;
+    Orthanc::ArrayFilledByThreads  studies_;
+
+    void ReadPatient();
+
+    virtual size_t GetFillerSize()
+    {
+      return patient_["Studies"].size();
+    }
+
+    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
+
+  public:
+    Patient(const OrthancConnection& connection,
+            const std::string& id);
+
+    void Reload()
+    {
+      studies_.Reload();
+    }
+
+    unsigned int GetStudyCount()
+    {
+      return studies_.GetSize();
+    }
+
+    Study& GetStudy(unsigned int index)
+    {
+      return dynamic_cast<Study&>(studies_.GetItem(index));
+    }
+
+    const std::string& GetId() const
+    {
+      return id_;
+    }
+
+    std::string GetMainDicomTag(const char* tag, 
+                                const char* defaultValue) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Series.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,432 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Series.h"
+
+#include "OrthancConnection.h"
+#include "../Core/OrthancException.h"
+
+#include <set>
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancClient
+{
+  namespace
+  {
+    class SliceLocator
+    {
+    private:
+      float normal_[3];
+
+    public:
+      SliceLocator(Instance& someSlice)
+      {
+        /**
+         * Compute the slice normal from Image Orientation Patient.
+         * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice
+         * http://www.itk.org/pipermail/insight-users/2003-September/004762.html
+         **/
+
+        std::vector<float> cosines;
+        someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient");
+
+        if (cosines.size() != 6)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+
+        normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4];
+        normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5];
+        normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3];
+      }
+
+
+      /**
+       * Compute the distance of some slice along the slice normal.
+       **/
+      float ComputeSliceLocation(Instance& instance) const
+      {
+        std::vector<float> ipp;
+        instance.SplitVectorOfFloats(ipp, "ImagePositionPatient");
+        if (ipp.size() != 3)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+
+        float dist = 0;
+
+        for (int i = 0; i < 3; i++)
+        {
+          dist += normal_[i] * ipp[i];
+        }
+
+        return dist;
+      }
+    };
+
+    class ImageDownloadCommand : public Orthanc::ICommand
+    {
+    private:
+      Orthanc::PixelFormat format_;
+      Orthanc::ImageExtractionMode mode_;
+      Instance& instance_;
+      void* target_;
+      size_t lineStride_;
+
+    public:
+      ImageDownloadCommand(Instance& instance, 
+                           Orthanc::PixelFormat format,
+                           Orthanc::ImageExtractionMode mode,
+                           void* target,
+                           size_t lineStride) :
+        format_(format),
+        mode_(mode),
+        instance_(instance),
+        target_(target),
+        lineStride_(lineStride)
+      {
+        instance_.SetImageExtractionMode(mode);
+      }
+
+      virtual bool Execute()
+      {
+        using namespace Orthanc;
+
+        unsigned int width = instance_.GetHeight();
+
+        for (unsigned int y = 0; y < instance_.GetHeight(); y++)
+        {
+          uint8_t* p = reinterpret_cast<uint8_t*>(target_) + y * lineStride_;
+
+          if (instance_.GetPixelFormat() == format_)
+          {
+            memcpy(p, instance_.GetBuffer(y), 2 * instance_.GetWidth());
+          }
+          else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 &&
+                   format_ == PixelFormat_RGB24)
+          {
+            const uint8_t* s = reinterpret_cast<const uint8_t*>(instance_.GetBuffer(y));
+            for (unsigned int x = 0; x < width; x++, s++, p += 3)
+            {
+              p[0] = *s;
+              p[1] = *s;
+              p[2] = *s;
+            }
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_NotImplemented);
+          }
+        }
+
+        // Do not keep the image in memory, as we are loading 3D images
+        instance_.DiscardImage();
+
+        return true;
+      }
+    };
+  }
+
+
+  void Series::Check3DImage()
+  {
+    if (!Is3DImage())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+  bool Series::Is3DImageInternal()
+  {
+    try
+    {
+      if (GetInstanceCount() == 0)
+      {
+        return true;
+      }
+
+      for (unsigned int i = 0; i < GetInstanceCount(); i++)
+      {
+        if (GetInstance(0).GetTagAsString("Columns") != GetInstance(i).GetTagAsString("Columns") ||
+            GetInstance(0).GetTagAsString("Rows") != GetInstance(i).GetTagAsString("Rows") ||
+            GetInstance(0).GetTagAsString("ImageOrientationPatient") != GetInstance(i).GetTagAsString("ImageOrientationPatient") ||
+            GetInstance(0).GetTagAsString("SliceThickness") != GetInstance(i).GetTagAsString("SliceThickness") ||
+            GetInstance(0).GetTagAsString("PixelSpacing") != GetInstance(i).GetTagAsString("PixelSpacing"))
+        {
+          return false;
+        }              
+      }
+
+      SliceLocator locator(GetInstance(0));
+      std::set<float> l;
+      for (unsigned int i = 0; i < GetInstanceCount(); i++)
+      {
+        l.insert(locator.ComputeSliceLocation(GetInstance(i)));
+      }
+
+      return l.size() == GetInstanceCount();
+    }
+    catch (Orthanc::OrthancException)
+    {
+      return false;
+    }
+  }
+
+  void Series::ReadSeries()
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+
+    client.SetUrl(connection_.GetOrthancUrl() + "/series/" + id_);
+    Json::Value v;
+    if (!client.Apply(series_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* Series::GetFillerItem(size_t index)
+  {
+    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
+    return new Instance(connection_, series_["Instances"][tmp].asString());
+  }
+
+  Series::Series(const OrthancConnection& connection,
+                 const std::string& id) :
+    connection_(connection),
+    id_(id),
+    instances_(*this)
+  {
+    ReadSeries();
+    status_ = Status3DImage_NotTested;
+
+    instances_.SetThreadCount(connection.GetThreadCount());
+  }
+
+
+  bool Series::Is3DImage()
+  {
+    if (status_ == Status3DImage_NotTested)
+    {
+      status_ = Is3DImageInternal() ? Status3DImage_True : Status3DImage_False;
+    }
+
+    return status_ == Status3DImage_True;
+  }
+
+  unsigned int Series::GetInstanceCount()
+  {
+    return instances_.GetSize();
+  }
+
+  Instance& Series::GetInstance(unsigned int index)
+  {
+    return dynamic_cast<Instance&>(instances_.GetItem(index));
+  }
+
+  std::string Series::GetUrl() const
+  {
+    return connection_.GetOrthancUrl() + "/series/" + id_;
+  }
+
+  unsigned int Series::GetWidth()
+  {
+    Check3DImage();
+
+    if (GetInstanceCount() == 0)
+      return 0;
+    else
+      return GetInstance(0).GetTagAsInt("Columns");
+  }
+
+  unsigned int Series::GetHeight()
+  {
+    Check3DImage();
+
+    if (GetInstanceCount() == 0)
+      return 0;
+    else
+      return GetInstance(0).GetTagAsInt("Rows");
+  }
+
+  void Series::GetVoxelSize(float& sizeX, float& sizeY, float& sizeZ)
+  {
+    Check3DImage();
+
+    if (GetInstanceCount() == 0)
+    {
+      sizeX = 0;
+      sizeY = 0;
+      sizeZ = 0;
+    }
+    else
+    {
+      try
+      {
+        std::string s = GetInstance(0).GetTagAsString("PixelSpacing");
+        size_t pos = s.find('\\');
+        assert(pos != std::string::npos);
+        std::string sy = s.substr(0, pos);
+        std::string sx = s.substr(pos + 1);
+
+        sizeX = boost::lexical_cast<float>(sx);
+        sizeY = boost::lexical_cast<float>(sy);
+        sizeZ = GetInstance(0).GetTagAsFloat("SliceThickness");
+      }
+      catch (boost::bad_lexical_cast)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+  }
+
+
+  std::string Series::GetMainDicomTag(const char* tag, const char* defaultValue) const
+  {
+    if (series_["MainDicomTags"].isMember(tag))
+    {
+      return series_["MainDicomTags"][tag].asString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  
+  void Series::Load3DImage(void* target,
+                           Orthanc::PixelFormat format,
+                           size_t lineStride,
+                           size_t stackStride,
+                           Orthanc::ThreadedCommandProcessor::IListener* listener)
+  {
+    using namespace Orthanc;
+
+    // Choose the extraction mode, depending on the format of the
+    // target image.
+
+    uint8_t bytesPerPixel;
+    ImageExtractionMode mode;
+
+    switch (format)
+    {
+      case PixelFormat_RGB24:
+        bytesPerPixel = 3;
+        mode = ImageExtractionMode_Preview;
+        break;
+
+      case PixelFormat_Grayscale8:
+        bytesPerPixel = 1;
+        mode = ImageExtractionMode_UInt8;  // Preview ???
+        break; 
+
+      case PixelFormat_Grayscale16:
+        bytesPerPixel = 2;
+        mode = ImageExtractionMode_UInt16;
+        break;
+
+      case PixelFormat_SignedGrayscale16:
+        bytesPerPixel = 2;
+        mode = ImageExtractionMode_UInt16;
+        format = PixelFormat_Grayscale16;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+
+    // Check that the target image is properly sized
+    unsigned int sx = GetWidth();
+    unsigned int sy = GetHeight();
+
+    if (lineStride < sx * bytesPerPixel ||
+        stackStride < sx * sy * bytesPerPixel)
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+    if (sx == 0 || sy == 0 || GetInstanceCount() == 0)
+    {
+      // Empty image, nothing to do
+      if (listener)
+        listener->SignalSuccess(0);
+      return;
+    }
+
+
+    /**
+     * Order the stacks according to their distance along the slice
+     * normal (using the "Image Position Patient" tag). This works
+     * even if the "SliceLocation" tag is absent.
+     **/
+    SliceLocator locator(GetInstance(0));
+
+    typedef std::map<float, Instance*> Instances;
+    Instances instances;
+    for (unsigned int i = 0; i < GetInstanceCount(); i++)
+    {
+      float dist = locator.ComputeSliceLocation(GetInstance(i));
+      instances[dist] = &GetInstance(i);
+    }
+
+    if (instances.size() != GetInstanceCount())
+    {
+      // Several instances have the same Z coordinate
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+
+    // Submit the download of each stack as a set of commands
+    ThreadedCommandProcessor processor(connection_.GetThreadCount());
+
+    if (listener != NULL)
+    {
+      processor.SetListener(*listener);
+    }
+
+    uint8_t* stackTarget = reinterpret_cast<uint8_t*>(target);
+    for (Instances::iterator it = instances.begin(); it != instances.end(); it++)
+    {
+      processor.Post(new ImageDownloadCommand(*it->second, format, mode, stackTarget, lineStride));
+      stackTarget += stackStride;
+    }
+
+
+    // Wait for all the stacks to be downloaded
+    if (!processor.Join())
+    {
+      throw OrthancException(ErrorCode_NetworkProtocol);
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Series.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,127 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Instance.h"
+
+#include "../Core/MultiThreading/ArrayFilledByThreads.h"
+#include "../Core/MultiThreading/ThreadedCommandProcessor.h"
+
+namespace OrthancClient
+{
+  class Series :
+    public Orthanc::IDynamicObject, 
+    private Orthanc::ArrayFilledByThreads::IFiller
+  {
+  private:
+    enum Status3DImage
+    {
+      Status3DImage_NotTested,
+      Status3DImage_True,
+      Status3DImage_False
+    };
+
+    const OrthancConnection& connection_;
+    std::string id_;
+    Json::Value series_;
+    Orthanc::ArrayFilledByThreads  instances_;
+    Status3DImage status_;
+  
+    void Check3DImage();
+
+    bool Is3DImageInternal();
+
+    void ReadSeries();
+
+    virtual size_t GetFillerSize()
+    {
+      return series_["Instances"].size();
+    }
+
+    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
+
+    void Load3DImage(void* target,
+                     Orthanc::PixelFormat format,
+                     size_t lineStride,
+                     size_t stackStride,
+                     Orthanc::ThreadedCommandProcessor::IListener* listener);
+
+  public:
+    Series(const OrthancConnection& connection,
+           const std::string& id);
+
+    void Reload()
+    {
+      instances_.Reload();
+    }
+
+    bool Is3DImage();
+
+    unsigned int GetInstanceCount();
+
+    Instance& GetInstance(unsigned int index);
+
+    const std::string& GetId() const
+    {
+      return id_;
+    }
+
+    std::string GetUrl() const;
+
+    unsigned int GetWidth();
+
+    unsigned int GetHeight();
+
+    void GetVoxelSize(float& sizeX, float& sizeY, float& sizeZ);  
+
+    std::string GetMainDicomTag(const char* tag, 
+                                const char* defaultValue) const;
+
+    void Load3DImage(void* target,
+                     Orthanc::PixelFormat format,
+                     size_t lineStride,
+                     size_t stackStride,
+                     Orthanc::ThreadedCommandProcessor::IListener& listener)
+    {
+      Load3DImage(target, format, lineStride, stackStride, &listener);
+    }
+
+    void Load3DImage(void* target,
+                     Orthanc::PixelFormat format,
+                     size_t lineStride,
+                     size_t stackStride)
+    {
+      Load3DImage(target, format, lineStride, stackStride, NULL);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Study.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,78 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Study.h"
+
+#include "OrthancConnection.h"
+#include "../Core/OrthancException.h"
+
+namespace OrthancClient
+{
+  void Study::ReadStudy()
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+    client.SetUrl(connection_.GetOrthancUrl() + "/studies/" + id_);
+    Json::Value v;
+    if (!client.Apply(study_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* Study::GetFillerItem(size_t index)
+  {
+    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
+    return new Series(connection_, study_["Series"][tmp].asString());
+  }
+
+  Study::Study(const OrthancConnection& connection,
+               const std::string& id) :
+    connection_(connection),
+    id_(id),
+    series_(*this)
+  {
+    series_.SetThreadCount(connection.GetThreadCount());
+    ReadStudy();
+  }
+
+  std::string Study::GetMainDicomTag(const char* tag, const char* defaultValue) const
+  {
+    if (study_["MainDicomTags"].isMember(tag))
+    {
+      return study_["MainDicomTags"][tag].asString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Study.h	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,85 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Series.h"
+
+namespace OrthancClient
+{
+  class Study : 
+    public Orthanc::IDynamicObject, 
+    private Orthanc::ArrayFilledByThreads::IFiller
+  {
+  private:
+    const OrthancConnection& connection_;
+    std::string id_;
+    Json::Value study_;
+    Orthanc::ArrayFilledByThreads  series_;
+
+    void ReadStudy();
+
+    virtual size_t GetFillerSize()
+    {
+      return study_["Series"].size();
+    }
+
+    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
+
+  public:
+    Study(const OrthancConnection& connection,
+          const std::string& id);
+
+    void Reload()
+    {
+      series_.Reload();
+    }
+
+    unsigned int GetSeriesCount()
+    {
+      return series_.GetSize();
+    }
+
+    Series& GetSeries(unsigned int index)
+    {
+      return dynamic_cast<Series&>(series_.GetItem(index));
+    }
+
+    const std::string& GetId() const
+    {
+      return id_;
+    }
+
+    std::string GetMainDicomTag(const char* tag, 
+                                const char* defaultValue) const;
+  };
+}
--- a/OrthancCppClient/main.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use, copy,
- * modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- **/
-
-
-#include "HttpClient.h"
-
-#include <iostream>
-
-int main()
-{
-  // Prepare a simple call to a Web service
-  Orthanc::HttpClient c;
-  c.SetUrl("http://nominatim.openstreetmap.org/search?format=json&q=chu+liege+belgium");
-  
-  // Do the request and store the result in a JSON structure
-  Json::Value result;
-  c.Apply(result);
-
-  // Display the JSON answer
-  std::cout << result << std::endl;
-
-  return 0;
-}
--- a/OrthancExplorer/explorer.html	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancExplorer/explorer.html	Wed Sep 18 15:27:07 2013 +0200
@@ -88,11 +88,24 @@
 	            <option value="on">Protected</option>
                   </select>
                 </div>
-                <!--a href="#find-patients" data-role="button" data-icon="search">Go to patient finder</a-->
-                <a href="#" data-role="button" data-icon="delete" id="patient-delete">Delete this patient</a>
-                <a href="#" data-role="button" data-icon="gear" id="patient-archive">Download ZIP</a>
-                <a href="#" data-role="button" data-icon="star" id="patient-anonymize">Anonymize</a>
               </p>
+              <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c">
+                <li data-role="list-divider">Interact</li>
+                <li data-icon="delete"><a href="#" id="patient-delete">Delete this patient</a></li>
+                <li data-icon="forward"><a href="#" id="patient-store">Send to remote modality</a></li>
+                <li data-icon="star"><a href="#" id="patient-anonymize">Anonymize</a></li>
+              </ul>
+
+              <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c">
+                <li data-role="list-divider">Access</li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="patient-anonymized-from">Before anonymization</a>
+                </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="patient-modified-from">Before modification</a>
+                </li>
+                <li data-icon="gear"><a href="#" id="patient-archive">Download ZIP</a></li>
+              </ul>
             </div>
           </div>
           <div class="ui-block-b" style="width:70%">
@@ -121,11 +134,24 @@
             <div style="padding-right:10px">
               <ul data-role="listview" data-inset="true" data-theme="a" id="study-info">
               </ul>
-              <p>
-                <a href="#" data-role="button" data-icon="delete" id="study-delete">Delete this study</a>
-                <a href="#" data-role="button" data-icon="gear" id="study-archive">Download ZIP</a>
-                <a href="#" data-role="button" data-icon="star" id="study-anonymize">Anonymize</a>
-              </p>
+
+              <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c">
+                <li data-role="list-divider">Interact</li>
+                <li data-icon="delete"><a href="#" id="study-delete">Delete this study</a></li>
+                <li data-icon="forward"><a href="#" id="study-store">Send to DICOM modality</a></li>
+                <li data-icon="star"><a href="#" id="study-anonymize">Anonymize</a></li>
+              </ul>
+
+              <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c">
+                <li data-role="list-divider">Access</li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="study-anonymized-from">Before anonymization</a>
+                </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="study-modified-from">Before modification</a>
+                </li>
+                <li data-icon="gear"><a href="#" id="study-archive">Download ZIP</a></li>
+              </ul>
             </div>
           </div>
           <div class="ui-block-b" style="width:70%">
@@ -154,15 +180,27 @@
         <div class="ui-grid-a">
           <div class="ui-block-a" style="width:30%">
             <div style="padding-right:10px">
-              <ul data-role="listview" data-inset="true" data-theme="a"  id="series-info">
+              <ul data-role="listview" data-inset="true" data-theme="a" id="series-info">
+              </ul>
+
+              <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c">
+                <li data-role="list-divider">Interact</li>
+                <li data-icon="delete"><a href="#" id="series-delete">Delete this series</a></li>
+                <li data-icon="forward"><a href="#" id="series-store">Send to DICOM modality</a></li>
+                <li data-icon="star"><a href="#" id="series-anonymize">Anonymize</a></li>
               </ul>
-              <p>
-                <a href="#" data-role="button" data-icon="delete" id="series-delete">Delete this series</a>
-                <a href="#" data-role="button" data-icon="search" id="series-preview">Preview this series</a>
-                <a href="#" data-role="button" data-icon="forward" id="series-store">Store in another DICOM modality</a>
-                <a href="#" data-role="button" data-icon="gear" id="series-archive">Download ZIP</a>
-                <a href="#" data-role="button" data-icon="star" id="series-anonymize">Anonymize</a>
-              </p>
+
+              <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c">
+                <li data-role="list-divider">Access</li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="series-anonymized-from">Before anonymization</a>
+                </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="series-modified-from">Before modification</a>
+                </li>
+                <li data-icon="search"><a href="#" id="series-preview">Preview this series</a></li>
+                <li data-icon="gear"><a href="#" id="series-archive">Download ZIP</a></li>
+              </ul>
             </div>
           </div>
           <div class="ui-block-b" style="width:70%">
@@ -193,13 +231,25 @@
             <div style="padding-right:10px">
               <ul data-role="listview" data-inset="true" data-theme="a"  id="instance-info">
               </ul>
-              <p>
-                <a href="#" data-role="button" data-icon="delete" id="instance-delete">Delete this instance</a>
-                <a href="#" data-role="button" data-icon="arrow-d" id="instance-download-dicom">Download the DICOM file</a>
-                <a href="#" data-role="button" data-icon="arrow-d" id="instance-download-json">Download the JSON file</a>
-                <a href="#" data-role="button" data-icon="search" id="instance-preview">Preview the instance</a>
-                <a href="#" data-role="button" data-icon="forward" id="instance-store">Store in another DICOM modality</a>
-              </p>
+
+              <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c">
+                <li data-role="list-divider">Interact</li>
+                <li data-icon="delete"><a href="#" id="instance-delete">Delete this instance</a></li>
+                <li data-icon="forward"><a href="#" id="instance-store">Send to DICOM modality</a></li>
+              </ul>
+
+              <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c">
+                <li data-role="list-divider">Access</li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="instance-anonymized-from">Before anonymization</a>
+                </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="instance-modified-from">Before modification</a>
+                </li>
+                <li data-icon="arrow-d"><a href="#" id="instance-download-dicom">Download the DICOM file</a></li>
+                <li data-icon="arrow-d"><a href="#" id="instance-download-json">Download the JSON file</a></li>
+                <li data-icon="search"><a href="#" id="instance-preview">Preview the instance</a></li>
+              </ul>
             </div>
           </div>
           <div class="ui-block-b" style="width:70%">
@@ -218,7 +268,12 @@
       </div>
     </div>
 
-    <div id="loading" style="display:none;" class="ui-body-c">
+    <div id="peer-store" style="display:none;" class="ui-body-c">
+      <p align="center"><b>Sending to Orthanc peer...</b></p>
+      <p><img src="libs/images/ajax-loader2.gif" alt="" /></p>
+    </div>
+
+    <div id="dicom-store" style="display:none;" class="ui-body-c">
       <p align="center"><b>Sending to DICOM modality...</b></p>
       <p><img src="libs/images/ajax-loader2.gif" alt="" /></p>
     </div>
--- a/OrthancExplorer/explorer.js	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancExplorer/explorer.js	Wed Sep 18 15:27:07 2013 +0200
@@ -12,6 +12,11 @@
 //$.mobile.page.prototype.options.addBackBtn = true;
 //$.mobile.defaultPageTransition = 'slide';
 
+
+var currentPage = '';
+var currentUuid = '';
+
+
 // http://stackoverflow.com/a/4673436
 String.prototype.format = function() {
   var args = arguments;
@@ -356,7 +361,25 @@
 
 
 
-$('#patient').live('pagebeforeshow', function() {
+function SetupAnonymizedOrModifiedFrom(buttonSelector, resource, resourceType, field)
+{
+  if (field in resource)
+  {
+    $(buttonSelector).closest('li').show();
+    $(buttonSelector).click(function(e) {
+      window.location.assign('explorer.html#' + resourceType + '?uuid=' + resource[field]);
+    });
+  }
+  else
+  {
+    $(buttonSelector).closest('li').hide();
+  }
+}
+
+
+
+function RefreshPatient()
+{
   if ($.mobile.pageData) {
     GetSingleResource('patients', $.mobile.pageData.uuid, function(patient) {
       GetMultipleResources('studies', patient.Studies, function(studies) {
@@ -381,6 +404,9 @@
           target.append(FormatStudy(studies[i], '#study?uuid=' + studies[i].ID));
         }
 
+        SetupAnonymizedOrModifiedFrom('#patient-anonymized-from', patient, 'patient', 'AnonymizedFrom');
+        SetupAnonymizedOrModifiedFrom('#patient-modified-from', patient, 'patient', 'ModifiedFrom');
+
         target.listview('refresh');
 
         // Check whether this patient is protected
@@ -395,13 +421,17 @@
             $('#protection').val(v).slider('refresh');
           }
         });
+
+        currentPage = 'patient';
+        currentUuid = $.mobile.pageData.uuid;
       });
     });
   }
-});
+}
 
 
-$('#study').live('pagebeforeshow', function() {
+function RefreshStudy()
+{
   if ($.mobile.pageData) {
     GetSingleResource('studies', $.mobile.pageData.uuid, function(study) {
       GetSingleResource('patients', study.ParentPatient, function(patient) {
@@ -417,6 +447,9 @@
             .append(FormatStudy(study))
             .listview('refresh');
 
+          SetupAnonymizedOrModifiedFrom('#study-anonymized-from', study, 'study', 'AnonymizedFrom');
+          SetupAnonymizedOrModifiedFrom('#study-modified-from', study, 'study', 'ModifiedFrom');
+
           var target = $('#list-series');
           $('li', target).remove();
           for (var i = 0; i < series.length; i++) {
@@ -428,14 +461,18 @@
             target.append(FormatSeries(series[i], '#series?uuid=' + series[i].ID));
           }
           target.listview('refresh');
+
+          currentPage = 'study';
+          currentUuid = $.mobile.pageData.uuid;
         });
       });  
     });
   }
-});
+}
   
 
-$('#series').live('pagebeforeshow', function() {
+function RefreshSeries() 
+{
   if ($.mobile.pageData) {
     GetSingleResource('series', $.mobile.pageData.uuid, function(series) {
       GetSingleResource('studies', series.ParentStudy, function(study) {
@@ -456,18 +493,24 @@
               .append(FormatSeries(series))
               .listview('refresh');
 
+            SetupAnonymizedOrModifiedFrom('#series-anonymized-from', series, 'series', 'AnonymizedFrom');
+            SetupAnonymizedOrModifiedFrom('#series-modified-from', series, 'series', 'ModifiedFrom');
+
             var target = $('#list-instances');
             $('li', target).remove();
             for (var i = 0; i < instances.length; i++) {
               target.append(FormatInstance(instances[i], '#instance?uuid=' + instances[i].ID));
             }
             target.listview('refresh');
+
+            currentPage = 'series';
+            currentUuid = $.mobile.pageData.uuid;
           });
         });
       });
     });
   }
-});
+}
 
 
 
@@ -522,7 +565,8 @@
 }
 
 
-$('#instance').live('pagebeforeshow', function() {
+function RefreshInstance()
+{
   if ($.mobile.pageData) {
     GetSingleResource('instances', $.mobile.pageData.uuid, function(instance) {
       GetSingleResource('series', instance.ParentSeries, function(series) {
@@ -554,15 +598,52 @@
               }
             });
 
+            SetupAnonymizedOrModifiedFrom('#instance-anonymized-from', instance, 'instance', 'AnonymizedFrom');
+            SetupAnonymizedOrModifiedFrom('#instance-modified-from', instance, 'instance', 'ModifiedFrom');
+
+            currentPage = 'instance';
+            currentUuid = $.mobile.pageData.uuid;
           });
         });
       });
     });
   }
+}
+
+$(document).live('pagebeforehide', function() {
+  currentPage = '';
+  currentUuid = '';
 });
 
 
 
+$('#patient').live('pagebeforeshow', RefreshPatient);
+$('#study').live('pagebeforeshow', RefreshStudy);
+$('#series').live('pagebeforeshow', RefreshSeries);
+$('#instance').live('pagebeforeshow', RefreshInstance);
+
+$(function() {
+  $(window).hashchange(function(e, data) {
+    // This fixes the navigation with the back button and with the anonymization
+    if ('uuid' in $.mobile.pageData &&
+        currentPage == $.mobile.pageData.active &&
+        currentUuid != $.mobile.pageData.uuid) {
+      if (currentPage == 'patient')
+        RefreshPatient();
+      else if (currentPage == 'study')
+        RefreshStudy();
+      else if (currentPage == 'series')
+        RefreshSeries();
+      else if (currentPage == 'instance')
+        RefreshInstance();
+    }
+  });
+});
+
+
+
+
+
 function DeleteResource(path)
 {
   $.ajax({
@@ -708,6 +789,13 @@
 
 function ChooseDicomModality(callback)
 {
+  var clickedModality = '';
+  var clickedPeer = '';
+  var items = $('<ul>')
+    .attr('data-divider-theme', 'd')
+    .attr('data-role', 'listview');
+
+  // Retrieve the list of the known DICOM modalities
   $.ajax({
     url: '../modalities',
     type: 'GET',
@@ -715,37 +803,66 @@
     async: false,
     cache: false,
     success: function(modalities) {
-      var clickedModality = '';
-      var items = $('<ul>')
-        .attr('data-role', 'listview');
+      if (modalities.length > 0)
+      {
+        items.append('<li data-role="list-divider">DICOM modalities</li>');
 
-      for (var i = 0; i < modalities.length; i++) {
-        var modality = modalities[i];
-        var item = $('<li>')
-          .html('<a href="#" rel="close">' + modality + '</a>')
-          .attr('modality', modality)
-          .click(function() { 
-            clickedModality = $(this).attr('modality');
-          });
-        items.append(item);
+        for (var i = 0; i < modalities.length; i++) {
+          var name = modalities[i];
+          var item = $('<li>')
+            .html('<a href="#" rel="close">' + name + '</a>')
+            .attr('name', name)
+            .click(function() { 
+              clickedModality = $(this).attr('name');
+            });
+          items.append(item);
+        }
       }
 
-      $('#dialog').simpledialog2({
-        mode: 'blank',
-        animate: false,
-        headerText: 'DICOM modality',
-        headerClose: true,
-        width: '100%',
-        blankContent: items,
-        callbackClose: function() {
-          var timer;
-          function WaitForDialogToClose() {
-            if (!$('#dialog').is(':visible')) {
-              clearInterval(timer);
-              callback(clickedModality);
+      // Retrieve the list of the known Orthanc peers
+      $.ajax({
+        url: '../peers',
+        type: 'GET',
+        dataType: 'json',
+        async: false,
+        cache: false,
+        success: function(peers) {
+          if (peers.length > 0)
+          {
+            items.append('<li data-role="list-divider">Orthanc peers</li>');
+
+            for (var i = 0; i < peers.length; i++) {
+              var name = peers[i];
+              var item = $('<li>')
+                .html('<a href="#" rel="close">' + name + '</a>')
+                .attr('name', name)
+                .click(function() { 
+                  clickedPeer = $(this).attr('name');
+                });
+              items.append(item);
             }
           }
-          timer = setInterval(WaitForDialogToClose, 100);
+
+          // Launch the dialog
+          $('#dialog').simpledialog2({
+            mode: 'blank',
+            animate: false,
+            headerText: 'Choose target',
+            headerClose: true,
+            forceInput: false,
+            width: '100%',
+            blankContent: items,
+            callbackClose: function() {
+              var timer;
+              function WaitForDialogToClose() {
+                if (!$('#dialog').is(':visible')) {
+                  clearInterval(timer);
+                  callback(clickedModality, clickedPeer);
+                }
+              }
+              timer = setInterval(WaitForDialogToClose, 100);
+            }
+          });
         }
       });
     }
@@ -753,29 +870,42 @@
 }
 
 
-$('#instance-store,#series-store').live('click', function(e) {
-  ChooseDicomModality(function(modality) {
-    if (modality != '') {
+$('#instance-store,#series-store,#study-store,#patient-store').live('click', function(e) {
+  ChooseDicomModality(function(modality, peer) {
+    var url;
+    var loading;
+
+    if (modality != '')
+    {
+      url = '../modalities/' + modality + '/store';
+      loading = '#dicom-store';
+    }
+
+    if (peer != '')
+    {
+      url = '../peers/' + peer + '/store';
+      loading = '#peer-store';
+    }
+
+    if (url != '') {
       $.ajax({
-        url: '../modalities/' + modality + '/store',
+        url: url,
         type: 'POST',
         dataType: 'text',
         data: $.mobile.pageData.uuid,
         async: true,  // Necessary to block UI
         beforeSend: function() {
-          $.blockUI({ message: $('#loading') });
+          $.blockUI({ message: $(loading) });
         },
         complete: function(s) {
           $.unblockUI();
         },
         success: function(s) {
-          //console.log('done !');
         },
         error: function() {
-          alert('Error during C-Store');
+          alert('Error during store');
         }
-      });
-      
+      });      
     }
   });
 });
@@ -841,7 +971,7 @@
               //$.mobile.changePage('explorer.html#patient?uuid=' + s.PatientID);
 
               window.location.assign('explorer.html#patient?uuid=' + s.PatientID);
-              window.location.reload();
+              //window.location.reload();
             }
           });
         },
--- a/OrthancExplorer/libs/jqm.page.params.js	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancExplorer/libs/jqm.page.params.js	Wed Sep 18 15:27:07 2013 +0200
@@ -105,7 +105,9 @@
 
 // http://stackoverflow.com/a/8295488
 $(document).bind("pagebeforechange", function( event, data ) {
-    $.mobile.pageData = (data && data.options && data.options.pageData)
+  $.mobile.pageData = (data && data.options && data.options.pageData)
     ? data.options.pageData
     : {};
+
+  $.mobile.pageData.active = data.toPage[0].id;
 });
--- a/OrthancServer/DatabaseWrapper.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DatabaseWrapper.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -340,6 +340,15 @@
     s.Run();
   }
 
+  void DatabaseWrapper::DeleteMetadata(int64_t id,
+                                       MetadataType type)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?");
+    s.BindInt(0, id);
+    s.BindInt(1, type);
+    s.Run();
+  }
+
   bool DatabaseWrapper::LookupMetadata(std::string& target,
                                        int64_t id,
                                        MetadataType type)
@@ -360,6 +369,23 @@
     }
   }
 
+  bool DatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target,
+                                              int64_t id)
+  {
+    target.clear();
+
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?");
+    s.BindInt(0, id);
+
+    while (s.Step())
+    {
+      target.push_back(static_cast<MetadataType>(s.ColumnInt(0)));
+    }
+
+    return true;
+  }
+
+
   std::string DatabaseWrapper::GetMetadata(int64_t id,
                                            MetadataType type,
                                            const std::string& defaultValue)
@@ -412,6 +438,21 @@
     s.Run();
   }
 
+  void DatabaseWrapper::ListAvailableAttachments(std::list<FileContentType>& result,
+                                                 int64_t id)
+  {
+    result.clear();
+
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT fileType FROM AttachedFiles WHERE id=?");
+    s.BindInt(0, id);
+
+    while (s.Step())
+    {
+      result.push_back(static_cast<FileContentType>(s.ColumnInt(0)));
+    }
+  }
+
   bool DatabaseWrapper::LookupAttachment(FileInfo& attachment,
                                          int64_t id,
                                          FileContentType contentType)
@@ -551,8 +592,8 @@
 
       Json::Value item = Json::objectValue;
       item["Seq"] = static_cast<int>(seq);
-      item["ChangeType"] = ToString(changeType);
-      item["ResourceType"] = ToString(resourceType);
+      item["ChangeType"] = EnumerationToString(changeType);
+      item["ResourceType"] = EnumerationToString(resourceType);
       item["ID"] = publicId;
       item["Path"] = GetBasePath(resourceType, publicId);
       item["Date"] = date;
@@ -626,7 +667,7 @@
 
       Json::Value item = Json::objectValue;
       item["Seq"] = static_cast<int>(seq);
-      item["ResourceType"] = ToString(resourceType);
+      item["ResourceType"] = EnumerationToString(resourceType);
       item["ID"] = publicId;
       item["Path"] = GetBasePath(resourceType, publicId);
       item["RemoteModality"] = s.ColumnString(3);
@@ -902,4 +943,56 @@
       return 1;
     }
   }
+
+
+  void DatabaseWrapper::ClearTable(const std::string& tableName)
+  {
+    db_.Execute("DELETE FROM " + tableName);    
+  }
+
+
+  bool DatabaseWrapper::IsExistingResource(int64_t internalId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT * FROM Resources WHERE internalId=?");
+    s.BindInt(0, internalId);
+    return s.Step();
+  }
+
+
+  void  DatabaseWrapper::LookupTagValue(std::list<int64_t>& result,
+                                        DicomTag tag,
+                                        const std::string& value)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT id FROM MainDicomTags WHERE tagGroup=? AND tagElement=? and value=?");
+
+    s.BindInt(0, tag.GetGroup());
+    s.BindInt(1, tag.GetElement());
+    s.BindString(2, value);
+
+    result.clear();
+
+    while (s.Step())
+    {
+      result.push_back(s.ColumnInt64(0));
+    }
+  }
+
+
+  void  DatabaseWrapper::LookupTagValue(std::list<int64_t>& result,
+                                        const std::string& value)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT id FROM MainDicomTags WHERE value=?");
+
+    s.BindString(0, value);
+
+    result.clear();
+
+    while (s.Step())
+    {
+      result.push_back(s.ColumnInt64(0));
+    }
+  }
 }
--- a/OrthancServer/DatabaseWrapper.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DatabaseWrapper.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -108,10 +108,16 @@
                      MetadataType type,
                      const std::string& value);
 
+    void DeleteMetadata(int64_t id,
+                        MetadataType type);
+
     bool LookupMetadata(std::string& target,
                         int64_t id,
                         MetadataType type);
 
+    bool ListAvailableMetadata(std::list<MetadataType>& target,
+                               int64_t id);
+
     std::string GetMetadata(int64_t id,
                             MetadataType type,
                             const std::string& defaultValue = "");
@@ -123,6 +129,9 @@
     void AddAttachment(int64_t id,
                        const FileInfo& attachment);
 
+    void ListAvailableAttachments(std::list<FileContentType>& result,
+                                  int64_t id);
+
     bool LookupAttachment(FileInfo& attachment,
                           int64_t id,
                           FileContentType contentType);
@@ -212,5 +221,16 @@
     }
 
     uint64_t IncrementGlobalSequence(GlobalProperty property);
+
+    void ClearTable(const std::string& tableName);
+
+    bool IsExistingResource(int64_t internalId);
+
+    void LookupTagValue(std::list<int64_t>& result,
+                        DicomTag tag,
+                        const std::string& value);
+
+    void LookupTagValue(std::list<int64_t>& result,
+                        const std::string& value);
   };
 }
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomServer.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomServer.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -82,6 +82,8 @@
     boost::filesystem::path p = directory;
     p = p / filename;
 
+    LOG(WARNING) << "Loading the external DICOM dictionary " << p;
+
     if (!dictionary.loadDictionary(p.string().c_str()))
     {
       throw OrthancException(ErrorCode_InternalError);
@@ -111,7 +113,7 @@
     LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_PRIVATE);
 
 #elif defined(__linux)
-    std::string path = "/usr/share/dcmtk";
+    std::string path = DCMTK_DICTIONARY_DIR;
 
     const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE);
     if (env != NULL)
@@ -385,7 +387,11 @@
   void DicomServer::Stop()
   {
     continue_ = false;
-    pimpl_->thread_.join();
+
+    if (pimpl_->thread_.joinable())
+    {
+      pimpl_->thread_.join();
+    }
 
     bagOfDispatchers_.StopAll();
   }
--- a/OrthancServer/DicomProtocol/DicomServer.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomServer.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -294,7 +294,20 @@
       break;
 
     case FindRootModel_Instance:
-      DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE");
+      if (manufacturer_ == ModalityManufacturer_ClearCanvas)
+      {
+        // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
+        // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
+        // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
+        printf("CLEAR CANVAS\n");
+        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE");
+      }
+      else
+      {
+        printf("GENERIC\n");
+        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE");
+      }
+
       sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
 
       // Accession number
@@ -453,6 +466,7 @@
     distantAet_ = "ANY-SCP";
     distantPort_ = 104;
     distantHost_ = "127.0.0.1";
+    manufacturer_ = ModalityManufacturer_Generic;
 
     pimpl_->net_ = NULL;
     pimpl_->params_ = NULL;
@@ -476,6 +490,12 @@
     distantAet_ = aet;
   }
 
+  void DicomUserConnection::SetDistantManufacturer(ModalityManufacturer manufacturer)
+  {
+    Close();
+    manufacturer_ = manufacturer;
+  }
+
 
   void DicomUserConnection::SetDistantHost(const std::string& host)
   {
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -33,6 +33,7 @@
 #pragma once
 
 #include "DicomFindAnswers.h"
+#include "../ServerEnumerations.h"
 
 #include <stdint.h>
 #include <boost/shared_ptr.hpp>
@@ -59,6 +60,7 @@
     std::string distantAet_;
     std::string distantHost_;
     uint16_t distantPort_;
+    ModalityManufacturer manufacturer_;
 
     void CheckIsOpen() const;
 
@@ -106,6 +108,13 @@
       return distantPort_;
     }
 
+    void SetDistantManufacturer(ModalityManufacturer manufacturer);
+
+    ModalityManufacturer GetDistantManufacturer() const
+    {
+      return manufacturer_;
+    }
+
     void Open();
 
     void Close();
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/FromDcmtkBridge.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -29,6 +29,49 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: GDCM (Grassroots DICOM). A DICOM library
+  Module:  http://gdcm.sourceforge.net/Copyright.html
+
+Copyright (c) 2006-2011 Mathieu Malaterre
+Copyright (c) 1993-2005 CREATIS
+(CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any
+   contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to
+   endorse or promote products derived from this software without specific
+   prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
 #ifndef NOMINMAX
 #define NOMINMAX
 #endif
@@ -38,7 +81,7 @@
 #include "ToDcmtkBridge.h"
 #include "../Core/Toolbox.h"
 #include "../Core/OrthancException.h"
-#include "../Core/PngWriter.h"
+#include "../Core/FileFormats/PngWriter.h"
 #include "../Core/Uuid.h"
 #include "../Core/DicomFormat/DicomString.h"
 #include "../Core/DicomFormat/DicomNullValue.h"
@@ -1097,9 +1140,9 @@
       for (unsigned int x = 0; x < accessor.GetWidth(); x++, pixel++)
       {
         int32_t v = accessor.GetValue(x, y);
-        if (v < std::numeric_limits<T>::min())
+        if (v < static_cast<int32_t>(std::numeric_limits<T>::min()))
           *pixel = std::numeric_limits<T>::min();
-        else if (v > std::numeric_limits<T>::max())
+        else if (v > static_cast<int32_t>(std::numeric_limits<T>::max()))
           *pixel = std::numeric_limits<T>::max();
         else
           *pixel = static_cast<T>(v);
@@ -1240,6 +1283,11 @@
       accessor.reset(new DicomIntegerPixelAccessor(m, pixData, privateContent.size()));
       accessor->SetCurrentFrame(frame);
     }
+    
+    if (accessor.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
 
     PixelFormat format;
     bool supported = false;
@@ -1263,6 +1311,11 @@
           format = PixelFormat_Grayscale16;
           break;
 
+        case ImageExtractionMode_Int16:
+          supported = true;
+          format = PixelFormat_SignedGrayscale16;
+          break;
+
         default:
           supported = false;
           break;
@@ -1314,6 +1367,10 @@
           ExtractPngImageTruncate<uint16_t>(result, *accessor, format);
           break;
 
+        case ImageExtractionMode_Int16:
+          ExtractPngImageTruncate<int16_t>(result, *accessor, format);
+          break;
+
         default:
           throw OrthancException(ErrorCode_NotImplemented);
       }
--- a/OrthancServer/FromDcmtkBridge.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/FromDcmtkBridge.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -43,13 +43,6 @@
 
 namespace Orthanc
 {
-  enum ImageExtractionMode
-  {
-    ImageExtractionMode_Preview,
-    ImageExtractionMode_UInt8,
-    ImageExtractionMode_UInt16
-  };
-
   enum DicomRootLevel
   {
     DicomRootLevel_Patient,
--- a/OrthancServer/IServerIndexListener.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/IServerIndexListener.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/CommandDispatcher.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -41,9 +41,149 @@
 #include <boost/lexical_cast.hpp>
 #include <glog/logging.h>
 
+#define ORTHANC_PROMISCUOUS 1
+
 static OFBool    opt_rejectWithoutImplementationUID = OFFalse;
 
 
+
+#if ORTHANC_PROMISCUOUS == 1
+static
+DUL_PRESENTATIONCONTEXT *
+findPresentationContextID(LST_HEAD * head,
+                          T_ASC_PresentationContextID presentationContextID)
+{
+  DUL_PRESENTATIONCONTEXT *pc;
+  LST_HEAD **l;
+  OFBool found = OFFalse;
+
+  if (head == NULL)
+    return NULL;
+
+  l = &head;
+  if (*l == NULL)
+    return NULL;
+
+  pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l));
+  (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc));
+
+  while (pc && !found) {
+    if (pc->presentationContextID == presentationContextID) {
+      found = OFTrue;
+    } else {
+      pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l));
+    }
+  }
+  return pc;
+}
+
+
+/** accept all presenstation contexts for unknown SOP classes,
+ *  i.e. UIDs appearing in the list of abstract syntaxes
+ *  where no corresponding name is defined in the UID dictionary.
+ *  @param params pointer to association parameters structure
+ *  @param transferSyntax transfer syntax to accept
+ *  @param acceptedRole SCU/SCP role to accept
+ */
+static OFCondition acceptUnknownContextsWithTransferSyntax(
+  T_ASC_Parameters * params,
+  const char* transferSyntax,
+  T_ASC_SC_ROLE acceptedRole)
+{
+  OFCondition cond = EC_Normal;
+  int n, i, k;
+  DUL_PRESENTATIONCONTEXT *dpc;
+  T_ASC_PresentationContext pc;
+  OFBool accepted = OFFalse;
+  OFBool abstractOK = OFFalse;
+
+  n = ASC_countPresentationContexts(params);
+  for (i = 0; i < n; i++)
+  {
+    cond = ASC_getPresentationContext(params, i, &pc);
+    if (cond.bad()) return cond;
+    abstractOK = OFFalse;
+    accepted = OFFalse;
+
+    if (dcmFindNameOfUID(pc.abstractSyntax) == NULL)
+    {
+      abstractOK = OFTrue;
+
+      /* check the transfer syntax */
+      for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++)
+      {
+        if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0)
+        {
+          accepted = OFTrue;
+        }
+      }
+    }
+
+    if (accepted)
+    {
+      cond = ASC_acceptPresentationContext(
+        params, pc.presentationContextID,
+        transferSyntax, acceptedRole);
+      if (cond.bad()) return cond;
+    } else {
+      T_ASC_P_ResultReason reason;
+
+      /* do not refuse if already accepted */
+      dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext,
+                                      pc.presentationContextID);
+      if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE)))
+      {
+
+        if (abstractOK) {
+          reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;
+        } else {
+          reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED;
+        }
+        /*
+         * If previously this presentation context was refused
+         * because of bad transfer syntax let it stay that way.
+         */
+        if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED))
+          reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;
+
+        cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason);
+        if (cond.bad()) return cond;
+      }
+    }
+  }
+  return EC_Normal;
+}
+
+
+/** accept all presenstation contexts for unknown SOP classes,
+ *  i.e. UIDs appearing in the list of abstract syntaxes
+ *  where no corresponding name is defined in the UID dictionary.
+ *  This method is passed a list of "preferred" transfer syntaxes.
+ *  @param params pointer to association parameters structure
+ *  @param transferSyntax transfer syntax to accept
+ *  @param acceptedRole SCU/SCP role to accept
+ */
+static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes(
+  T_ASC_Parameters * params,
+  const char* transferSyntaxes[], int transferSyntaxCount,
+  T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT)
+{
+  OFCondition cond = EC_Normal;
+  /*
+  ** Accept in the order "least wanted" to "most wanted" transfer
+  ** syntax.  Accepting a transfer syntax will override previously
+  ** accepted transfer syntaxes.
+  */
+  for (int i = transferSyntaxCount - 1; i >= 0; i--)
+  {
+    cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole);
+    if (cond.bad()) return cond;
+  }
+  return cond;
+}
+#endif
+
+
 namespace Orthanc
 {
   namespace Internals
@@ -150,6 +290,18 @@
         return NULL;
       }
 
+#if ORTHANC_PROMISCUOUS == 1
+      /* accept everything not known not to be a storage SOP class */
+      cond = acceptUnknownContextsWithPreferredTransferSyntaxes(
+        assoc->params, transferSyntaxes, numTransferSyntaxes);
+      if (cond.bad())
+      {
+        LOG(INFO) << cond.text();
+        AssociationCleanup(assoc);
+        return NULL;
+      }
+#endif
+
       /* set our app title */
       ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str());
 
--- a/OrthancServer/Internals/CommandDispatcher.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/Internals/CommandDispatcher.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/FindScp.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/Internals/FindScp.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/FindScp.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/Internals/FindScp.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/MoveScp.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/Internals/MoveScp.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/MoveScp.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/Internals/MoveScp.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/StoreScp.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -117,7 +117,7 @@
             FromDcmtkBridge::Convert(summary, **imageDataSet);
             FromDcmtkBridge::ToJson(dicomJson, **imageDataSet);       
 
-            if (FromDcmtkBridge::SaveToMemoryBuffer(buffer, *imageDataSet) < 0)
+            if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, *imageDataSet))
             {
               LOG(ERROR) << "cannot write DICOM file to memory";
               rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
--- a/OrthancServer/Internals/StoreScp.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/Internals/StoreScp.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/OrthancInitialization.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -32,8 +32,10 @@
 
 #include "OrthancInitialization.h"
 
+#include "../Core/HttpClient.h"
 #include "../Core/OrthancException.h"
 #include "../Core/Toolbox.h"
+#include "ServerEnumerations.h"
 
 #include <boost/lexical_cast.hpp>
 #include <boost/filesystem.hpp>
@@ -47,6 +49,7 @@
 
   static boost::mutex globalMutex_;
   static std::auto_ptr<Json::Value> configuration_;
+  static boost::filesystem::path defaultDirectory_;
 
 
   static void ReadGlobalConfiguration(const char* configurationFile)
@@ -58,6 +61,7 @@
     if (configurationFile)
     {
       Toolbox::ReadFile(content, configurationFile);
+      defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path();
       LOG(INFO) << "Using the configuration from: " << configurationFile;
     }
     else
@@ -116,11 +120,51 @@
   }
 
 
+  static void RegisterUserMetadata()
+  {
+    if (configuration_->isMember("UserMetadata"))
+    {
+      const Json::Value& parameter = (*configuration_) ["UserMetadata"];
+
+      Json::Value::Members members = parameter.getMemberNames();
+      for (size_t i = 0; i < members.size(); i++)
+      {
+        std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString();
+        LOG(INFO) << "Registering user-defined metadata: " << info;
+
+        if (!parameter[members[i]].asBool())
+        {
+          LOG(ERROR) << "Not a number in this user-defined metadata: " << info;
+          throw OrthancException(ErrorCode_BadParameterType);
+        }
+
+        int metadata = parameter[members[i]].asInt();
+
+        try
+        {
+          RegisterUserMetadata(metadata, members[i]);
+        }
+        catch (OrthancException e)
+        {
+          LOG(ERROR) << "Cannot register this user-defined metadata: " << info;
+          throw e;
+        }
+      }
+    }
+  }
+
+
   void OrthancInitialize(const char* configurationFile)
   {
     boost::mutex::scoped_lock lock(globalMutex_);
+
+    InitializeServerEnumerations();
+    defaultDirectory_ = boost::filesystem::current_path();
     ReadGlobalConfiguration(configurationFile);
-    curl_global_init(CURL_GLOBAL_ALL);
+
+    HttpClient::GlobalInitialize();
+
+    RegisterUserMetadata();
   }
 
 
@@ -128,7 +172,7 @@
   void OrthancFinalize()
   {
     boost::mutex::scoped_lock lock(globalMutex_);
-    curl_global_cleanup();
+    HttpClient::GlobalFinalize();
     configuration_.reset(NULL);
   }
 
@@ -186,7 +230,8 @@
   void GetDicomModality(const std::string& name,
                         std::string& aet,
                         std::string& address,
-                        int& port)
+                        int& port,
+                        ModalityManufacturer& manufacturer)
   {
     boost::mutex::scoped_lock lock(globalMutex_);
 
@@ -197,7 +242,8 @@
 
     const Json::Value& modalities = (*configuration_) ["DicomModalities"];
     if (modalities.type() != Json::objectValue ||
-        !modalities.isMember(name))
+        !modalities.isMember(name) ||
+        (modalities[name].size() != 3 && modalities[name].size() != 4))
     {
       throw OrthancException("");
     }
@@ -207,6 +253,15 @@
       aet = modalities[name].get(0u, "").asString();
       address = modalities[name].get(1u, "").asString();
       port = modalities[name].get(2u, "").asInt();
+
+      if (modalities[name].size() == 4)
+      {
+        manufacturer = StringToModalityManufacturer(modalities[name].get(3u, "").asString());
+      }
+      else
+      {
+        manufacturer = ModalityManufacturer_Generic;
+      }
     }
     catch (...)
     {
@@ -216,36 +271,111 @@
 
 
 
-  void GetListOfDicomModalities(std::set<std::string>& target)
+  void GetOrthancPeer(const std::string& name,
+                      std::string& url,
+                      std::string& username,
+                      std::string& password)
+  {
+    boost::mutex::scoped_lock lock(globalMutex_);
+
+    if (!configuration_->isMember("OrthancPeers"))
+    {
+      throw OrthancException("");
+    }
+
+    const Json::Value& modalities = (*configuration_) ["OrthancPeers"];
+    if (modalities.type() != Json::objectValue ||
+        !modalities.isMember(name))
+    {
+      throw OrthancException("");
+    }
+
+    try
+    {
+      url = modalities[name].get(0u, "").asString();
+
+      if (modalities[name].size() == 1)
+      {
+        username = "";
+        password = "";
+      }
+      else if (modalities[name].size() == 3)
+      {
+        username = modalities[name].get(1u, "").asString();
+        password = modalities[name].get(2u, "").asString();
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+    catch (...)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    if (url.size() != 0 && url[url.size() - 1] != '/')
+    {
+      url += '/';
+    }
+  }
+
+
+  static bool ReadKeys(std::set<std::string>& target,
+                       const char* parameter,
+                       bool onlyAlphanumeric)
   {
     boost::mutex::scoped_lock lock(globalMutex_);
 
     target.clear();
   
-    if (!configuration_->isMember("DicomModalities"))
+    if (!configuration_->isMember(parameter))
     {
-      return;
+      return true;
     }
 
-    const Json::Value& modalities = (*configuration_) ["DicomModalities"];
+    const Json::Value& modalities = (*configuration_) [parameter];
     if (modalities.type() != Json::objectValue)
     {
-      throw OrthancException("Badly formatted list of DICOM modalities");
+      throw OrthancException(ErrorCode_BadFileFormat);
     }
 
     Json::Value::Members members = modalities.getMemberNames();
     for (size_t i = 0; i < members.size(); i++)
     {
-      for (size_t j = 0; j < members[i].size(); j++)
+      if (onlyAlphanumeric)
       {
-        if (!isalnum(members[i][j]) && members[i][j] != '-')
+        for (size_t j = 0; j < members[i].size(); j++)
         {
-          throw OrthancException("Only alphanumeric and dash characters are allowed in the names of the modalities");
+          if (!isalnum(members[i][j]) && members[i][j] != '-')
+          {
+            return false;
+          }
         }
       }
 
       target.insert(members[i]);
     }
+
+    return true;
+  }
+
+
+  void GetListOfDicomModalities(std::set<std::string>& target)
+  {
+    if (!ReadKeys(target, "DicomModalities", true))
+    {
+      throw OrthancException("Only alphanumeric and dash characters are allowed in the names of the modalities");
+    }
+  }
+
+
+  void GetListOfOrthancPeers(std::set<std::string>& target)
+  {
+    if (!ReadKeys(target, "OrthancPeers", true))
+    {
+      throw OrthancException("Only alphanumeric and dash characters are allowed in the names of Orthanc peers");
+    }
   }
 
 
@@ -275,4 +405,63 @@
       httpServer.RegisterUser(username.c_str(), password.c_str());
     }
   }
+
+
+  std::string InterpretRelativePath(const std::string& baseDirectory,
+                                    const std::string& relativePath)
+  {
+    boost::filesystem::path base(baseDirectory);
+    boost::filesystem::path relative(relativePath);
+
+    /**
+       The following lines should be equivalent to this one: 
+
+       return (base / relative).string();
+
+       However, for some unknown reason, some versions of Boost do not
+       make the proper path resolution when "baseDirectory" is an
+       absolute path. So, a hack is used below.
+     **/
+
+    if (relative.is_absolute())
+    {
+      return relative.string();
+    }
+    else
+    {
+      return (base / relative).string();
+    }
+  }
+
+  std::string InterpretStringParameterAsPath(const std::string& parameter)
+  {
+    boost::mutex::scoped_lock lock(globalMutex_);
+    return InterpretRelativePath(defaultDirectory_.string(), parameter);
+  }
+
+
+  void GetGlobalListOfStringsParameter(std::list<std::string>& target,
+                                       const std::string& key)
+  {
+    boost::mutex::scoped_lock lock(globalMutex_);
+
+    target.clear();
+  
+    if (!configuration_->isMember(key))
+    {
+      return;
+    }
+
+    const Json::Value& lst = (*configuration_) [key];
+
+    if (lst.type() != Json::arrayValue)
+    {
+      throw OrthancException("Badly formatted list of strings");
+    }
+
+    for (Json::Value::ArrayIndex i = 0; i < lst.size(); i++)
+    {
+      target.push_back(lst[i].asString());
+    }    
+  }
 }
--- a/OrthancServer/OrthancInitialization.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/OrthancInitialization.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -37,6 +37,7 @@
 #include <json/json.h>
 #include <stdint.h>
 #include "../Core/HttpServer/MongooseServer.h"
+#include "ServerEnumerations.h"
 
 namespace Orthanc
 {
@@ -56,9 +57,25 @@
   void GetDicomModality(const std::string& name,
                         std::string& aet,
                         std::string& address,
-                        int& port);
+                        int& port,
+                        ModalityManufacturer& manufacturer);
+
+  void GetOrthancPeer(const std::string& name,
+                      std::string& url,
+                      std::string& username,
+                      std::string& password);
 
   void GetListOfDicomModalities(std::set<std::string>& target);
 
+  void GetListOfOrthancPeers(std::set<std::string>& target);
+
   void SetupRegisteredUsers(MongooseServer& httpServer);
+
+  std::string InterpretRelativePath(const std::string& baseDirectory,
+                                    const std::string& relativePath);
+
+  std::string InterpretStringParameterAsPath(const std::string& parameter);
+
+  void GetGlobalListOfStringsParameter(std::list<std::string>& target,
+                                       const std::string& key);
 }
--- a/OrthancServer/OrthancRestApi.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/OrthancRestApi.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -32,9 +32,10 @@
 
 #include "OrthancRestApi.h"
 
+#include "../Core/Compression/HierarchicalZipWriter.h"
+#include "../Core/HttpClient.h"
 #include "../Core/HttpServer/FilesystemHttpSender.h"
 #include "../Core/Uuid.h"
-#include "../Core/Compression/HierarchicalZipWriter.h"
 #include "DicomProtocol/DicomUserConnection.h"
 #include "FromDcmtkBridge.h"
 #include "OrthancInitialization.h"
@@ -52,9 +53,13 @@
   ServerContext& context = contextApi.GetContext()
 
 #define RETRIEVE_MODALITIES(call)                                       \
-  const OrthancRestApi::Modalities& modalities =                        \
+  const OrthancRestApi::SetOfStrings& modalities =                      \
     dynamic_cast<OrthancRestApi&>(call.GetContext()).GetModalities();
 
+#define RETRIEVE_PEERS(call)                                            \
+  const OrthancRestApi::SetOfStrings& peers =                           \
+    dynamic_cast<OrthancRestApi&>(call.GetContext()).GetPeers();
+
 
 
 namespace Orthanc
@@ -71,11 +76,13 @@
   {
     std::string aet, address;
     int port;
-    GetDicomModality(name, aet, address, port);
+    ModalityManufacturer manufacturer;
+    GetDicomModality(name, aet, address, port, manufacturer);
     connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC"));
     connection.SetDistantApplicationEntityTitle(aet);
     connection.SetDistantHost(address);
     connection.SetDistantPort(port);
+    connection.SetDistantManufacturer(manufacturer);
     connection.Open();
   }
 
@@ -174,6 +181,34 @@
     call.GetOutput().AnswerJson(result);
   }
 
+  static void DicomFindInstance(RestApi::PostCall& call)
+  {
+    DicomMap m;
+    DicomMap::SetupFindInstanceTemplate(m);
+    if (!MergeQueryAndTemplate(m, call.GetPostBody()))
+    {
+      return;
+    }
+
+    if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 &&
+         m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) ||
+        m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 ||
+        m.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2)
+    {
+      return;
+    }        
+         
+    DicomUserConnection connection;
+    ConnectToModality(connection, call.GetUriComponent("id", ""));
+  
+    DicomFindAnswers answers;
+    connection.FindInstance(answers, m);
+
+    Json::Value result;
+    answers.ToJson(result);
+    call.GetOutput().AnswerJson(result);
+  }
+
   static void DicomFind(RestApi::PostCall& call)
   {
     DicomMap m;
@@ -244,50 +279,90 @@
   }
 
 
+  static bool GetInstancesToExport(std::list<std::string>& instances,
+                                   const std::string& remote,
+                                   RestApi::PostCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+
+    std::string stripped = Toolbox::StripSpaces(call.GetPostBody());
+
+    Json::Value request;
+    if (Toolbox::IsSHA1(stripped))
+    {
+      // This is for compatibility with Orthanc <= 0.5.1.
+      request = stripped;
+    }
+    else if (!call.ParseJsonRequest(request))
+    {
+      // Bad JSON request
+      return false;
+    }
+
+    if (request.isString())
+    {
+      context.GetIndex().LogExportedResource(request.asString(), remote);
+      context.GetIndex().GetChildInstances(instances, request.asString());
+    }
+    else if (request.isArray())
+    {
+      for (Json::Value::ArrayIndex i = 0; i < request.size(); i++)
+      {
+        if (!request[i].isString())
+        {
+          return false;
+        }
+
+        std::string stripped = Toolbox::StripSpaces(request[i].asString());
+        if (!Toolbox::IsSHA1(stripped))
+        {
+          return false;
+        }
+
+        context.GetIndex().LogExportedResource(stripped, remote);
+       
+        std::list<std::string> tmp;
+        context.GetIndex().GetChildInstances(tmp, stripped);
+        instances.merge(tmp);
+        assert(tmp.size() == 0);
+      }
+    }
+    else
+    {
+      // Neither a string, nor a list of strings. Bad request.
+      return false;
+    }
+
+    return true;
+  }
+
+
   static void DicomStore(RestApi::PostCall& call)
   {
     RETRIEVE_CONTEXT(call);
 
     std::string remote = call.GetUriComponent("id", "");
+
+    std::list<std::string> instances;
+    if (!GetInstancesToExport(instances, remote, call))
+    {
+      return;
+    }
+
     DicomUserConnection connection;
     ConnectToModality(connection, remote);
 
-    const std::string& resourceId = call.GetPostBody();
-
-    Json::Value found;
-    if (context.GetIndex().LookupResource(found, resourceId, ResourceType_Series))
+    for (std::list<std::string>::const_iterator 
+           it = instances.begin(); it != instances.end(); it++)
     {
-      // 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);
+      LOG(INFO) << "Sending resource " << *it << " to modality \"" << remote << "\"";
 
       std::string dicom;
-      context.ReadFile(dicom, resourceId, FileContentType_Dicom);
+      context.ReadFile(dicom, *it, 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");
-    }
+
+    call.GetOutput().AnswerBuffer("{}", "application/json");
   }
 
 
@@ -338,6 +413,23 @@
     }
   }
 
+  static void ExecuteScript(RestApi::PostCall& call)
+  {
+    std::string result;
+    RETRIEVE_CONTEXT(call);
+    context.GetLuaContext().Execute(result, call.GetPostBody());
+    call.GetOutput().AnswerBuffer(result, "text/plain");
+  }
+
+  static void GetNowIsoString(RestApi::GetCall& call)
+  {
+    call.GetOutput().AnswerBuffer(Toolbox::GetNowIsoString(), "text/plain");
+  }
+
+
+
+
+
 
   // List all the patients, studies, series or instances ----------------------
  
@@ -615,6 +707,14 @@
   }
 
 
+  static void DeleteChanges(RestApi::DeleteCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+    context.GetIndex().DeleteChanges();
+    call.GetOutput().AnswerBuffer("", "text/plain");
+  }
+
+
   static void GetExports(RestApi::GetCall& call)
   {
     RETRIEVE_CONTEXT(call);
@@ -632,6 +732,14 @@
     }
   }
 
+
+  static void DeleteExports(RestApi::DeleteCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+    context.GetIndex().DeleteExportedResources();
+    call.GetOutput().AnswerBuffer("", "text/plain");
+  }
+
   
   // Get information about a single patient -----------------------------------
  
@@ -678,6 +786,21 @@
   }
 
 
+  static void ExportInstanceFile(RestApi::PostCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+
+    std::string publicId = call.GetUriComponent("id", "");
+
+    std::string dicom;
+    context.ReadFile(dicom, publicId, FileContentType_Dicom);
+
+    Toolbox::WriteFile(dicom, call.GetPostBody());
+
+    call.GetOutput().AnswerBuffer("{}", "application/json");
+  }
+
+
   template <bool simplify>
   static void GetInstanceTags(RestApi::GetCall& call)
   {
@@ -801,7 +924,7 @@
       result["Path"] = GetBasePath(ResourceType_Instance, publicId);
     }
 
-    result["Status"] = ToString(status);
+    result["Status"] = EnumerationToString(status);
     call.GetOutput().AnswerJson(result);
   }
 
@@ -809,7 +932,7 @@
 
   // DICOM bridge -------------------------------------------------------------
 
-  static bool IsExistingModality(const OrthancRestApi::Modalities& modalities,
+  static bool IsExistingModality(const OrthancRestApi::SetOfStrings& modalities,
                                  const std::string& id)
   {
     return modalities.find(id) != modalities.end();
@@ -820,7 +943,7 @@
     RETRIEVE_MODALITIES(call);
 
     Json::Value result = Json::arrayValue;
-    for (OrthancRestApi::Modalities::const_iterator 
+    for (OrthancRestApi::SetOfStrings::const_iterator 
            it = modalities.begin(); it != modalities.end(); it++)
     {
       result.append(*it);
@@ -841,6 +964,7 @@
       result.append("find-patient");
       result.append("find-study");
       result.append("find-series");
+      result.append("find-instance");
       result.append("find");
       result.append("store");
       call.GetOutput().AnswerJson(result);
@@ -909,8 +1033,6 @@
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    target.clear();
-
     for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++)
     {
       std::string name = removals[i].asString();
@@ -930,8 +1052,6 @@
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    target.clear();
-
     Json::Value::Members members = replacements.getMemberNames();
     for (size_t i = 0; i < members.size(); i++)
     {
@@ -977,7 +1097,7 @@
     removals.insert(DicomTag(0x0008, 0x1155));  // Referenced SOP Instance UID 
     removals.insert(DicomTag(0x0008, 0x2111));  // Derivation Description 
     removals.insert(DicomTag(0x0010, 0x0010));  // Patient's Name 
-    removals.insert(DicomTag(0x0010, 0x0020));  // Patient ID
+    //removals.insert(DicomTag(0x0010, 0x0020));  // Patient ID => cf. below (*)
     removals.insert(DicomTag(0x0010, 0x0030));  // Patient's Birth Date 
     removals.insert(DicomTag(0x0010, 0x0032));  // Patient's Birth Time 
     removals.insert(DicomTag(0x0010, 0x0040));  // Patient's Sex 
@@ -993,8 +1113,8 @@
     removals.insert(DicomTag(0x0010, 0x4000));  // Patient Comments 
     removals.insert(DicomTag(0x0018, 0x1000));  // Device Serial Number 
     removals.insert(DicomTag(0x0018, 0x1030));  // Protocol Name 
-    //removals.insert(DicomTag(0x0020, 0x000d));  // Study Instance UID => generated below
-    //removals.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => generated below
+    //removals.insert(DicomTag(0x0020, 0x000d));  // Study Instance UID => cf. below (*)
+    //removals.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => cf. below (*)
     removals.insert(DicomTag(0x0020, 0x0010));  // Study ID 
     removals.insert(DicomTag(0x0020, 0x0052));  // Frame of Reference UID 
     removals.insert(DicomTag(0x0020, 0x0200));  // Synchronization Frame of Reference UID 
@@ -1006,29 +1126,25 @@
     removals.insert(DicomTag(0x3006, 0x0024));  // Referenced Frame of Reference UID 
     removals.insert(DicomTag(0x3006, 0x00c2));  // Related Frame of Reference UID 
 
+    /**
+     *   (*) Patient ID, Study Instance UID and Series Instance UID
+     * are modified by "AnonymizeInstance()" if anonymizing a single
+     * instance, or by "RetrieveMappedUid()" if anonymizing a
+     * patient/study/series.
+     **/
+
+
     // Some more removals (from the experience of DICOM files at the CHU of Liege)
     removals.insert(DicomTag(0x0010, 0x1040));  // Patient's Address
     removals.insert(DicomTag(0x0032, 0x1032));  // Requesting Physician
+    removals.insert(DicomTag(0x0010, 0x2154));  // PatientTelephoneNumbers
+    removals.insert(DicomTag(0x0010, 0x2000));  // Medical Alerts
 
     // Set the DeidentificationMethod tag
     replacements.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"));
 
-    // Set the PatientIdentityRemoved
+    // Set the PatientIdentityRemoved tag
     replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES"));
-
-    // Generate random study UID if not specified
-    if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end())
-    {
-      replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, 
-                                         FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study)));
-    }
-
-    // Generate random series UID if not specified
-    if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end())
-    {
-      replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, 
-                                         FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series)));
-    }
   }
 
 
@@ -1131,13 +1247,6 @@
         replacements.insert(std::make_pair(DicomTag(0x0010, 0x0010), GeneratePatientName(context)));
       }
 
-      // Generate random Patient's ID if none is specified
-      if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end())
-      {
-        replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, 
-                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient)));
-      }
-
       return true;
     }
     else
@@ -1170,14 +1279,23 @@
                                 UidMap& uidMap)
   {
     std::auto_ptr<DicomTag> tag;
-    if (level == DicomRootLevel_Series)
+
+    switch (level)
     {
-      tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID));
-    }
-    else
-    {
-      assert(level == DicomRootLevel_Study);
-      tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID));
+      case DicomRootLevel_Series:
+        tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID));
+        break;
+
+      case DicomRootLevel_Study:
+        tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID));
+        break;
+
+      case DicomRootLevel_Patient:
+        tag.reset(new DicomTag(DICOM_TAG_PATIENT_ID));
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
     }
 
     std::string original;
@@ -1207,7 +1325,8 @@
   }
 
 
-  static void AnonymizeOrModifyResource(Removals& removals,
+  static void AnonymizeOrModifyResource(bool isAnonymization,
+                                        Removals& removals,
                                         Replacements& replacements,
                                         bool removePrivateTags,
                                         MetadataType metadataType,
@@ -1244,15 +1363,20 @@
       LOG(INFO) << "Modifying instance " << *it;
       ParsedDicomFile& original = context.GetDicomFile(*it);
 
-      bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap);
+      DicomInstanceHasher originalHasher = original.GetHasher();
 
-      bool isNewStudy = false;
-      if (resourceType == ResourceType_Study ||
-          resourceType == ResourceType_Patient)
+      if (isFirst && !isAnonymization)
       {
-        isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap);
+        // If modifying a study or a series, keep the original patient ID.
+        std::string patientId = originalHasher.GetPatientId();
+        uidMap[std::make_pair(DicomRootLevel_Patient, patientId)] = patientId;
       }
 
+      bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap);
+      bool isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap);
+      bool isNewPatient = RetrieveMappedUid(original, DicomRootLevel_Patient, replacements, uidMap);
+
+
       /**
        * Compute the resulting DICOM instance and store it into the Orthanc store.
        **/
@@ -1269,11 +1393,10 @@
 
 
       /**
-       * Record metadata information (AnonimizedFrom/ModifiedFrom).
+       * Record metadata information (AnonymizedFrom/ModifiedFrom).
        **/
 
       DicomInstanceHasher modifiedHasher = modified->GetHasher();
-      DicomInstanceHasher originalHasher = original.GetHasher();
 
       if (isNewSeries)
       {
@@ -1287,6 +1410,12 @@
                                        metadataType, originalHasher.HashStudy());
       }
 
+      if (isNewPatient)
+      {
+        context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), 
+                                       metadataType, originalHasher.HashPatient());
+      }
+
       assert(*it == originalHasher.HashInstance());
       assert(modifiedInstance == modifiedHasher.HashInstance());
       context.GetIndex().SetMetadata(modifiedInstance, metadataType, *it);
@@ -1318,7 +1447,7 @@
             throw OrthancException(ErrorCode_InternalError);
         }
 
-        result["Type"] = ToString(resourceType);
+        result["Type"] = EnumerationToString(resourceType);
         result["ID"] = newId;
         result["Path"] = GetBasePath(resourceType, newId);
         result["PatientID"] = modifiedHasher.HashPatient();
@@ -1352,6 +1481,27 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
+      // Generate random patient ID if not specified
+      if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end())
+      {
+        replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, 
+                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient)));
+      }
+
+      // Generate random study UID if not specified
+      if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end())
+      {
+        replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, 
+                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study)));
+      }
+
+      // Generate random series UID if not specified
+      if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end())
+      {
+        replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, 
+                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series)));
+      }
+
       AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call);
     }
   }
@@ -1365,7 +1515,7 @@
 
     if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, 
                                 MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, 
                                 ResourceType_Series, call);
     }
@@ -1380,7 +1530,7 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, 
                                 MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, 
                                 ResourceType_Series, call);
     }
@@ -1395,7 +1545,7 @@
 
     if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, 
                                 MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, 
                                 ResourceType_Study, call);
     }
@@ -1410,14 +1560,14 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, 
                                 MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, 
                                 ResourceType_Study, call);
     }
   }
 
 
-  static void ModifyPatientInplace(RestApi::PostCall& call)
+  /*static void ModifyPatientInplace(RestApi::PostCall& call)
   {
     Removals removals;
     Replacements replacements;
@@ -1425,11 +1575,11 @@
 
     if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, 
                                 MetadataType_ModifiedFrom, ChangeType_ModifiedPatient, 
                                 ResourceType_Patient, call);
     }
-  }
+    }*/
 
 
   static void AnonymizePatientInplace(RestApi::PostCall& call)
@@ -1440,13 +1590,182 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, 
                                 MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient, 
                                 ResourceType_Patient, call);
     }
   }
 
 
+  // Handling of metadata -----------------------------------------------------
+
+  static void ListMetadata(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+    
+    std::string publicId = call.GetUriComponent("id", "");
+    std::list<MetadataType> metadata;
+    if (context.GetIndex().ListAvailableMetadata(metadata, publicId))
+    {
+      Json::Value result = Json::arrayValue;
+
+      for (std::list<MetadataType>::const_iterator 
+             it = metadata.begin(); it != metadata.end(); it++)
+      {
+        result.append(EnumerationToString(*it));
+      }
+
+      call.GetOutput().AnswerJson(result);
+    }
+  }
+
+
+  static void GetMetadata(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+    
+    std::string publicId = call.GetUriComponent("id", "");
+    std::string name = call.GetUriComponent("name", "");
+    MetadataType metadata = StringToMetadata(name);
+
+    std::string value;
+    if (context.GetIndex().LookupMetadata(value, publicId, metadata))
+    {
+      call.GetOutput().AnswerBuffer(value, "text/plain");
+    }
+  }
+
+
+  static void DeleteMetadata(RestApi::DeleteCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+    
+    std::string publicId = call.GetUriComponent("id", "");
+    std::string name = call.GetUriComponent("name", "");
+    MetadataType metadata = StringToMetadata(name);
+
+    if (metadata >= MetadataType_StartUser &&
+        metadata <= MetadataType_EndUser)
+    {
+      // It is forbidden to modify internal metadata
+      context.GetIndex().DeleteMetadata(publicId, metadata);
+      call.GetOutput().AnswerBuffer("", "text/plain");
+    }
+  }
+
+
+  static void SetMetadata(RestApi::PutCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+
+    std::string publicId = call.GetUriComponent("id", "");
+    std::string name = call.GetUriComponent("name", "");
+    MetadataType metadata = StringToMetadata(name);
+    std::string value = call.GetPutBody();
+
+    if (metadata >= MetadataType_StartUser &&
+        metadata <= MetadataType_EndUser)
+    {
+      // It is forbidden to modify internal metadata
+      context.GetIndex().SetMetadata(publicId, metadata, value);
+      call.GetOutput().AnswerBuffer("", "text/plain");
+    }
+  }
+
+
+  static void GetResourceStatistics(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+    std::string publicId = call.GetUriComponent("id", "");
+    Json::Value result;
+    context.GetIndex().GetStatistics(result, publicId);
+    call.GetOutput().AnswerJson(result);
+  }
+
+
+
+  // Orthanc Peers ------------------------------------------------------------
+
+  static bool IsExistingPeer(const OrthancRestApi::SetOfStrings& peers,
+                             const std::string& id)
+  {
+    return peers.find(id) != peers.end();
+  }
+
+  static void ListPeers(RestApi::GetCall& call)
+  {
+    RETRIEVE_PEERS(call);
+
+    Json::Value result = Json::arrayValue;
+    for (OrthancRestApi::SetOfStrings::const_iterator 
+           it = peers.begin(); it != peers.end(); it++)
+    {
+      result.append(*it);
+    }
+
+    call.GetOutput().AnswerJson(result);
+  }
+
+  static void ListPeerOperations(RestApi::GetCall& call)
+  {
+    RETRIEVE_PEERS(call);
+
+    std::string id = call.GetUriComponent("id", "");
+    if (IsExistingPeer(peers, id))
+    {
+      Json::Value result = Json::arrayValue;
+      result.append("store");
+      call.GetOutput().AnswerJson(result);
+    }
+  }
+
+  static void PeerStore(RestApi::PostCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+
+    std::string remote = call.GetUriComponent("id", "");
+
+    std::list<std::string> instances;
+    if (!GetInstancesToExport(instances, remote, call))
+    {
+      return;
+    }
+
+    std::string url, username, password;
+    GetOrthancPeer(remote, url, username, password);
+
+    // Configure the HTTP client
+    HttpClient client;
+    if (username.size() != 0 && password.size() != 0)
+    {
+      client.SetCredentials(username.c_str(), password.c_str());
+    }
+
+    client.SetUrl(url + "instances");
+    client.SetMethod(HttpMethod_Post);
+
+    // Loop over the instances that are to be sent
+    for (std::list<std::string>::const_iterator 
+           it = instances.begin(); it != instances.end(); it++)
+    {
+      LOG(INFO) << "Sending resource " << *it << " to peer \"" << remote << "\"";
+
+      context.ReadFile(client.AccessPostData(), *it, FileContentType_Dicom);
+
+      std::string answer;
+      if (!client.Apply(answer))
+      {
+        LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << remote << "\"";
+        return;
+      }
+    }
+
+    call.GetOutput().AnswerBuffer("{}", "application/json");
+  }
+
+
+
+
 
   // Registration of the various REST handlers --------------------------------
 
@@ -1454,12 +1773,15 @@
     context_(context)
   {
     GetListOfDicomModalities(modalities_);
+    GetListOfOrthancPeers(peers_);
 
     Register("/", ServeRoot);
     Register("/system", GetSystemInformation);
     Register("/statistics", GetStatistics);
     Register("/changes", GetChanges);
+    Register("/changes", DeleteChanges);
     Register("/exports", GetExports);
+    Register("/exports", DeleteExports);
 
     Register("/instances", UploadDicomFile);
     Register("/instances", ListResources<ResourceType_Instance>);
@@ -1480,9 +1802,32 @@
     Register("/studies/{id}/archive", GetArchive<ResourceType_Study>);
     Register("/series/{id}/archive", GetArchive<ResourceType_Series>);
 
+    Register("/instances/{id}/statistics", GetResourceStatistics);
+    Register("/patients/{id}/statistics", GetResourceStatistics);
+    Register("/studies/{id}/statistics", GetResourceStatistics);
+    Register("/series/{id}/statistics", GetResourceStatistics);
+
+    Register("/instances/{id}/metadata", ListMetadata);
+    Register("/instances/{id}/metadata/{name}", DeleteMetadata);
+    Register("/instances/{id}/metadata/{name}", GetMetadata);
+    Register("/instances/{id}/metadata/{name}", SetMetadata);
+    Register("/patients/{id}/metadata", ListMetadata);
+    Register("/patients/{id}/metadata/{name}", DeleteMetadata);
+    Register("/patients/{id}/metadata/{name}", GetMetadata);
+    Register("/patients/{id}/metadata/{name}", SetMetadata);
+    Register("/series/{id}/metadata", ListMetadata);
+    Register("/series/{id}/metadata/{name}", DeleteMetadata);
+    Register("/series/{id}/metadata/{name}", GetMetadata);
+    Register("/series/{id}/metadata/{name}", SetMetadata);
+    Register("/studies/{id}/metadata", ListMetadata);
+    Register("/studies/{id}/metadata/{name}", DeleteMetadata);
+    Register("/studies/{id}/metadata/{name}", GetMetadata);
+    Register("/studies/{id}/metadata/{name}", SetMetadata);
+
     Register("/patients/{id}/protected", IsProtectedPatient);
     Register("/patients/{id}/protected", SetPatientProtection);
     Register("/instances/{id}/file", GetInstanceFile);
+    Register("/instances/{id}/export", ExportInstanceFile);
     Register("/instances/{id}/tags", GetInstanceTags<false>);
     Register("/instances/{id}/simplified-tags", GetInstanceTags<true>);
     Register("/instances/{id}/frames", ListFrames);
@@ -1491,22 +1836,29 @@
     Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>);
     Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
     Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
+    Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>);
     Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>);
     Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
     Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
+    Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>);
 
     Register("/modalities", ListModalities);
     Register("/modalities/{id}", ListModalityOperations);
     Register("/modalities/{id}/find-patient", DicomFindPatient);
     Register("/modalities/{id}/find-study", DicomFindStudy);
     Register("/modalities/{id}/find-series", DicomFindSeries);
+    Register("/modalities/{id}/find-instance", DicomFindInstance);
     Register("/modalities/{id}/find", DicomFind);
     Register("/modalities/{id}/store", DicomStore);
 
+    Register("/peers", ListPeers);
+    Register("/peers/{id}", ListPeerOperations);
+    Register("/peers/{id}/store", PeerStore);
+
     Register("/instances/{id}/modify", ModifyInstance);
     Register("/series/{id}/modify", ModifySeriesInplace);
     Register("/studies/{id}/modify", ModifyStudyInplace);
-    Register("/patients/{id}/modify", ModifyPatientInplace);
+    //Register("/patients/{id}/modify", ModifyPatientInplace);
 
     Register("/instances/{id}/anonymize", AnonymizeInstance);
     Register("/series/{id}/anonymize", AnonymizeSeriesInplace);
@@ -1514,5 +1866,7 @@
     Register("/patients/{id}/anonymize", AnonymizePatientInplace);
 
     Register("/tools/generate-uid", GenerateUid);
+    Register("/tools/execute-script", ExecuteScript);
+    Register("/tools/now", GetNowIsoString);
   }
 }
--- a/OrthancServer/OrthancRestApi.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/OrthancRestApi.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -42,11 +42,12 @@
   class OrthancRestApi : public RestApi
   {
   public:
-    typedef std::set<std::string> Modalities;
+    typedef std::set<std::string> SetOfStrings;
 
   private:
     ServerContext& context_;
-    Modalities modalities_;
+    SetOfStrings modalities_;
+    SetOfStrings peers_;
 
   public:
     OrthancRestApi(ServerContext& context);
@@ -56,9 +57,14 @@
       return context_;
     }
 
-    Modalities& GetModalities()
+    SetOfStrings& GetModalities()
     {
       return modalities_;
     }
+
+    SetOfStrings& GetPeers()
+    {
+      return peers_;
+    }
   };
 }
--- a/OrthancServer/ServerContext.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/ServerContext.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -33,11 +33,15 @@
 #include "ServerContext.h"
 
 #include "../Core/HttpServer/FilesystemHttpSender.h"
+#include "../Core/Lua/LuaFunctionCall.h"
+#include "ServerToolbox.h"
 
 #include <glog/logging.h>
+#include <EmbeddedResources.h>
 
 #define ENABLE_DICOM_CACHE  1
 
+static const char* RECEIVED_INSTANCE_FILTER = "ReceivedInstanceFilter";
 
 static const size_t DICOM_CACHE_SIZE = 2;
 
@@ -60,6 +64,7 @@
     provider_(*this),
     dicomCache_(provider_, DICOM_CACHE_SIZE)
   {
+    lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
   }
 
   void ServerContext::SetCompressionEnabled(bool enabled)
@@ -83,6 +88,23 @@
                                    const Json::Value& dicomJson,
                                    const std::string& remoteAet)
   {
+    // Test if the instance must be filtered out
+    if (lua_.IsExistingFunction(RECEIVED_INSTANCE_FILTER))
+    {
+      Json::Value simplified;
+      SimplifyTags(simplified, dicomJson);
+
+      LuaFunctionCall call(lua_, RECEIVED_INSTANCE_FILTER);
+      call.PushJSON(simplified);
+      call.PushString(remoteAet);
+
+      if (!call.ExecutePredicate())
+      {
+        LOG(INFO) << "An incoming instance has been discarded by the filter";
+        return StoreStatus_FilteredOut;
+      }
+    }
+
     if (compressionEnabled_)
     {
       accessor_.SetCompressionForNextOperations(CompressionType_Zlib);
@@ -109,17 +131,21 @@
 
     switch (status)
     {
-    case StoreStatus_Success:
-      LOG(INFO) << "New instance stored";
-      break;
+      case StoreStatus_Success:
+        LOG(INFO) << "New instance stored";
+        break;
+
+      case StoreStatus_AlreadyStored:
+        LOG(INFO) << "Already stored";
+        break;
 
-    case StoreStatus_AlreadyStored:
-      LOG(INFO) << "Already stored";
-      break;
+      case StoreStatus_Failure:
+        LOG(ERROR) << "Store failure";
+        break;
 
-    case StoreStatus_Failure:
-      LOG(ERROR) << "Store failure";
-      break;
+      default:
+        // This should never happen
+        break;
     }
 
     return status;
--- a/OrthancServer/ServerContext.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/ServerContext.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -36,6 +36,7 @@
 #include "../Core/FileStorage/CompressedFileStorageAccessor.h"
 #include "../Core/FileStorage/FileStorage.h"
 #include "../Core/RestApi/RestApiOutput.h"
+#include "../Core/Lua/LuaContext.h"
 #include "ServerIndex.h"
 #include "FromDcmtkBridge.h"
 
@@ -70,6 +71,8 @@
     DicomCacheProvider provider_;
     MemoryCache dicomCache_;
 
+    LuaContext lua_;
+
   public:
     ServerContext(const boost::filesystem::path& storagePath,
                   const boost::filesystem::path& indexPath);
@@ -129,5 +132,10 @@
 
     // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD
     ParsedDicomFile& GetDicomFile(const std::string& instancePublicId);
+
+    LuaContext& GetLuaContext()
+    {
+      return lua_;
+    }
   };
 }
--- a/OrthancServer/ServerEnumerations.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -32,10 +32,57 @@
 #include "ServerEnumerations.h"
 
 #include "../Core/OrthancException.h"
+#include "../Core/EnumerationDictionary.h"
+
+#include <boost/thread.hpp>
 
 namespace Orthanc
 {
-  const char* ToString(ResourceType type)
+  static boost::mutex enumerationsMutex_;
+  static Toolbox::EnumerationDictionary<MetadataType> dictMetadataType_;
+
+  void InitializeServerEnumerations()
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+    
+    dictMetadataType_.Add(MetadataType_Instance_IndexInSeries, "IndexInSeries");
+    dictMetadataType_.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate");
+    dictMetadataType_.Add(MetadataType_Instance_RemoteAet, "RemoteAET");
+    dictMetadataType_.Add(MetadataType_Series_ExpectedNumberOfInstances, "ExpectedNumberOfInstances");
+    dictMetadataType_.Add(MetadataType_ModifiedFrom, "ModifiedFrom");
+    dictMetadataType_.Add(MetadataType_AnonymizedFrom, "AnonymizedFrom");
+    dictMetadataType_.Add(MetadataType_LastUpdate, "LastUpdate");
+  }
+
+  void RegisterUserMetadata(int metadata,
+                            const std::string name)
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+
+    if (metadata < static_cast<int>(MetadataType_StartUser) ||
+        metadata > static_cast<int>(MetadataType_EndUser))
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    dictMetadataType_.Add(static_cast<MetadataType>(metadata), name);
+  }
+
+  std::string EnumerationToString(MetadataType type)
+  {
+    // This function MUST return a "std::string" and not "const
+    // char*", as the result is not a static string
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+    return dictMetadataType_.Translate(type);
+  }
+
+  MetadataType StringToMetadata(const std::string& str)
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+    return dictMetadataType_.Translate(str);
+  }
+
+  const char* EnumerationToString(ResourceType type)
   {
     switch (type)
     {
@@ -78,7 +125,7 @@
     }
   }
 
-  const char* ToString(SeriesStatus status)
+  const char* EnumerationToString(SeriesStatus status)
   {
     switch (status)
     {
@@ -99,7 +146,7 @@
     }
   }
 
-  const char* ToString(StoreStatus status)
+  const char* EnumerationToString(StoreStatus status)
   {
     switch (status)
     {
@@ -112,13 +159,16 @@
       case StoreStatus_Failure:
         return "Failure";
 
+      case StoreStatus_FilteredOut:
+        return "FilteredOut";
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
 
 
-  const char* ToString(ChangeType type)
+  const char* EnumerationToString(ChangeType type)
   {
     switch (type)
     {
@@ -155,6 +205,15 @@
       case ChangeType_ModifiedPatient:
         return "ModifiedPatient";
 
+      case ChangeType_StablePatient:
+        return "StablePatient";
+
+      case ChangeType_StableStudy:
+        return "StableStudy";
+
+      case ChangeType_StableSeries:
+        return "StableSeries";
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -197,4 +256,39 @@
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
+
+
+  const char* EnumerationToString(ModalityManufacturer manufacturer)
+  {
+    switch (manufacturer)
+    {
+      case ModalityManufacturer_Generic:
+        return "Generic";
+
+      case ModalityManufacturer_ClearCanvas:
+        return "ClearCanvas";
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer)
+  {
+    if (manufacturer == "Generic")
+    {
+      return ModalityManufacturer_Generic;
+    }
+    else if (manufacturer == "ClearCanvas")
+    {
+      return ModalityManufacturer_ClearCanvas;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
 }
--- a/OrthancServer/ServerEnumerations.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/ServerEnumerations.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -47,7 +47,14 @@
   {
     StoreStatus_Success,
     StoreStatus_AlreadyStored,
-    StoreStatus_Failure
+    StoreStatus_Failure,
+    StoreStatus_FilteredOut     // Removed by NewInstanceFilter
+  };
+
+  enum ModalityManufacturer
+  {
+    ModalityManufacturer_Generic,
+    ModalityManufacturer_ClearCanvas
   };
 
 
@@ -79,7 +86,12 @@
     MetadataType_Instance_RemoteAet = 3,
     MetadataType_Series_ExpectedNumberOfInstances = 4,
     MetadataType_ModifiedFrom = 5,
-    MetadataType_AnonymizedFrom = 6
+    MetadataType_AnonymizedFrom = 6,
+    MetadataType_LastUpdate = 7,
+
+    // Make sure that the value "65535" can be stored into this enumeration
+    MetadataType_StartUser = 1024,
+    MetadataType_EndUser = 65535
   };
 
   enum ChangeType
@@ -94,19 +106,35 @@
     ChangeType_ModifiedStudy = 8,
     ChangeType_ModifiedSeries = 9,
     ChangeType_AnonymizedPatient = 10,
-    ChangeType_ModifiedPatient = 11
+    ChangeType_ModifiedPatient = 11,
+    ChangeType_StablePatient = 12,
+    ChangeType_StableStudy = 13,
+    ChangeType_StableSeries = 14
   };
 
+  void InitializeServerEnumerations();
+
+  void RegisterUserMetadata(int metadata,
+                            const std::string name);
+
   std::string GetBasePath(ResourceType type,
                           const std::string& publicId);
 
-  const char* ToString(ResourceType type);
+  MetadataType StringToMetadata(const std::string& str);
+
+  const char* EnumerationToString(ResourceType type);
 
-  const char* ToString(SeriesStatus status);
+  std::string EnumerationToString(MetadataType type);
+
+  const char* EnumerationToString(SeriesStatus status);
 
-  const char* ToString(StoreStatus status);
+  const char* EnumerationToString(StoreStatus status);
+
+  const char* EnumerationToString(ChangeType type);
 
-  const char* ToString(ChangeType type);
+  const char* EnumerationToString(ModalityManufacturer manufacturer);
+
+  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer);
 
   ResourceType GetParentResourceType(ResourceType type);
 
--- a/OrthancServer/ServerIndex.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/ServerIndex.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -37,6 +37,7 @@
 #endif
 
 #include "EmbeddedResources.h"
+#include "OrthancInitialization.h"
 #include "../Core/Toolbox.h"
 #include "../Core/Uuid.h"
 #include "../Core/DicomFormat/DicomArray.h"
@@ -48,6 +49,8 @@
 #include <stdio.h>
 #include <glog/logging.h>
 
+static const uint64_t MEGA_BYTES = 1024 * 1024;
+
 namespace Orthanc
 {
   namespace Internals
@@ -185,6 +188,27 @@
   };
 
 
+  struct ServerIndex::UnstableResourcePayload
+  {
+    Orthanc::ResourceType type_;
+    boost::posix_time::ptime time_;
+
+    UnstableResourcePayload() : type_(Orthanc::ResourceType_Instance)
+    {
+    }
+
+    UnstableResourcePayload(Orthanc::ResourceType type) : type_(type)
+    {
+      time_ = boost::posix_time::second_clock::local_time();
+    }
+
+    unsigned int GetAge() const
+    {
+      return (boost::posix_time::second_clock::local_time() - time_).total_seconds();
+    }
+  };
+
+
   bool ServerIndex::DeleteResource(Json::Value& target,
                                    const std::string& uuid,
                                    ResourceType expectedType)
@@ -211,7 +235,7 @@
 
       target["RemainingAncestor"] = Json::Value(Json::objectValue);
       target["RemainingAncestor"]["Path"] = GetBasePath(type, uuid);
-      target["RemainingAncestor"]["Type"] = ToString(type);
+      target["RemainingAncestor"]["Type"] = EnumerationToString(type);
       target["RemainingAncestor"]["ID"] = uuid;
     }
     else
@@ -225,23 +249,86 @@
   }
 
 
-  static void FlushThread(DatabaseWrapper* db,
-                          boost::mutex* mutex,
-                          unsigned int sleep)
+  void ServerIndex::FlushThread(ServerIndex* that)
   {
+    unsigned int sleep;
+
+    try
+    {
+      std::string sleepString = that->db_->GetGlobalProperty(GlobalProperty_FlushSleep);
+      sleep = boost::lexical_cast<unsigned int>(sleepString);
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      // By default, wait for 10 seconds before flushing
+      sleep = 10;
+    }
+
     LOG(INFO) << "Starting the database flushing thread (sleep = " << sleep << ")";
 
-    while (1)
+    unsigned int count = 0;
+
+    while (!that->done_)
     {
-      boost::this_thread::sleep(boost::posix_time::seconds(sleep));
-      boost::mutex::scoped_lock lock(*mutex);
-      db->FlushToDisk();
+      boost::this_thread::sleep(boost::posix_time::seconds(1));
+      count++;
+      if (count < sleep)
+      {
+        continue;
+      }
+
+      boost::mutex::scoped_lock lock(that->mutex_);
+      that->db_->FlushToDisk();
+      count = 0;
+    }
+
+    LOG(INFO) << "Stopping the database flushing thread";
+  }
+
+
+  static void ComputeExpectedNumberOfInstances(DatabaseWrapper& db,
+                                               int64_t series,
+                                               const DicomMap& dicomSummary)
+  {
+    const DicomValue* value;
+    const DicomValue* value2;
+          
+    try
+    {
+      if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL &&
+          (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS)) != NULL)
+      {
+        // Patch for series with temporal positions thanks to Will Ryder
+        int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->AsString());
+        int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->AsString());
+        std::string expected = boost::lexical_cast<std::string>(imagesInAcquisition * countTemporalPositions);
+        db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected);
+      }
+
+      else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL &&
+               (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TIME_SLICES)) != NULL)
+      {
+        // Support of Cardio-PET images
+        int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->AsString());
+        int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->AsString());
+        std::string expected = boost::lexical_cast<std::string>(numberOfSlices * numberOfTimeSlices);
+        db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected);
+      }
+
+      else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL)
+      {
+        db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->AsString());
+      }
+    }
+    catch (boost::bad_lexical_cast)
+    {
     }
   }
 
 
   ServerIndex::ServerIndex(ServerContext& context,
                            const std::string& dbPath) : 
+    done_(false),
     maximumStorageSize_(0),
     maximumPatients_(0)
   {
@@ -272,27 +359,24 @@
     // execution of Orthanc
     StandaloneRecycling();
 
-    unsigned int sleep;
-    try
-    {
-      std::string sleepString = db_->GetGlobalProperty(GlobalProperty_FlushSleep);
-      sleep = boost::lexical_cast<unsigned int>(sleepString);
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      // By default, wait for 10 seconds before flushing
-      sleep = 10;
-    }
-
-    flushThread_ = boost::thread(FlushThread, db_.get(), &mutex_, sleep);
+    flushThread_ = boost::thread(FlushThread, this);
+    unstableResourcesMonitorThread_ = boost::thread(UnstableResourcesMonitorThread, this);
   }
 
 
   ServerIndex::~ServerIndex()
   {
-    LOG(INFO) << "Stopping the database flushing thread";
-    /*flushThread_.terminate();
-      flushThread_.join();*/
+    done_ = true;
+
+    if (flushThread_.joinable())
+    {
+      flushThread_.join();
+    }
+
+    if (unstableResourcesMonitorThread_.joinable())
+    {
+      unstableResourcesMonitorThread_.join();
+    }
   }
 
 
@@ -309,15 +393,15 @@
     {
       Transaction t(*this);
 
-      int64_t patient, study, series, instance;
-      ResourceType type;
-      bool isNewSeries = false;
-
       // Do nothing if the instance already exists
-      if (db_->LookupResource(hasher.HashInstance(), patient, type))
       {
-        assert(type == ResourceType_Instance);
-        return StoreStatus_AlreadyStored;
+        ResourceType type;
+        int64_t tmp;
+        if (db_->LookupResource(hasher.HashInstance(), tmp, type))
+        {
+          assert(type == ResourceType_Instance);
+          return StoreStatus_AlreadyStored;
+        }
       }
 
       // Ensure there is enough room in the storage for the new instance
@@ -331,56 +415,101 @@
       Recycle(instanceSize, hasher.HashPatient());
 
       // Create the instance
-      instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance);
+      int64_t instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance);
 
       DicomMap dicom;
       dicomSummary.ExtractInstanceInformation(dicom);
       db_->SetMainDicomTags(instance, dicom);
 
-      // Create the patient/study/series/instance hierarchy
-      if (!db_->LookupResource(hasher.HashSeries(), series, type))
+      // Detect up to which level the patient/study/series/instance
+      // hierarchy must be created
+      int64_t patient = -1, study = -1, series = -1;
+      bool isNewPatient = false;
+      bool isNewStudy = false;
+      bool isNewSeries = false;
+
       {
-        // This is a new series
-        isNewSeries = true;
+        ResourceType dummy;
+
+        if (db_->LookupResource(hasher.HashSeries(), series, dummy))
+        {
+          assert(dummy == ResourceType_Series);
+          // The patient, the study and the series already exist
+
+          bool ok = (db_->LookupResource(hasher.HashPatient(), patient, dummy) &&
+                     db_->LookupResource(hasher.HashStudy(), study, dummy));
+          assert(ok);
+        }
+        else if (db_->LookupResource(hasher.HashStudy(), study, dummy))
+        {
+          assert(dummy == ResourceType_Study);
+
+          // New series: The patient and the study already exist
+          isNewSeries = true;
+
+          bool ok = db_->LookupResource(hasher.HashPatient(), patient, dummy);
+          assert(ok);
+        }
+        else if (db_->LookupResource(hasher.HashPatient(), patient, dummy))
+        {
+          assert(dummy == ResourceType_Patient);
+
+          // New study and series: The patient already exist
+          isNewStudy = true;
+          isNewSeries = true;
+        }
+        else
+        {
+          // New patient, study and series: Nothing exists
+          isNewPatient = true;
+          isNewStudy = true;
+          isNewSeries = true;
+        }
+      }
+
+      // Create the series if needed
+      if (isNewSeries)
+      {
         series = db_->CreateResource(hasher.HashSeries(), ResourceType_Series);
         dicomSummary.ExtractSeriesInformation(dicom);
         db_->SetMainDicomTags(series, dicom);
-        db_->AttachChild(series, instance);
+      }
 
-        if (!db_->LookupResource(hasher.HashStudy(), study, type))
-        {
-          // This is a new study
-          study = db_->CreateResource(hasher.HashStudy(), ResourceType_Study);
-          dicomSummary.ExtractStudyInformation(dicom);
-          db_->SetMainDicomTags(study, dicom);
-          db_->AttachChild(study, series);
+      // Create the study if needed
+      if (isNewStudy)
+      {
+        study = db_->CreateResource(hasher.HashStudy(), ResourceType_Study);
+        dicomSummary.ExtractStudyInformation(dicom);
+        db_->SetMainDicomTags(study, dicom);
+      }
+
+      // Create the patient if needed
+      if (isNewPatient)
+      {
+        patient = db_->CreateResource(hasher.HashPatient(), ResourceType_Patient);
+        dicomSummary.ExtractPatientInformation(dicom);
+        db_->SetMainDicomTags(patient, dicom);
+      }
 
-          if (!db_->LookupResource(hasher.HashPatient(), patient, type))
-          {
-            // This is a new patient
-            patient = db_->CreateResource(hasher.HashPatient(), ResourceType_Patient);
-            dicomSummary.ExtractPatientInformation(dicom);
-            db_->SetMainDicomTags(patient, dicom);
-            db_->AttachChild(patient, study);
-          }
-          else
-          {
-            assert(type == ResourceType_Patient);
-            db_->AttachChild(patient, study);
-          }
-        }
-        else
-        {
-          assert(type == ResourceType_Study);
-          db_->AttachChild(study, series);
-        }
+      // Create the parent-to-child links
+      db_->AttachChild(series, instance);
+
+      if (isNewSeries)
+      {
+        db_->AttachChild(study, series);
       }
-      else
+
+      if (isNewStudy)
       {
-        assert(type == ResourceType_Series);
-        db_->AttachChild(series, instance);
+        db_->AttachChild(patient, study);
       }
 
+      // Sanity checks
+      assert(patient != -1);
+      assert(study != -1);
+      assert(series != -1);
+      assert(instance != -1);
+
       // Attach the files to the newly created instance
       for (Attachments::const_iterator it = attachments.begin();
            it != attachments.end(); it++)
@@ -389,7 +518,11 @@
       }
 
       // Attach the metadata
-      db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, Toolbox::GetNowIsoString());
+      std::string now = Toolbox::GetNowIsoString();
+      db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, now);
+      db_->SetMetadata(series, MetadataType_LastUpdate, now);
+      db_->SetMetadata(study, MetadataType_LastUpdate, now);
+      db_->SetMetadata(patient, MetadataType_LastUpdate, now);
       db_->SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet);
 
       const DicomValue* value;
@@ -401,12 +534,7 @@
 
       if (isNewSeries)
       {
-        if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL ||
-            (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL ||
-            (value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL)
-        {
-          db_->SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->AsString());
-        }
+        ComputeExpectedNumberOfInstances(*db_, series, dicomSummary);
       }
 
       // Check whether the series of this new instance is now completed
@@ -416,6 +544,11 @@
         db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series);
       }
 
+      // Mark the parent resources of this instance as unstable
+      MarkAsUnstable(patient, ResourceType_Patient);
+      MarkAsUnstable(study, ResourceType_Study);
+      MarkAsUnstable(series, ResourceType_Series);
+
       t.Commit(instanceSize);
 
       return StoreStatus_Success;
@@ -432,18 +565,16 @@
 
   void ServerIndex::ComputeStatistics(Json::Value& target)
   {
-    static const uint64_t MB = 1024 * 1024;
-
     boost::mutex::scoped_lock lock(mutex_);
     target = Json::objectValue;
 
     uint64_t cs = currentStorageSize_;
     assert(cs == db_->GetTotalCompressedSize());
     uint64_t us = db_->GetTotalUncompressedSize();
-    target["TotalDiskSpace"] = boost::lexical_cast<std::string>(cs);
+    target["TotalDiskSize"] = boost::lexical_cast<std::string>(cs);
     target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us);
-    target["TotalDiskSpaceMB"] = boost::lexical_cast<unsigned int>(cs / MB);
-    target["TotalUncompressedSizeMB"] = boost::lexical_cast<unsigned int>(us / MB);
+    target["TotalDiskSizeMB"] = boost::lexical_cast<unsigned int>(cs / MEGA_BYTES);
+    target["TotalUncompressedSizeMB"] = boost::lexical_cast<unsigned int>(us / MEGA_BYTES);
 
     target["CountPatients"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Patient));
     target["CountStudies"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Study));
@@ -622,7 +753,7 @@
       case ResourceType_Series:
       {
         result["Type"] = "Series";
-        result["Status"] = ToString(GetSeriesStatus(id));
+        result["Status"] = EnumerationToString(GetSeriesStatus(id));
 
         int i;
         if (db_->GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances))
@@ -673,6 +804,13 @@
     if (tmp.size() != 0)
       result["ModifiedFrom"] = tmp;
 
+    if (type == ResourceType_Patient ||
+        type == ResourceType_Study ||
+        type == ResourceType_Series)
+    {
+      result["IsStable"] = !unstableResources_.Contains(id);
+    }
+
     return true;
   }
 
@@ -918,7 +1056,7 @@
     }
     else
     {
-      LOG(WARNING) << "At most " << (size / (1024 * 1024)) << "MB will be used for the storage area";
+      LOG(WARNING) << "At most " << (size / MEGA_BYTES) << "MB will be used for the storage area";
     }
 
     StandaloneRecycling();
@@ -1040,6 +1178,23 @@
     db_->SetMetadata(id, type, value);
   }
 
+
+  void ServerIndex::DeleteMetadata(const std::string& publicId,
+                                   MetadataType type)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    ResourceType rtype;
+    int64_t id;
+    if (!db_->LookupResource(publicId, id, rtype))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    db_->DeleteMetadata(id, type);
+  }
+
+
   bool ServerIndex::LookupMetadata(std::string& target,
                                    const std::string& publicId,
                                    MetadataType type)
@@ -1057,6 +1212,22 @@
   }
 
 
+  bool ServerIndex::ListAvailableMetadata(std::list<MetadataType>& target,
+                                          const std::string& publicId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    ResourceType rtype;
+    int64_t id;
+    if (!db_->LookupResource(publicId, id, rtype))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    return db_->ListAvailableMetadata(target, id);
+  }
+
+
   bool ServerIndex::LookupParent(std::string& target,
                                  const std::string& publicId)
   {
@@ -1115,4 +1286,224 @@
 
     transaction->Commit();
   }
+
+
+  void ServerIndex::DeleteChanges()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    db_->ClearTable("Changes");
+  }
+
+  void ServerIndex::DeleteExportedResources()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    db_->ClearTable("ExportedResources");
+  }
+
+
+  void ServerIndex::GetStatistics(Json::Value& target,
+                                  const std::string& publicId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    ResourceType type;
+    int64_t top;
+    if (!db_->LookupResource(publicId, top, type))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    std::stack<int64_t> toExplore;
+    toExplore.push(top);
+
+    int countInstances = 0;
+    int countSeries = 0;
+    int countStudies = 0;
+    uint64_t compressedSize = 0;
+    uint64_t uncompressedSize = 0;
+
+    while (!toExplore.empty())
+    {
+      // Get the internal ID of the current resource
+      int64_t resource = toExplore.top();
+      toExplore.pop();
+
+      ResourceType thisType = db_->GetResourceType(resource);
+
+      if (thisType == ResourceType_Instance)
+      {
+        std::list<FileContentType> f;
+        db_->ListAvailableAttachments(f, resource);
+
+        for (std::list<FileContentType>::const_iterator
+               it = f.begin(); it != f.end(); it++)
+        {
+          FileInfo attachment;
+          if (db_->LookupAttachment(attachment, resource, *it))
+          {
+            compressedSize += attachment.GetCompressedSize();
+            uncompressedSize += attachment.GetUncompressedSize();
+          }
+        }
+
+        countInstances++;
+      }
+      else
+      {
+        switch (thisType)
+        {
+          case ResourceType_Study:
+            countStudies++;
+            break;
+
+          case ResourceType_Series:
+            countSeries++;
+            break;
+
+          default:
+            break;
+        }
+
+        // Tag all the children of this resource as to be explored
+        std::list<int64_t> tmp;
+        db_->GetChildrenInternalId(tmp, resource);
+        for (std::list<int64_t>::const_iterator 
+               it = tmp.begin(); it != tmp.end(); it++)
+        {
+          toExplore.push(*it);
+        }
+      }
+    }
+
+    target = Json::objectValue;
+    target["DiskSize"] = boost::lexical_cast<std::string>(compressedSize);
+    target["DiskSizeMB"] = boost::lexical_cast<unsigned int>(compressedSize / MEGA_BYTES);
+    target["UncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize);
+    target["UncompressedSizeMB"] = boost::lexical_cast<unsigned int>(uncompressedSize / MEGA_BYTES);
+
+    switch (type)
+    {
+      // Do NOT add "break" below this point!
+      case ResourceType_Patient:
+        target["CountStudies"] = countStudies;
+
+      case ResourceType_Study:
+        target["CountSeries"] = countSeries;
+
+      case ResourceType_Series:
+        target["CountInstances"] = countInstances;
+
+      case ResourceType_Instance:
+      default:
+        break;
+    }
+  }
+
+
+  void ServerIndex::UnstableResourcesMonitorThread(ServerIndex* that)
+  {
+    int stableAge = GetGlobalIntegerParameter("StableAge", 60);
+    if (stableAge <= 0)
+    {
+      stableAge = 60;
+    }
+
+    LOG(INFO) << "Starting the monitor for stable resources (stable age = " << stableAge << ")";
+
+    while (!that->done_)
+    {
+      // Check for stable resources each second
+      boost::this_thread::sleep(boost::posix_time::seconds(1));
+
+      boost::mutex::scoped_lock lock(that->mutex_);
+
+      while (!that->unstableResources_.IsEmpty() &&
+             that->unstableResources_.GetOldestPayload().GetAge() > static_cast<unsigned int>(stableAge))
+      {
+        // This DICOM resource has not received any new instance for
+        // some time. It can be considered as stable.
+          
+        UnstableResourcePayload payload;
+        int64_t id = that->unstableResources_.RemoveOldest(payload);
+
+        // Ensure that the resource is still existing before logging the change
+        if (that->db_->IsExistingResource(id))
+        {
+          switch (payload.type_)
+          {
+            case Orthanc::ResourceType_Patient:
+              that->db_->LogChange(ChangeType_StablePatient, id, ResourceType_Patient);
+              break;
+
+            case Orthanc::ResourceType_Study:
+              that->db_->LogChange(ChangeType_StableStudy, id, ResourceType_Study);
+              break;
+
+            case Orthanc::ResourceType_Series:
+              that->db_->LogChange(ChangeType_StableSeries, id, ResourceType_Series);
+              break;
+
+            default:
+              throw OrthancException(ErrorCode_InternalError);
+          }
+
+          //LOG(INFO) << "Stable resource: " << EnumerationToString(payload.type_) << " " << id;
+        }
+      }
+    }
+
+    LOG(INFO) << "Closing the monitor thread for stable resources";
+  }
+  
+
+  void ServerIndex::MarkAsUnstable(int64_t id,
+                                   Orthanc::ResourceType type)
+  {
+    // WARNING: Before calling this method, "mutex_" must be locked.
+
+    assert(type == Orthanc::ResourceType_Patient ||
+           type == Orthanc::ResourceType_Study ||
+           type == Orthanc::ResourceType_Series);
+
+    unstableResources_.AddOrMakeMostRecent(id, type);
+    //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id;
+  }
+
+
+
+  void ServerIndex::LookupTagValue(std::list<std::string>& result,
+                                   DicomTag tag,
+                                   const std::string& value)
+  {
+    result.clear();
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    std::list<int64_t> id;
+    db_->LookupTagValue(id, tag, value);
+
+    for (std::list<int64_t>::const_iterator 
+           it = id.begin(); it != id.end(); it++)
+    {
+      result.push_back(db_->GetPublicId(*it));
+    }
+  }
+
+
+  void ServerIndex::LookupTagValue(std::list<std::string>& result,
+                                   const std::string& value)
+  {
+    result.clear();
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    std::list<int64_t> id;
+    db_->LookupTagValue(id, value);
+
+    for (std::list<int64_t>::const_iterator 
+           it = id.begin(); it != id.end(); it++)
+    {
+      result.push_back(db_->GetPublicId(*it));
+    }
+  }
 }
--- a/OrthancServer/ServerIndex.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/ServerIndex.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -34,6 +34,7 @@
 
 #include <boost/thread.hpp>
 #include <boost/noncopyable.hpp>
+#include "../Core/Cache/LeastRecentlyUsedIndex.h"
 #include "../Core/SQLite/Connection.h"
 #include "../Core/DicomFormat/DicomMap.h"
 #include "../Core/DicomFormat/DicomInstanceHasher.h"
@@ -55,17 +56,25 @@
   {
   private:
     class Transaction;
+    struct UnstableResourcePayload;
 
+    bool done_;
     boost::mutex mutex_;
     boost::thread flushThread_;
+    boost::thread unstableResourcesMonitorThread_;
 
     std::auto_ptr<Internals::ServerIndexListener> listener_;
     std::auto_ptr<DatabaseWrapper> db_;
+    LeastRecentlyUsedIndex<int64_t, UnstableResourcePayload>  unstableResources_;
 
     uint64_t currentStorageSize_;
     uint64_t maximumStorageSize_;
     unsigned int maximumPatients_;
 
+    static void FlushThread(ServerIndex* that);
+
+    static void UnstableResourcesMonitorThread(ServerIndex* that);
+
     void MainDicomTagsToJson(Json::Value& result,
                              int64_t resourceId);
 
@@ -78,6 +87,9 @@
 
     void StandaloneRecycling();
 
+    void MarkAsUnstable(int64_t id,
+                        Orthanc::ResourceType type);
+
   public:
     typedef std::list<FileInfo> Attachments;
 
@@ -150,10 +162,16 @@
                      MetadataType type,
                      const std::string& value);
 
+    void DeleteMetadata(const std::string& publicId,
+                        MetadataType type);
+
     bool LookupMetadata(std::string& target,
                         const std::string& publicId,
                         MetadataType type);
 
+    bool ListAvailableMetadata(std::list<MetadataType>& target,
+                               const std::string& publicId);
+
     bool LookupParent(std::string& target,
                       const std::string& publicId);
 
@@ -161,5 +179,19 @@
 
     void LogChange(ChangeType changeType,
                    const std::string& publicId);
+
+    void DeleteChanges();
+
+    void DeleteExportedResources();
+
+    void GetStatistics(Json::Value& target,
+                       const std::string& publicId);
+
+    void LookupTagValue(std::list<std::string>& result,
+                        DicomTag tag,
+                        const std::string& value);
+
+    void LookupTagValue(std::list<std::string>& result,
+                        const std::string& value);
   };
 }
--- a/OrthancServer/ServerToolbox.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/ServerToolbox.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/ServerToolbox.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/ServerToolbox.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/ToDcmtkBridge.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/ToDcmtkBridge.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/ToDcmtkBridge.h	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/ToDcmtkBridge.h	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/OrthancServer/main.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/OrthancServer/main.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
@@ -38,7 +38,7 @@
 
 #include "../Core/HttpServer/EmbeddedResourceHttpHandler.h"
 #include "../Core/HttpServer/FilesystemHttpHandler.h"
-#include "../Core/HttpServer/MongooseServer.h"
+#include "../Core/Lua/LuaFunctionCall.h"
 #include "../Core/DicomFormat/DicomArray.h"
 #include "DicomProtocol/DicomServer.h"
 #include "OrthancInitialization.h"
@@ -51,11 +51,11 @@
 class MyStoreRequestHandler : public IStoreRequestHandler
 {
 private:
-  ServerContext& context_;
+  ServerContext& server_;
 
 public:
   MyStoreRequestHandler(ServerContext& context) :
-    context_(context)
+    server_(context)
   {
   }
 
@@ -66,7 +66,7 @@
   {
     if (dicomFile.size() > 0)
     {
-      context_.Store(&dicomFile[0], dicomFile.size(), dicomSummary, dicomJson, remoteAet);
+      server_.Store(&dicomFile[0], dicomFile.size(), dicomSummary, dicomJson, remoteAet);
     }
   }
 };
@@ -148,6 +148,66 @@
 };
 
 
+class MyIncomingHttpRequestFilter : public IIncomingHttpRequestFilter
+{
+private:
+  ServerContext& context_;
+
+public:
+  MyIncomingHttpRequestFilter(ServerContext& context) : context_(context)
+  {
+  }
+
+  virtual bool IsAllowed(HttpMethod method,
+                         const char* uri,
+                         const char* ip,
+                         const char* username) const
+  {
+    static const char* HTTP_FILTER = "IncomingHttpRequestFilter";
+
+    // Test if the instance must be filtered out
+    if (context_.GetLuaContext().IsExistingFunction(HTTP_FILTER))
+    {
+      LuaFunctionCall call(context_.GetLuaContext(), HTTP_FILTER);
+
+      switch (method)
+      {
+        case HttpMethod_Get:
+          call.PushString("GET");
+          break;
+
+        case HttpMethod_Put:
+          call.PushString("PUT");
+          break;
+
+        case HttpMethod_Post:
+          call.PushString("POST");
+          break;
+
+        case HttpMethod_Delete:
+          call.PushString("DELETE");
+          break;
+
+        default:
+          return true;
+      }
+
+      call.PushString(uri);
+      call.PushString(ip);
+      call.PushString(username);
+
+      if (!call.ExecutePredicate())
+      {
+        LOG(INFO) << "An incoming HTTP request has been discarded by the filter";
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+
 void PrintHelp(char* path)
 {
   std::cout 
@@ -264,8 +324,10 @@
       OrthancInitialize();
     }
 
-    boost::filesystem::path storageDirectory = GetGlobalStringParameter("StorageDirectory", "OrthancStorage");
-    boost::filesystem::path indexDirectory = GetGlobalStringParameter("IndexDirectory", storageDirectory.string());
+    std::string storageDirectoryStr = GetGlobalStringParameter("StorageDirectory", "OrthancStorage");
+    boost::filesystem::path storageDirectory = InterpretStringParameterAsPath(storageDirectoryStr);
+    boost::filesystem::path indexDirectory = 
+      InterpretStringParameterAsPath(GetGlobalStringParameter("IndexDirectory", storageDirectoryStr));
     ServerContext context(storageDirectory, indexDirectory);
 
     LOG(WARNING) << "Storage directory: " << storageDirectory;
@@ -273,6 +335,19 @@
 
     context.SetCompressionEnabled(GetGlobalBoolParameter("StorageCompression", false));
 
+    std::list<std::string> luaScripts;
+    GetGlobalListOfStringsParameter(luaScripts, "LuaScripts");
+    for (std::list<std::string>::const_iterator
+           it = luaScripts.begin(); it != luaScripts.end(); it++)
+    {
+      std::string path = InterpretStringParameterAsPath(*it);
+      LOG(WARNING) << "Installing the Lua scripts from: " << path;
+      std::string script;
+      Toolbox::ReadFile(script, path);
+      context.GetLuaContext().Execute(script);
+    }
+
+
     try
     {
       context.GetIndex().SetMaximumPatientCount(GetGlobalIntegerParameter("MaximumPatientCount", 0));
@@ -306,16 +381,19 @@
       dicomServer.SetApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC"));
 
       // HTTP server
+      MyIncomingHttpRequestFilter httpFilter(context);
       MongooseServer httpServer;
       httpServer.SetPortNumber(GetGlobalIntegerParameter("HttpPort", 8042));
       httpServer.SetRemoteAccessAllowed(GetGlobalBoolParameter("RemoteAccessAllowed", false));
+      httpServer.SetIncomingHttpRequestFilter(httpFilter);
 
       httpServer.SetAuthenticationEnabled(GetGlobalBoolParameter("AuthenticationEnabled", false));
       SetupRegisteredUsers(httpServer);
 
       if (GetGlobalBoolParameter("SslEnabled", false))
       {
-        std::string certificate = GetGlobalStringParameter("SslCertificate", "certificate.pem");
+        std::string certificate = 
+          InterpretStringParameterAsPath(GetGlobalStringParameter("SslCertificate", "certificate.pem"));
         httpServer.SetSslEnabled(true);
         httpServer.SetSslCertificate(certificate.c_str());
       }
--- a/README	Mon Apr 29 12:48:10 2013 +0200
+++ b/README	Wed Sep 18 15:27:07 2013 +0200
@@ -41,12 +41,18 @@
 exception:
 http://people.gnome.org/~markmc/openssl-and-the-gpl.html
 
-Because Orthanc uses the Software-as-a-Service paradigm, commercial
-products are allowed to access the Orthanc REST services and to bundle
-Orthanc in an commercial aggregate.
+We also kindly require scientific works and clinical studies that make
+use of Orthanc to cite Orthanc in their associated
+publications. Similarly, we require open-source and closed-source
+products that make use of Orthanc to warn us about this use. You can
+cite our work using the following BibTeX entry:
 
-We also kindly require scientific works and clinical studies that make
-use of Orthanc to cite Orthanc in their associated publications.
+@inproceedings{Jodogne:ISBI2013,
+  author = "Jodogne, S. and Bernard, C. and Devillers, M. and Lenaerts, E. and Coucke, P.",
+  title = "Orthanc -- {A} Lightweight, {REST}ful {DICOM} Server for Healthcare and Medical Research",
+  booktitle = "Proc. of the International Symposium on Biomedical Imaging ({ISBI})",
+  year = "2013",
+}
 
 
 Licensing of special directories
@@ -54,10 +60,6 @@
 
 The following directories have separate licensing terms:
 
-* The files of the "OrthancCppClient/" directory are licensed under
-  the MIT license, which allows commercial products to statically link
-  against the C++ client library.
-
 * The file of the "Core/SQLite/" directory are licensed under the
   3-clause BSD license, as they are derived from the Chromium project.
 
@@ -68,7 +70,7 @@
 This archive contains the following directories:
 
 * Core/               - The core C++ classes (independent of DCMTK)
-* OrthancCppClient/   - Code of the C++ client (under MIT license)
+* OrthancCppClient/   - Code of the C++ client
 * OrthancExplorer/    - Code of the Web application (HTML5/Javascript)
 * OrthancServer/      - Code of the Orthanc server (depends on DCMTK)
 * Resources/          - Scripts, resources for building third-party code
--- a/Resources/CMake/BoostConfiguration.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/BoostConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -39,9 +39,17 @@
 
 
 if (BOOST_STATIC)
-  SET(BOOST_NAME boost_1_49_0)
+  # Parameters for Boost 1.54.0
+  SET(BOOST_NAME boost_1_54_0)
+  SET(BOOST_MD5 "cee688c35a9c7775b7305587e782e3f5")
+  SET(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src")
+  
   SET(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
-  DownloadPackage("http://switch.dl.sourceforge.net/project/boost/boost/1.49.0/${BOOST_NAME}.tar.gz" "${BOOST_SOURCES_DIR}" "${BOOST_PRELOADED}" "${BOOST_NAME}/boost ${BOOST_NAME}/libs/thread/src ${BOOST_NAME}/libs/system/src ${BOOST_NAME}/libs/filesystem/v3/src ${BOOST_NAME}/libs/locale/src ${BOOST_NAME}/libs/date_time/src")
+  DownloadPackage(
+    "${BOOST_MD5}"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_bcpdigest.tar.gz"
+    "${BOOST_SOURCES_DIR}"
+    )
 
   set(BOOST_SOURCES)
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
@@ -62,7 +70,7 @@
       ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp
       ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp
       ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp
-      ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/windows_file_codecvt.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp
       )
     add_definitions(
       -DBOOST_LOCALE_WITH_WCONV=1
@@ -73,10 +81,10 @@
 
   list(APPEND BOOST_SOURCES
     ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp
-    ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/codecvt_error_category.cpp
-    ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/operations.cpp
-    ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/path.cpp
-    ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/path_traits.cpp
+    ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp
+    ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp
+    ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp
+    ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp
     ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp
     ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp
     )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/BoostConfiguration.sh	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+set -e
+set -u
+
+## Starting with version 0.6.2, Orthanc is shipped with a subset of the
+## Boost libraries that is generated with the BCP tool:
+##
+## http://www.boost.org/doc/libs/1_54_0/tools/bcp/doc/html/index.html
+##
+## This script generates this subset.
+
+rm -rf /tmp/boost_1_54_0
+rm -rf /tmp/bcp/boost_1_54_0
+
+cd /tmp
+echo "Uncompressing the source of Boost 1.54.0..."
+tar xfz boost_1_54_0.tar.gz 
+
+echo "Generating the subset..."
+mkdir -p /tmp/bcp/boost_1_54_0
+bcp --boost=/tmp/boost_1_54_0 thread system locale date_time filesystem math/special_functions algorithm /tmp/bcp/boost_1_54_0
+cd /tmp/bcp
+
+echo "Compressing the subset..."
+tar cfz boost_1_54_0_bcpdigest.tar.gz boost_1_54_0
+ls -l boost_1_54_0_bcpdigest.tar.gz
+md5sum boost_1_54_0_bcpdigest.tar.gz
--- a/Resources/CMake/Compiler.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/Compiler.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -29,28 +29,6 @@
 
 
 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()
@@ -59,9 +37,9 @@
     -D_LARGEFILE64_SOURCE=1 
     -D_FILE_OFFSET_BITS=64
     )
-  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed ${DEBIAN_LD_FLAGS}")
-  set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined ${DEBIAN_LD_FLAGS}")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined ${DEBIAN_LD_FLAGS}")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
+  set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
 
   # Remove the "-rdynamic" option
   # http://www.mail-archive.com/cmake@cmake.org/msg08837.html
@@ -74,6 +52,12 @@
     -D_CRT_SECURE_NO_WARNINGS=1
     )
   link_libraries(rpcrt4 ws2_32)
+
+  if (${CMAKE_COMPILER_IS_GNUCXX})
+    # This is a patch for MinGW64
+    SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
+  endif()
+
 endif()
 
 
--- a/Resources/CMake/DcmtkConfiguration.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -1,7 +1,12 @@
+add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}")
+
 if (${STATIC_BUILD})
   SET(DCMTK_VERSION_NUMBER 360)
   SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0)
-  DownloadPackage("ftp://dicom.offis.de/pub/dicom/offis/software/dcmtk/dcmtk360/dcmtk-3.6.0.zip" "${DCMTK_SOURCES_DIR}" "" "")
+  DownloadPackage(
+    "219ad631b82031806147e4abbfba4fa4"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip" 
+    "${DCMTK_SOURCES_DIR}")
 
   IF(CMAKE_CROSSCOMPILING)
     SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.")
@@ -42,6 +47,15 @@
     list(REMOVE_ITEM DCMTK_SOURCES 
       ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc
       )
+
+    if (${CMAKE_COMPILER_IS_GNUCXX})
+      # This is a patch for MinGW64
+      execute_process(
+        COMMAND patch -p0 -i ${CMAKE_SOURCE_DIR}/Resources/Patches/dcmtk-mingw64.patch
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        )
+    endif()
+
   endif()
 
   list(REMOVE_ITEM DCMTK_SOURCES 
--- a/Resources/CMake/DownloadPackage.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/DownloadPackage.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -10,20 +10,16 @@
 endmacro()
 
 
-macro(DownloadPackage Url TargetDirectory PreloadedVariable UncompressArguments)
+macro(DownloadPackage MD5 Url TargetDirectory)
   if (NOT IS_DIRECTORY "${TargetDirectory}")
     GetUrlFilename(TMP_FILENAME "${Url}")
-    if ("${PreloadedVariable}" STREQUAL "")
-      set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}")
-      if (NOT EXISTS "${TMP_PATH}")
-        message("Downloading ${Url}")
-        file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS)
-      else()
-        message("Using local copy of ${Url}")
-      endif()
+
+    set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}")
+    if (NOT EXISTS "${TMP_PATH}")
+      message("Downloading ${Url}")
+      file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS EXPECTED_MD5 "${MD5}")
     else()
-      message("Using preloaded archive ${PreloadedVariable} for ${Url}")
-      set(TMP_PATH "${PreloadedVariable}")
+      message("Using local copy of ${Url}")
     endif()
 
     GetUrlExtension(TMP_EXTENSION "${Url}")
@@ -48,37 +44,18 @@
           message(FATAL_ERROR "Error while running the uncompression tool")
         endif()
 
-        set(ARGS ${UncompressArguments})
-        SEPARATE_ARGUMENTS(ARGS)
-        list(LENGTH ARGS TMP_LENGTH)
-
         if ("${TMP_EXTENSION}" STREQUAL "tgz")
           string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}")
         else()
           string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
         endif()
 
-        if (TMP_LENGTH EQUAL 0)
-          execute_process(
-            COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2}
-            WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-            RESULT_VARIABLE Failure
-            OUTPUT_QUIET
-            )
-        else()
-          foreach(SUBDIR ${ARGS})
-            execute_process(
-              COMMAND ${ZIP_EXECUTABLE} x -y "-i!${SUBDIR}" "${TMP_FILENAME2}"
-              WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-              RESULT_VARIABLE Failure
-              OUTPUT_QUIET
-              )
-
-            if (Failure)
-              message(FATAL_ERROR "Error while running the uncompression tool")
-            endif()
-          endforeach()
-        endif()
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
       elseif ("${TMP_EXTENSION}" STREQUAL "zip")
         execute_process(
           COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH}
@@ -93,20 +70,20 @@
     else()
       if ("${TMP_EXTENSION}" STREQUAL "zip")
         execute_process(
-          COMMAND sh -c "unzip -q ${TMP_PATH} ${UncompressArguments}"
+          COMMAND sh -c "unzip -q ${TMP_PATH}"
           WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
           RESULT_VARIABLE Failure
         )
       elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz"))
-        #message("tar xvfz ${TMP_PATH} ${UncompressArguments}")
+        #message("tar xvfz ${TMP_PATH}")
         execute_process(
-          COMMAND sh -c "tar xfz ${TMP_PATH} ${UncompressArguments}"
+          COMMAND sh -c "tar xfz ${TMP_PATH}"
           WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
           RESULT_VARIABLE Failure
           )
       elseif ("${TMP_EXTENSION}" STREQUAL "bz2")
         execute_process(
-          COMMAND sh -c "tar xfj ${TMP_PATH} ${UncompressArguments}"
+          COMMAND sh -c "tar xfj ${TMP_PATH}"
           WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
           RESULT_VARIABLE Failure
           )
@@ -124,4 +101,3 @@
     endif()
   endif()
 endmacro()
-
--- a/Resources/CMake/GoogleLogConfiguration.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/GoogleLogConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,9 @@
 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}" "" "")
+  DownloadPackage(
+    "897fbff90d91ea2b6d6e78c8cea641cc"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/glog-0.3.2.tar.gz"
+    "${GOOGLE_LOG_SOURCES_DIR}")
 
   set(GOOGLE_LOG_HEADERS
     ${GOOGLE_LOG_SOURCES_DIR}/src/glog/logging.h
@@ -94,8 +97,16 @@
       -DNO_FRAME_POINTER=1
       -DGOOGLE_GLOG_DLL_DECL=
       )
+
+    if (${CMAKE_COMPILER_IS_GNUCXX})
+      # This is a patch for MinGW64
+      add_definitions(-D_TIME_H__S=1)
+    endif()
+
   endif()
  
+
+
   add_library(GoogleLog STATIC ${GOOGLE_LOG_SOURCES})
   link_libraries(GoogleLog)
 
--- a/Resources/CMake/GoogleTestConfiguration.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/GoogleTestConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -9,7 +9,10 @@
 
 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}" "" "")
+  DownloadPackage(
+    "4577b49f2973c90bf9ba69aa8166b786"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gtest-1.6.0.zip"
+    "${GTEST_SOURCES_DIR}")
 
   include_directories(
     ${GTEST_SOURCES_DIR}/include
--- a/Resources/CMake/JsonCppConfiguration.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/JsonCppConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -8,8 +8,11 @@
   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}" "" "")
+  SET(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.6.0-rc2)
+  DownloadPackage(
+    "363e2f4cbd3aeb63bf4e571f377400fb"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-src-0.6.0-rc2.tar.gz"
+    "${JSONCPP_SOURCES_DIR}")
 
   list(APPEND THIRD_PARTY_SOURCES
     ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp
--- a/Resources/CMake/LibCurlConfiguration.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/LibCurlConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,9 @@
 if (${STATIC_BUILD})
   SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.26.0)
-  DownloadPackage("http://curl.haxx.se/download/curl-7.26.0.tar.gz" "${CURL_SOURCES_DIR}" "" "")
+  DownloadPackage(
+    "3fa4d5236f2a36ca5c3af6715e837691"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/curl-7.26.0.tar.gz"
+    "${CURL_SOURCES_DIR}")
 
   include_directories(${CURL_SOURCES_DIR}/include)
   AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES)
--- a/Resources/CMake/LibPngConfiguration.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/LibPngConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,9 @@
 if (${STATIC_BUILD})
   SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12)
-  DownloadPackage("http://download.sourceforge.net/libpng/libpng-1.5.12.tar.gz" "${LIBPNG_SOURCES_DIR}" "${LIBPNG_PRELOADED}" "")
+  DownloadPackage(
+    "8ea7f60347a306c5faf70b977fa80e28"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz"
+    "${LIBPNG_SOURCES_DIR}")
 
   include_directories(
     ${LIBPNG_SOURCES_DIR}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/LuaConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,67 @@
+if (STATIC_BUILD OR NOT USE_DYNAMIC_LUA)
+  SET(LUA_SOURCES_DIR ${CMAKE_BINARY_DIR}/lua-5.1.5)
+  DownloadPackage(
+    "2e115fe26e435e33b0d5c022e4490567"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/lua-5.1.5.tar.gz"
+    "${LUA_SOURCES_DIR}")
+
+  add_definitions(
+    #-DLUA_LIB=1
+    #-Dluaall_c=1
+    #-DLUA_COMPAT_ALL=1  # Compile a generic version of Lua
+    )
+
+  include_directories(
+    ${LUA_SOURCES_DIR}/src
+    )
+
+  set(LUA_SOURCES
+    # Core Lua
+    ${LUA_SOURCES_DIR}/src/lapi.c
+    ${LUA_SOURCES_DIR}/src/lcode.c 
+    ${LUA_SOURCES_DIR}/src/ldebug.c 
+    ${LUA_SOURCES_DIR}/src/ldo.c 
+    ${LUA_SOURCES_DIR}/src/ldump.c 
+    ${LUA_SOURCES_DIR}/src/lfunc.c 
+    ${LUA_SOURCES_DIR}/src/lgc.c
+    ${LUA_SOURCES_DIR}/src/llex.c
+    ${LUA_SOURCES_DIR}/src/lmem.c 
+    ${LUA_SOURCES_DIR}/src/lobject.c 
+    ${LUA_SOURCES_DIR}/src/lopcodes.c 
+    ${LUA_SOURCES_DIR}/src/lparser.c
+    ${LUA_SOURCES_DIR}/src/lstate.c 
+    ${LUA_SOURCES_DIR}/src/lstring.c
+    ${LUA_SOURCES_DIR}/src/ltable.c
+    ${LUA_SOURCES_DIR}/src/ltm.c
+    ${LUA_SOURCES_DIR}/src/lundump.c 
+    ${LUA_SOURCES_DIR}/src/lvm.c 
+    ${LUA_SOURCES_DIR}/src/lzio.c
+
+    # Base Lua modules
+    ${LUA_SOURCES_DIR}/src/lauxlib.c
+    ${LUA_SOURCES_DIR}/src/lbaselib.c
+    ${LUA_SOURCES_DIR}/src/ldblib.c
+    ${LUA_SOURCES_DIR}/src/liolib.c
+    ${LUA_SOURCES_DIR}/src/lmathlib.c
+    ${LUA_SOURCES_DIR}/src/loslib.c
+    ${LUA_SOURCES_DIR}/src/ltablib.c
+    ${LUA_SOURCES_DIR}/src/lstrlib.c
+    ${LUA_SOURCES_DIR}/src/loadlib.c
+    ${LUA_SOURCES_DIR}/src/linit.c
+    )
+
+  add_library(Lua STATIC ${LUA_SOURCES})
+  link_libraries(Lua)
+
+  source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*)
+
+else()
+  include(FindLua51)
+
+  if (NOT LUA51_FOUND)
+    message(FATAL_ERROR "Please install the liblua-dev package")
+  endif()
+
+  include_directories(${LUA_INCLUDE_DIR})
+  link_libraries(${LUA_LIBRARIES})
+endif()
--- a/Resources/CMake/MongooseConfiguration.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/MongooseConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,9 @@
 if (STATIC_BUILD OR NOT USE_DYNAMIC_MONGOOSE)
   SET(MONGOOSE_SOURCES_DIR ${CMAKE_BINARY_DIR}/mongoose)
-  DownloadPackage("http://mongoose.googlecode.com/files/mongoose-3.1.tgz" "${MONGOOSE_SOURCES_DIR}" "" "")
+  DownloadPackage(
+    "e718fc287b4eb1bd523be3fa00942bb0"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/mongoose-3.1.tgz"
+    "${MONGOOSE_SOURCES_DIR}")
 
   # Patch mongoose
   execute_process(
@@ -31,6 +34,14 @@
       )
   endif()
 
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    if (${CMAKE_COMPILER_IS_GNUCXX})
+      # This is a patch for MinGW64
+      add_definitions(-D_TIMESPEC_DEFINED=1)
+    endif()
+  endif()
+
   source_group(ThirdParty\\Mongoose REGULAR_EXPRESSION ${MONGOOSE_SOURCES_DIR}/.*)
 
 else()
--- a/Resources/CMake/OpenSslConfiguration.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/OpenSslConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,9 @@
 if (${STATIC_BUILD})
   SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.1c)
-  DownloadPackage("http://www.openssl.org/source/openssl-1.0.1c.tar.gz" "${OPENSSL_SOURCES_DIR}" "" "")
+  DownloadPackage(
+    "ae412727c8c15b67880aef7bd2999b2e"
+    "www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/openssl-1.0.1c.tar.gz"
+    "${OPENSSL_SOURCES_DIR}")
 
   if (NOT EXISTS "${OPENSSL_SOURCES_DIR}/include/PATCHED")
     if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
--- a/Resources/CMake/SQLiteConfiguration.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/SQLiteConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,9 @@
 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}" "" "")
+  DownloadPackage(
+    "5fbeff9645ab035a1f580e90b279a16d"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/sqlite-amalgamation-3071300.zip"
+    "${SQLITE_SOURCES_DIR}")
 
   list(APPEND THIRD_PARTY_SOURCES
     ${SQLITE_SOURCES_DIR}/sqlite3.c
@@ -18,6 +21,7 @@
     )
 
   source_group(ThirdParty\\SQLite REGULAR_EXPRESSION ${SQLITE_SOURCES_DIR}/.*)
+
 else()
   CHECK_INCLUDE_FILE_CXX(sqlite3.h HAVE_SQLITE_H)
   if (NOT HAVE_SQLITE_H)
--- a/Resources/CMake/ZlibConfiguration.cmake	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/CMake/ZlibConfiguration.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -6,7 +6,10 @@
 
 if (${STATIC_BUILD})
   SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7)
-  DownloadPackage("http://zlib.net/zlib-1.2.7.tar.gz" "${ZLIB_SOURCES_DIR}" "${ZLIB_PRELOADED}" "")
+  DownloadPackage(
+    "60df6a37c56e7c1366cca812414f7b85"
+    "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz"
+    "${ZLIB_SOURCES_DIR}")
 
   include_directories(
     ${ZLIB_SOURCES_DIR}
--- a/Resources/Configuration.json	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/Configuration.json	Wed Sep 18 15:27:07 2013 +0200
@@ -1,98 +1,136 @@
 {
-    /**
-     * General configuration of Orthanc
-     **/
+  /**
+   * 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",
+  // The logical name of this instance of Orthanc. This one is
+  // displayed in Orthanc Explorer and at the URI "/system".
+  "Name" : "MyOrthanc",
 
-    // Path to the directory that holds the heavyweight files
-    // (i.e. the raw DICOM instances)
-    "StorageDirectory" : "OrthancStorage",
+  // Path to the directory that holds the heavyweight files
+  // (i.e. the raw DICOM instances)
+  "StorageDirectory" : "OrthancStorage",
 
-    // Path to the directory that holds the SQLite index (if unset,
-    // the value of StorageDirectory is used). This index could be
-    // stored on a RAM-drive or a SSD device for performance reasons.
-    "IndexDirectory" : "OrthancStorage",
+  // Path to the directory that holds the SQLite index (if unset,
+  // the value of StorageDirectory is used). This index could be
+  // stored on a RAM-drive or a SSD device for performance reasons.
+  "IndexDirectory" : "OrthancStorage",
 
-    // Enable the transparent compression of the DICOM instances
-    "StorageCompression" : false,
+  // 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 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,
+  // Maximum number of patients that can be stored at a given time
+  // in the storage (a value of "0" indicates no limit on the number
+  // of patients)
+  "MaximumPatientCount" : 0,
+  
+  // List of paths to the custom Lua scripts to load into this
+  // instance of Orthanc
+  "LuaScripts" : [
+  ],
 
 
-    /**
-     * Configuration of the HTTP server
-     **/
+
+  /**
+   * Configuration of the HTTP server
+   **/
+
+  // HTTP port for the REST services and for the GUI
+  "HttpPort" : 8042,
+
+
 
-    // HTTP port for the REST services and for the GUI
-    "HttpPort" : 8042,
+  /**
+   * Configuration of the DICOM server
+   **/
+
+  // The DICOM Application Entity Title
+  "DicomAet" : "ORTHANC",
+
+  // Check whether the called AET corresponds during a DICOM request
+  "DicomCheckCalledAet" : false,
+
+  // The DICOM port
+  "DicomPort" : 4242,
 
 
 
-    /**
-     * Configuration of the DICOM server
-     **/
+  /**
+   * Security-related options for the HTTP server
+   **/
+
+  // Whether remote hosts can connect to the HTTP server
+  "RemoteAccessAllowed" : false,
+
+  // Whether or not SSL is enabled
+  "SslEnabled" : false,
 
-    // The DICOM Application Entity Title
-    "DicomAet" : "ORTHANC",
+  // Path to the SSL certificate (meaningful only if SSL is enabled)
+  "SslCertificate" : "certificate.pem",
+
+  // Whether or not the password protection is enabled
+  "AuthenticationEnabled" : false,
 
-    // Check whether the called AET corresponds during a DICOM request
-    "DicomCheckCalledAet" : false,
-
-    // The DICOM port
-    "DicomPort" : 4242,
+  // The list of the registered users. Because Orthanc uses HTTP
+  // Basic Authentication, the passwords are stored as plain text.
+  "RegisteredUsers" : {
+    // "alice" : "alicePassword"
+  },
 
 
 
-    /**
-     * Security-related options for the HTTP server
-     **/
+  /**
+   * Network topology
+   **/
 
-    // Whether remote hosts can connect to the HTTP server
-    "RemoteAccessAllowed" : false,
-
-    // Whether or not SSL is enabled
-    "SslEnabled" : false,
+  // The list of the known DICOM modalities
+  "DicomModalities" : {
+    /**
+     * Uncommenting the following line would enable Orthanc to
+     * connect to an instance of the "storescp" open-source DICOM
+     * store (shipped in the DCMTK distribution) started by the
+     * command line "storescp 2000".
+     **/
+    // "sample" : [ "STORESCP", "localhost", 2000 ]
 
-    // Path to the SSL certificate (meaningful only if SSL is enabled)
-    "SslCertificate" : "certificate.pem",
-
-    // Whether or not the password protection is enabled
-    "AuthenticationEnabled" : false,
+    /**
+     * A fourth parameter is available to enable patches for a
+     * specific PACS manufacturer. The allowed values are currently
+     * "Generic" (default value) and "ClearCanvas". This parameter is
+     * case-sensitive.
+     **/
+    // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ]
+  },
 
-    // The list of the registered users. Because Orthanc uses HTTP
-    // Basic Authentication, the passwords are stored as plain text.
-    "RegisteredUsers" : {
-        "alice" : "alicePassword"
-    },
+  // The list of the known Orthanc peers
+  "OrthancPeers" : {
+    /**
+     * Each line gives the base URL of an Orthanc peer, possibly
+     * followed by the username/password pair (if the password
+     * protection is enabled on the peer).
+     **/
+    // "peer"  : [ "http://localhost:8043/", "alice", "alicePassword" ]
+    // "peer2" : [ "http://localhost:8044/" ]
+  },
 
 
 
-    /**
-     * Network topology
-     **/
+  /**
+   * Advanced options
+   **/
 
-    // The list of the known DICOM modalities
-    "DicomModalities" : {
-      /**
-       * Uncommenting the following line would enable Orthanc to
-       * connect to an instance of the "storescp" open-source DICOM
-       * store (shipped in the DCMTK distribution) started by the
-       * command line "storescp 2000".
-       **/
-      // "sample" : [ "STORESCP", "localhost", 2000 ]
-    },
+  // Dictionary of symbolic names for the user-defined metadata. Each
+  // entry must map a number between 1024 and 65535 to an unique
+  // string.
+  "UserMetadata" : {
+    // "Sample" : 1024
+  },
 
-    // The list of the known Orthanc peers (currently unused)
-    "OrthancPeers" : {
-    }
+  // Number of seconds without receiving any instance before a
+  // patient, a study or a series is considered as stable.
+  "StableAge" : 60
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/EclipseCodingStyle.xml	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<profiles version="1">
+<profile kind="CodeFormatterProfile" name="Orthanc" version="1">
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.lineSplit" value="80"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_member_access" value="0"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_base_types" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_constructor_initializer_list" value="0"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_exception_specification" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_base_types" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_access_specifier" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_exception_specification" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_arguments" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.comment.min_distance_between_code_and_line_comment" value="1"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_expressions_in_array_initializer" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_declarator_list" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_bracket" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.tabulation.size" value="2"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_enumerator_list" value="49"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_declarator_list" value="16"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_empty_lines" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_method_declaration" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_arguments" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_base_clause" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.join_wrapped_lines" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_declarator_list" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_arguments_in_method_invocation" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.comment.never_indent_line_comments_on_first_column" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_brackets" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_bracket" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_parameters_in_method_declaration" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_type_declaration" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_arguments" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_expression_list" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_parameters" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.continuation_indentation" value="2"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_expression_list" value="0"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_parameters" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression" value="34"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_extra_spaces" value="0"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_compare_to_type_header" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_namespace_header" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_compact_if" value="16"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_assignment" value="16"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression_chain" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_parameters" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_expression_list" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_exception_specification" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_identifier_in_function_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_base_clause_in_type_declaration" value="80"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_exception_specification" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_declaration_compare_to_template_header" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_body" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_binary_expression" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_block" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_arguments" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_parameters" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.tabulation.char" value="space"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_parameters" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_colon_in_constructor_initializer_list" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block_in_case" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.compact_else_if" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_template_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_base_clause" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_switch" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_overloaded_left_shift_chain" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indentation.size" value="2"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_namespace_declaration" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_arguments" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_array_initializer" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_namespace_declaration" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_bracket" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_while_in_do_statement" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_parameters" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_arguments" value="do not insert"/>
+</profile>
+</profiles>
--- a/Resources/EmbedResources.py	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/EmbedResources.py	Wed Sep 18 15:27:07 2013 +0200
@@ -1,5 +1,5 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+# Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
 # Belgium
 #
 # This program is free software: you can redistribute it and/or
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Patches/dcmtk-mingw64.patch	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,22 @@
+diff -urEb dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h
+--- dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h	2010-12-17 11:50:30.000000000 +0100
++++ dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h	2013-07-19 15:56:25.688996134 +0200
+@@ -196,7 +196,7 @@
+   OFBool popen(const char *command, const char *modes)
+   {
+     if (file_) fclose();
+-#ifdef _WIN32
++#if 0
+     file_ = _popen(command, modes);
+ #else
+     file_ = :: popen(command, modes);
+@@ -258,7 +258,7 @@
+     {
+       if (popened_)
+       {
+-#ifdef _WIN32
++#if 0
+         result = _pclose(file_);
+ #else
+         result = :: pclose(file_);
+Only in dcmtk-3.6.0/ofstd/include/dcmtk/ofstd: offile.h~
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Patches/dcmtk-mingw64.txt	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,1 @@
+diff -urEb dcmtk-3.6.0.orig/ dcmtk-3.6.0 > ../Resources/Patches/dcmtk-mingw64.patch
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/OrthancCppClient/Basic/CMakeLists.txt	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,16 @@
+# Mini-project to check whether "OrthancCppClient" can compile in a
+# standalone fashion
+
+cmake_minimum_required(VERSION 2.8)
+
+project(OrthancCppClientTest)
+
+set(STATIC_BUILD OFF)
+set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../../..)
+
+include(../OrthancCppClient.cmake)
+
+add_executable(Test
+  main.cpp
+  ${ORTHANC_CPP_CLIENT_SOURCES}
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/OrthancCppClient/Basic/main.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,73 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+
+#include <iostream>
+
+#include "../../../../Core/HttpClient.h"
+#include "../../../../OrthancCppClient/OrthancConnection.h"
+
+int main()
+{
+  // Prepare a simple call to a Web service
+  Orthanc::HttpClient c;
+  c.SetUrl("http://nominatim.openstreetmap.org/search?format=json&q=chu+liege+belgium");
+  
+  // Do the request and store the result in a JSON structure
+  Json::Value result;
+  c.Apply(result);
+
+  // Display the JSON answer
+  std::cout << result << std::endl;
+
+  // Display the content of the local Orthanc instance
+  OrthancClient::OrthancConnection orthanc("http://localhost:8042");
+
+  for (unsigned int i = 0; i < orthanc.GetPatientCount(); i++)
+  {
+    OrthancClient::Patient& patient = orthanc.GetPatient(i);
+    std::cout << "Patient: " << patient.GetId() << std::endl;
+
+    for (unsigned int j = 0; j < patient.GetStudyCount(); j++)
+    {
+      OrthancClient::Study& study = patient.GetStudy(j);
+      std::cout << "  Study: " << study.GetId() << std::endl;
+
+      for (unsigned int k = 0; k < study.GetSeriesCount(); k++)
+      {
+        OrthancClient::Series& series = study.GetSeries(k);
+        std::cout << "    Series: " << series.GetId() << std::endl;
+
+        for (unsigned int l = 0; l < series.GetInstanceCount(); l++)
+        {
+          std::cout << "      Instance: " << series.GetInstance(l).GetId() << std::endl;
+        }
+      }
+    }
+  }
+
+  return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/OrthancCppClient/OrthancCppClient.cmake	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,34 @@
+include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/LibCurlConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/LibPngConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
+
+if (${CMAKE_COMPILER_IS_GNUCXX})
+  set(CMAKE_C_FLAGS "-Wall -pedantic -Wno-implicit-function-declaration")  # --std=c99 makes libcurl not to compile
+  set(CMAKE_CXX_FLAGS "-Wall -pedantic -Wno-long-long -Wno-variadic-macros")
+  set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed")
+  set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined")
+elseif (${MSVC})
+  add_definitions(-D_CRT_SECURE_NO_WARNINGS=1)  
+endif()
+
+set(ORTHANC_CPP_CLIENT_SOURCES
+  ${THIRD_PARTY_SOURCES}
+  ${ORTHANC_ROOT}/Core/OrthancException.cpp
+  ${ORTHANC_ROOT}/Core/Enumerations.cpp
+  ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  ${ORTHANC_ROOT}/Core/HttpClient.cpp
+  ${ORTHANC_ROOT}/Core/MultiThreading/ArrayFilledByThreads.cpp
+  ${ORTHANC_ROOT}/Core/MultiThreading/ThreadedCommandProcessor.cpp
+  ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp
+  ${ORTHANC_ROOT}/Core/FileFormats/PngReader.cpp
+  ${ORTHANC_ROOT}/OrthancCppClient/OrthancConnection.cpp
+  ${ORTHANC_ROOT}/OrthancCppClient/Series.cpp
+  ${ORTHANC_ROOT}/OrthancCppClient/Study.cpp
+  ${ORTHANC_ROOT}/OrthancCppClient/Instance.cpp
+  ${ORTHANC_ROOT}/OrthancCppClient/Patient.cpp
+  ${ORTHANC_ROOT}/Resources/sha1/sha1.cpp
+  ${ORTHANC_ROOT}/Resources/md5/md5.c
+  ${ORTHANC_ROOT}/Resources/base64/base64.cpp
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/OrthancCppClient/Vtk/CMakeLists.txt	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(OrthancCppClientTest)
+
+set(STATIC_BUILD OFF)
+set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../../..)
+
+include(../OrthancCppClient.cmake)
+
+find_package(VTK REQUIRED)
+include(${VTK_USE_FILE})
+
+add_executable(Test
+  main.cpp
+  ${ORTHANC_CPP_CLIENT_SOURCES}
+  )
+
+if(VTK_LIBRARIES)
+  target_link_libraries(Test ${VTK_LIBRARIES})
+else()
+  target_link_libraries(Test vtkHybrid vtkVolumeRendering)
+endif()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/OrthancCppClient/Vtk/main.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,196 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+
+#include <iostream>
+
+#include <vtkRenderWindow.h>
+#include <vtkImageData.h>
+#include <vtkPiecewiseFunction.h>
+#include <vtkFixedPointVolumeRayCastMapper.h>
+#include <vtkColorTransferFunction.h>
+#include <vtkVolumeProperty.h>
+#include <vtkRenderWindowInteractor.h>
+#include <vtkRenderer.h>
+#include <vtkSmartPointer.h>
+#include <vtkOpenGLRenderer.h>
+#include <vtkInteractorStyleTrackballCamera.h>
+
+#include "../../../../OrthancCppClient/OrthancConnection.h"
+
+
+class DisplayProgress : public Orthanc::ThreadedCommandProcessor::IListener
+{
+public:
+  virtual void SignalProgress(unsigned int current,
+                              unsigned int total)
+  {
+    std::cout << "Slice loaded (" << current << "/" << total << ")" << std::endl;
+  }
+
+  virtual void SignalSuccess(unsigned int total)
+  {
+    std::cout << "Success loading image (" << total << " images)" << std::endl;
+  }
+
+  virtual void SignalCancel()
+  {
+  }
+
+  virtual void SignalFailure()
+  {
+    std::cout << "Error loading image" << std::endl;
+  }
+};
+
+
+
+void Display(OrthancClient::Series& series)
+{
+  /**
+   * Load the 3D image from Orthanc into VTK.
+   **/
+
+  vtkSmartPointer<vtkImageData> image = vtkSmartPointer<vtkImageData>::New();
+  image->SetDimensions(series.GetWidth(), series.GetHeight(), series.GetInstanceCount());
+  image->SetScalarType(VTK_SHORT);
+  image->AllocateScalars();
+
+  if (series.GetWidth() != 0 &&
+      series.GetHeight() != 0 && 
+      series.GetInstanceCount() != 0)
+  {
+    DisplayProgress listener;
+    series.Load3DImage(image->GetScalarPointer(0, 0, 0), Orthanc::PixelFormat_SignedGrayscale16,
+                       2 * series.GetWidth(), 2 * series.GetHeight() * series.GetWidth(), listener);
+  }
+
+  float sx, sy, sz;
+  series.GetVoxelSize(sx, sy, sz);
+  image->SetSpacing(sx, sy, sz);
+
+
+  /**
+   * The following code is based on the VTK sample for MIP
+   * http://www.vtk.org/Wiki/VTK/Examples/Cxx/VolumeRendering/MinIntensityRendering
+   **/
+
+  // Create a transfer function mapping scalar value to opacity
+  double range[2];
+  image->GetScalarRange(range);
+
+  vtkSmartPointer<vtkPiecewiseFunction> opacityTransfer = 
+    vtkSmartPointer<vtkPiecewiseFunction>::New();
+  opacityTransfer->AddSegment(range[0], 0.0, range[1], 1.0);
+ 
+  vtkSmartPointer<vtkColorTransferFunction> colorTransfer = 
+    vtkSmartPointer<vtkColorTransferFunction>::New();
+  colorTransfer->AddRGBPoint(0, 1.0, 1.0, 1.0);
+  colorTransfer->AddRGBPoint(range[1], 1.0, 1.0, 1.0);
+ 
+  vtkSmartPointer<vtkVolumeProperty> property = 
+    vtkSmartPointer<vtkVolumeProperty>::New();
+  property->SetScalarOpacity(opacityTransfer);
+  property->SetColor(colorTransfer);
+  property->SetInterpolationTypeToLinear();
+
+  // Create a Maximum Intensity Projection rendering
+  vtkSmartPointer<vtkFixedPointVolumeRayCastMapper> mapper = 
+    vtkSmartPointer<vtkFixedPointVolumeRayCastMapper>::New();
+  mapper->SetBlendModeToMaximumIntensity();
+  mapper->SetInput(image);
+
+  vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New();
+  volume->SetMapper(mapper);
+  volume->SetProperty(property);
+  
+  vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkOpenGLRenderer>::New();
+  renderer->AddViewProp(volume);
+  renderer->SetBackground(0.1, 0.2, 0.3); // Background color dark blue
+
+  vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = 
+    vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
+ 
+  vtkSmartPointer<vtkRenderWindow> window = vtkSmartPointer<vtkRenderWindow>::New();
+  window->AddRenderer(renderer); 
+
+  vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
+  interactor->SetRenderWindow(window);
+  interactor->SetInteractorStyle(style);
+  interactor->Start();
+}
+
+
+int main()
+{
+  // Use the commented code below if you know the identifier of a
+  // series that corresponds to a 3D image.
+
+  /*
+     {
+     OrthancClient::OrthancConnection orthanc("http://localhost:8042");
+     OrthancClient::Series series(orthanc, "c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5");
+     Display(series);
+     return 0;
+     }
+  */
+
+
+  // Try and find a 3D image inside the local store
+  OrthancClient::OrthancConnection orthanc("http://localhost:8042");
+
+  for (unsigned int i = 0; i < orthanc.GetPatientCount(); i++)
+  {
+    OrthancClient::Patient& patient = orthanc.GetPatient(i);
+    std::cout << "Patient: " << patient.GetId() << std::endl;
+
+    for (unsigned int j = 0; j < patient.GetStudyCount(); j++)
+    {
+      OrthancClient::Study& study = patient.GetStudy(j);
+      std::cout << "  Study: " << study.GetId() << std::endl;
+
+      for (unsigned int k = 0; k < study.GetSeriesCount(); k++)
+      {
+        OrthancClient::Series& series = study.GetSeries(k);
+        std::cout << "    Series: " << series.GetId() << std::endl;
+
+        if (series.Is3DImage())
+        {
+          Display(series);
+          return 0;
+        }
+        else
+        {
+          std::cout << "      => Not a 3D image..." << std::endl;
+        }
+      }
+    }
+  }
+
+  std::cout << "Unable to find a 3D image in the local Orthanc store" << std::endl;
+
+  return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Python/HighPerformanceAutoRouting.py	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,142 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+
+URL = 'http://localhost:8042'
+TARGET = 'sample'
+
+
+#
+# This sample code shows how to setup a simple, high-performance DICOM
+# auto-routing. All the DICOM instances that arrive inside Orthanc
+# will be sent to a remote modality. A producer-consumer pattern is
+# used. The target modality is specified by the TARGET variable above:
+# It must match an entry in the Orthanc configuration file inside the
+# "DicomModalities" section.
+#
+# NOTE: This sample only works with Orthanc >= 0.5.2. Make sure that
+# Orthanc was built with "-DCMAKE_BUILD_TYPE=Release" to get the best
+# performance.
+#
+
+import Queue
+import sys
+import time
+import threading
+
+import RestToolbox
+
+
+#
+# Queue that is shared between the producer and the consumer
+# threads. It holds the instances that are still to be sent.
+#
+
+queue = Queue.Queue()
+
+
+#
+# The producer thread. It monitors the arrival of new instances into
+# Orthanc, and pushes their ID into the shared queue. This code is
+# based upon the "ChangesLoop.py" sample code.
+#
+
+def Producer(queue):
+    current = 0
+
+    while True:
+        r = RestToolbox.DoGet(URL + '/changes', {
+            'since' : current,
+            'limit' : 4   # Retrieve at most 4 changes at once
+            })
+
+        for change in r['Changes']:
+            # We are only interested interested in the arrival of new instances
+            if change['ChangeType'] == 'NewInstance':
+                queue.put(change['ID'])
+
+        current = r['Last']
+
+        if r['Done']:
+            time.sleep(1)
+
+
+#
+# The consumer thread. It continuously reads the instances from the
+# queue, and send them to the remote modality. Each time a packet of
+# instances is sent, a single DICOM connexion is used, hence improving
+# the performance.
+#
+
+def Consumer(queue):
+    TIMEOUT = 0.1
+    
+    while True:
+        instances = []
+
+        while True:
+            try:
+                # Block for a while, waiting for the arrival of a new
+                # instance
+                instance = queue.get(True, TIMEOUT)
+
+                # A new instance has arrived: Record its ID
+                instances.append(instance)
+                queue.task_done()
+
+            except Queue.Empty:
+                # Timeout: No more data was received
+                break
+
+        if len(instances) > 0:
+            print 'Sending a packet of %d instances' % len(instances)
+            start = time.time()
+
+            # Send all the instances with a single DICOM connexion
+            RestToolbox.DoPost('%s/modalities/sample/store' % URL, instances)
+
+            # Remove all the instances from Orthanc
+            for instance in instances:
+                RestToolbox.DoDelete('%s/instances/%s' % (URL, instance))
+
+            # Clear the log of the exported instances (to prevent the
+            # SQLite database from growing indefinitely)
+            RestToolbox.DoDelete('%s/exports' % URL)
+
+            end = time.time()
+            print 'The packet of %d instances has been sent in %d seconds' % (len(instances), end - start)
+
+
+#
+# Thread to display the progress
+#
+
+def PrintProgress(queue):
+    while True:
+        print 'Current queue size: %d' % (queue.qsize())
+        time.sleep(1)
+
+
+#
+# Start the various threads
+#
+
+progress = threading.Thread(None, PrintProgress, None, (queue, ))
+progress.daemon = True
+progress.start()
+
+producer = threading.Thread(None, Producer, None, (queue, ))
+producer.daemon = True
+producer.start()
+
+consumer = threading.Thread(None, Consumer, None, (queue, ))
+consumer.daemon = True
+consumer.start()
+
+
+#
+# Active waiting for Ctrl-C
+#
+
+while True:
+    time.sleep(0.1)
--- a/Resources/Samples/Python/RestToolbox.py	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/Samples/Python/RestToolbox.py	Wed Sep 18 15:27:07 2013 +0200
@@ -13,7 +13,7 @@
     if _credentials != None:
         h.add_credentials(_credentials[0], _credentials[1])
 
-def DoGet(uri, data = {}):
+def DoGet(uri, data = {}, interpretAsJson = True):
     d = ''
     if len(data.keys()) > 0:
         d = '?' + urlencode(data)
@@ -23,6 +23,8 @@
     resp, content = h.request(uri + d, 'GET')
     if not (resp.status in [ 200 ]):
         raise Exception(resp.status)
+    elif not interpretAsJson:
+        return content
     else:
         try:
             return json.loads(content)
--- a/Resources/Samples/RestApi/CMakeLists.txt	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/Samples/RestApi/CMakeLists.txt	Wed Sep 18 15:27:07 2013 +0200
@@ -22,8 +22,8 @@
   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)
+  CMAKE_ARGS -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DUSE_DYNAMIC_GOOGLE_LOG=OFF -DUSE_DYNAMIC_SQLITE=OFF -DENABLE_SSL=OFF ${TOOLCHAIN}
+  BUILD_COMMAND $(MAKE) CoreLibrary
   INSTALL_COMMAND ""
   BUILD_IN_SOURCE 0
   )
--- a/Resources/Samples/RestApi/Sample.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/Samples/RestApi/Sample.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- a/Resources/Samples/RestApiLinuxDynamic/Sample.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/Resources/Samples/RestApiLinuxDynamic/Sample.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,6 +1,6 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
  * Belgium
  *
  * This program is free software: you can redistribute it and/or
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Toolbox.lua	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,21 @@
+--[[ PrintRecursive(struct, [limit], [indent])   Recursively print arbitrary data. 
+Set limit (default 100) to stanch infinite loops.
+Indents tables as [KEY] VALUE, nested tables as [KEY] [KEY]...[KEY] VALUE
+Set indent ("") to prefix each line:    Mytable [KEY] [KEY]...[KEY] VALUE
+Source: https://gist.github.com/stuby/5445834#file-rprint-lua
+--]]
+
+function PrintRecursive(s, l, i) -- recursive Print (structure, limit, indent)
+   l = (l) or 100; i = i or "";	-- default item limit, indent string
+   if (l<1) then print "ERROR: Item limit reached."; return l-1 end;
+   local ts = type(s);
+   if (ts ~= "table") then print (i,ts,s); return l-1 end
+   print (i,ts);           -- print "table"
+   for k,v in pairs(s) do  -- print "[KEY] VALUE"
+      l = PrintRecursive(v, l, i.."\t["..tostring(k).."]");
+      if (l < 0) then break end
+   end
+   return l
+end	
+
+print('Lua toolbox installed')
--- a/THANKS	Mon Apr 29 12:48:10 2013 +0200
+++ b/THANKS	Wed Sep 18 15:27:07 2013 +0200
@@ -13,6 +13,11 @@
 
 * Cyril Paulus (cyril.paulus@student.ulg.ac.be), for the build process
   and suggestions about the REST API.
+* Will Ryder (will.ryder@sydney.edu.au), for improvements with the
+  handling of series with temporal positions (fMRI and dynamic PET).
+* Ryan Walklin (ryanwalklin@gmail.com), for Mac OS X build.
+* Peter Somlo (peter.somlo@gmail.com), for ClearCanvas support.
+* 12maksqwe@gmail.com, for fixing issue #8.
 
 
 Artwork
@@ -30,4 +35,10 @@
 ------
 
 * Mathieu Malaterre (mathieu.malaterre@gmail.com), for sponsoring Orthanc.
-* Andreas Tille (andreas@an3as.eu), for help about Debian packaging. 
+* Andreas Tille (andreas@an3as.eu), for help about packaging. 
+
+
+Fedora and Red Hat
+------------------
+
+* Mario Ceresa (mrceresa@gmail.com), for help about packaging.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTests/Lua.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,72 @@
+#include "gtest/gtest.h"
+
+#include "../Core/Lua/LuaFunctionCall.h"
+
+
+TEST(Lua, Simple)
+{
+  Orthanc::LuaContext lua;
+  lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
+  lua.Execute("a={}");
+  lua.Execute("a['x'] = 10");
+  lua.Execute("a['y'] = {}");
+  lua.Execute("a['y'][1] = 20");
+  lua.Execute("a['y'][2] = 20");
+  lua.Execute("PrintRecursive(a)");
+
+  lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end");
+
+  Json::Value v, vv, o;
+  //v["a"] = "b";
+  v.append("hello");
+  v.append("world");
+  v.append("42");
+  vv.append("sub");
+  vv.append("set");
+  v.append(vv);
+  o = Json::objectValue;
+  o["x"] = 10;
+  o["y"] = 20;
+  o["z"] = 20.5f;
+  v.append(o);
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushJSON(v);
+    f.Execute();
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJSON(o);
+    ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException);
+  }
+
+  o["bool"] = false;
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJSON(o);
+    ASSERT_FALSE(f.ExecutePredicate());
+  }
+
+  o["bool"] = true;
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJSON(o);
+    ASSERT_TRUE(f.ExecutePredicate());
+  }
+}
+
+
+TEST(Lua, Existing)
+{
+  Orthanc::LuaContext lua;
+  lua.Execute("a={}");
+  lua.Execute("function f() end");
+
+  ASSERT_TRUE(lua.IsExistingFunction("f"));
+  ASSERT_FALSE(lua.IsExistingFunction("a"));
+  ASSERT_FALSE(lua.IsExistingFunction("Dummy"));
+}
--- a/UnitTests/MemoryCache.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/UnitTests/MemoryCache.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -8,46 +8,53 @@
 #include "../Core/Cache/MemoryCache.h"
 
 
-TEST(CacheIndex, Basic)
+TEST(LRU, Basic)
 {
-  Orthanc::CacheIndex<std::string> r;
+  Orthanc::LeastRecentlyUsedIndex<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");
+  r.MakeMostRecent("a");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("b");
+  r.MakeMostRecent("c");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("c");
 
+  ASSERT_EQ("a", r.GetOldest());
   ASSERT_EQ("a", r.RemoveOldest());
+  ASSERT_EQ("b", r.GetOldest());
   ASSERT_EQ("b", r.RemoveOldest());
+  ASSERT_EQ("d", r.GetOldest());
   ASSERT_EQ("d", r.RemoveOldest());
+  ASSERT_EQ("c", r.GetOldest());
   ASSERT_EQ("c", r.RemoveOldest());
 
   ASSERT_TRUE(r.IsEmpty());
+
+  ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException);
+  ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException);
 }
 
 
-TEST(CacheIndex, Payload)
+TEST(LRU, Payload)
 {
-  Orthanc::CacheIndex<std::string, int> r;
+  Orthanc::LeastRecentlyUsedIndex<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");
+  r.MakeMostRecent("a");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("b");
+  r.MakeMostRecent("c");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("c");
 
   ASSERT_TRUE(r.Contains("b"));
   ASSERT_EQ(421, r.Invalidate("b"));
@@ -58,14 +65,76 @@
   ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p);
   ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p);
 
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(420, r.GetOldestPayload());
   ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p);
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(423, r.GetOldestPayload());
   ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p);
+
+  ASSERT_EQ("c", r.GetOldest());
+  ASSERT_EQ(422, r.GetOldestPayload());
   ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p);
 
   ASSERT_TRUE(r.IsEmpty());
 }
 
 
+TEST(LRU, PayloadUpdate)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
+  
+  r.Add("a", 420);
+  r.Add("b", 421);
+  r.Add("d", 423);
+
+  r.MakeMostRecent("a", 424);
+  r.MakeMostRecent("d", 421);
+
+  ASSERT_EQ("b", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(424, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
+
+TEST(LRU, PayloadUpdateBis)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
+  
+  r.AddOrMakeMostRecent("a", 420);
+  r.AddOrMakeMostRecent("b", 421);
+  r.AddOrMakeMostRecent("d", 423);
+  r.AddOrMakeMostRecent("a", 424);
+  r.AddOrMakeMostRecent("d", 421);
+
+  ASSERT_EQ("b", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(424, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
 
 
 namespace
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTests/Png.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -0,0 +1,109 @@
+#include "gtest/gtest.h"
+
+#include <stdint.h>
+#include "../Core/FileFormats/PngReader.h"
+#include "../Core/FileFormats/PngWriter.h"
+
+TEST(PngWriter, ColorPattern)
+{
+  Orthanc::PngWriter w;
+  int width = 17;
+  int height = 61;
+  int pitch = width * 3;
+
+  std::vector<uint8_t> image(height * pitch);
+  for (int y = 0; y < height; y++)
+  {
+    uint8_t *p = &image[0] + y * pitch;
+    for (int x = 0; x < width; x++, p += 3)
+    {
+      p[0] = (y % 3 == 0) ? 255 : 0;
+      p[1] = (y % 3 == 1) ? 255 : 0;
+      p[2] = (y % 3 == 2) ? 255 : 0;
+    }
+  }
+
+  w.WriteToFile("ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]);
+}
+
+TEST(PngWriter, Gray8Pattern)
+{
+  Orthanc::PngWriter w;
+  int width = 17;
+  int height = 256;
+  int pitch = width;
+
+  std::vector<uint8_t> image(height * pitch);
+  for (int y = 0; y < height; y++)
+  {
+    uint8_t *p = &image[0] + y * pitch;
+    for (int x = 0; x < width; x++, p++)
+    {
+      *p = y;
+    }
+  }
+
+  w.WriteToFile("Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]);
+}
+
+TEST(PngWriter, Gray16Pattern)
+{
+  Orthanc::PngWriter w;
+  int width = 256;
+  int height = 256;
+  int pitch = width * 2 + 16;
+
+  std::vector<uint8_t> image(height * pitch);
+
+  int v = 0;
+  for (int y = 0; y < height; y++)
+  {
+    uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch);
+    for (int x = 0; x < width; x++, p++, v++)
+    {
+      *p = v;
+    }
+  }
+
+  w.WriteToFile("Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]);
+}
+
+TEST(PngWriter, EndToEnd)
+{
+  Orthanc::PngWriter w;
+  int width = 256;
+  int height = 256;
+  int pitch = width * 2 + 16;
+
+  std::vector<uint8_t> image(height * pitch);
+
+  int v = 0;
+  for (int y = 0; y < height; y++)
+  {
+    uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch);
+    for (int x = 0; x < width; x++, p++, v++)
+    {
+      *p = v;
+    }
+  }
+
+  std::string s;
+  w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]);
+
+  Orthanc::PngReader r;
+  r.ReadFromMemory(s);
+
+  ASSERT_EQ(r.GetWidth(), width);
+  ASSERT_EQ(r.GetHeight(), height);
+
+  v = 0;
+  for (int y = 0; y < height; y++)
+  {
+    uint16_t *p = reinterpret_cast<uint16_t*>((uint8_t*) r.GetBuffer() + y * r.GetPitch());
+    for (int x = 0; x < width; x++, p++, v++)
+    {
+      ASSERT_EQ(*p, v);
+    }
+  }
+
+}
--- a/UnitTests/PngWriter.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-#include "gtest/gtest.h"
-
-#include <stdint.h>
-#include "../Core/PngWriter.h"
-
-TEST(PngWriter, ColorPattern)
-{
-  Orthanc::PngWriter w;
-  int width = 17;
-  int height = 61;
-  int pitch = width * 3;
-
-  std::vector<uint8_t> image(height * pitch);
-  for (int y = 0; y < height; y++)
-  {
-    uint8_t *p = &image[0] + y * pitch;
-    for (int x = 0; x < width; x++, p += 3)
-    {
-      p[0] = (y % 3 == 0) ? 255 : 0;
-      p[1] = (y % 3 == 1) ? 255 : 0;
-      p[2] = (y % 3 == 2) ? 255 : 0;
-    }
-  }
-
-  w.WriteToFile("ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]);
-}
-
-TEST(PngWriter, Gray8Pattern)
-{
-  Orthanc::PngWriter w;
-  int width = 17;
-  int height = 256;
-  int pitch = width;
-
-  std::vector<uint8_t> image(height * pitch);
-  for (int y = 0; y < height; y++)
-  {
-    uint8_t *p = &image[0] + y * pitch;
-    for (int x = 0; x < width; x++, p++)
-    {
-      *p = y;
-    }
-  }
-
-  w.WriteToFile("Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]);
-}
-
-TEST(PngWriter, Gray16Pattern)
-{
-  Orthanc::PngWriter w;
-  int width = 256;
-  int height = 256;
-  int pitch = width * 2 + 16;
-
-  std::vector<uint8_t> image(height * pitch);
-
-  int v = 0;
-  for (int y = 0; y < height; y++)
-  {
-    uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch);
-    for (int x = 0; x < width; x++, p++, v++)
-    {
-      *p = v;
-    }
-  }
-
-  w.WriteToFile("Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]);
-}
--- a/UnitTests/ServerIndex.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/UnitTests/ServerIndex.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -5,6 +5,7 @@
 
 #include <ctype.h>
 #include <glog/logging.h>
+#include <algorithm>
 
 using namespace Orthanc;
 
@@ -135,10 +136,25 @@
     ASSERT_EQ("e", l.front());
   }
 
+  std::list<MetadataType> md;
+  index.ListAvailableMetadata(md, a[4]);
+  ASSERT_EQ(0u, md.size());
+
   index.AddAttachment(a[4], FileInfo("my json file", FileContentType_Json, 42, CompressionType_Zlib, 21));
   index.AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42));
   index.AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44));
   index.SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE");
+  
+  index.ListAvailableMetadata(md, a[4]);
+  ASSERT_EQ(1u, md.size());
+  ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front());
+  index.SetMetadata(a[4], MetadataType_ModifiedFrom, "TUTU");
+  index.ListAvailableMetadata(md, a[4]);
+  ASSERT_EQ(2u, md.size());
+  index.DeleteMetadata(a[4], MetadataType_ModifiedFrom);
+  index.ListAvailableMetadata(md, a[4]);
+  ASSERT_EQ(1u, md.size());
+  ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front());
 
   ASSERT_EQ(21u + 42u + 44u, index.GetTotalCompressedSize());
   ASSERT_EQ(42u + 42u + 44u, index.GetTotalUncompressedSize());
@@ -417,3 +433,57 @@
   ASSERT_EQ(3u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
   ASSERT_EQ(4u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
 }
+
+
+
+TEST(DatabaseWrapper, LookupTagValue)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  int64_t a[] = {
+    index.CreateResource("a", ResourceType_Study),   // 0
+    index.CreateResource("b", ResourceType_Study),   // 1
+    index.CreateResource("c", ResourceType_Study),   // 2
+    index.CreateResource("d", ResourceType_Series)   // 3
+  };
+
+  DicomMap m;
+  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[0], m);
+  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "1"); index.SetMainDicomTags(a[1], m);
+  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[2], m);
+  m.Clear(); m.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "0"); index.SetMainDicomTags(a[3], m);
+
+  std::list<int64_t> s;
+
+  index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "0");
+  ASSERT_EQ(2u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end());
+
+  index.LookupTagValue(s, "0");
+  ASSERT_EQ(3u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[3]) != s.end());
+
+  index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1");
+  ASSERT_EQ(1u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end());
+
+  index.LookupTagValue(s, "1");
+  ASSERT_EQ(1u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end());
+
+
+  /*{
+      std::list<std::string> s;
+      context.GetIndex().LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1.2.250.1.74.20130819132500.29000036381059");
+      for (std::list<std::string>::iterator i = s.begin(); i != s.end(); i++)
+      {
+        std::cout << "*** " << *i << std::endl;;
+      }      
+      }*/
+
+
+}
--- a/UnitTests/Versions.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/UnitTests/Versions.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -8,6 +8,7 @@
 #include <curl/curl.h>
 #include <boost/version.hpp>
 #include <sqlite3.h>
+#include <lua.h>
 
 
 TEST(Versions, Zlib)
@@ -40,6 +41,14 @@
 }
 
 
+TEST(Versions, Lua)
+{
+  // Ensure that the Lua version is above 5.1.0. This version has
+  // introduced some API changes.
+  ASSERT_GE(LUA_VERSION_NUM, 501);
+}
+
+
 #if ORTHANC_STATIC == 1
 TEST(Versions, ZlibStatic)
 {
@@ -48,7 +57,7 @@
 
 TEST(Versions, BoostStatic)
 {
-  ASSERT_STREQ("1_49", BOOST_LIB_VERSION);
+  ASSERT_STREQ("1_54", BOOST_LIB_VERSION);
 }
 
 TEST(Versions, CurlStatic)
@@ -63,7 +72,7 @@
   ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING);
 }
 
-TEST(Versions, CurlSsl)
+TEST(Versions, CurlSslStatic)
 {
   curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW);
 
@@ -76,4 +85,10 @@
   ASSERT_TRUE(curlSupportsSsl);
 #endif
 }
+
+TEST(Version, LuaStatic)
+{
+  ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE);
+}
 #endif
+
--- a/UnitTests/main.cpp	Mon Apr 29 12:48:10 2013 +0200
+++ b/UnitTests/main.cpp	Wed Sep 18 15:27:07 2013 +0200
@@ -1,16 +1,18 @@
+#include "../Core/EnumerationDictionary.h"
+
 #include "gtest/gtest.h"
 
 #include <ctype.h>
 
 #include "../Core/Compression/ZlibCompressor.h"
 #include "../Core/DicomFormat/DicomTag.h"
-#include "../OrthancCppClient/HttpClient.h"
 #include "../Core/HttpServer/HttpHandler.h"
 #include "../Core/OrthancException.h"
 #include "../Core/Toolbox.h"
 #include "../Core/Uuid.h"
 #include "../OrthancServer/FromDcmtkBridge.h"
 #include "../OrthancServer/OrthancInitialization.h"
+#include "../Core/MultiThreading/SharedMessageQueue.h"
 
 using namespace Orthanc;
 
@@ -29,6 +31,23 @@
   ASSERT_FALSE(Toolbox::IsUuid(""));
   ASSERT_FALSE(Toolbox::IsUuid("012345678901234567890123456789012345"));
   ASSERT_TRUE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-446655440000"));
+  ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-44665544000"));
+  ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000"));
+  ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000 ok"));
+  ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000ok"));
+}
+
+TEST(Toolbox, IsSHA1)
+{
+  ASSERT_FALSE(Toolbox::IsSHA1(""));
+  ASSERT_FALSE(Toolbox::IsSHA1("01234567890123456789012345678901234567890123"));
+  ASSERT_FALSE(Toolbox::IsSHA1("012345678901234567890123456789012345678901234"));
+  ASSERT_TRUE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b9"));
+
+  std::string s;
+  Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog");
+  ASSERT_TRUE(Toolbox::IsSHA1(s));
+  ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s);
 }
 
 TEST(Zlib, Basic)
@@ -318,6 +337,149 @@
 }
 
 
+#if defined(__linux)
+TEST(OrthancInitialization, AbsoluteDirectory)
+{
+  ASSERT_EQ("/tmp/hello", InterpretRelativePath("/tmp", "hello"));
+  ASSERT_EQ("/tmp", InterpretRelativePath("/tmp", "/tmp"));
+}
+#endif
+
+
+
+#include "../OrthancServer/ServerEnumerations.h"
+
+TEST(EnumerationDictionary, Simple)
+{
+  Toolbox::EnumerationDictionary<MetadataType>  d;
+
+  ASSERT_THROW(d.Translate("ReceptionDate"), OrthancException);
+  ASSERT_EQ(MetadataType_ModifiedFrom, d.Translate("5"));
+  ASSERT_EQ(256, d.Translate("256"));
+
+  d.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate");
+
+  ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("ReceptionDate"));
+  ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("2"));
+  ASSERT_EQ("ReceptionDate", d.Translate(MetadataType_Instance_ReceptionDate));
+
+  ASSERT_THROW(d.Add(MetadataType_Instance_ReceptionDate, "Hello"), OrthancException);
+  ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "ReceptionDate"), OrthancException); // already used
+  ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "1024"), OrthancException); // cannot register numbers
+  d.Add(MetadataType_ModifiedFrom, "ModifiedFrom");  // ok
+}
+
+
+TEST(EnumerationDictionary, ServerEnumerations)
+{
+  ASSERT_STREQ("Patient", EnumerationToString(ResourceType_Patient));
+  ASSERT_STREQ("Study", EnumerationToString(ResourceType_Study));
+  ASSERT_STREQ("Series", EnumerationToString(ResourceType_Series));
+  ASSERT_STREQ("Instance", EnumerationToString(ResourceType_Instance));
+
+  ASSERT_STREQ("ModifiedSeries", EnumerationToString(ChangeType_ModifiedSeries));
+
+  ASSERT_STREQ("Failure", EnumerationToString(StoreStatus_Failure));
+  ASSERT_STREQ("Success", EnumerationToString(StoreStatus_Success));
+
+  ASSERT_STREQ("CompletedSeries", EnumerationToString(ChangeType_CompletedSeries));
+
+  ASSERT_EQ("IndexInSeries", EnumerationToString(MetadataType_Instance_IndexInSeries));
+  ASSERT_EQ("LastUpdate", EnumerationToString(MetadataType_LastUpdate));
+
+  ASSERT_EQ(2047, StringToMetadata("2047"));
+  ASSERT_THROW(StringToMetadata("Ceci est un test"), OrthancException);
+  ASSERT_THROW(RegisterUserMetadata(128, ""), OrthancException); // too low (< 1024)
+  ASSERT_THROW(RegisterUserMetadata(128000, ""), OrthancException); // too high (> 65535)
+  RegisterUserMetadata(2047, "Ceci est un test");
+  ASSERT_EQ(2047, StringToMetadata("2047"));
+  ASSERT_EQ(2047, StringToMetadata("Ceci est un test"));
+}
+
+
+
+class DynamicInteger : public IDynamicObject
+{
+private:
+  int value_;
+
+public:
+  DynamicInteger(int value) : value_(value)
+  {
+  }
+
+  int GetValue() const
+  {
+    return value_;
+  }
+};
+
+
+TEST(SharedMessageQueue, Basic)
+{
+  SharedMessageQueue q;
+  ASSERT_TRUE(q.WaitEmpty(0));
+  q.Enqueue(new DynamicInteger(10));
+  ASSERT_FALSE(q.WaitEmpty(1));
+  q.Enqueue(new DynamicInteger(20));
+  q.Enqueue(new DynamicInteger(30));
+  q.Enqueue(new DynamicInteger(40));
+
+  std::auto_ptr<DynamicInteger> i;
+  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue());
+  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue());
+  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue());
+  ASSERT_FALSE(q.WaitEmpty(1));
+  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue());
+  ASSERT_TRUE(q.WaitEmpty(0));
+  ASSERT_EQ(NULL, q.Dequeue(1));
+}
+
+
+TEST(SharedMessageQueue, Clean)
+{
+  try
+  {
+    SharedMessageQueue q;
+    q.Enqueue(new DynamicInteger(10));
+    q.Enqueue(new DynamicInteger(20));  
+    throw OrthancException("Nope");
+  }
+  catch (OrthancException&)
+  {
+  }
+}
+
+
+TEST(Toolbox, WriteFile)
+{
+  std::string path;
+
+  {
+    Toolbox::TemporaryFile tmp;
+    path = tmp.GetPath();
+
+    std::string s;
+    s.append("Hello");
+    s.push_back('\0');
+    s.append("World");
+    ASSERT_EQ(11u, s.size());
+
+    Toolbox::WriteFile(s, path.c_str());
+
+    std::string t;
+    Toolbox::ReadFile(t, path.c_str());
+
+    ASSERT_EQ(11u, t.size());
+    ASSERT_EQ(0, t[5]);
+    ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size()));
+  }
+
+  std::string u;
+  ASSERT_THROW(Toolbox::ReadFile(u, path.c_str()), OrthancException);
+}
+
+
 int main(int argc, char **argv)
 {
   // Initialize Google's logging library.
@@ -327,6 +489,8 @@
   // Go to trace-level verbosity
   //FLAGS_v = 1;
 
+  Toolbox::DetectEndianness();
+
   google::InitGoogleLogging("Orthanc");
 
   OrthancInitialize();