changeset 1308:f7966e9950e4 db-changes

integration mainline->db-changes
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 11 Feb 2015 10:32:14 +0100
parents 178de5edc0a8 (current diff) f796207e3df1 (diff)
children 8f4487d8f79e
files Plugins/OrthancCPlugin/OrthancCPlugin.h Resources/CMake/PlustacheConfiguration.cmake Resources/CMake/PlustacheConfiguration.patch UnitTestsSources/PlustacheTests.cpp
diffstat 309 files changed, 5934 insertions(+), 3677 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.travis.yml	Wed Feb 11 10:32:14 2015 +0100
@@ -0,0 +1,68 @@
+language: cpp
+
+env:
+  - TRAVIS_MINGW=OFF
+  - TRAVIS_MINGW=ON
+
+compiler:
+  - gcc
+  - clang
+
+os:
+  - osx
+  - linux
+
+osx_image: xcode61
+
+matrix:
+  exclude:
+    # This excludes OSX builds from the build matrix for gcc
+    - os: osx
+      compiler: gcc
+
+    # Do not compile for OS X or clang when MinGW is enabled
+    - os: osx
+      env: TRAVIS_MINGW=ON
+    - compiler: clang
+      env: TRAVIS_MINGW=ON
+
+before_install:
+  - if [ $TRAVIS_OS_NAME == linux ]; then sudo apt-get update -qq && sudo apt-get install
+    -qq build-essential unzip cmake mercurial uuid-dev libcurl4-openssl-dev liblua5.1-0-dev
+    libgtest-dev libpng-dev libsqlite3-dev libssl-dev zlib1g-dev libdcmtk2-dev libwrap0-dev
+    libcharls-dev; fi
+  - if [ $TRAVIS_OS_NAME == linux -a $TRAVIS_MINGW == ON ]; then sudo apt-get install mingw32; fi
+
+
+before_script:
+  - mkdir Build
+  - cd Build
+  - if [ $TRAVIS_OS_NAME == linux -a $TRAVIS_MINGW == OFF ]; then cmake
+    -DCMAKE_BUILD_TYPE=Debug "-DDCMTK_LIBRARIES=CharLS;dcmjpls;wrap;oflog"
+    -DALLOW_DOWNLOADS=ON -DUSE_SYSTEM_BOOST=OFF -DUSE_SYSTEM_MONGOOSE=OFF -DUSE_SYSTEM_JSONCPP=OFF
+    -DUSE_SYSTEM_GOOGLE_LOG=OFF -DUSE_SYSTEM_PUGIXML=OFF -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON
+    ..; fi
+  - if [ $TRAVIS_OS_NAME == linux -a $TRAVIS_MINGW == ON ]; then cmake
+    -DCMAKE_BUILD_TYPE=Debug -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DALLOW_DOWNLOADS=ON
+    -DCMAKE_TOOLCHAIN_FILE=Resources/MinGWToolchain.cmake
+    ..; fi
+  - if [ $TRAVIS_OS_NAME == osx ]; then cmake 
+    -DCMAKE_BUILD_TYPE=Debug -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DALLOW_DOWNLOADS=ON
+    ..; fi
+
+script: make && if [ $TRAVIS_MINGW == OFF ]; then ./UnitTests; fi
+
+#script: cp ../README Orthanc
+#deploy:
+#  provider: releases
+#  api_key:
+#    secure: WU+niKLAKMoJHST5EK23BayK4qXSrXELKlJYc8wRjMO4ay1KSgvzlY2UGKeW1EPClBfZZ0Uh5VKF8l34exsfirFuwCX2qceozduZproUszZ4Z88X8wt8Ctu8tBuuKLZYFc9iNH4zw+QZyRuPyXK9iWpS0L9O20pqy5upTsagM3o=
+#  file_glob: true
+#  file:
+#    - 'Build/Orthanc'
+#    - 'Build/UnitTests'
+#    - 'BuildMinGW32/Orthanc.exe'
+#    - 'BuildMinGW32/UnitTests.exe'
+#  skip_cleanup: true
+#  on:
+#    all_branches: true
--- a/AUTHORS	Tue Nov 04 13:56:17 2014 +0100
+++ b/AUTHORS	Wed Feb 11 10:32:14 2015 +0100
@@ -6,7 +6,9 @@
 ------------------
 
 * Sebastien Jodogne <s.jodogne@gmail.com>
-  Department of Medical Physics, CHU of Liege, Belgium
+  Department of Medical Physics
+  University Hospital of Liege
+  Belgium
 
   Overall design and lead developer.
 
--- a/CMakeLists.txt	Tue Nov 04 13:56:17 2014 +0100
+++ b/CMakeLists.txt	Wed Feb 11 10:32:14 2015 +0100
@@ -37,9 +37,7 @@
 SET(USE_SYSTEM_PUGIXML ON CACHE BOOL "Use the system version of Pugixml)")
 
 # Experimental options
-SET(USE_PLUSTACHE OFF CACHE BOOL "Use the Plustache templating engine (experimental)")
 SET(USE_PUGIXML ON CACHE BOOL "Use the Pugixml parser (turn off only for debug)")
-SET(USE_SYSTEM_PLUSTACHE OFF CACHE BOOL "Use the system version of Plustache (experimental)")
 
 # Distribution-specific settings
 SET(USE_GTEST_DEBIAN_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
@@ -166,6 +164,7 @@
   OrthancServer/ServerToolbox.cpp
   OrthancServer/OrthancFindRequestHandler.cpp
   OrthancServer/OrthancMoveRequestHandler.cpp
+  OrthancServer/ExportedResource.cpp
 
   # From "lua-scripting" branch
   OrthancServer/DicomInstanceToStore.cpp
@@ -198,7 +197,16 @@
   UnitTestsSources/ImageProcessingTests.cpp
   UnitTestsSources/JpegLosslessTests.cpp
   UnitTestsSources/PluginsTests.cpp
-  UnitTestsSources/PlustacheTests.cpp
+  )
+
+
+set(ORTHANC_EMBEDDED_FILES
+  PREPARE_DATABASE            ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql
+  UPGRADE_DATABASE_3_TO_4     ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade3To4.sql
+  UPGRADE_DATABASE_4_TO_5     ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade4To5.sql
+  CONFIGURATION_SAMPLE        ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json
+  DICOM_CONFORMANCE_STATEMENT ${CMAKE_CURRENT_SOURCE_DIR}/Resources/DicomConformanceStatement.txt
+  LUA_TOOLBOX                 ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua
   )
 
 
@@ -229,7 +237,6 @@
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/LuaConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibCurlConfiguration.cmake)
-include(${CMAKE_SOURCE_DIR}/Resources/CMake/PlustacheConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/PugixmlConfiguration.cmake)
 
 
@@ -260,21 +267,11 @@
 ## Autogeneration of files
 #####################################################################
 
-# Prepare the embedded files
-set(EMBEDDED_FILES
-  PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql
-  UPGRADE_DATABASE_3_TO_4 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade3To4.sql
-  UPGRADE_DATABASE_4_TO_5 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade4To5.sql
-  CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json
-  DICOM_CONFORMANCE_STATEMENT ${CMAKE_CURRENT_SOURCE_DIR}/Resources/DicomConformanceStatement.txt
-  LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua
-  )
-
 if (${STANDALONE_BUILD})
   # We embed all the resources in the binaries for standalone builds
   add_definitions(-DORTHANC_STANDALONE=1)
   EmbedResources(
-    ${EMBEDDED_FILES}
+    ${ORTHANC_EMBEDDED_FILES}
     ORTHANC_EXPLORER ${CMAKE_CURRENT_SOURCE_DIR}/OrthancExplorer
     ${DCMTK_DICTIONARIES}
     )
@@ -284,7 +281,7 @@
     -DORTHANC_PATH=\"${CMAKE_SOURCE_DIR}\"
     )
   EmbedResources(
-    ${EMBEDDED_FILES}
+    ${ORTHANC_EMBEDDED_FILES}
     )
 endif()
 
@@ -479,7 +476,7 @@
   install(
     FILES
     ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h 
-    ${ORTHANC_ROOT}/Plugins/OrthancCPlugin/OrthancCPlugin.h 
+    ${ORTHANC_ROOT}/Plugins/Include/OrthancCPlugin.h 
     DESTINATION include/orthanc
     )
 endif()
--- a/Core/Cache/ICachePageProvider.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Cache/ICachePageProvider.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Cache/LeastRecentlyUsedIndex.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Cache/LeastRecentlyUsedIndex.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Cache/MemoryCache.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Cache/MemoryCache.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Cache/MemoryCache.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Cache/MemoryCache.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ChunkedBuffer.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ChunkedBuffer.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ChunkedBuffer.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ChunkedBuffer.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Compression/BufferCompressor.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Compression/BufferCompressor.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Compression/BufferCompressor.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Compression/BufferCompressor.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Compression/HierarchicalZipWriter.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Compression/HierarchicalZipWriter.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Compression/HierarchicalZipWriter.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Compression/HierarchicalZipWriter.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -114,6 +114,16 @@
       return writer_.GetCompressionLevel();
     }
 
+    void SetAppendToExisting(bool append)
+    {
+      writer_.SetAppendToExisting(append);
+    }
+    
+    bool IsAppendToExisting() const
+    {
+      return writer_.IsAppendToExisting();
+    }
+    
     void OpenFile(const char* name);
 
     void OpenDirectory(const char* name);
--- a/Core/Compression/ZipWriter.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Compression/ZipWriter.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -38,10 +38,11 @@
 
 #include "ZipWriter.h"
 
-#include "../../Resources/ThirdParty/minizip/zip.h"
+#include <limits>
+#include <boost/filesystem.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
-#include <limits>
 
+#include "../../Resources/ThirdParty/minizip/zip.h"
 #include "../OrthancException.h"
 
 
@@ -77,15 +78,19 @@
   struct ZipWriter::PImpl
   {
     zipFile file_;
+
+    PImpl() : file_(NULL)
+    {
+    }
   };
 
-  ZipWriter::ZipWriter() : pimpl_(new PImpl)
+  ZipWriter::ZipWriter() :
+    pimpl_(new PImpl),
+    isZip64_(false),
+    hasFileInZip_(false),
+    append_(false),
+    compressionLevel_(6)
   {
-    compressionLevel_ = 6;
-    hasFileInZip_ = false;
-    isZip64_ = false;
-
-    pimpl_->file_ = NULL;
   }
 
   ZipWriter::~ZipWriter()
@@ -122,13 +127,20 @@
 
     hasFileInZip_ = false;
 
+    int mode = APPEND_STATUS_CREATE;
+    if (append_ && 
+        boost::filesystem::exists(path_))
+    {
+      mode = APPEND_STATUS_ADDINZIP;
+    }
+
     if (isZip64_)
     {
-      pimpl_->file_ = zipOpen64(path_.c_str(), APPEND_STATUS_CREATE);
+      pimpl_->file_ = zipOpen64(path_.c_str(), mode);
     }
     else
     {
-      pimpl_->file_ = zipOpen(path_.c_str(), APPEND_STATUS_CREATE);
+      pimpl_->file_ = zipOpen(path_.c_str(), mode);
     }
 
     if (!pimpl_->file_)
@@ -230,4 +242,13 @@
       length -= bytes;
     }
   }
+
+
+  void ZipWriter::SetAppendToExisting(bool append)
+  {
+    Close();
+    append_ = append;
+  }
+    
+
 }
--- a/Core/Compression/ZipWriter.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Compression/ZipWriter.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -50,6 +50,7 @@
 
     bool isZip64_;
     bool hasFileInZip_;
+    bool append_;
     uint8_t compressionLevel_;
     std::string path_;
 
@@ -71,6 +72,13 @@
     {
       return compressionLevel_;
     }
+
+    void SetAppendToExisting(bool append);
+    
+    bool IsAppendToExisting() const
+    {
+      return append_;
+    }
     
     void Open();
 
--- a/Core/Compression/ZlibCompressor.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Compression/ZlibCompressor.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Compression/ZlibCompressor.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Compression/ZlibCompressor.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomArray.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomArray.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomArray.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomArray.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomElement.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomElement.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomImageInformation.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomImageInformation.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomImageInformation.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomImageInformation.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomInstanceHasher.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomInstanceHasher.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -48,8 +48,7 @@
     seriesUid_ = seriesUid;
     instanceUid_ = instanceUid;
 
-    if (patientId_.size() == 0 ||
-        studyUid_.size() == 0 ||
+    if (studyUid_.size() == 0 ||
         seriesUid_.size() == 0 ||
         instanceUid_.size() == 0)
     {
@@ -59,7 +58,9 @@
 
   DicomInstanceHasher::DicomInstanceHasher(const DicomMap& instance)
   {
-    Setup(instance.GetValue(DICOM_TAG_PATIENT_ID).AsString(),
+    const DicomValue* patientId = instance.TestAndGetValue(DICOM_TAG_PATIENT_ID);
+
+    Setup(patientId == NULL ? "" : patientId->AsString(),
           instance.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString(),
           instance.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString(),
           instance.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString());
--- a/Core/DicomFormat/DicomInstanceHasher.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomInstanceHasher.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomMap.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomMap.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomMap.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomMap.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -125,11 +125,13 @@
 
     const DicomValue& GetValue(const DicomTag& tag) const;
 
+    // DO NOT delete the returned value!
     const DicomValue* TestAndGetValue(uint16_t group, uint16_t element) const
     {
       return TestAndGetValue(DicomTag(group, element));
     }       
 
+    // DO NOT delete the returned value!
     const DicomValue* TestAndGetValue(const DicomTag& tag) const;
 
     void Remove(const DicomTag& tag);
--- a/Core/DicomFormat/DicomNullValue.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomNullValue.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomString.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomString.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/DicomFormat/DicomTag.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomTag.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -119,14 +119,14 @@
 
 
   void DicomTag::GetTagsForModule(std::set<DicomTag>& target,
-                                  ResourceType module)
+                                  DicomModule module)
   {
     // REFERENCE: 11_03pu.pdf, DICOM PS 3.3 2011 - Information Object Definitions
     target.clear();
 
     switch (module)
     {
-      case ResourceType_Patient:
+      case DicomModule_Patient:
         // This is Table C.7-1 "Patient Module Attributes" (p. 373)
         target.insert(DicomTag(0x0010, 0x0010));   // Patient's name
         target.insert(DicomTag(0x0010, 0x0020));   // Patient ID
@@ -156,7 +156,7 @@
         target.insert(DicomTag(0x0010, 0x0024));   // Issuer of Patient ID qualifiers sequence
         break;
 
-      case ResourceType_Study:
+      case DicomModule_Study:
         // This is Table C.7-3 "General Study Module Attributes" (p. 378)
         target.insert(DicomTag(0x0020, 0x000d));   // Study instance UID
         target.insert(DicomTag(0x0008, 0x0020));   // Study date
@@ -177,7 +177,7 @@
         target.insert(DicomTag(0x0040, 0x1012));   // Reason for performed procedure code sequence
         break;
 
-      case ResourceType_Series:
+      case DicomModule_Series:
         // This is Table C.7-5 "General Series Module Attributes" (p. 385)
         target.insert(DicomTag(0x0008, 0x0060));   // Modality 
         target.insert(DicomTag(0x0020, 0x000e));   // Series Instance UID 
@@ -210,7 +210,7 @@
         target.insert(DicomTag(0x0040, 0x0280));   // Comments on the Performed Procedure Step
         break;
 
-      case ResourceType_Instance:
+      case DicomModule_Instance:
         // This is Table C.12-1 "SOP Common Module Attributes" (p. 1207)
         target.insert(DicomTag(0x0008, 0x0016));   // SOP Class UID
         target.insert(DicomTag(0x0008, 0x0018));   // SOP Instance UID 
--- a/Core/DicomFormat/DicomTag.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomTag.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -85,7 +85,7 @@
     friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag);
 
     static void GetTagsForModule(std::set<DicomTag>& target,
-                                 ResourceType module);
+                                 DicomModule module);
 
     bool IsIdentifier() const;
   };
@@ -109,10 +109,11 @@
 
   static const DicomTag DICOM_TAG_PATIENT_NAME(0x0010, 0x0010);
 
-  // The following is used for "modify" operations
+  // The following is used for "modify/anonymize" operations
   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);
+  static const DicomTag DICOM_TAG_DEIDENTIFICATION_METHOD(0x0012, 0x0063);
 
   // DICOM tags used for fMRI (thanks to Will Ryder)
   static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105);
--- a/Core/DicomFormat/DicomValue.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/DicomFormat/DicomValue.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/EnumerationDictionary.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/EnumerationDictionary.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Enumerations.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Enumerations.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Enumerations.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Enumerations.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -58,6 +58,7 @@
     ErrorCode_BadRequest,
     ErrorCode_NetworkProtocol,
     ErrorCode_SystemCommand,
+    ErrorCode_Database,
 
     // Specific error codes
     ErrorCode_UriSyntax,
@@ -273,6 +274,15 @@
     PhotometricInterpretation_Unknown
   };
 
+  enum DicomModule
+  {
+    DicomModule_Patient,
+    DicomModule_Study,
+    DicomModule_Series,
+    DicomModule_Instance,
+    DicomModule_Image
+  };
+
 
   /**
    * WARNING: Do not change the explicit values in the enumerations
--- a/Core/FileStorage/CompressedFileStorageAccessor.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/FileStorage/CompressedFileStorageAccessor.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/FileStorage/CompressedFileStorageAccessor.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/FileStorage/FileInfo.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/FileStorage/FileInfo.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/FileStorage/FileStorageAccessor.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/FileStorage/FileStorageAccessor.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/FileStorage/FileStorageAccessor.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/FileStorage/FileStorageAccessor.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/FileStorage/FilesystemStorage.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/FileStorage/FilesystemStorage.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -139,7 +139,7 @@
 
   void FilesystemStorage::Read(std::string& content,
                                const std::string& uuid,
-                               FileContentType /*type*/) const
+                               FileContentType /*type*/)
   {
     content.clear();
     Toolbox::ReadFile(content, GetPath(uuid).string());
--- a/Core/FileStorage/FilesystemStorage.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/FileStorage/FilesystemStorage.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -60,7 +60,7 @@
 
     virtual void Read(std::string& content,
                       const std::string& uuid,
-                      FileContentType type) const;
+                      FileContentType type);
 
     virtual void Remove(const std::string& uuid,
                         FileContentType type);
--- a/Core/FileStorage/IStorageArea.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/FileStorage/IStorageArea.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -52,7 +52,7 @@
 
     virtual void Read(std::string& content,
                       const std::string& uuid,
-                      FileContentType type) const = 0;
+                      FileContentType type) = 0;
 
     virtual void Remove(const std::string& uuid,
                         FileContentType type) = 0;
--- a/Core/FileStorage/StorageAccessor.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/FileStorage/StorageAccessor.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/FileStorage/StorageAccessor.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/FileStorage/StorageAccessor.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpClient.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpClient.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -171,6 +171,7 @@
     CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, NULL));
     CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL));
     CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, NULL));
 
     // Set timeouts
     if (timeout_ <= 0)
@@ -189,6 +190,11 @@
       CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, credentials_.c_str()));
     }
 
+    if (proxy_.size() != 0)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, proxy_.c_str()));
+    }
+
     switch (method_)
     {
     case HttpMethod_Get:
--- a/Core/HttpClient.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpClient.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -53,6 +53,7 @@
     std::string postData_;
     bool isVerbose_;
     long timeout_;
+    std::string proxy_;
 
     void Setup();
 
@@ -134,6 +135,11 @@
     void SetCredentials(const char* username,
                         const char* password);
 
+    void SetProxy(const std::string& proxy)
+    {
+      proxy_ = proxy;
+    }
+
     static void GlobalInitialize();
   
     static void GlobalFinalize();
--- a/Core/HttpServer/BufferHttpSender.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/BufferHttpSender.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/FilesystemHttpHandler.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/FilesystemHttpHandler.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/FilesystemHttpHandler.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/FilesystemHttpHandler.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/FilesystemHttpSender.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/FilesystemHttpSender.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/FilesystemHttpSender.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/HttpFileSender.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/HttpFileSender.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/HttpFileSender.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/HttpFileSender.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/HttpHandler.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/HttpHandler.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/HttpHandler.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/HttpHandler.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/HttpOutput.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/HttpOutput.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/HttpOutput.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/HttpOutput.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/IHttpOutputStream.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/IHttpOutputStream.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/HttpServer/MongooseServer.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/MongooseServer.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -418,7 +418,8 @@
     if (auth != headers.end())
     {
       std::string s = auth->second;
-      if (s.substr(0, 6) == "Basic ")
+      if (s.size() > 6 &&
+          s.substr(0, 6) == "Basic ")
       {
         std::string b64 = s.substr(6);
         granted = that.IsValidBasicHttpAuthentication(b64);
@@ -439,7 +440,8 @@
     }
 
     std::string s = auth->second;
-    if (s.substr(0, 6) != "Basic ")
+    if (s.size() <= 6 ||
+        s.substr(0, 6) != "Basic ")
     {
       return "";
     }
--- a/Core/HttpServer/MongooseServer.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/HttpServer/MongooseServer.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ICommand.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ICommand.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/IDynamicObject.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/IDynamicObject.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ImageFormats/ImageAccessor.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ImageFormats/ImageAccessor.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ImageFormats/ImageAccessor.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ImageFormats/ImageAccessor.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ImageFormats/ImageBuffer.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ImageFormats/ImageBuffer.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ImageFormats/ImageBuffer.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ImageFormats/ImageBuffer.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ImageFormats/ImageProcessing.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ImageFormats/ImageProcessing.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ImageFormats/ImageProcessing.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ImageFormats/ImageProcessing.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ImageFormats/PngReader.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ImageFormats/PngReader.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ImageFormats/PngReader.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ImageFormats/PngReader.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ImageFormats/PngWriter.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ImageFormats/PngWriter.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/ImageFormats/PngWriter.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/ImageFormats/PngWriter.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Lua/LuaContext.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Lua/LuaContext.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Lua/LuaContext.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Lua/LuaContext.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -102,5 +102,10 @@
     {
       httpClient_.SetCredentials(username, password);
     }
+
+    void SetHttpProxy(const std::string& proxy)
+    {
+      httpClient_.SetProxy(proxy);
+    }
   };
 }
--- a/Core/Lua/LuaException.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Lua/LuaException.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Lua/LuaFunctionCall.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Lua/LuaFunctionCall.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Lua/LuaFunctionCall.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Lua/LuaFunctionCall.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/ArrayFilledByThreads.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/ArrayFilledByThreads.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/BagOfRunnablesBySteps.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/BagOfRunnablesBySteps.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/ILockable.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/ILockable.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/IRunnableBySteps.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/IRunnableBySteps.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/Locker.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/Locker.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/Mutex.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/Mutex.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/Mutex.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/Mutex.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/ReaderWriterLock.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/ReaderWriterLock.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/ReaderWriterLock.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/ReaderWriterLock.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/Semaphore.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/Semaphore.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/Semaphore.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/Semaphore.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/SharedMessageQueue.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/SharedMessageQueue.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -33,11 +33,40 @@
 #include "../PrecompiledHeaders.h"
 #include "SharedMessageQueue.h"
 
+
+
+/**
+ * FIFO (queue):
+ * 
+ *            back                         front
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ * Enqueue -> |  |  |  |  |  |  |  |  |  |  |  |
+ *            |  |  |  |  |  |  |  |  |  |  |  | -> Dequeue
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *                                            ^
+ *                                            |
+ *                                      Make room here
+ *
+ *
+ * LIFO (stack):
+ * 
+ *            back                         front
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *            |  |  |  |  |  |  |  |  |  |  |  | <- Enqueue
+ *            |  |  |  |  |  |  |  |  |  |  |  | -> Dequeue
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *              ^
+ *              |
+ *        Make room here
+ **/
+
+
 namespace Orthanc
 {
-  SharedMessageQueue::SharedMessageQueue(unsigned int maxSize)
+  SharedMessageQueue::SharedMessageQueue(unsigned int maxSize) :
+    isFifo_(true),
+    maxSize_(maxSize)
   {
-    maxSize_ = maxSize;
   }
 
 
@@ -56,12 +85,31 @@
 
     if (maxSize_ != 0 && queue_.size() > maxSize_)
     {
-      // Too many elements in the queue: First remove the oldest
-      delete queue_.front();
-      queue_.pop_front();
+      if (isFifo_)
+      {
+        // Too many elements in the queue: Make room
+        delete queue_.front();
+        queue_.pop_front();
+      }
+      else
+      {
+        // Too many elements in the stack: Make room
+        delete queue_.back();
+        queue_.pop_back();
+      }
     }
 
-    queue_.push_back(message);
+    if (isFifo_)
+    {
+      // Queue policy (FIFO)
+      queue_.push_back(message);
+    }
+    else
+    {
+      // Stack policy (LIFO)
+      queue_.push_front(message);
+    }
+
     elementAvailable_.notify_one();
   }
 
@@ -124,4 +172,17 @@
 
     return true;
   }
+
+
+  void SharedMessageQueue::SetFifoPolicy()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    isFifo_ = true;
+  }
+
+  void SharedMessageQueue::SetLifoPolicy()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    isFifo_ = false;
+  }
 }
--- a/Core/MultiThreading/SharedMessageQueue.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/SharedMessageQueue.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -45,6 +45,7 @@
   private:
     typedef std::list<IDynamicObject*>  Queue;
 
+    bool isFifo_;
     unsigned int maxSize_;
     Queue queue_;
     boost::mutex mutex_;
@@ -63,5 +64,19 @@
     IDynamicObject* Dequeue(int32_t millisecondsTimeout);
 
     bool WaitEmpty(int32_t millisecondsTimeout);
+
+    bool IsFifoPolicy() const
+    {
+      return isFifo_;
+    }
+
+    bool IsLifoPolicy() const
+    {
+      return !isFifo_;
+    }
+
+    void SetFifoPolicy();
+
+    void SetLifoPolicy();
   };
 }
--- a/Core/MultiThreading/ThreadedCommandProcessor.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/ThreadedCommandProcessor.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/MultiThreading/ThreadedCommandProcessor.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/MultiThreading/ThreadedCommandProcessor.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/OrthancException.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/OrthancException.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -130,6 +130,9 @@
       case ErrorCode_Plugin:
         return "Error encountered inside a plugin";
 
+      case ErrorCode_Database:
+        return "Error with the database engine";
+
       case ErrorCode_Custom:
       default:
         return "???";
--- a/Core/OrthancException.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/OrthancException.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/PrecompiledHeaders.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/PrecompiledHeaders.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/PrecompiledHeaders.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/PrecompiledHeaders.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApi.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApi.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApi.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApi.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiCall.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiCall.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiCall.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiCall.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiDeleteCall.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiDeleteCall.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiGetCall.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiGetCall.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiGetCall.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiGetCall.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiHierarchy.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiHierarchy.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -159,7 +159,7 @@
   void RestApiHierarchy::DeleteChildren(Children& children)
   {
     for (Children::iterator it = children.begin();
-         it != children.end(); it++)
+         it != children.end(); ++it)
     {
       delete it->second;
     }
@@ -238,7 +238,7 @@
 
       // Try and go down in the hierarchy, using wildcard rules for children
       for (child = wildcardChildren_.begin();
-           child != wildcardChildren_.end(); child++)
+           child != wildcardChildren_.end(); ++child)
       {
         HttpHandler::Arguments subComponents = components;
         subComponents[child->first] = uri[level];
@@ -276,7 +276,7 @@
   bool RestApiHierarchy::CanGenerateDirectory() const
   {
     return (universalHandlers_.IsEmpty() &&
-            wildcardChildren_.size() == 0);
+            wildcardChildren_.empty());
   }
 
 
@@ -291,7 +291,7 @@
         result = Json::arrayValue;
 
         for (Children::const_iterator it = children_.begin();
-             it != children_.end(); it++)
+             it != children_.end(); ++it)
         {
           result.append(it->first);
         }
@@ -314,7 +314,7 @@
     }
 
     for (child = wildcardChildren_.begin(); 
-         child != wildcardChildren_.end(); child++)
+         child != wildcardChildren_.end(); ++child)
     {
       if (child->second->GetDirectory(result, uri, level + 1))
       {
@@ -388,13 +388,13 @@
       target = s;*/
       
     for (Children::const_iterator it = children_.begin();
-         it != children_.end(); it++)
+         it != children_.end(); ++it)
     {
       it->second->CreateSiteMap(target[it->first]);
     }
       
     for (Children::const_iterator it = wildcardChildren_.begin();
-         it != wildcardChildren_.end(); it++)
+         it != wildcardChildren_.end(); ++it)
     {
       it->second->CreateSiteMap(target["<" + it->first + ">"]);
     }
--- a/Core/RestApi/RestApiHierarchy.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiHierarchy.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiOutput.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiOutput.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiOutput.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiOutput.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiPath.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiPath.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiPath.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiPath.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiPostCall.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiPostCall.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/RestApi/RestApiPutCall.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/RestApi/RestApiPutCall.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/SQLite/Connection.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/Connection.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -34,15 +35,21 @@
  **/
 
 
+#if ORTHANC_SQLITE_STANDALONE != 1
 #include "../PrecompiledHeaders.h"
+#endif
+
 #include "Connection.h"
+#include "OrthancSQLiteException.h"
 
 #include <memory>
 #include <cassert>
 #include <sqlite3.h>
 #include <string.h>
 
+#if ORTHANC_SQLITE_STANDALONE != 1
 #include <glog/logging.h>
+#endif
 
 
 namespace Orthanc
@@ -67,7 +74,7 @@
     {
       if (!db_)
       {
-        throw OrthancException("SQLite: The database is not opened");
+        throw OrthancSQLiteException("SQLite: The database is not opened");
       }
     }
 
@@ -75,7 +82,7 @@
     {
       if (db_) 
       {
-        throw OrthancException("SQLite: Connection is already open");
+        throw OrthancSQLiteException("SQLite: Connection is already open");
       }
 
       int err = sqlite3_open(path.c_str(), &db_);
@@ -83,7 +90,7 @@
       {
         Close();
         db_ = NULL;
-        throw OrthancException("SQLite: Unable to open the database");
+        throw OrthancSQLiteException("SQLite: Unable to open the database");
       }
 
       // Execute PRAGMAs at this point
@@ -129,7 +136,7 @@
       {
         if (i->second->GetReferenceCount() >= 1)
         {
-          throw OrthancException("SQLite: This cached statement is already being referred to");
+          throw OrthancSQLiteException("SQLite: This cached statement is already being referred to");
         }
 
         return *i->second;
@@ -145,13 +152,16 @@
 
     bool Connection::Execute(const char* sql) 
     {
+#if ORTHANC_SQLITE_STANDALONE != 1
       VLOG(1) << "SQLite::Connection::Execute " << sql;
+#endif
+
       CheckIsOpen();
 
       int error = sqlite3_exec(db_, sql, NULL, NULL, NULL);
       if (error == SQLITE_ERROR)
       {
-        throw OrthancException("SQLite Execute error: " + std::string(sqlite3_errmsg(db_)));
+        throw OrthancSQLiteException("SQLite Execute error: " + std::string(sqlite3_errmsg(db_)));
       }
       else
       {
@@ -272,7 +282,7 @@
     {
       if (!transactionNesting_)
       {
-        throw OrthancException("Rolling back a nonexistent transaction");
+        throw OrthancSQLiteException("Rolling back a nonexistent transaction");
       }
 
       transactionNesting_--;
@@ -291,7 +301,7 @@
     {
       if (!transactionNesting_) 
       {
-        throw OrthancException("Committing a nonexistent transaction");
+        throw OrthancSQLiteException("Committing a nonexistent transaction");
       }
       transactionNesting_--;
 
@@ -359,7 +369,7 @@
       if (err != SQLITE_OK)
       {
         delete func;
-        throw OrthancException("SQLite: Unable to register a function");
+        throw OrthancSQLiteException("SQLite: Unable to register a function");
       }
 
       return func;
@@ -368,12 +378,15 @@
 
     void Connection::FlushToDisk()
     {
+#if ORTHANC_SQLITE_STANDALONE != 1
       VLOG(1) << "SQLite::Connection::FlushToDisk";
+#endif
+
       int err = sqlite3_wal_checkpoint(db_, NULL);
 
       if (err != SQLITE_OK)
       {
-        throw OrthancException("SQLite: Unable to flush the database");
+        throw OrthancSQLiteException("SQLite: Unable to flush the database");
       }
     }
   }
--- a/Core/SQLite/Connection.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/Connection.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -40,7 +41,6 @@
 #include "IScalarFunction.h"
 
 #include <string>
-#include <boost/noncopyable.hpp>
 #include <map>
 
 struct sqlite3;
@@ -52,7 +52,7 @@
 {
   namespace SQLite
   {
-    class Connection : boost::noncopyable
+    class Connection : NonCopyable
     {
       friend class Statement;
       friend class Transaction;
--- a/Core/SQLite/FunctionContext.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/FunctionContext.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -31,8 +32,12 @@
  **/
 
 
+#if ORTHANC_SQLITE_STANDALONE != 1
 #include "../PrecompiledHeaders.h"
+#endif
+
 #include "FunctionContext.h"
+#include "OrthancSQLiteException.h"
 
 #include <sqlite3.h>
 
@@ -57,7 +62,7 @@
     {
       if (index >= argc_)
       {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
+        throw OrthancSQLiteException("Parameter out of range");
       }
     }
 
--- a/Core/SQLite/FunctionContext.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/FunctionContext.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -33,8 +34,6 @@
 
 #pragma once
 
-#include <boost/noncopyable.hpp>
-
 #include "Statement.h"
 
 struct sqlite3_context;
@@ -44,7 +43,7 @@
 {
   namespace SQLite
   {
-    class FunctionContext : public boost::noncopyable
+    class FunctionContext : public NonCopyable
     {
       friend class Connection;
 
--- a/Core/SQLite/IScalarFunction.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/IScalarFunction.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -33,13 +34,14 @@
 
 #pragma once
 
+#include "NonCopyable.h"
 #include "FunctionContext.h"
 
 namespace Orthanc
 {
   namespace SQLite
   {
-    class IScalarFunction : public boost::noncopyable
+    class IScalarFunction : public NonCopyable
     {
     public:
       virtual ~IScalarFunction()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/SQLite/ITransaction.h	Wed Feb 11 10:32:14 2015 +0100
@@ -0,0 +1,66 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. 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 the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors 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 COPYRIGHT
+ * OWNER 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.
+ **/
+
+
+#pragma once
+
+#include "NonCopyable.h"
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class ITransaction : public NonCopyable
+    {
+    public:
+      virtual ~ITransaction()
+      {
+      }
+
+      // Begins the transaction. This uses the default sqlite "deferred" transaction
+      // type, which means that the DB lock is lazily acquired the next time the
+      // database is accessed, not in the begin transaction command.
+      virtual void Begin() = 0;
+
+      // Rolls back the transaction. This will happen automatically if you do
+      // nothing when the transaction goes out of scope.
+      virtual void Rollback() = 0;
+
+      // Commits the transaction, returning true on success.
+      virtual void Commit() = 0;
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/SQLite/NonCopyable.h	Wed Feb 11 10:32:14 2015 +0100
@@ -0,0 +1,60 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * 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 the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors 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 COPYRIGHT
+ * OWNER 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.
+ **/
+
+
+#pragma once
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    // This class mimics "boost::noncopyable"
+    class NonCopyable
+    {
+    private:
+      NonCopyable(const NonCopyable&);
+
+      NonCopyable& operator= (const NonCopyable&);
+
+    protected:
+      NonCopyable()
+      {
+      }
+
+      ~NonCopyable()
+      {
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/SQLite/OrthancSQLiteException.h	Wed Feb 11 10:32:14 2015 +0100
@@ -0,0 +1,67 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
+ *
+ * Copyright (c) 2012 The Chromium Authors. 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 the name of Google Inc., the name of the CHU of Liege,
+ * nor the names of its contributors 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 COPYRIGHT
+ * OWNER 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.
+ **/
+
+
+#pragma once
+
+
+#if ORTHANC_SQLITE_STANDALONE == 1
+#include <stdexcept>
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class OrthancSQLiteException : public ::std::runtime_error
+    {
+    public:
+      OrthancSQLiteException(const std::string& what) :
+        ::std::runtime_error(what)
+      {
+      }
+
+      OrthancSQLiteException(const char* what) : 
+        ::std::runtime_error(what)
+      {
+      }
+    };
+  }
+}
+
+#else
+#  include "../OrthancException.h"
+#  define OrthancSQLiteException ::Orthanc::OrthancException
+#endif
--- a/Core/SQLite/README.txt	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/README.txt	Wed Feb 11 10:32:14 2015 +0100
@@ -19,6 +19,17 @@
   coding conventions.
 
 
+Reuse in another software
+=========================
+
+To use the Orthanc SQLite wrapper in another project than Orthanc, you
+just have to define the "ORTHANC_SQLITE_STANDALONE" macro.
+
+All the C++ exceptions generated by the wrapper will be objects of the
+class "::Orthanc::SQLite::OrthancSQLiteException", that derives from
+the standard exception class "::std::runtime_error".
+
+
 Licensing
 =========
 
@@ -26,4 +37,4 @@
 order to respect the original license of the code.
 
 It is pretty straightforward to extract the code from this folder and
-to include it in another project.
+to include it in another project. 
--- a/Core/SQLite/Statement.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/Statement.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -34,16 +35,25 @@
  **/
 
 
+#if ORTHANC_SQLITE_STANDALONE != 1
 #include "../PrecompiledHeaders.h"
+#endif
+
 #include "Statement.h"
-
 #include "Connection.h"
-#include "../Toolbox.h"
 
-#include <boost/lexical_cast.hpp>
 #include <sqlite3.h>
 #include <string.h>
+#include <stdio.h>
+#include <algorithm>
+
+#if ORTHANC_SQLITE_STANDALONE != 1
 #include <glog/logging.h>
+#endif
+
+#if defined(_MSC_VER)
+#define snprintf _snprintf
+#endif
 
 namespace Orthanc
 {
@@ -54,7 +64,9 @@
       bool succeeded = (err == SQLITE_OK || err == SQLITE_ROW || err == SQLITE_DONE);
       if (!succeeded)
       {
-        throw OrthancException("SQLite error code " + boost::lexical_cast<std::string>(err));
+        char buffer[128];
+        snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err);
+        throw OrthancSQLiteException(buffer);
       }
 
       return err;
@@ -65,11 +77,13 @@
       if (err == SQLITE_RANGE)
       {
         // Binding to a non-existent variable is evidence of a serious error.
-        throw OrthancException("Bind value out of range");
+        throw OrthancSQLiteException("Bind value out of range");
       }
       else if (err != SQLITE_OK)
       {
-        throw OrthancException("SQLite error code " + boost::lexical_cast<std::string>(err));
+        char buffer[128];
+        snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err);
+        throw OrthancSQLiteException(buffer);
       }
     }
 
@@ -108,13 +122,19 @@
 
     bool Statement::Run()
     {
+#if ORTHANC_SQLITE_STANDALONE != 1
       VLOG(1) << "SQLite::Statement::Run " << sqlite3_sql(GetStatement());
+#endif
+
       return CheckError(sqlite3_step(GetStatement())) == SQLITE_DONE;
     }
 
     bool Statement::Step()
     {
+#if ORTHANC_SQLITE_STANDALONE != 1
       VLOG(1) << "SQLite::Statement::Step " << sqlite3_sql(GetStatement());
+#endif
+
       return CheckError(sqlite3_step(GetStatement())) == SQLITE_ROW;
     }
 
@@ -206,7 +226,7 @@
     ColumnType Statement::GetDeclaredColumnType(int col) const 
     {
       std::string column_type(sqlite3_column_decltype(GetStatement(), col));
-      Toolbox::ToLowerCase(column_type);
+      std::transform(column_type.begin(), column_type.end(), column_type.begin(), tolower);
 
       if (column_type == "integer")
         return COLUMN_TYPE_INTEGER;
--- a/Core/SQLite/Statement.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/Statement.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -36,13 +37,13 @@
 
 #pragma once
 
-#include "../OrthancException.h"
+#include "NonCopyable.h"
+#include "OrthancSQLiteException.h"
 #include "StatementId.h"
 #include "StatementReference.h"
 
 #include <vector>
 #include <stdint.h>
-#include <boost/noncopyable.hpp>
 
 #if ORTHANC_BUILD_UNIT_TESTS == 1
 #include <gtest/gtest_prod.h>
@@ -68,7 +69,7 @@
       COLUMN_TYPE_NULL = 5
     };
 
-    class Statement : public boost::noncopyable
+    class Statement : public NonCopyable
     {
       friend class Connection;
 
--- a/Core/SQLite/StatementId.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/StatementId.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -34,7 +35,10 @@
  **/
 
 
+#if ORTHANC_SQLITE_STANDALONE != 1
 #include "../PrecompiledHeaders.h"
+#endif
+
 #include "StatementId.h"
 
 #include <string.h>
--- a/Core/SQLite/StatementId.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/StatementId.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
--- a/Core/SQLite/StatementReference.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/StatementReference.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -34,13 +35,18 @@
  **/
 
 
+#if ORTHANC_SQLITE_STANDALONE != 1
 #include "../PrecompiledHeaders.h"
+#endif
+
 #include "StatementReference.h"
+#include "OrthancSQLiteException.h"
 
-#include "../OrthancException.h"
+#if ORTHANC_SQLITE_STANDALONE != 1
+#include <glog/logging.h>
+#endif
 
 #include <cassert>
-#include <glog/logging.h>
 #include "sqlite3.h"
 
 namespace Orthanc
@@ -65,7 +71,7 @@
     {
       if (database == NULL || sql == NULL)
       {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
+        throw OrthancSQLiteException("Parameter out of range");
       }
 
       root_ = NULL;
@@ -74,7 +80,7 @@
       int error = sqlite3_prepare_v2(database, sql, -1, &statement_, NULL);
       if (error != SQLITE_OK)
       {
-        throw OrthancException("SQLite: " + std::string(sqlite3_errmsg(database)));
+        throw OrthancSQLiteException("SQLite: " + std::string(sqlite3_errmsg(database)));
       }
 
       assert(IsRoot());
@@ -109,7 +115,9 @@
           // an exception because:
           // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html
 
+#if ORTHANC_SQLITE_STANDALONE != 1
           LOG(ERROR) << "Bad value of the reference counter";
+#endif
         }
         else if (statement_ != NULL)
         {
@@ -124,7 +132,9 @@
           // an exception because:
           // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html
 
+#if ORTHANC_SQLITE_STANDALONE != 1
           LOG(ERROR) << "Bad value of the reference counter";
+#endif
         }
         else
         {
--- a/Core/SQLite/StatementReference.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/StatementReference.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -36,7 +37,8 @@
 
 #pragma once
 
-#include <boost/noncopyable.hpp>
+#include "NonCopyable.h"
+
 #include <stdint.h>
 #include <cassert>
 #include <stdlib.h>
@@ -48,7 +50,7 @@
 {
   namespace SQLite
   {
-    class StatementReference : boost::noncopyable
+    class StatementReference : NonCopyable
     {
     private:
       StatementReference* root_;   // Only used for non-root nodes
--- a/Core/SQLite/Transaction.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/Transaction.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -34,8 +35,12 @@
  **/
 
 
+#if ORTHANC_SQLITE_STANDALONE != 1
 #include "../PrecompiledHeaders.h"
+#endif
+
 #include "Transaction.h"
+#include "OrthancSQLiteException.h"
 
 namespace Orthanc
 {
@@ -59,13 +64,13 @@
     {
       if (isOpen_) 
       {
-        throw OrthancException("SQLite: Beginning a transaction twice!");
+        throw OrthancSQLiteException("SQLite: Beginning a transaction twice!");
       }
 
       isOpen_ = connection_.BeginTransaction();
       if (!isOpen_)
       {
-        throw OrthancException("SQLite: Unable to create a transaction");
+        throw OrthancSQLiteException("SQLite: Unable to create a transaction");
       }
     }
 
@@ -73,8 +78,8 @@
     {
       if (!isOpen_) 
       {
-        throw OrthancException("SQLite: Attempting to roll back a nonexistent transaction. "
-                                "Did you remember to call Begin()?");
+        throw OrthancSQLiteException("SQLite: Attempting to roll back a nonexistent transaction. "
+                                     "Did you remember to call Begin()?");
       }
 
       isOpen_ = false;
@@ -86,15 +91,15 @@
     {
       if (!isOpen_) 
       {
-        throw OrthancException("SQLite: Attempting to roll back a nonexistent transaction. "
-                                "Did you remember to call Begin()?");
+        throw OrthancSQLiteException("SQLite: Attempting to roll back a nonexistent transaction. "
+                                     "Did you remember to call Begin()?");
       }
 
       isOpen_ = false;
 
       if (!connection_.CommitTransaction())
       {
-        throw OrthancException("SQLite: Failure when committing the transaction");
+        throw OrthancSQLiteException("SQLite: Failure when committing the transaction");
       }
     }
   }
--- a/Core/SQLite/Transaction.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/SQLite/Transaction.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,8 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ *
+ * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>,
+ * Medical Physics Department, CHU of Liege, Belgium
  *
  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
  *
@@ -37,12 +38,13 @@
 #pragma once
 
 #include "Connection.h"
+#include "ITransaction.h"
 
 namespace Orthanc
 {
   namespace SQLite
   {
-    class Transaction : public boost::noncopyable
+    class Transaction : public ITransaction
     {
     private:
       Connection& connection_;
@@ -53,22 +55,17 @@
 
     public:
       explicit Transaction(Connection& connection);
-      ~Transaction();
+
+      virtual ~Transaction();
 
       // Returns true when there is a transaction that has been successfully begun.
       bool IsOpen() const { return isOpen_; }
 
-      // Begins the transaction. This uses the default sqlite "deferred" transaction
-      // type, which means that the DB lock is lazily acquired the next time the
-      // database is accessed, not in the begin transaction command.
-      void Begin();
+      virtual void Begin();
 
-      // Rolls back the transaction. This will happen automatically if you do
-      // nothing when the transaction goes out of scope.
-      void Rollback();
+      virtual void Rollback();
 
-      // Commits the transaction, returning true on success.
-      void Commit();
+      virtual void Commit();
     };
   }
 }
--- a/Core/Toolbox.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Toolbox.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Toolbox.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Toolbox.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Uuid.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Uuid.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Core/Uuid.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Core/Uuid.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/LinuxCompilation.txt	Tue Nov 04 13:56:17 2014 +0100
+++ b/LinuxCompilation.txt	Wed Feb 11 10:32:14 2015 +0100
@@ -44,6 +44,8 @@
 directory ("rm -rf ~/OrthancBuild") after installing this package,
 then run cmake again.
 
+Note 3- To build the documentation, you will have to install doxyen.
+
 
 Use system-wide libraries under Linux
 =====================================
@@ -58,6 +60,7 @@
 # cmake -DCMAKE_BUILD_TYPE=Debug ~/Orthanc
 # make
 
+Note that to build the documentation, you will have to install doxyen.
 
 However, on some Linux distributions, it is still required to download
 and static link against some third-party dependencies, e.g. when the
--- a/NEWS	Tue Nov 04 13:56:17 2014 +0100
+++ b/NEWS	Wed Feb 11 10:32:14 2015 +0100
@@ -1,10 +1,53 @@
 Pending changes in the mainline
 ===============================
 
+Major
+-----
+
+* URIs to get all the parents of a given resource in a single REST call
+* Instances without PatientID are now allowed
+* Support of HTTP proxy to access Orthanc peers
+
+Minor
+-----
+
+* Support of Tudor DICOM in Query/Retrieve
+* More flexible "/modify" and "/anonymize" for single instance
+* Access to called AET and remote AET from Lua scripts ("OnStoredInstance")
+* Option "DicomAssociationCloseDelay" to set delay before closing DICOM association
+
+Plugins
+-------
+
+* Introspection of plugins (cf. the "/plugins" URI)
+* Plugins can access the command-line arguments used to launch Orthanc
+* Plugins can extend Orthanc Explorer with custom JavaScript
+* Plugins can get/set global properties to save their configuration
+* Plugins can do REST calls to other plugins (cf. "xxxAfterPlugins()")
+* Scan of folders for plugins
+
+Fixes
+-----
+
+* Code refactorings
+* Fix issue 25 (AET with underscore not allowed)
+* Fix replacement and insertion of private DICOM tags
+
+
+Version 0.8.5 (2014/11/04)
+==========================
+
+General
+-------
+
 * Major speed-up thanks to a new database schema
+* Plugins can monitor changes through callbacks
 * Download ZIP + DICOMDIR from Orthanc Explorer
-* Plugins can monitor changes through callbacks
-* Sample plugin framework to serve static resources
+* Sample plugin framework to serve static resources (./Plugins/Samples/WebSkeleton/)
+
+Fixes
+-----
+
 * Fix issue 19 (YBR_FULL are decoded incorrectly)
 * Fix issue 21 (Microsoft Visual Studio precompiled headers)
 * Fix issue 22 (Error decoding multi-frame instances)
--- a/OrthancCppClient/Instance.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/Instance.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/Instance.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/Instance.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/OrthancClientException.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/OrthancClientException.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/OrthancConnection.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/OrthancConnection.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/OrthancConnection.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/OrthancConnection.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/OrthancCppClient.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/OrthancCppClient.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/Patient.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/Patient.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/Patient.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/Patient.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/Series.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/Series.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/Series.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/Series.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1489,7 +1489,7 @@
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetCompany()
   {
-    return "CHU of Liege";
+    return "University Hospital of Liege";
   }
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetProduct()
@@ -1499,7 +1499,7 @@
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetCopyright()
   {
-    return "(c) 2012-2014, Sebastien Jodogne, University Hospital of Liege";
+    return "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege";
   }
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetVersion()
@@ -1509,12 +1509,12 @@
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFileVersion()
   {
-    return "0.8.0.4";
+    return "0.8.0.5";
   }
 
   LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFullVersion()
   {
-    return "0.8.4";
+    return "0.8.5";
   }
 
   LAAW_EXPORT_DLL_API void LAAW_CALL_CONVENTION LAAW_EXTERNC_FreeString(char* str)
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 #include <winver.h>
 
 VS_VERSION_INFO VERSIONINFO
-   FILEVERSION 0,8,0,4
+   FILEVERSION 0,8,0,5
    PRODUCTVERSION 0,8,0,0
    FILEOS VOS_NT_WINDOWS32
    FILETYPE VFT_DLL
@@ -10,13 +10,13 @@
       BEGIN
          BLOCK "040904E4"
          BEGIN
-            VALUE "Comments", "Release 0.8.4"
-            VALUE "CompanyName", "CHU of Liege"
+            VALUE "Comments", "Release 0.8.5"
+            VALUE "CompanyName", "University Hospital of Liege"
             VALUE "FileDescription", "Native client to the REST API of Orthanc"
-            VALUE "FileVersion", "0.8.0.4"
+            VALUE "FileVersion", "0.8.0.5"
             VALUE "InternalName", "OrthancClient"
-            VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, University Hospital of Liege"
-            VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/"
+            VALUE "LegalCopyright", "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege"
+            VALUE "LegalTrademarks", "Licensing information is available on http://www.orthanc-server.com/"
             VALUE "OriginalFilename", "OrthancClient_Windows32.dll"
             VALUE "ProductName", "OrthancClient"
             VALUE "ProductVersion", "0.8"
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 #include <winver.h>
 
 VS_VERSION_INFO VERSIONINFO
-   FILEVERSION 0,8,0,4
+   FILEVERSION 0,8,0,5
    PRODUCTVERSION 0,8,0,0
    FILEOS VOS_NT_WINDOWS32
    FILETYPE VFT_DLL
@@ -10,13 +10,13 @@
       BEGIN
          BLOCK "040904E4"
          BEGIN
-            VALUE "Comments", "Release 0.8.4"
-            VALUE "CompanyName", "CHU of Liege"
+            VALUE "Comments", "Release 0.8.5"
+            VALUE "CompanyName", "University Hospital of Liege"
             VALUE "FileDescription", "Native client to the REST API of Orthanc"
-            VALUE "FileVersion", "0.8.0.4"
+            VALUE "FileVersion", "0.8.0.5"
             VALUE "InternalName", "OrthancClient"
-            VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, University Hospital of Liege"
-            VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/"
+            VALUE "LegalCopyright", "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege"
+            VALUE "LegalTrademarks", "Licensing information is available on http://www.orthanc-server.com/"
             VALUE "OriginalFilename", "OrthancClient_Windows64.dll"
             VALUE "ProductName", "OrthancClient"
             VALUE "ProductVersion", "0.8"
--- a/OrthancCppClient/SharedLibrary/Product.json	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/Product.json	Wed Feb 11 10:32:14 2015 +0100
@@ -1,8 +1,8 @@
 {
   "Product" : "OrthancClient",
   "Description" : "Native client to the REST API of Orthanc",
-  "Company" : "CHU of Liege",
-  "Copyright" : "(c) 2012-2014, Sebastien Jodogne, University Hospital of Liege",
-  "Legal" : "Licensing information is available on https://code.google.com/p/orthanc/",
-  "Version" : "0.8.4"
+  "Company" : "University Hospital of Liege",
+  "Copyright" : "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege",
+  "Legal" : "Licensing information is available on http://www.orthanc-server.com/",
+  "Version" : "0.8.5"
 }
--- a/OrthancCppClient/SharedLibrary/SharedLibrary.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/SharedLibrary/SharedLibrary.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/Study.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/Study.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancCppClient/Study.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancCppClient/Study.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancExplorer/explorer.html	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancExplorer/explorer.html	Wed Feb 11 10:32:14 2015 +0100
@@ -30,11 +30,13 @@
     <link rel="stylesheet" href="explorer.css" />
     <script src="file-upload.js"></script>
     <script src="explorer.js"></script>
+    <script src="../plugins/explorer.js"></script>
   </head>
   <body>
     <div data-role="page" id="find-patients" >
       <div data-role="header" >
 	<h1><span class="orthanc-name"></span>Find a patient</h1>
+        <a href="#plugins" data-icon="grid" class="ui-btn-left" data-direction="reverse">Plugins</a>
         <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a>
       </div>
       <div data-role="content">
@@ -46,7 +48,7 @@
     <div data-role="page" id="upload" >
       <div data-role="header" >
 	<h1><span class="orthanc-name"></span>Upload DICOM files</h1>
-        <a href="#find-patients" data-icon="search" class="ui-btn-left" data-direction="reverse">Find patient</a>
+        <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a>
       </div>
       <div data-role="content">
         <div style="display:none">
@@ -72,7 +74,7 @@
     <div data-role="page" id="patient" >
       <div data-role="header" >
 	<h1><span class="orthanc-name"></span>Patient</h1>
-        <a href="#find-patients" data-icon="search" class="ui-btn-left" data-direction="reverse">Find patient</a>
+        <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a>
         <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a>
       </div>
       <div data-role="content">
@@ -126,7 +128,7 @@
           <a href="#" class="patient-link">Patient</a> &raquo; 
           Study
         </h1>
-        <a href="#find-patients" data-icon="search" class="ui-btn-left" data-direction="reverse">Find patient</a>
+        <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a>
         <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a>
       </div>
       <div data-role="content">
@@ -175,7 +177,7 @@
           Series
         </h1>
 
-        <a href="#find-patients" data-icon="search" class="ui-btn-left" data-direction="reverse">Find patient</a>
+        <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a>
         <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a>
       </div>
       <div data-role="content">
@@ -225,7 +227,7 @@
           <a href="#" class="series-link">Series</a> &raquo; 
           Instance
         </h1>
-        <a href="#find-patients" data-icon="search" class="ui-btn-left" data-direction="reverse">Find patient</a>
+        <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a>
         <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a>
       </div>
       <div data-role="content">
@@ -271,6 +273,18 @@
       </div>
     </div>
 
+    <div data-role="page" id="plugins" >
+      <div data-role="header" >
+	<h1><span class="orthanc-name"></span>Plugins</h1>
+        <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a>
+      </div>
+      <div data-role="content">
+        <ul id="all-plugins" data-role="listview" data-inset="true" data-filter="true">
+        </ul>
+      </div>
+    </div>
+
+
     <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>
--- a/OrthancExplorer/explorer.js	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancExplorer/explorer.js	Wed Feb 11 10:32:14 2015 +0100
@@ -1023,3 +1023,46 @@
   OpenAnonymizeResourceDialog('../patients/' + $.mobile.pageData.uuid,
                               'Anonymize this patient?');
 });
+
+
+$('#plugins').live('pagebeforeshow', function() {
+  $.ajax({
+    url: '../plugins',
+    dataType: 'json',
+    async: false,
+    cache: false,
+    success: function(plugins) {
+      var target = $('#all-plugins');
+      $('li', target).remove();
+
+      plugins.map(function(id) {
+        return $.ajax({
+          url: '../plugins/' + id,
+          dataType: 'json',
+          async: false,
+          cache: false,
+          success: function(plugin) {
+            var li = $('<li>');
+            var item = li;
+
+            if ('RootUri' in plugin)
+            {
+              item = $('<a>');
+              li.append(item);
+              item.click(function() {
+                window.open(plugin.RootUri);
+              });
+            }
+
+            item.append($('<h1>').text(plugin.ID));
+            item.append($('<p>').text(plugin.Description));
+            item.append($('<span>').addClass('ui-li-count').text(plugin.Version));
+            target.append(li);
+          }
+        });
+      });
+
+      target.listview('refresh');
+    }
+  });
+});
--- a/OrthancServer/DatabaseWrapper.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DatabaseWrapper.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -39,6 +39,7 @@
 
 #include <glog/logging.h>
 #include <stdio.h>
+#include <boost/lexical_cast.hpp>
 
 namespace Orthanc
 {
@@ -213,20 +214,6 @@
     }
   }
 
-  std::string DatabaseWrapper::GetGlobalProperty(GlobalProperty property,
-                                                 const std::string& defaultValue)
-  {
-    std::string s;
-    if (LookupGlobalProperty(s, property))
-    {
-      return s;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
   int64_t DatabaseWrapper::CreateResource(const std::string& publicId,
                                           ResourceType type)
   {
@@ -234,38 +221,12 @@
     s.BindInt(0, type);
     s.BindString(1, publicId);
     s.Run();
-    int64_t id = db_.GetLastInsertRowId();
-
-    ChangeType changeType;
-    switch (type)
-    {
-    case ResourceType_Patient: 
-      changeType = ChangeType_NewPatient; 
-      break;
-
-    case ResourceType_Study: 
-      changeType = ChangeType_NewStudy; 
-      break;
-
-    case ResourceType_Series: 
-      changeType = ChangeType_NewSeries; 
-      break;
-
-    case ResourceType_Instance: 
-      changeType = ChangeType_NewInstance; 
-      break;
-
-    default:
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    LogChange(id, changeType, type, publicId);
-    return id;
+    return db_.GetLastInsertRowId();
   }
 
-  bool DatabaseWrapper::LookupResource(const std::string& publicId,
-                                       int64_t& id,
-                                       ResourceType& type)
+  bool DatabaseWrapper::LookupResource(int64_t& id,
+                                       ResourceType& type,
+                                       const std::string& publicId)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, 
                         "SELECT internalId, resourceType FROM Resources WHERE publicId=?");
@@ -349,16 +310,17 @@
     s.Run();
   }
 
-  void DatabaseWrapper::GetChildren(Json::Value& childrenPublicIds,
+
+  void DatabaseWrapper::GetChildren(std::list<std::string>& childrenPublicIds,
                                     int64_t id)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?");
     s.BindInt64(0, id);
 
-    childrenPublicIds = Json::arrayValue;
+    childrenPublicIds.clear();
     while (s.Step())
     {
-      childrenPublicIds.append(s.ColumnString(0));
+      childrenPublicIds.push_back(s.ColumnString(0));
     }
   }
 
@@ -371,10 +333,11 @@
     s.BindInt64(0, id);
     s.Run();
 
-    if (signalRemainingAncestor_->HasRemainingAncestor())
+    if (signalRemainingAncestor_->HasRemainingAncestor() &&
+        listener_ != NULL)
     {
-      listener_.SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorType(),
-                                        signalRemainingAncestor_->GetRemainingAncestorId());
+      listener_->SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorType(),
+                                         signalRemainingAncestor_->GetRemainingAncestorId());
     }
   }
 
@@ -433,45 +396,6 @@
   }
 
 
-  std::string DatabaseWrapper::GetMetadata(int64_t id,
-                                           MetadataType type,
-                                           const std::string& defaultValue)
-  {
-    std::string s;
-    if (LookupMetadata(s, id, type))
-    {
-      return s;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  bool DatabaseWrapper::GetMetadataAsInteger(int& result,
-                                             int64_t id,
-                                             MetadataType type)
-  {
-    std::string s = GetMetadata(id, type, "");
-    if (s.size() == 0)
-    {
-      return false;
-    }
-
-    try
-    {
-      result = boost::lexical_cast<int>(s);
-      return true;
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      return false;
-    }
-  }
-
-
-
   void DatabaseWrapper::AddAttachment(int64_t id,
                                       const FileInfo& attachment)
   {
@@ -499,10 +423,10 @@
 
 
 
-  void DatabaseWrapper::ListAvailableAttachments(std::list<FileContentType>& result,
+  void DatabaseWrapper::ListAvailableAttachments(std::list<FileContentType>& target,
                                                  int64_t id)
   {
-    result.clear();
+    target.clear();
 
     SQLite::Statement s(db_, SQLITE_FROM_HERE, 
                         "SELECT fileType FROM AttachedFiles WHERE id=?");
@@ -510,7 +434,7 @@
 
     while (s.Step())
     {
-      result.push_back(static_cast<FileContentType>(s.ColumnInt(0)));
+      target.push_back(static_cast<FileContentType>(s.ColumnInt(0)));
     }
   }
 
@@ -543,32 +467,30 @@
 
   static void SetMainDicomTagsInternal(SQLite::Statement& s,
                                        int64_t id,
-                                       const DicomElement& element)
+                                       const DicomTag& tag,
+                                       const std::string& value)
   {
     s.BindInt64(0, id);
-    s.BindInt(1, element.GetTag().GetGroup());
-    s.BindInt(2, element.GetTag().GetElement());
-    s.BindString(3, element.GetValue().AsString());
+    s.BindInt(1, tag.GetGroup());
+    s.BindInt(2, tag.GetElement());
+    s.BindString(3, value);
     s.Run();
   }
 
 
-  void DatabaseWrapper::SetMainDicomTags(int64_t id,
-                                         const DicomMap& tags)
+  void DatabaseWrapper::SetMainDicomTag(int64_t id,
+                                        const DicomTag& tag,
+                                        const std::string& value)
   {
-    DicomArray flattened(tags);
-    for (size_t i = 0; i < flattened.GetSize(); i++)
+    if (tag.IsIdentifier())
     {
-      if (flattened.GetElement(i).GetTag().IsIdentifier())
-      {
-        SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
-        SetMainDicomTagsInternal(s, id, flattened.GetElement(i));
-      }
-      else
-      {
-        SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
-        SetMainDicomTagsInternal(s, id, flattened.GetElement(i));
-      }
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
+      SetMainDicomTagsInternal(s, id, tag, value);
+    }
+    else
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
+      SetMainDicomTagsInternal(s, id, tag, value);
     }
   }
 
@@ -597,7 +519,7 @@
   }
 
 
-  bool DatabaseWrapper::GetParentPublicId(std::string& result,
+  bool DatabaseWrapper::GetParentPublicId(std::string& target,
                                           int64_t id)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b "
@@ -606,7 +528,7 @@
 
     if (s.Step())
     {
-      result = s.ColumnString(0);
+      target = s.ColumnString(0);
       return true;
     }
     else
@@ -616,34 +538,34 @@
   }
 
 
-  void DatabaseWrapper::GetChildrenPublicId(std::list<std::string>& result,
+  void DatabaseWrapper::GetChildrenPublicId(std::list<std::string>& target,
                                             int64_t id)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b  "
                         "WHERE a.parentId = b.internalId AND b.internalId = ?");     
     s.BindInt64(0, id);
 
-    result.clear();
+    target.clear();
 
     while (s.Step())
     {
-      result.push_back(s.ColumnString(0));
+      target.push_back(s.ColumnString(0));
     }
   }
 
 
-  void DatabaseWrapper::GetChildrenInternalId(std::list<int64_t>& result,
+  void DatabaseWrapper::GetChildrenInternalId(std::list<int64_t>& target,
                                               int64_t id)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b  "
                         "WHERE a.parentId = b.internalId AND b.internalId = ?");     
     s.BindInt64(0, id);
 
-    result.clear();
+    target.clear();
 
     while (s.Step())
     {
-      result.push_back(s.ColumnInt64(0));
+      target.push_back(s.ColumnInt64(0));
     }
   }
 
@@ -651,171 +573,124 @@
   void DatabaseWrapper::LogChange(int64_t internalId,
                                   const ServerIndexChange& change)
   {
-    if (change.GetChangeType() <= ChangeType_INTERNAL_LastLogged)
-    {
-      const boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
-
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)");
-      s.BindInt(0, change.GetChangeType());
-      s.BindInt64(1, internalId);
-      s.BindInt(2, change.GetResourceType());
-      s.BindString(3, boost::posix_time::to_iso_string(now));
-      s.Run();
-    }
-
-    listener_.SignalChange(change);
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)");
+    s.BindInt(0, change.GetChangeType());
+    s.BindInt64(1, internalId);
+    s.BindInt(2, change.GetResourceType());
+    s.BindString(3, change.GetDate());
+    s.Run();
   }
 
 
-  void DatabaseWrapper::GetChangesInternal(Json::Value& target,
+  void DatabaseWrapper::GetChangesInternal(std::list<ServerIndexChange>& target,
+                                           bool& done,
                                            SQLite::Statement& s,
-                                           int64_t since,
-                                           unsigned int maxResults)
+                                           uint32_t maxResults)
   {
-    Json::Value changes = Json::arrayValue;
-    int64_t last = since;
+    target.clear();
 
-    while (changes.size() < maxResults && s.Step())
+    while (target.size() < maxResults && s.Step())
     {
       int64_t seq = s.ColumnInt64(0);
       ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1));
-      int64_t internalId = s.ColumnInt64(2);
       ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3));
       const std::string& date = s.ColumnString(4);
+
+      int64_t internalId = s.ColumnInt64(2);
       std::string publicId = GetPublicId(internalId);
 
-      Json::Value item = Json::objectValue;
-      item["Seq"] = static_cast<int>(seq);
-      item["ChangeType"] = EnumerationToString(changeType);
-      item["ResourceType"] = EnumerationToString(resourceType);
-      item["ID"] = publicId;
-      item["Path"] = GetBasePath(resourceType, publicId);
-      item["Date"] = date;
-      last = seq;
-
-      changes.append(item);
+      target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date));
     }
 
-    target = Json::objectValue;
-    target["Changes"] = changes;
-    target["Done"] = !(changes.size() == maxResults && s.Step());
-    target["Last"] = static_cast<int>(last);
+    done = !(target.size() == maxResults && s.Step());
   }
 
 
-  void DatabaseWrapper::GetChanges(Json::Value& target,
+  void DatabaseWrapper::GetChanges(std::list<ServerIndexChange>& target,
+                                   bool& done,
                                    int64_t since,
-                                   unsigned int maxResults)
+                                   uint32_t maxResults)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?");
     s.BindInt64(0, since);
     s.BindInt(1, maxResults + 1);
-    GetChangesInternal(target, s, since, maxResults);
+    GetChangesInternal(target, done, s, maxResults);
   }
 
-  void DatabaseWrapper::GetLastChange(Json::Value& target)
+  void DatabaseWrapper::GetLastChange(std::list<ServerIndexChange>& target)
   {
+    bool done;  // Ignored
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1");
-    GetChangesInternal(target, s, 0, 1);
+    GetChangesInternal(target, done, s, 1);
   }
 
 
-  void DatabaseWrapper::LogExportedResource(ResourceType resourceType,
-                                            const std::string& publicId,
-                                            const std::string& remoteModality,
-                                            const std::string& patientId,
-                                            const std::string& studyInstanceUid,
-                                            const std::string& seriesInstanceUid,
-                                            const std::string& sopInstanceUid,
-                                            const boost::posix_time::ptime& date)
+  void DatabaseWrapper::LogExportedResource(const ExportedResource& resource)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, 
                         "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
 
-    s.BindInt(0, resourceType);
-    s.BindString(1, publicId);
-    s.BindString(2, remoteModality);
-    s.BindString(3, patientId);
-    s.BindString(4, studyInstanceUid);
-    s.BindString(5, seriesInstanceUid);
-    s.BindString(6, sopInstanceUid);
-    s.BindString(7, boost::posix_time::to_iso_string(date));
-
+    s.BindInt(0, resource.GetResourceType());
+    s.BindString(1, resource.GetPublicId());
+    s.BindString(2, resource.GetModality());
+    s.BindString(3, resource.GetPatientId());
+    s.BindString(4, resource.GetStudyInstanceUid());
+    s.BindString(5, resource.GetSeriesInstanceUid());
+    s.BindString(6, resource.GetSopInstanceUid());
+    s.BindString(7, resource.GetDate());
     s.Run();      
   }
 
 
-  void DatabaseWrapper::GetExportedResourcesInternal(Json::Value& target,
+  void DatabaseWrapper::GetExportedResourcesInternal(std::list<ExportedResource>& target,
+                                                     bool& done,
                                                      SQLite::Statement& s,
-                                                     int64_t since,
-                                                     unsigned int maxResults)
+                                                     uint32_t maxResults)
   {
-    Json::Value changes = Json::arrayValue;
-    int64_t last = since;
+    target.clear();
 
-    while (changes.size() < maxResults && s.Step())
+    while (target.size() < maxResults && s.Step())
     {
       int64_t seq = s.ColumnInt64(0);
       ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1));
       std::string publicId = s.ColumnString(2);
 
-      Json::Value item = Json::objectValue;
-      item["Seq"] = static_cast<int>(seq);
-      item["ResourceType"] = EnumerationToString(resourceType);
-      item["ID"] = publicId;
-      item["Path"] = GetBasePath(resourceType, publicId);
-      item["RemoteModality"] = s.ColumnString(3);
-      item["Date"] = s.ColumnString(8);
-
-      // WARNING: Do not add "break" below and do not reorder the case items!
-      switch (resourceType)
-      {
-      case ResourceType_Instance:
-        item["SopInstanceUid"] = s.ColumnString(7);
+      ExportedResource resource(seq, 
+                                resourceType,
+                                publicId,
+                                s.ColumnString(3),  // modality
+                                s.ColumnString(8),  // date
+                                s.ColumnString(4),  // patient ID
+                                s.ColumnString(5),  // study instance UID
+                                s.ColumnString(6),  // series instance UID
+                                s.ColumnString(7)); // sop instance UID
 
-      case ResourceType_Series:
-        item["SeriesInstanceUid"] = s.ColumnString(6);
-
-      case ResourceType_Study:
-        item["StudyInstanceUid"] = s.ColumnString(5);
-
-      case ResourceType_Patient:
-        item["PatientId"] = s.ColumnString(4);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-      }
-
-      last = seq;
-
-      changes.append(item);
+      target.push_back(resource);
     }
 
-    target = Json::objectValue;
-    target["Exports"] = changes;
-    target["Done"] = !(changes.size() == maxResults && s.Step());
-    target["Last"] = static_cast<int>(last);
+    done = !(target.size() == maxResults && s.Step());
   }
 
 
-  void DatabaseWrapper::GetExportedResources(Json::Value& target,
+  void DatabaseWrapper::GetExportedResources(std::list<ExportedResource>& target,
+                                             bool& done,
                                              int64_t since,
-                                             unsigned int maxResults)
+                                             uint32_t maxResults)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, 
                         "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?");
     s.BindInt64(0, since);
     s.BindInt(1, maxResults + 1);
-    GetExportedResourcesInternal(target, s, since, maxResults);
+    GetExportedResourcesInternal(target, done, s, maxResults);
   }
 
     
-  void DatabaseWrapper::GetLastExportedResource(Json::Value& target)
+  void DatabaseWrapper::GetLastExportedResource(std::list<ExportedResource>& target)
   {
+    bool done;  // Ignored
     SQLite::Statement s(db_, SQLITE_FROM_HERE, 
                         "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1");
-    GetExportedResourcesInternal(target, s, 0, 1);
+    GetExportedResourcesInternal(target, done, s, 1);
   }
 
 
@@ -854,16 +729,16 @@
     return static_cast<uint64_t>(s.ColumnInt64(0));
   }
 
-  void DatabaseWrapper::GetAllPublicIds(Json::Value& target,
+  void DatabaseWrapper::GetAllPublicIds(std::list<std::string>& target,
                                         ResourceType resourceType)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?");
     s.BindInt(0, resourceType);
 
-    target = Json::arrayValue;
+    target.clear();
     while (s.Step())
     {
-      target.append(s.ColumnString(0));
+      target.push_back(s.ColumnString(0));
     }
   }
 
@@ -878,16 +753,13 @@
   }
 
 
-  DatabaseWrapper::DatabaseWrapper(const std::string& path,
-                                   IServerIndexListener& listener) :
-    listener_(listener)
+  DatabaseWrapper::DatabaseWrapper(const std::string& path) : listener_(NULL)
   {
     db_.Open(path);
     Open();
   }
 
-  DatabaseWrapper::DatabaseWrapper(IServerIndexListener& listener) :
-    listener_(listener)
+  DatabaseWrapper::DatabaseWrapper() : listener_(NULL)
   {
     db_.OpenInMemory();
     Open();
@@ -912,7 +784,12 @@
     }
 
     // Check the version of the database
-    std::string version = GetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "Unknown");
+    std::string version;
+    if (!LookupGlobalProperty(version, GlobalProperty_DatabaseSchemaVersion))
+    {
+      version = "Unknown";
+    }
+
     bool ok = false;
     try
     {
@@ -958,8 +835,13 @@
 
     signalRemainingAncestor_ = new Internals::SignalRemainingAncestor;
     db_.Register(signalRemainingAncestor_);
-    db_.Register(new Internals::SignalFileDeleted(listener_));
-    db_.Register(new Internals::SignalResourceDeleted(listener_));
+  }
+
+  void DatabaseWrapper::SetListener(IServerIndexListener& listener)
+  {
+    listener_ = &listener;
+    db_.Register(new Internals::SignalFileDeleted(listener));
+    db_.Register(new Internals::SignalResourceDeleted(listener));
   }
 
   uint64_t DatabaseWrapper::GetResourceCount(ResourceType resourceType)
@@ -1046,33 +928,6 @@
   }
 
 
-  uint64_t DatabaseWrapper::IncrementGlobalSequence(GlobalProperty property)
-  {
-    std::string oldValue;
-
-    if (LookupGlobalProperty(oldValue, property))
-    {
-      uint64_t oldNumber;
-
-      try
-      {
-        oldNumber = boost::lexical_cast<uint64_t>(oldValue);
-        SetGlobalProperty(property, boost::lexical_cast<std::string>(oldNumber + 1));
-        return oldNumber + 1;
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-    else
-    {
-      // Initialize the sequence at "1"
-      SetGlobalProperty(property, "1");
-      return 1;
-    }
-  }
-
 
   void DatabaseWrapper::ClearTable(const std::string& tableName)
   {
@@ -1089,7 +944,7 @@
   }
 
 
-  void  DatabaseWrapper::LookupIdentifier(std::list<int64_t>& result,
+  void  DatabaseWrapper::LookupIdentifier(std::list<int64_t>& target,
                                           const DicomTag& tag,
                                           const std::string& value)
   {
@@ -1105,16 +960,16 @@
     s.BindInt(1, tag.GetElement());
     s.BindString(2, value);
 
-    result.clear();
+    target.clear();
 
     while (s.Step())
     {
-      result.push_back(s.ColumnInt64(0));
+      target.push_back(s.ColumnInt64(0));
     }
   }
 
 
-  void  DatabaseWrapper::LookupIdentifier(std::list<int64_t>& result,
+  void  DatabaseWrapper::LookupIdentifier(std::list<int64_t>& target,
                                           const std::string& value)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, 
@@ -1122,19 +977,19 @@
 
     s.BindString(0, value);
 
-    result.clear();
+    target.clear();
 
     while (s.Step())
     {
-      result.push_back(s.ColumnInt64(0));
+      target.push_back(s.ColumnInt64(0));
     }
   }
 
 
-  void DatabaseWrapper::GetAllMetadata(std::map<MetadataType, std::string>& result,
+  void DatabaseWrapper::GetAllMetadata(std::map<MetadataType, std::string>& target,
                                        int64_t id)
   {
-    result.clear();
+    target.clear();
 
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type, value FROM Metadata WHERE id=?");
     s.BindInt64(0, id);
@@ -1142,7 +997,7 @@
     while (s.Step())
     {
       MetadataType key = static_cast<MetadataType>(s.ColumnInt(0));
-      result[key] = s.ColumnString(1);
+      target[key] = s.ColumnString(1);
     }
   }
 
--- a/OrthancServer/DatabaseWrapper.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DatabaseWrapper.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -32,14 +32,10 @@
 
 #pragma once
 
+#include "IDatabaseWrapper.h"
+
 #include "../Core/SQLite/Connection.h"
 #include "../Core/SQLite/Transaction.h"
-#include "../Core/DicomFormat/DicomInstanceHasher.h"
-#include "../Core/FileStorage/FileInfo.h"
-#include "IServerIndexListener.h"
-
-#include <list>
-#include <boost/date_time/posix_time/posix_time.hpp>
 
 namespace Orthanc
 {
@@ -53,197 +49,193 @@
    * translates low-level requests into SQL statements. Mutual
    * exclusion MUST be implemented at a higher level.
    **/
-  class DatabaseWrapper
+  class DatabaseWrapper : public IDatabaseWrapper
   {
   private:
-    IServerIndexListener& listener_;
+    IServerIndexListener* listener_;
     SQLite::Connection db_;
     Internals::SignalRemainingAncestor* signalRemainingAncestor_;
 
     void Open();
 
-    void GetChangesInternal(Json::Value& target,
+    void GetChangesInternal(std::list<ServerIndexChange>& target,
+                            bool& done,
                             SQLite::Statement& s,
-                            int64_t since,
-                            unsigned int maxResults);
+                            uint32_t maxResults);
 
-    void GetExportedResourcesInternal(Json::Value& target,
+    void GetExportedResourcesInternal(std::list<ExportedResource>& target,
+                                      bool& done,
                                       SQLite::Statement& s,
-                                      int64_t since,
-                                      unsigned int maxResults);
+                                      uint32_t maxResults);
+
+    void ClearTable(const std::string& tableName);
 
   public:
-    void SetGlobalProperty(GlobalProperty property,
-                           const std::string& value);
+    DatabaseWrapper(const std::string& path);
 
-    bool LookupGlobalProperty(std::string& target,
-                              GlobalProperty property);
+    DatabaseWrapper();
+
+    virtual void SetListener(IServerIndexListener& listener);
 
-    std::string GetGlobalProperty(GlobalProperty property,
-                                  const std::string& defaultValue = "");
+    virtual void SetGlobalProperty(GlobalProperty property,
+                                   const std::string& value);
 
-    int64_t CreateResource(const std::string& publicId,
-                           ResourceType type);
+    virtual bool LookupGlobalProperty(std::string& target,
+                                      GlobalProperty property);
+
+    virtual int64_t CreateResource(const std::string& publicId,
+                                   ResourceType type);
 
-    bool LookupResource(const std::string& publicId,
-                        int64_t& id,
-                        ResourceType& type);
+    virtual bool LookupResource(int64_t& id,
+                                ResourceType& type,
+                                const std::string& publicId);
 
-    bool LookupParent(int64_t& parentId,
-                      int64_t resourceId);
+    virtual bool LookupParent(int64_t& parentId,
+                              int64_t resourceId);
 
-    std::string GetPublicId(int64_t resourceId);
+    virtual std::string GetPublicId(int64_t resourceId);
 
-    ResourceType GetResourceType(int64_t resourceId);
+    virtual ResourceType GetResourceType(int64_t resourceId);
 
-    void AttachChild(int64_t parent,
-                     int64_t child);
+    virtual void AttachChild(int64_t parent,
+                             int64_t child);
 
-    void GetChildren(Json::Value& childrenPublicIds,
-                     int64_t id);
+    virtual void DeleteResource(int64_t id);
 
-    void DeleteResource(int64_t id);
+    virtual void SetMetadata(int64_t id,
+                             MetadataType type,
+                             const std::string& value);
 
-    void SetMetadata(int64_t id,
-                     MetadataType type,
-                     const std::string& value);
+    virtual void DeleteMetadata(int64_t id,
+                                MetadataType type);
 
-    void DeleteMetadata(int64_t id,
-                        MetadataType type);
+    virtual bool LookupMetadata(std::string& target,
+                                int64_t id,
+                                MetadataType type);
 
-    bool LookupMetadata(std::string& target,
-                        int64_t id,
-                        MetadataType type);
-
-    void ListAvailableMetadata(std::list<MetadataType>& target,
-                               int64_t id);
+    virtual void ListAvailableMetadata(std::list<MetadataType>& target,
+                                       int64_t id);
 
-    std::string GetMetadata(int64_t id,
-                            MetadataType type,
-                            const std::string& defaultValue = "");
+    virtual void AddAttachment(int64_t id,
+                               const FileInfo& attachment);
 
-    bool GetMetadataAsInteger(int& result,
-                              int64_t id,
-                              MetadataType type);
+    virtual void DeleteAttachment(int64_t id,
+                                  FileContentType attachment);
+
+    virtual void ListAvailableAttachments(std::list<FileContentType>& target,
+                                          int64_t id);
 
-    void AddAttachment(int64_t id,
-                       const FileInfo& attachment);
+    virtual bool LookupAttachment(FileInfo& attachment,
+                                  int64_t id,
+                                  FileContentType contentType);
 
-    void DeleteAttachment(int64_t id,
-                          FileContentType attachment);
+    virtual void SetMainDicomTag(int64_t id,
+                                 const DicomTag& tag,
+                                 const std::string& value);
 
-    void ListAvailableAttachments(std::list<FileContentType>& result,
+    virtual void GetMainDicomTags(DicomMap& map,
                                   int64_t id);
 
-    bool LookupAttachment(FileInfo& attachment,
-                          int64_t id,
-                          FileContentType contentType);
-
-    void SetMainDicomTags(int64_t id,
-                          const DicomMap& tags);
+    virtual void GetChildrenPublicId(std::list<std::string>& target,
+                                     int64_t id);
 
-    void GetMainDicomTags(DicomMap& map,
-                          int64_t id);
+    virtual void GetChildrenInternalId(std::list<int64_t>& target,
+                                       int64_t id);
 
-    bool GetParentPublicId(std::string& result,
-                           int64_t id);
-
-    void GetChildrenPublicId(std::list<std::string>& result,
-                             int64_t id);
+    virtual void LogChange(int64_t internalId,
+                           const ServerIndexChange& change);
 
-    void GetChildrenInternalId(std::list<int64_t>& result,
-                               int64_t id);
+    virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
+                            bool& done /*out*/,
+                            int64_t since,
+                            uint32_t maxResults);
+
+    virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/);
 
-    void LogChange(int64_t internalId,
-                   ChangeType changeType,
-                   ResourceType resourceType,
-                   const std::string& publicId)
-    {
-      ServerIndexChange change(changeType, resourceType, publicId);
-      LogChange(internalId, change);
-    }
-
-    void LogChange(int64_t internalId,
-                   const ServerIndexChange& change);
-
-    void GetChanges(Json::Value& target,
-                    int64_t since,
-                    unsigned int maxResults);
-
-    void GetLastChange(Json::Value& target);
+    virtual void LogExportedResource(const ExportedResource& resource);
+    
+    virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
+                                      bool& done /*out*/,
+                                      int64_t since,
+                                      uint32_t maxResults);
 
-    void LogExportedResource(ResourceType resourceType,
-                             const std::string& publicId,
-                             const std::string& remoteModality,
-                             const std::string& patientId,
-                             const std::string& studyInstanceUid,
-                             const std::string& seriesInstanceUid,
-                             const std::string& sopInstanceUid,
-                             const boost::posix_time::ptime& date = 
-                             boost::posix_time::second_clock::local_time());
-    
-    void GetExportedResources(Json::Value& target,
-                              int64_t since,
-                              unsigned int maxResults);
+    virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/);
 
-    void GetLastExportedResource(Json::Value& target);
-
-    // For unit testing only!
-    int64_t GetTableRecordCount(const std::string& table);
-    
-    uint64_t GetTotalCompressedSize();
+    virtual uint64_t GetTotalCompressedSize();
     
-    uint64_t GetTotalUncompressedSize();
+    virtual uint64_t GetTotalUncompressedSize();
 
-    uint64_t GetResourceCount(ResourceType resourceType);
+    virtual uint64_t GetResourceCount(ResourceType resourceType);
 
-    void GetAllPublicIds(Json::Value& target,
-                         ResourceType resourceType);
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 ResourceType resourceType);
 
-    bool SelectPatientToRecycle(int64_t& internalId);
-
-    bool SelectPatientToRecycle(int64_t& internalId,
-                                int64_t patientIdToAvoid);
+    virtual bool SelectPatientToRecycle(int64_t& internalId);
 
-    bool IsProtectedPatient(int64_t internalId);
+    virtual bool SelectPatientToRecycle(int64_t& internalId,
+                                        int64_t patientIdToAvoid);
 
-    void SetProtectedPatient(int64_t internalId, 
-                             bool isProtected);
+    virtual bool IsProtectedPatient(int64_t internalId);
 
-    DatabaseWrapper(const std::string& path,
-                    IServerIndexListener& listener);
+    virtual void SetProtectedPatient(int64_t internalId, 
+                                     bool isProtected);
 
-    DatabaseWrapper(IServerIndexListener& listener);
-
-    SQLite::Transaction* StartTransaction()
+    virtual SQLite::ITransaction* StartTransaction()
     {
       return new SQLite::Transaction(db_);
     }
 
+    virtual void FlushToDisk()
+    {
+      db_.FlushToDisk();
+    }
+
+    virtual bool HasFlushToDisk() const
+    {
+      return true;
+    }
+
+    virtual void ClearChanges()
+    {
+      ClearTable("Changes");
+    }
+
+    virtual void ClearExportedResources()
+    {
+      ClearTable("ExportedResources");
+    }
+
+    virtual bool IsExistingResource(int64_t internalId);
+
+    virtual void LookupIdentifier(std::list<int64_t>& target,
+                                  const DicomTag& tag,
+                                  const std::string& value);
+
+    virtual void LookupIdentifier(std::list<int64_t>& target,
+                                  const std::string& value);
+
+    virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
+                                int64_t id);
+
+
+
+
+    /**
+     * The methods declared below are for unit testing only!
+     **/
+
     const char* GetErrorMessage() const
     {
       return db_.GetErrorMessage();
     }
 
-    void FlushToDisk()
-    {
-      db_.FlushToDisk();
-    }
-
-    uint64_t IncrementGlobalSequence(GlobalProperty property);
-
-    void ClearTable(const std::string& tableName);
+    void GetChildren(std::list<std::string>& childrenPublicIds,
+                     int64_t id);
 
-    bool IsExistingResource(int64_t internalId);
-
-    void LookupIdentifier(std::list<int64_t>& result,
-                          const DicomTag& tag,
-                          const std::string& value);
+    int64_t GetTableRecordCount(const std::string& table);
+    
+    bool GetParentPublicId(std::string& target,
+                           int64_t id);
 
-    void LookupIdentifier(std::list<int64_t>& result,
-                          const std::string& value);
-
-    void GetAllMetadata(std::map<MetadataType, std::string>& result,
-                        int64_t id);
   };
 }
--- a/OrthancServer/DicomDirWriter.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomDirWriter.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomDirWriter.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomDirWriter.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -34,9 +34,11 @@
 
 #include "ParsedDicomFile.h"
 
+#include <boost/noncopyable.hpp>
+
 namespace Orthanc
 {
-  class DicomDirWriter
+  class DicomDirWriter : public boost::noncopyable
   {
   private:
     class PImpl;
--- a/OrthancServer/DicomInstanceToStore.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomInstanceToStore.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomInstanceToStore.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomInstanceToStore.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -60,7 +60,7 @@
       }
 
     public:
-      SmartContainer() : content_(NULL), toDelete_(false)
+      SmartContainer() : content_(NULL), toDelete_(false), isReadOnly_(true)
       {
       }
 
@@ -144,6 +144,7 @@
     SmartContainer<Json::Value>  json_;
 
     std::string remoteAet_;
+    std::string calledAet_;
     ServerIndex::MetadataMap metadata_;
 
     void ComputeMissingInformation();
@@ -169,7 +170,7 @@
       json_.SetConstReference(json);
     }
 
-    const std::string GetRemoteAet() const
+    const std::string& GetRemoteAet() const
     {
       return remoteAet_;
     }
@@ -179,6 +180,16 @@
       remoteAet_ = aet;
     }
 
+    const std::string& GetCalledAet() const
+    {
+      return calledAet_;
+    }
+
+    void SetCalledAet(const std::string& aet)
+    {
+      calledAet_ = aet;
+    }
+
     void AddMetadata(ResourceType level,
                      MetadataType metadata,
                      const std::string& value);
--- a/OrthancServer/DicomModification.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomModification.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -39,8 +39,23 @@
 #include <memory>   // For std::auto_ptr
 #include <glog/logging.h>
 
+
+static const std::string ORTHANC_DEIDENTIFICATION_METHOD = "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1";
+
 namespace Orthanc
 {
+  void DicomModification::MarkNotOrthancAnonymization()
+  {
+    Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD);
+
+    if (it != replacements_.end() &&
+        it->second == ORTHANC_DEIDENTIFICATION_METHOD)
+    {
+      replacements_.erase(it);
+    }
+  }
+
+
   void DicomModification::MapDicomIdentifier(ParsedDicomFile& dicom,
                                              ResourceType level)
   {
@@ -90,6 +105,7 @@
   {
     removePrivateTags_ = false;
     level_ = ResourceType_Instance;
+    allowManualIdentifiers_ = true;
   }
 
   void DicomModification::Keep(const DicomTag& tag)
@@ -101,6 +117,8 @@
     {
       privateTagsToKeep_.insert(tag);
     }
+
+    MarkNotOrthancAnonymization();
   }
 
   void DicomModification::Remove(const DicomTag& tag)
@@ -108,6 +126,8 @@
     removals_.insert(tag);
     replacements_.erase(tag);
     privateTagsToKeep_.erase(tag);
+
+    MarkNotOrthancAnonymization();
   }
 
   bool DicomModification::IsRemoved(const DicomTag& tag) const
@@ -116,11 +136,17 @@
   }
 
   void DicomModification::Replace(const DicomTag& tag,
-                                  const std::string& value)
+                                  const std::string& value,
+                                  bool safeForAnonymization)
   {
     removals_.erase(tag);
     privateTagsToKeep_.erase(tag);
     replacements_[tag] = value;
+
+    if (!safeForAnonymization)
+    {
+      MarkNotOrthancAnonymization();
+    }
   }
 
   bool DicomModification::IsReplaced(const DicomTag& tag) const
@@ -145,12 +171,22 @@
   void DicomModification::SetRemovePrivateTags(bool removed)
   {
     removePrivateTags_ = removed;
+
+    if (!removed)
+    {
+      MarkNotOrthancAnonymization();
+    }
   }
 
   void DicomModification::SetLevel(ResourceType level)
   {
     uidMap_.clear();
     level_ = level;
+
+    if (level != ResourceType_Patient)
+    {
+      MarkNotOrthancAnonymization();
+    }
   }
 
   void DicomModification::SetupAnonymization()
@@ -219,7 +255,7 @@
     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"));
+    replacements_.insert(std::make_pair(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD));
 
     // Set the PatientIdentityRemoved tag
     replacements_.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES"));
@@ -246,49 +282,59 @@
     }
     
 
-    // Sanity checks
+    // Sanity checks at the patient level
     if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID))
     {
       LOG(ERROR) << "When modifying a patient, her PatientID is required to be modified";
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
+    if (!allowManualIdentifiers_)
     {
-      LOG(ERROR) << "When modifying a patient, the StudyInstanceUID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
+      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a patient, the StudyInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
+
+      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a patient, the SeriesInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
+
+      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a patient, the SopInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
     }
 
-    if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
-    {
-      LOG(ERROR) << "When modifying a patient, the SeriesInstanceUID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
-    }
 
-    if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
-    {
-      LOG(ERROR) << "When modifying a patient, the SopInstanceUID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-
+    // Sanity checks at the study level
     if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_PATIENT_ID))
     {
       LOG(ERROR) << "When modifying a study, the parent PatientID cannot be manually modified";
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
+    if (!allowManualIdentifiers_)
     {
-      LOG(ERROR) << "When modifying a study, the SeriesInstanceUID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
+      if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a study, the SeriesInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
+
+      if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a study, the SopInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
     }
 
-    if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
-    {
-      LOG(ERROR) << "When modifying a study, the SopInstanceUID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
-    }
 
+    // Sanity checks at the series level
     if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_PATIENT_ID))
     {
       LOG(ERROR) << "When modifying a series, the parent PatientID cannot be manually modified";
@@ -301,12 +347,17 @@
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
+    if (!allowManualIdentifiers_)
     {
-      LOG(ERROR) << "When modifying a series, the SopInstanceUID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
+      if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a series, the SopInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
     }
 
+
+    // Sanity checks at the instance level
     if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_PATIENT_ID))
     {
       LOG(ERROR) << "When modifying an instance, the parent PatientID cannot be manually modified";
@@ -347,17 +398,20 @@
     }
 
     // (4) Update the DICOM identifiers
-    if (level_ <= ResourceType_Study)
+    if (level_ <= ResourceType_Study &&
+        !IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
     {
       MapDicomIdentifier(toModify, ResourceType_Study);
     }
 
-    if (level_ <= ResourceType_Series)
+    if (level_ <= ResourceType_Series &&
+        !IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
     {
       MapDicomIdentifier(toModify, ResourceType_Series);
     }
 
-    if (level_ <= ResourceType_Instance)  // Always true
+    if (level_ <= ResourceType_Instance &&  // Always true
+        !IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
     {
       MapDicomIdentifier(toModify, ResourceType_Instance);
     }
--- a/OrthancServer/DicomModification.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomModification.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -56,10 +56,13 @@
     ResourceType level_;
     UidMap uidMap_;
     SetOfTags privateTagsToKeep_;
+    bool allowManualIdentifiers_;
 
     void MapDicomIdentifier(ParsedDicomFile& dicom,
                             ResourceType level);
 
+    void MarkNotOrthancAnonymization();
+
   public:
     DicomModification();
 
@@ -70,7 +73,8 @@
     bool IsRemoved(const DicomTag& tag) const;
 
     void Replace(const DicomTag& tag,
-                 const std::string& value);
+                 const std::string& value,
+                 bool safeForAnonymization = false);
 
     bool IsReplaced(const DicomTag& tag) const;
 
@@ -93,5 +97,15 @@
     void SetupAnonymization();
 
     void Apply(ParsedDicomFile& toModify);
+
+    void SetAllowManualIdentifiers(bool check)
+    {
+      allowManualIdentifiers_ = check;
+    }
+
+    bool AreAllowManualIdentifiers() const
+    {
+      return allowManualIdentifiers_;
+    }
   };
 }
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomProtocol/DicomServer.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomServer.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -278,11 +278,20 @@
       throw OrthancException("Too short AET");
     }
 
+    if (aet.size() > 16)
+    {
+      throw OrthancException("AET must be shorter than 16 characters");
+    }
+
     for (size_t i = 0; i < aet.size(); i++)
     {
-      if (!isalnum(aet[i]) && aet[i] != '-')
+      if (!(aet[i] == '-' ||
+            aet[i] == '_' ||
+            isdigit(aet[i]) ||
+            (aet[i] >= 'A' && aet[i] <= 'Z')))
       {
-        throw OrthancException("Only alphanumeric characters are allowed in AET");
+        LOG(WARNING) << "For best interoperability, only upper case, alphanumeric characters should be present in AET: \"" << aet << "\"";
+        break;
       }
     }
 
--- a/OrthancServer/DicomProtocol/DicomServer.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomServer.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -454,7 +454,7 @@
     int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass);
     if (presID == 0)
     {
-      throw OrthancException("DicomUserConnection: The C-FIND command is not supported by the distant AET");
+      throw OrthancException("DicomUserConnection: The C-FIND command is not supported by the remote AET");
     }
 
     T_DIMSE_C_FindRQ request;
@@ -546,7 +546,7 @@
     int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass);
     if (presID == 0)
     {
-      throw OrthancException("DicomUserConnection: The C-MOVE command is not supported by the distant AET");
+      throw OrthancException("DicomUserConnection: The C-MOVE command is not supported by the remote AET");
     }
 
     T_DIMSE_C_MoveRQ request;
@@ -614,10 +614,10 @@
     pimpl_(new PImpl),
     preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX),
     localAet_("STORESCU"),
-    distantAet_("ANY-SCP"),
-    distantHost_("127.0.0.1")
+    remoteAet_("ANY-SCP"),
+    remoteHost_("127.0.0.1")
   {
-    distantPort_ = 104;
+    remotePort_ = 104;
     manufacturer_ = ModalityManufacturer_Generic;
 
     SetTimeout(10); 
@@ -642,10 +642,10 @@
 
   void DicomUserConnection::Connect(const RemoteModalityParameters& parameters)
   {
-    SetDistantApplicationEntityTitle(parameters.GetApplicationEntityTitle());
-    SetDistantHost(parameters.GetHost());
-    SetDistantPort(parameters.GetPort());
-    SetDistantManufacturer(parameters.GetManufacturer());
+    SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle());
+    SetRemoteHost(parameters.GetHost());
+    SetRemotePort(parameters.GetPort());
+    SetRemoteManufacturer(parameters.GetManufacturer());
   }
 
 
@@ -658,16 +658,16 @@
     }
   }
 
-  void DicomUserConnection::SetDistantApplicationEntityTitle(const std::string& aet)
+  void DicomUserConnection::SetRemoteApplicationEntityTitle(const std::string& aet)
   {
-    if (distantAet_ != aet)
+    if (remoteAet_ != aet)
     {
       Close();
-      distantAet_ = aet;
+      remoteAet_ = aet;
     }
   }
 
-  void DicomUserConnection::SetDistantManufacturer(ModalityManufacturer manufacturer)
+  void DicomUserConnection::SetRemoteManufacturer(ModalityManufacturer manufacturer)
   {
     if (manufacturer_ != manufacturer)
     {
@@ -691,26 +691,26 @@
   }
 
 
-  void DicomUserConnection::SetDistantHost(const std::string& host)
+  void DicomUserConnection::SetRemoteHost(const std::string& host)
   {
-    if (distantHost_ != host)
+    if (remoteHost_ != host)
     {
       if (host.size() > HOST_NAME_MAX - 10)
       {
-        throw OrthancException("Distant host name is too long");
+        throw OrthancException("Remote host name is too long");
       }
 
       Close();
-      distantHost_ = host;
+      remoteHost_ = host;
     }
   }
 
-  void DicomUserConnection::SetDistantPort(uint16_t port)
+  void DicomUserConnection::SetRemotePort(uint16_t port)
   {
-    if (distantPort_ != port)
+    if (remotePort_ != port)
     {
       Close();
-      distantPort_ = port;
+      remotePort_ = port;
     }
   }
 
@@ -723,30 +723,30 @@
     }
 
     LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() 
-              << "\" to AET \"" << GetDistantApplicationEntityTitle() << "\" on host "
-              << GetDistantHost() << ":" << GetDistantPort() 
-              << " (manufacturer: " << EnumerationToString(GetDistantManufacturer()) << ")";
+              << "\" to AET \"" << GetRemoteApplicationEntityTitle() << "\" on host "
+              << GetRemoteHost() << ":" << GetRemotePort() 
+              << " (manufacturer: " << EnumerationToString(GetRemoteManufacturer()) << ")";
 
     Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_));
     Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU));
 
     // Set this application's title and the called application's title in the params
-    Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), distantAet_.c_str(), NULL));
+    Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL));
 
-    // Set the network addresses of the local and distant entities
+    // Set the network addresses of the local and remote entities
     char localHost[HOST_NAME_MAX];
     gethostname(localHost, HOST_NAME_MAX - 1);
 
-    char distantHostAndPort[HOST_NAME_MAX];
+    char remoteHostAndPort[HOST_NAME_MAX];
 
 #ifdef _MSC_VER
     _snprintf
 #else
       snprintf
 #endif
-      (distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_);
+      (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", remoteHost_.c_str(), remotePort_);
 
-    Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, distantHostAndPort));
+    Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort));
 
     // Set various options
     Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false));
@@ -942,7 +942,7 @@
              defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES)
     {
       // Make room in the default storage syntaxes
-      assert(defaultStorageSOPClasses_.size() > 0);  // Necessarily true because condition (*) is false
+      assert(!defaultStorageSOPClasses_.empty());  // Necessarily true because condition (*) is false
       defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin());
     }
 
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -60,9 +60,9 @@
     // Connection parameters
     std::string preferredTransferSyntax_;
     std::string localAet_;
-    std::string distantAet_;
-    std::string distantHost_;
-    uint16_t distantPort_;
+    std::string remoteAet_;
+    std::string remoteHost_;
+    uint16_t remotePort_;
     ModalityManufacturer manufacturer_;
     std::set<std::string> storageSOPClasses_;
     std::list<std::string> reservedStorageSOPClasses_;
@@ -97,30 +97,30 @@
       return localAet_;
     }
 
-    void SetDistantApplicationEntityTitle(const std::string& aet);
+    void SetRemoteApplicationEntityTitle(const std::string& aet);
 
-    const std::string& GetDistantApplicationEntityTitle() const
+    const std::string& GetRemoteApplicationEntityTitle() const
     {
-      return distantAet_;
+      return remoteAet_;
     }
 
-    void SetDistantHost(const std::string& host);
+    void SetRemoteHost(const std::string& host);
 
-    const std::string& GetDistantHost() const
+    const std::string& GetRemoteHost() const
     {
-      return distantHost_;
+      return remoteHost_;
     }
 
-    void SetDistantPort(uint16_t port);
+    void SetRemotePort(uint16_t port);
 
-    uint16_t GetDistantPort() const
+    uint16_t GetRemotePort() const
     {
-      return distantPort_;
+      return remotePort_;
     }
 
-    void SetDistantManufacturer(ModalityManufacturer manufacturer);
+    void SetRemoteManufacturer(ModalityManufacturer manufacturer);
 
-    ModalityManufacturer GetDistantManufacturer() const
+    ModalityManufacturer GetRemoteManufacturer() const
     {
       return manufacturer_;
     }
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -50,6 +50,7 @@
     virtual void Handle(const std::string& dicomFile,
                         const DicomMap& dicomSummary,
                         const Json::Value& dicomJson,
-                        const std::string& distantAet) = 0;
+                        const std::string& remoteAet,
+                        const std::string& calledAet) = 0;
   };
 }
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -50,10 +50,10 @@
                                          ModalityManufacturer manufacturer)
   {
     if (connection_ != NULL &&
-        connection_->GetDistantApplicationEntityTitle() == remoteAet &&
-        connection_->GetDistantHost() == address &&
-        connection_->GetDistantPort() == port &&
-        connection_->GetDistantManufacturer() == manufacturer)
+        connection_->GetRemoteApplicationEntityTitle() == remoteAet &&
+        connection_->GetRemoteHost() == address &&
+        connection_->GetRemotePort() == port &&
+        connection_->GetRemoteManufacturer() == manufacturer)
     {
       // The current connection can be reused
       LOG(INFO) << "Reusing the previous SCU connection";
@@ -64,10 +64,10 @@
 
     connection_ = new DicomUserConnection();
     connection_->SetLocalApplicationEntityTitle(localAet_);
-    connection_->SetDistantApplicationEntityTitle(remoteAet);
-    connection_->SetDistantHost(address);
-    connection_->SetDistantPort(port);
-    connection_->SetDistantManufacturer(manufacturer);
+    connection_->SetRemoteApplicationEntityTitle(remoteAet);
+    connection_->SetRemoteHost(address);
+    connection_->SetRemotePort(port);
+    connection_->SetRemoteManufacturer(manufacturer);
     connection_->Open();
   }
     
@@ -152,9 +152,15 @@
     Close();
   }
 
-  void ReusableDicomUserConnection::SetMillisecondsBeforeClose(unsigned int ms)
+  void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms)
   {
     boost::mutex::scoped_lock lock(mutex_);
+
+    if (ms == 0)
+    {
+      ms = 1;
+    }
+
     timeBeforeClose_ = boost::posix_time::milliseconds(ms);
   }
 
@@ -173,7 +179,7 @@
   void ReusableDicomUserConnection::Unlock()
   {
     if (connection_ != NULL &&
-        connection_->GetDistantManufacturer() == ModalityManufacturer_StoreScp)
+        connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp)
     {
       // "storescp" from DCMTK has problems when reusing a
       // connection. Always close.
--- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -88,12 +88,12 @@
 
     virtual ~ReusableDicomUserConnection();
 
-    unsigned int GetMillisecondsBeforeClose() const
+    uint64_t GetMillisecondsBeforeClose() const
     {
-      return static_cast<unsigned int>(timeBeforeClose_.total_milliseconds());
+      return static_cast<uint64_t>(timeBeforeClose_.total_milliseconds());
     }
 
-    void SetMillisecondsBeforeClose(unsigned int ms);
+    void SetMillisecondsBeforeClose(uint64_t ms);
 
     const std::string& GetLocalApplicationEntityTitle() const;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ExportedResource.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -0,0 +1,69 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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 "ExportedResource.h"
+
+#include "../Core/OrthancException.h"
+
+namespace Orthanc
+{
+  void ExportedResource::Format(Json::Value& item) const
+  {
+    item = Json::objectValue;
+    item["Seq"] = static_cast<int>(seq_);
+    item["ResourceType"] = EnumerationToString(resourceType_);
+    item["ID"] = publicId_;
+    item["Path"] = GetBasePath(resourceType_, publicId_);
+    item["RemoteModality"] = modality_;
+    item["Date"] = date_;
+
+    // WARNING: Do not add "break" below and do not reorder the case items!
+    switch (resourceType_)
+    {
+      case ResourceType_Instance:
+        item["SOPInstanceUID"] = sopInstanceUid_;
+
+      case ResourceType_Series:
+        item["SeriesInstanceUID"] = seriesInstanceUid_;
+
+      case ResourceType_Study:
+        item["StudyInstanceUID"] = studyInstanceUid_;
+
+      case ResourceType_Patient:
+        item["PatientID"] = patientId_;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ExportedResource.h	Wed Feb 11 10:32:14 2015 +0100
@@ -0,0 +1,125 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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 "ServerEnumerations.h"
+#include "../Core/Toolbox.h"
+
+#include <string>
+#include <json/value.h>
+
+namespace Orthanc
+{
+  class ExportedResource
+  {
+  private:
+    int64_t      seq_;
+    ResourceType resourceType_;
+    std::string  publicId_;
+    std::string  modality_;
+    std::string  date_;
+    std::string  patientId_;
+    std::string  studyInstanceUid_;
+    std::string  seriesInstanceUid_;
+    std::string  sopInstanceUid_;
+
+  public:
+    ExportedResource(int64_t seq,
+                     ResourceType resourceType,
+                     const std::string& publicId,
+                     const std::string& modality,
+                     const std::string& date,
+                     const std::string& patientId,
+                     const std::string& studyInstanceUid,
+                     const std::string& seriesInstanceUid,
+                     const std::string& sopInstanceUid) :
+      seq_(seq),
+      resourceType_(resourceType),
+      publicId_(publicId),
+      modality_(modality),
+      date_(date),
+      patientId_(patientId),
+      studyInstanceUid_(studyInstanceUid),
+      seriesInstanceUid_(seriesInstanceUid),
+      sopInstanceUid_(sopInstanceUid)
+    {
+    }
+
+    int64_t  GetSeq() const
+    {
+      return seq_;
+    }
+
+    ResourceType  GetResourceType() const
+    {
+      return resourceType_;
+    }
+
+    const std::string&  GetPublicId() const
+    {
+      return publicId_;
+    }
+
+    const std::string& GetModality() const
+    {
+      return modality_;
+    }
+
+    const std::string& GetDate() const
+    {
+      return date_;
+    }
+
+    const std::string& GetPatientId() const
+    {
+      return patientId_;
+    }
+
+    const std::string& GetStudyInstanceUid() const
+    {
+      return studyInstanceUid_;
+    }
+
+    const std::string& GetSeriesInstanceUid() const
+    {
+      return seriesInstanceUid_;
+    }
+
+    const std::string& GetSopInstanceUid() const
+    {
+      return sopInstanceUid_;
+    }
+
+    void Format(Json::Value& item) const;
+  };
+}
--- a/OrthancServer/FromDcmtkBridge.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -186,15 +186,27 @@
 
   bool FromDcmtkBridge::IsPrivateTag(DcmTag& tag)
   {
+#if 1
+    DcmTagKey tmp(tag.getGTag(), tag.getETag());
+    return tmp.isPrivate();
+#else
+    // Implementation for Orthanc versions <= 0.8.5
     return (tag.getPrivateCreator() != NULL ||
             !strcmp("PrivateCreator", tag.getTagName()));  // TODO - This may change with future versions of DCMTK
+#endif
   }
 
 
   bool FromDcmtkBridge::IsPrivateTag(const DicomTag& tag)
   {
+#if 1
+    DcmTagKey tmp(tag.GetGroup(), tag.GetElement());
+    return tmp.isPrivate();
+#else
+    // Implementation for Orthanc versions <= 0.8.5
     DcmTag tmp(tag.GetGroup(), tag.GetElement());
     return IsPrivateTag(tmp);
+#endif
   }
 
 
--- a/OrthancServer/FromDcmtkBridge.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/FromDcmtkBridge.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/IDatabaseWrapper.h	Wed Feb 11 10:32:14 2015 +0100
@@ -0,0 +1,181 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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/DicomFormat/DicomMap.h"
+#include "../Core/SQLite/ITransaction.h"
+#include "../Core/FileStorage/FileInfo.h"
+#include "IServerIndexListener.h"
+#include "ExportedResource.h"
+
+#include <list>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IDatabaseWrapper : public boost::noncopyable
+  {
+  public:
+    virtual ~IDatabaseWrapper()
+    {
+    }
+
+    virtual void AddAttachment(int64_t id,
+                               const FileInfo& attachment) = 0;
+
+    virtual void AttachChild(int64_t parent,
+                             int64_t child) = 0;
+
+    virtual void ClearChanges() = 0;
+
+    virtual void ClearExportedResources() = 0;
+
+    virtual int64_t CreateResource(const std::string& publicId,
+                                   ResourceType type) = 0;
+
+    virtual void DeleteAttachment(int64_t id,
+                                  FileContentType attachment) = 0;
+
+    virtual void DeleteMetadata(int64_t id,
+                                MetadataType type) = 0;
+
+    virtual void DeleteResource(int64_t id) = 0;
+
+    virtual void FlushToDisk() = 0;
+
+    virtual bool HasFlushToDisk() const = 0;
+
+    virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
+                                int64_t id) = 0;
+
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 ResourceType resourceType) = 0;
+
+
+    virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
+                            bool& done /*out*/,
+                            int64_t since,
+                            uint32_t maxResults) = 0;
+
+    virtual void GetChildrenInternalId(std::list<int64_t>& target,
+                                       int64_t id) = 0;
+
+    virtual void GetChildrenPublicId(std::list<std::string>& target,
+                                     int64_t id) = 0;
+
+    virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
+                                      bool& done /*out*/,
+                                      int64_t since,
+                                      uint32_t maxResults) = 0;
+
+    virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) = 0;
+
+    virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) = 0;
+
+    virtual void GetMainDicomTags(DicomMap& map,
+                                  int64_t id) = 0;
+
+    virtual std::string GetPublicId(int64_t resourceId) = 0;
+
+    virtual uint64_t GetResourceCount(ResourceType resourceType) = 0;
+
+    virtual ResourceType GetResourceType(int64_t resourceId) = 0;
+
+    virtual uint64_t GetTotalCompressedSize() = 0;
+    
+    virtual uint64_t GetTotalUncompressedSize() = 0;
+
+    virtual bool IsExistingResource(int64_t internalId) = 0;
+
+    virtual bool IsProtectedPatient(int64_t internalId) = 0;
+
+    virtual void ListAvailableMetadata(std::list<MetadataType>& target,
+                                       int64_t id) = 0;
+
+    virtual void ListAvailableAttachments(std::list<FileContentType>& target,
+                                          int64_t id) = 0;
+
+    virtual void LogChange(int64_t internalId,
+                           const ServerIndexChange& change) = 0;
+
+    virtual void LogExportedResource(const ExportedResource& resource) = 0;
+    
+    virtual bool LookupAttachment(FileInfo& attachment,
+                                  int64_t id,
+                                  FileContentType contentType) = 0;
+
+    virtual bool LookupGlobalProperty(std::string& target,
+                                      GlobalProperty property) = 0;
+
+    virtual void LookupIdentifier(std::list<int64_t>& target,
+                                  const DicomTag& tag,
+                                  const std::string& value) = 0;
+
+    virtual void LookupIdentifier(std::list<int64_t>& target,
+                                  const std::string& value) = 0;
+
+    virtual bool LookupMetadata(std::string& target,
+                                int64_t id,
+                                MetadataType type) = 0;
+
+    virtual bool LookupParent(int64_t& parentId,
+                              int64_t resourceId) = 0;
+
+    virtual bool LookupResource(int64_t& id,
+                                ResourceType& type,
+                                const std::string& publicId) = 0;
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId) = 0;
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId,
+                                        int64_t patientIdToAvoid) = 0;
+
+    virtual void SetGlobalProperty(GlobalProperty property,
+                                   const std::string& value) = 0;
+
+    virtual void SetMainDicomTag(int64_t id,
+                                 const DicomTag& tag,
+                                 const std::string& value) = 0;
+
+    virtual void SetMetadata(int64_t id,
+                             MetadataType type,
+                             const std::string& value) = 0;
+
+    virtual void SetProtectedPatient(int64_t internalId, 
+                                     bool isProtected) = 0;
+
+    virtual SQLite::ITransaction* StartTransaction() = 0;
+
+    virtual void SetListener(IServerIndexListener& listener) = 0;
+  };
+}
--- a/OrthancServer/IServerIndexListener.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/IServerIndexListener.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Internals/CommandDispatcher.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Internals/CommandDispatcher.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Internals/CommandDispatcher.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Internals/DicomImageDecoder.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Internals/DicomImageDecoder.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Internals/DicomImageDecoder.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Internals/DicomImageDecoder.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Internals/FindScp.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Internals/FindScp.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Internals/FindScp.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Internals/FindScp.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Internals/MoveScp.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Internals/MoveScp.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Internals/MoveScp.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Internals/MoveScp.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Internals/StoreScp.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Internals/StoreScp.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -102,7 +102,8 @@
     struct StoreCallbackData
     {
       IStoreRequestHandler* handler;
-      const char* distantAET;
+      const char* remoteAET;
+      const char* calledAET;
       const char* modality;
       const char* affectedSOPInstanceUID;
       uint32_t messageID;
@@ -201,7 +202,7 @@
             {
               try
               {
-                cbdata->handler->Handle(buffer, summary, dicomJson, cbdata->distantAET);
+                cbdata->handler->Handle(buffer, summary, dicomJson, cbdata->remoteAET, cbdata->calledAET);
               }
               catch (OrthancException& e)
               {
@@ -255,11 +256,13 @@
     callbackData.messageID = req->MessageID;
     if (assoc && assoc->params)
     {
-      callbackData.distantAET = assoc->params->DULparams.callingAPTitle;
+      callbackData.remoteAET = assoc->params->DULparams.callingAPTitle;
+      callbackData.calledAET = assoc->params->DULparams.calledAPTitle;
     }
     else
     {
-      callbackData.distantAET = "";
+      callbackData.remoteAET = "";
+      callbackData.calledAET = "";
     }
 
     DcmFileFormat dcmff;
--- a/OrthancServer/Internals/StoreScp.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Internals/StoreScp.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/OrthancFindRequestHandler.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/OrthancInitialization.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancInitialization.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -45,6 +45,9 @@
 #include <boost/thread.hpp>
 #include <glog/logging.h>
 
+#include "DatabaseWrapper.h"
+#include "../Core/FileStorage/FilesystemStorage.h"
+
 
 #if ORTHANC_JPEG_ENABLED == 1
 #include <dcmtk/dcmjpeg/djdecode.h>
@@ -183,6 +186,52 @@
   }
 
 
+  static std::string GetStringValue(const Json::Value& configuration,
+                                    const std::string& key,
+                                    const std::string& defaultValue)
+  {
+    if (configuration.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    if (!configuration.isMember(key))
+    {
+      return defaultValue;
+    }
+
+    if (configuration[key].type() != Json::stringValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    return configuration[key].asString();
+  }
+
+
+  static int GetIntegerValue(const Json::Value& configuration,
+                             const std::string& key,
+                             int defaultValue)
+  {
+    if (configuration.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    if (!configuration.isMember(key))
+    {
+      return defaultValue;
+    }
+
+    if (configuration[key].type() != Json::intValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    return configuration[key].asInt();
+  }
+
+
   void OrthancInitialize(const char* configurationFile)
   {
     boost::mutex::scoped_lock lock(globalMutex_);
@@ -654,4 +703,108 @@
   {
     return configurationAbsolutePath_;
   }
+
+
+  static IDatabaseWrapper* CreateSQLiteWrapper()
+  {
+    std::string storageDirectoryStr = Configuration::GetGlobalStringParameter("StorageDirectory", "OrthancStorage");
+
+    // Open the database
+    boost::filesystem::path indexDirectory = Configuration::InterpretStringParameterAsPath(
+      Configuration::GetGlobalStringParameter("IndexDirectory", storageDirectoryStr));
+
+    LOG(WARNING) << "SQLite index directory: " << indexDirectory;
+
+    try
+    {
+      boost::filesystem::create_directories(indexDirectory);
+    }
+    catch (boost::filesystem::filesystem_error)
+    {
+    }
+
+    return new DatabaseWrapper(indexDirectory.string() + "/index");
+  }
+
+
+  namespace
+  {
+    // Anonymous namespace to avoid clashes between compilation modules
+
+    class FilesystemStorageWithoutDicom : public IStorageArea
+    {
+    private:
+      FilesystemStorage storage_;
+
+    public:
+      FilesystemStorageWithoutDicom(const std::string& path) : storage_(path)
+      {
+      }
+
+      virtual void Create(const std::string& uuid,
+                          const void* content, 
+                          size_t size,
+                          FileContentType type)
+      {
+        if (type != FileContentType_Dicom)
+        {
+          storage_.Create(uuid, content, size, type);
+        }
+      }
+
+      virtual void Read(std::string& content,
+                        const std::string& uuid,
+                        FileContentType type)
+      {
+        if (type != FileContentType_Dicom)
+        {
+          storage_.Read(content, uuid, type);
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_UnknownResource);
+        }
+      }
+
+      virtual void Remove(const std::string& uuid,
+                          FileContentType type) 
+      {
+        if (type != FileContentType_Dicom)
+        {
+          storage_.Remove(uuid, type);
+        }
+      }
+    };
+  }
+
+
+  static IStorageArea* CreateFilesystemStorage()
+  {
+    std::string storageDirectoryStr = Configuration::GetGlobalStringParameter("StorageDirectory", "OrthancStorage");
+
+    boost::filesystem::path storageDirectory = Configuration::InterpretStringParameterAsPath(storageDirectoryStr);
+    LOG(WARNING) << "Storage directory: " << storageDirectory;
+
+    if (Configuration::GetGlobalBoolParameter("StoreDicom", true))
+    {
+      return new FilesystemStorage(storageDirectory.string());
+    }
+    else
+    {
+      LOG(WARNING) << "The DICOM files will not be stored, Orthanc running in index-only mode";
+      return new FilesystemStorageWithoutDicom(storageDirectory.string());
+    }
+  }
+
+
+  IDatabaseWrapper* Configuration::CreateDatabaseWrapper()
+  {
+    return CreateSQLiteWrapper();
+  }
+
+
+  IStorageArea* Configuration::CreateStorageArea()
+  {
+    return CreateFilesystemStorage();
+  }  
 }
--- a/OrthancServer/OrthancInitialization.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancInitialization.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -40,6 +40,8 @@
 #include "DicomProtocol/RemoteModalityParameters.h"
 #include "ServerEnumerations.h"
 #include "OrthancPeerParameters.h"
+#include "IDatabaseWrapper.h"
+#include "../Core/FileStorage/IStorageArea.h"
 
 namespace Orthanc
 {
@@ -102,5 +104,9 @@
     static void RemovePeer(const std::string& symbolicName);
 
     static const std::string& GetConfigurationAbsolutePath();
+
+    static IDatabaseWrapper* CreateDatabaseWrapper();
+
+    static IStorageArea* CreateStorageArea();
   };
 }
--- a/OrthancServer/OrthancMoveRequestHandler.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -102,8 +102,8 @@
 
 
   bool OrthancMoveRequestHandler::LookupIdentifier(std::string& publicId,
-                                                 DicomTag tag,
-                                                 const DicomMap& input)
+                                                   DicomTag tag,
+                                                   const DicomMap& input)
   {
     if (!input.HasTag(tag))
     {
@@ -132,18 +132,42 @@
   {
     LOG(WARNING) << "Move-SCU request received for AET \"" << aet << "\"";
 
-
     /**
      * Retrieve the query level.
      **/
 
+    ResourceType level;
     const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL);
-    if (levelTmp == NULL) 
+
+    if (levelTmp != NULL) 
+    {
+      level = StringToResourceType(levelTmp->AsString().c_str());
+    }
+    else
     {
-      throw OrthancException(ErrorCode_BadRequest);
+      // The query level is not present in the C-Move request, which
+      // does not follow the DICOM standard. This is for instance the
+      // behavior of Tudor DICOM. Try and automatically deduce the
+      // query level: Start from the instance level, going up to the
+      // patient level until a valid DICOM identifier is found.
+
+      std::string publicId;
+
+      if (LookupIdentifier(publicId, DICOM_TAG_SOP_INSTANCE_UID, input) ||
+          LookupIdentifier(publicId, DICOM_TAG_SERIES_INSTANCE_UID, input) ||
+          LookupIdentifier(publicId, DICOM_TAG_STUDY_INSTANCE_UID, input) ||
+          LookupIdentifier(publicId, DICOM_TAG_PATIENT_ID, input))
+      {
+        return new OrthancMoveRequestIterator(context_, aet, publicId);
+      }
+      else
+      {
+        // No identifier is present in the request.
+        throw OrthancException(ErrorCode_BadRequest);
+      }
     }
 
-    ResourceType level = StringToResourceType(levelTmp->AsString().c_str());
+
 
     /**
      * Lookup for the resource to be sent.
--- a/OrthancServer/OrthancMoveRequestHandler.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancMoveRequestHandler.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/OrthancPeerParameters.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancPeerParameters.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/OrthancPeerParameters.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancPeerParameters.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -194,7 +194,7 @@
       {
         // Overwrite the random Patient's Name by one that is more
         // user-friendly (provided none was specified by the user)
-        target.Replace(DICOM_TAG_PATIENT_NAME, GeneratePatientName(OrthancRestApi::GetContext(call)));
+        target.Replace(DICOM_TAG_PATIENT_NAME, GeneratePatientName(OrthancRestApi::GetContext(call)), true);
       }
 
       return true;
@@ -361,6 +361,7 @@
   static void ModifyInstance(RestApiPostCall& call)
   {
     DicomModification modification;
+    modification.SetAllowManualIdentifiers(true);
 
     if (ParseModifyRequest(modification, call))
     {
@@ -389,6 +390,7 @@
   static void AnonymizeInstance(RestApiPostCall& call)
   {
     DicomModification modification;
+    modification.SetAllowManualIdentifiers(true);
 
     if (ParseAnonymizationRequest(modification, call))
     {
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -324,7 +324,7 @@
 
       size_t pos = 0;
       for (std::list<std::string>::const_iterator
-             it = instances.begin(); it != instances.end(); it++, pos++)
+             it = instances.begin(); it != instances.end(); ++it, ++pos)
       {
         // "DICOM restricts the filenames on DICOM media to 8
         // characters (some systems wrongly use 8.3, but this does not
--- a/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -81,11 +81,16 @@
     GetSinceAndLimit(since, limit, last, call);
 
     Json::Value result;
-    if ((!last && context.GetIndex().GetChanges(result, since, limit)) ||
-        ( last && context.GetIndex().GetLastChange(result)))
+    if (last)
     {
-      call.GetOutput().AnswerJson(result);
+      context.GetIndex().GetLastChange(result);
     }
+    else
+    {
+      context.GetIndex().GetChanges(result, since, limit);
+    }
+
+    call.GetOutput().AnswerJson(result);
   }
 
 
@@ -108,11 +113,16 @@
     GetSinceAndLimit(since, limit, last, call);
 
     Json::Value result;
-    if ((!last && context.GetIndex().GetExportedResources(result, since, limit)) ||
-        ( last && context.GetIndex().GetLastExportedResource(result)))
+    if (last)
     {
-      call.GetOutput().AnswerJson(result);
+      context.GetIndex().GetLastExportedResource(result);
     }
+    else
+    {
+      context.GetIndex().GetExportedResources(result, since, limit);
+    }
+
+    call.GetOutput().AnswerJson(result);
   }
 
 
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -626,7 +626,7 @@
     shared = Json::objectValue;
 
     for (Instances::const_iterator it = instances.begin();
-         it != instances.end(); it++)
+         it != instances.end(); ++it)
     {
       // Get the tags of the current instance, in the simplified format
       Json::Value tags;
@@ -713,10 +713,14 @@
 
   static void GetModuleInternal(RestApiGetCall& call,
                                 ResourceType resourceType,
-                                ResourceType queryLevel)
+                                DicomModule module)
   {
-    if (resourceType != queryLevel &&
-        !(resourceType == ResourceType_Study && queryLevel == ResourceType_Patient))
+    if (!((resourceType == ResourceType_Patient && module == DicomModule_Patient) ||
+          (resourceType == ResourceType_Study && module == DicomModule_Patient) ||
+          (resourceType == ResourceType_Study && module == DicomModule_Study) ||
+          (resourceType == ResourceType_Series && module == DicomModule_Series) ||
+          (resourceType == ResourceType_Instance && module == DicomModule_Instance) ||
+          (resourceType == ResourceType_Instance && module == DicomModule_Image)))
     {
       throw OrthancException(ErrorCode_NotImplemented);
     }
@@ -725,9 +729,9 @@
     std::string publicId = call.GetUriComponent("id", "");
     bool simplify = call.HasArgument("simplify");
 
-    typedef std::set<DicomTag> Module;
-    Module module;
-    DicomTag::GetTagsForModule(module, queryLevel);
+    typedef std::set<DicomTag> ModuleTags;
+    ModuleTags moduleTags;
+    DicomTag::GetTagsForModule(moduleTags, module);
 
     Json::Value tags;
 
@@ -751,9 +755,9 @@
     
     // Filter the tags of the instance according to the module
     Json::Value result = Json::objectValue;
-    for (Module::const_iterator it = module.begin(); it != module.end(); it++)
+    for (ModuleTags::const_iterator tag = moduleTags.begin(); tag != moduleTags.end(); ++tag)
     {
-      std::string s = it->Format();
+      std::string s = tag->Format();
       if (tags.isMember(s))
       {
         result[s] = tags[s];
@@ -774,16 +778,11 @@
     
 
 
-  template <enum ResourceType resourceType>
+  template <enum ResourceType resourceType, 
+            enum DicomModule module>
   static void GetModule(RestApiGetCall& call)
   {
-    GetModuleInternal(call, resourceType, resourceType);
-  }
-
-
-  static void GetPatientModuleForStudy(RestApiGetCall& call)
-  {
-    GetModuleInternal(call, ResourceType_Study, ResourceType_Patient);
+    GetModuleInternal(call, resourceType, module);
   }
 
 
@@ -799,7 +798,7 @@
     Json::Value result = Json::arrayValue;
     
     for (Resources::const_iterator it = resources.begin();
-         it != resources.end(); it++)
+         it != resources.end(); ++it)
     {     
       ResourceType type = it->first;
       const std::string& id = it->second;
@@ -831,7 +830,7 @@
       b.clear();
 
       for (std::list<std::string>::const_iterator
-             it = a.begin(); it != a.end(); it++)
+             it = a.begin(); it != a.end(); ++it)
       {
         index.GetChildren(c, *it);
         b.splice(b.begin(), c);
@@ -862,7 +861,7 @@
     Json::Value result = Json::arrayValue;
 
     for (std::list<std::string>::const_iterator
-           it = a.begin(); it != a.end(); it++)
+           it = a.begin(); it != a.end(); ++it)
     {
       Json::Value item;
 
@@ -891,7 +890,7 @@
     Json::Value result = Json::objectValue;
 
     for (Instances::const_iterator it = instances.begin();
-         it != instances.end(); it++)
+         it != instances.end(); ++it)
     {
       Json::Value full;
       context.ReadJson(full, *it);
@@ -913,6 +912,47 @@
 
 
 
+  template <enum ResourceType start, 
+            enum ResourceType end>
+  static void GetParentResource(RestApiGetCall& call)
+  {
+    assert(start > end);
+
+    ServerIndex& index = OrthancRestApi::GetIndex(call);
+    
+    std::string current = call.GetUriComponent("id", "");
+    ResourceType currentType = start;
+    while (currentType > end)
+    {
+      std::string parent;
+      if (!index.LookupParent(parent, current))
+      {
+        // Error that could happen if the resource gets deleted by
+        // another concurrent call
+        return;
+      }
+      
+      current = parent;
+      switch (currentType)
+      {
+        case ResourceType_Instance:  currentType = ResourceType_Series; break;
+        case ResourceType_Series:    currentType = ResourceType_Study; break;
+        case ResourceType_Study:     currentType = ResourceType_Patient; break;
+        default:                     throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    assert(currentType == end);
+
+    Json::Value result;
+    if (index.LookupResource(result, current, end))
+    {
+      call.GetOutput().AnswerJson(result);
+    }
+  }
+
+
+
   void OrthancRestApi::RegisterResources()
   {
     Register("/instances", ListResources<ResourceType_Instance>);
@@ -938,11 +978,11 @@
     Register("/series/{id}/shared-tags", GetSharedTags);
     Register("/studies/{id}/shared-tags", GetSharedTags);
 
-    Register("/instances/{id}/module", GetModule<ResourceType_Instance>);
-    Register("/patients/{id}/module", GetModule<ResourceType_Patient>);
-    Register("/series/{id}/module", GetModule<ResourceType_Series>);
-    Register("/studies/{id}/module", GetModule<ResourceType_Study>);
-    Register("/studies/{id}/module-patient", GetPatientModuleForStudy);
+    Register("/instances/{id}/module", GetModule<ResourceType_Instance, DicomModule_Instance>);
+    Register("/patients/{id}/module", GetModule<ResourceType_Patient, DicomModule_Patient>);
+    Register("/series/{id}/module", GetModule<ResourceType_Series, DicomModule_Series>);
+    Register("/studies/{id}/module", GetModule<ResourceType_Study, DicomModule_Study>);
+    Register("/studies/{id}/module-patient", GetModule<ResourceType_Study, DicomModule_Patient>);
 
     Register("/instances/{id}/file", GetInstanceFile);
     Register("/instances/{id}/export", ExportInstanceFile);
@@ -990,6 +1030,13 @@
     Register("/studies/{id}/instances", GetChildResources<ResourceType_Study, ResourceType_Instance>);
     Register("/series/{id}/instances", GetChildResources<ResourceType_Series, ResourceType_Instance>);
 
+    Register("/studies/{id}/patient", GetParentResource<ResourceType_Study, ResourceType_Patient>);
+    Register("/series/{id}/patient", GetParentResource<ResourceType_Series, ResourceType_Patient>);
+    Register("/series/{id}/study", GetParentResource<ResourceType_Series, ResourceType_Study>);
+    Register("/instances/{id}/patient", GetParentResource<ResourceType_Instance, ResourceType_Patient>);
+    Register("/instances/{id}/study", GetParentResource<ResourceType_Instance, ResourceType_Study>);
+    Register("/instances/{id}/series", GetParentResource<ResourceType_Instance, ResourceType_Series>);
+
     Register("/patients/{id}/instances-tags", GetChildInstancesTags);
     Register("/studies/{id}/instances-tags", GetChildInstancesTags);
     Register("/series/{id}/instances-tags", GetChildInstancesTags);
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -35,6 +35,8 @@
 
 #include "../OrthancInitialization.h"
 #include "../FromDcmtkBridge.h"
+#include "../../Plugins/Engine/PluginsManager.h"
+#include "../../Plugins/Engine/OrthancPlugins.h"
 
 #include <glog/logging.h>
 
@@ -54,6 +56,7 @@
 
     result["Version"] = ORTHANC_VERSION;
     result["Name"] = Configuration::GetGlobalStringParameter("Name", "");
+    result["DatabaseVersion"] = boost::lexical_cast<int>(OrthancRestApi::GetIndex(call).GetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "0"));
 
     call.GetOutput().AnswerJson(result);
   }
@@ -113,6 +116,95 @@
   }
 
 
+  // Plugins information ------------------------------------------------------
+
+  static void ListPlugins(RestApiGetCall& call)
+  {
+    Json::Value v = Json::arrayValue;
+
+    v.append("explorer.js");
+
+    if (OrthancRestApi::GetContext(call).HasPlugins())
+    {
+      std::list<std::string> plugins;
+      OrthancRestApi::GetContext(call).GetPluginsManager().ListPlugins(plugins);
+
+      for (std::list<std::string>::const_iterator 
+             it = plugins.begin(); it != plugins.end(); ++it)
+      {
+        v.append(*it);
+      }
+    }
+
+    call.GetOutput().AnswerJson(v);
+  }
+
+
+  static void GetPlugin(RestApiGetCall& call)
+  {
+    if (!OrthancRestApi::GetContext(call).HasPlugins())
+    {
+      return;
+    }
+
+    const PluginsManager& manager = OrthancRestApi::GetContext(call).GetPluginsManager();
+    std::string id = call.GetUriComponent("id", "");
+
+    if (manager.HasPlugin(id))
+    {
+      Json::Value v = Json::objectValue;
+      v["ID"] = id;
+      v["Version"] = manager.GetPluginVersion(id);
+
+      const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetOrthancPlugins();
+      const char *c = plugins.GetProperty(id.c_str(), _OrthancPluginProperty_RootUri);
+      if (c != NULL)
+      {
+        v["RootUri"] = c;
+      }
+
+      c = plugins.GetProperty(id.c_str(), _OrthancPluginProperty_Description);
+      if (c != NULL)
+      {
+        v["Description"] = c;
+      }
+
+      c = plugins.GetProperty(id.c_str(), _OrthancPluginProperty_OrthancExplorer);
+      v["ExtendsOrthancExplorer"] = (c != NULL);
+
+      call.GetOutput().AnswerJson(v);
+    }
+  }
+
+
+  static void GetOrthancExplorerPlugins(RestApiGetCall& call)
+  {
+    std::string s = "// Extensions to Orthanc Explorer by the registered plugins\n\n";
+
+    if (OrthancRestApi::GetContext(call).HasPlugins())
+    {
+      const PluginsManager& manager = OrthancRestApi::GetContext(call).GetPluginsManager();
+      const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetOrthancPlugins();
+
+      std::list<std::string> lst;
+      OrthancRestApi::GetContext(call).GetPluginsManager().ListPlugins(lst);
+
+      for (std::list<std::string>::const_iterator
+             it = lst.begin(); it != lst.end(); ++it)
+      {
+        const char* tmp = plugins.GetProperty(it->c_str(), _OrthancPluginProperty_OrthancExplorer);
+        if (tmp != NULL)
+        {
+          s += "/**\n * From plugin: " + *it + " (version " + manager.GetPluginVersion(*it) + ")\n **/\n\n";
+          s += std::string(tmp) + "\n\n";
+        }
+      }
+    }
+
+    call.GetOutput().AnswerBuffer(s, "application/javascript");
+  }
+
+
   void OrthancRestApi::RegisterSystem()
   {
     Register("/", ServeRoot);
@@ -122,5 +214,9 @@
     Register("/tools/execute-script", ExecuteScript);
     Register("/tools/now", GetNowIsoString);
     Register("/tools/dicom-conformance", GetDicomConformanceStatement);
+
+    Register("/plugins", ListPlugins);
+    Register("/plugins/{id}", GetPlugin);
+    Register("/plugins/explorer.js", GetOrthancExplorerPlugins);
   }
 }
--- a/OrthancServer/ParsedDicomFile.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ParsedDicomFile.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -807,13 +807,30 @@
 
 
 
+
   void ParsedDicomFile::Insert(const DicomTag& tag,
                                const std::string& value)
   {
-    std::auto_ptr<DcmElement> element(CreateElementForTag(tag));
-    FillElementWithString(*element, tag, value);
+    OFCondition cond;
+
+    if (FromDcmtkBridge::IsPrivateTag(tag))
+    {
+      // This is a private tag
+      // http://support.dcmtk.org/redmine/projects/dcmtk/wiki/howto_addprivatedata
 
-    if (!pimpl_->file_->getDataset()->insert(element.release(), false, false).good())
+      DcmTag key(tag.GetGroup(), tag.GetElement(), EVR_OB);
+      cond = pimpl_->file_->getDataset()->putAndInsertUint8Array
+        (key, (const Uint8*) value.c_str(), value.size(), false);
+    }
+    else
+    {
+      std::auto_ptr<DcmElement> element(CreateElementForTag(tag));
+      FillElementWithString(*element, tag, value);
+
+      cond = pimpl_->file_->getDataset()->insert(element.release(), false, false);
+    }
+
+    if (!cond.good())
     {
       // This field already exists
       throw OrthancException(ErrorCode_InternalError);
@@ -847,7 +864,17 @@
     }
     else
     {
-      FillElementWithString(*element, tag, value);
+      if (FromDcmtkBridge::IsPrivateTag(tag))
+      {
+        if (!element->putUint8Array((const Uint8*) value.c_str(), value.size()).good())
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+      else
+      {
+        FillElementWithString(*element, tag, value);
+      }
     }
 
 
@@ -888,25 +915,53 @@
   {
     DcmTagKey k(tag.GetGroup(), tag.GetElement());
     DcmDataset& dataset = *pimpl_->file_->getDataset();
-    DcmElement* element = NULL;
-    if (!dataset.findAndGetElement(k, element).good() ||
-        element == NULL)
+
+    if (FromDcmtkBridge::IsPrivateTag(tag))
     {
-      return false;
-    }
+      const Uint8* data = NULL;   // This is freed in the destructor of the dataset
+      long unsigned int count = 0;
 
-    std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element, pimpl_->encoding_));
+      if (dataset.findAndGetUint8Array(k, data, &count).good())
+      {
+        if (count > 0)
+        {
+          assert(data != NULL);
+          value.assign(reinterpret_cast<const char*>(data), count);
+        }
+        else
+        {
+          value.clear();
+        }
 
-    if (v.get() == NULL)
-    {
-      value = "";
+        return true;
+      }
+      else
+      {
+        return false;
+      }
     }
     else
     {
-      value = v->AsString();
+      DcmElement* element = NULL;
+      if (!dataset.findAndGetElement(k, element).good() ||
+          element == NULL)
+      {
+        return false;
+      }
+
+      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element, pimpl_->encoding_));
+
+      if (v.get() == NULL)
+      {
+        value = "";
+      }
+      else
+      {
+        value = v->AsString();
+      }
+
+      return true;
     }
-
-    return true;
   }
 
 
--- a/OrthancServer/ParsedDicomFile.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ParsedDicomFile.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/PrecompiledHeadersServer.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/PrecompiledHeadersServer.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/PrecompiledHeadersServer.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/PrecompiledHeadersServer.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/CallSystemCommand.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/CallSystemCommand.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/CallSystemCommand.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/CallSystemCommand.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/DeleteInstanceCommand.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/DeleteInstanceCommand.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/DeleteInstanceCommand.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/DeleteInstanceCommand.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/IServerCommand.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/IServerCommand.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/ModifyInstanceCommand.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/ModifyInstanceCommand.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/ServerCommandInstance.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/ServerCommandInstance.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -60,10 +60,10 @@
     }
 
     for (std::list<ServerCommandInstance*>::iterator
-           it = next_.begin(); it != next_.end(); it++)
+           it = next_.begin(); it != next_.end(); ++it)
     {
       for (ListOfStrings::const_iterator
-             output = outputs.begin(); output != outputs.end(); output++)
+             output = outputs.begin(); output != outputs.end(); ++output)
       {
         (*it)->AddInput(*output);
       }
--- a/OrthancServer/Scheduler/ServerCommandInstance.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/ServerCommandInstance.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/ServerJob.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/ServerJob.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -44,18 +44,18 @@
 
     unsigned int count = 0;
     for (std::list<ServerCommandInstance*>::const_iterator
-           it = filters_.begin(); it != filters_.end(); it++)
+           it = filters_.begin(); it != filters_.end(); ++it)
     {
       index[*it] = count++;
     }
 
     for (std::list<ServerCommandInstance*>::const_iterator
-           it = filters_.begin(); it != filters_.end(); it++)
+           it = filters_.begin(); it != filters_.end(); ++it)
     {
       const std::list<ServerCommandInstance*>& nextCommands = (*it)->GetNextCommands();
 
       for (std::list<ServerCommandInstance*>::const_iterator
-             next = nextCommands.begin(); next != nextCommands.end(); next++)
+             next = nextCommands.begin(); next != nextCommands.end(); ++next)
       {
         if (index.find(*next) == index.end() ||
             index[*next] <= index[*it])
@@ -82,7 +82,7 @@
     size_t size = filters_.size();
 
     for (std::list<ServerCommandInstance*>::iterator 
-           it = filters_.begin(); it != filters_.end(); it++)
+           it = filters_.begin(); it != filters_.end(); ++it)
     {
       target.Enqueue(*it);
     }
@@ -94,24 +94,24 @@
   }
 
 
-  ServerJob::ServerJob()
+  ServerJob::ServerJob() :
+    jobId_(Toolbox::GenerateUuid()),
+    submitted_(false),
+    description_("no description")
   {
-    jobId_ = Toolbox::GenerateUuid();      
-    submitted_ = false;
-    description_ = "no description";
   }
 
 
   ServerJob::~ServerJob()
   {
     for (std::list<ServerCommandInstance*>::iterator
-           it = filters_.begin(); it != filters_.end(); it++)
+           it = filters_.begin(); it != filters_.end(); ++it)
     {
       delete *it;
     }
 
     for (std::list<IDynamicObject*>::iterator
-           it = payloads_.begin(); it != payloads_.end(); it++)
+           it = payloads_.begin(); it != payloads_.end(); ++it)
     {
       delete *it;
     }
--- a/OrthancServer/Scheduler/ServerJob.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/ServerJob.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/ServerScheduler.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/ServerScheduler.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -55,7 +55,7 @@
                          const ListOfStrings& inputs)
       {
         for (ListOfStrings::const_iterator 
-               it = inputs.begin(); it != inputs.end(); it++)
+               it = inputs.begin(); it != inputs.end(); ++it)
         {
           target_.push_back(*it);
         }
@@ -232,7 +232,7 @@
     ServerCommandInstance& sink = job.AddCommand(new Sink(outputs));
 
     for (std::list<ServerCommandInstance*>::iterator
-           it = job.filters_.begin(); it != job.filters_.end(); it++)
+           it = job.filters_.begin(); it != job.filters_.end(); ++it)
     {
       if ((*it) != &sink &&
           (*it)->IsConnectedToSink())
@@ -328,7 +328,7 @@
     jobs.clear();
 
     for (Jobs::const_iterator 
-           it = jobs_.begin(); it != jobs_.end(); it++)
+           it = jobs_.begin(); it != jobs_.end(); ++it)
     {
       jobs.push_back(it->first);
     }
--- a/OrthancServer/Scheduler/ServerScheduler.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/ServerScheduler.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/StorePeerCommand.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/StorePeerCommand.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -52,6 +52,7 @@
   {
     // Configure the HTTP client
     HttpClient client;
+    client.SetProxy(Configuration::GetGlobalStringParameter("HttpProxy", ""));
     if (peer_.GetUsername().size() != 0 && 
         peer_.GetPassword().size() != 0)
     {
--- a/OrthancServer/Scheduler/StorePeerCommand.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/StorePeerCommand.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/StoreScuCommand.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/StoreScuCommand.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/Scheduler/StoreScuCommand.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/Scheduler/StoreScuCommand.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/ServerContext.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ServerContext.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -71,18 +71,22 @@
 
 namespace Orthanc
 {
-  ServerContext::ServerContext(const boost::filesystem::path& indexPath) :
-    index_(*this, indexPath.string()),
+  ServerContext::ServerContext(IDatabaseWrapper& database) :
+    index_(*this, database),
     compressionEnabled_(false),
     provider_(*this),
     dicomCache_(provider_, DICOM_CACHE_SIZE),
     scheduler_(Configuration::GetGlobalIntegerParameter("LimitJobs", 10)),
-    plugins_(NULL)
+    plugins_(NULL),
+    pluginsManager_(NULL)
   {
     scu_.SetLocalApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC"));
-    //scu_.SetMillisecondsBeforeClose(1);  // The connection is always released
+
+    uint64_t s = Configuration::GetGlobalIntegerParameter("DicomAssociationCloseDelay", 5);  // In seconds
+    scu_.SetMillisecondsBeforeClose(s * 1000);  // Milliseconds are expected here
 
     lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
+    lua_.SetHttpProxy(Configuration::GetGlobalStringParameter("HttpProxy", ""));
   }
 
   void ServerContext::SetCompressionEnabled(bool enabled)
@@ -207,7 +211,9 @@
 
   void ServerContext::ApplyLuaOnStoredInstance(const std::string& instanceId,
                                                const Json::Value& simplifiedDicom,
-                                               const Json::Value& metadata)
+                                               const Json::Value& metadata,
+                                               const std::string& remoteAet,
+                                               const std::string& calledAet)
   {
     LuaContextLocker locker(*this);
 
@@ -219,6 +225,8 @@
       call.PushString(instanceId);
       call.PushJson(simplifiedDicom);
       call.PushJson(metadata);
+      call.PushJson(remoteAet);
+      call.PushJson(calledAet);
       call.Execute();
 
       Json::Value operations;
@@ -312,7 +320,7 @@
       dicom.GetMetadata().clear();
 
       for (InstanceMetadata::const_iterator it = instanceMetadata.begin();
-           it != instanceMetadata.end(); it++)
+           it != instanceMetadata.end(); ++it)
       {
         dicom.GetMetadata().insert(std::make_pair(std::make_pair(ResourceType_Instance, it->first),
                                                   it->second));
@@ -356,11 +364,12 @@
 
         try
         {
-          ApplyLuaOnStoredInstance(resultPublicId, simplified, metadata);
+          ApplyLuaOnStoredInstance(resultPublicId, simplified, metadata, 
+                                   dicom.GetRemoteAet(), dicom.GetCalledAet());
         }
         catch (OrthancException& e)
         {
-          LOG(ERROR) << "Error in OnStoredInstance callback (Lua): " << e.What();
+          LOG(ERROR) << "Error in " << ON_STORED_INSTANCE << " callback (Lua): " << e.What();
         }
 
         if (plugins_ != NULL)
@@ -371,7 +380,7 @@
           }
           catch (OrthancException& e)
           {
-            LOG(ERROR) << "Error in OnStoredInstance callback (plugins): " << e.What();
+            LOG(ERROR) << "Error in " << ON_STORED_INSTANCE << " callback (plugins): " << e.What();
           }
         }
       }
@@ -536,4 +545,36 @@
       }
     }
   }
+
+
+  bool ServerContext::HasPlugins() const
+  {
+    return (pluginsManager_ && plugins_);
+  }
+
+
+  const PluginsManager& ServerContext::GetPluginsManager() const
+  {
+    if (HasPlugins())
+    {
+      return *pluginsManager_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  const OrthancPlugins& ServerContext::GetOrthancPlugins() const
+  {
+    if (HasPlugins())
+    {
+      return *plugins_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
 }
--- a/OrthancServer/ServerContext.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ServerContext.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -49,6 +49,7 @@
 namespace Orthanc
 {
   class OrthancPlugins;
+  class PluginsManager;
 
   /**
    * This class is responsible for maintaining the storage area on the
@@ -76,7 +77,9 @@
 
     void ApplyLuaOnStoredInstance(const std::string& instanceId,
                                   const Json::Value& simplifiedDicom,
-                                  const Json::Value& metadata);
+                                  const Json::Value& metadata,
+                                  const std::string& remoteAet,
+                                  const std::string& calledAet);
 
     ServerIndex index_;
     CompressedFileStorageAccessor accessor_;
@@ -91,6 +94,7 @@
     boost::mutex luaMutex_;
     LuaContext lua_;
     OrthancPlugins* plugins_;  // TODO Turn it into a listener pattern (idem for Lua callbacks)
+    const PluginsManager* pluginsManager_;
 
   public:
     class DicomCacheLocker : public boost::noncopyable
@@ -135,7 +139,7 @@
     };
 
 
-    ServerContext(const boost::filesystem::path& indexPath);
+    ServerContext(IDatabaseWrapper& database);
 
     void SetStorageArea(IStorageArea& storage)
     {
@@ -195,15 +199,29 @@
       return scheduler_;
     }
 
-    void SetOrthancPlugins(OrthancPlugins& plugins)
+    void SetOrthancPlugins(const PluginsManager& manager,
+                           OrthancPlugins& plugins)
     {
+      pluginsManager_ = &manager;
       plugins_ = &plugins;
     }
 
+    void ResetOrthancPlugins()
+    {
+      pluginsManager_ = NULL;
+      plugins_ = NULL;
+    }
+
     bool DeleteResource(Json::Value& target,
                         const std::string& uuid,
                         ResourceType expectedType);
 
     void SignalChange(const ServerIndexChange& change);
+
+    bool HasPlugins() const;
+
+    const PluginsManager& GetPluginsManager() const;
+
+    const OrthancPlugins& GetOrthancPlugins() const;
   };
 }
--- a/OrthancServer/ServerEnumerations.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ServerEnumerations.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/ServerEnumerations.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ServerEnumerations.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/ServerIndex.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ServerIndex.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -43,7 +43,6 @@
 #include "../Core/Toolbox.h"
 #include "../Core/Uuid.h"
 #include "../Core/DicomFormat/DicomArray.h"
-#include "../Core/SQLite/Transaction.h"
 #include "FromDcmtkBridge.h"
 #include "ServerContext.h"
 
@@ -140,7 +139,7 @@
       {
         for (std::list<ServerIndexChange>::const_iterator 
                it = pendingChanges_.begin(); 
-             it != pendingChanges_.end(); it++)
+             it != pendingChanges_.end(); ++it)
         {
           context_.SignalChange(*it);
         }
@@ -214,7 +213,7 @@
   {
   private:
     ServerIndex& index_;
-    std::auto_ptr<SQLite::Transaction> transaction_;
+    std::auto_ptr<SQLite::ITransaction> transaction_;
     bool isCommitted_;
 
   public:
@@ -222,10 +221,10 @@
       index_(index),
       isCommitted_(false)
     {
-      assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize());
+      transaction_.reset(index_.db_.StartTransaction());
+      transaction_->Begin();
 
-      transaction_.reset(index_.db_->StartTransaction());
-      transaction_->Begin();
+      assert(index_.currentStorageSize_ == index_.db_.GetTotalCompressedSize());
 
       index_.listener_->StartTransaction();
     }
@@ -251,8 +250,6 @@
         assert(index_.currentStorageSize_ >= index_.listener_->GetSizeOfFilesToRemove());
         index_.currentStorageSize_ -= index_.listener_->GetSizeOfFilesToRemove();
 
-        assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize());
-
         // Send all the pending changes to the Orthanc plugins
         index_.listener_->CommitChanges();
 
@@ -309,13 +306,13 @@
 
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(uuid, id, type) ||
+    if (!db_.LookupResource(id, type, uuid) ||
         expectedType != type)
     {
       return false;
     }
       
-    db_->DeleteResource(id);
+    db_.DeleteResource(id);
 
     if (listener_->HasRemainingLevel())
     {
@@ -346,9 +343,10 @@
     try
     {
       boost::mutex::scoped_lock lock(that->mutex_);
-      std::string sleepString = that->db_->GetGlobalProperty(GlobalProperty_FlushSleep);
+      std::string sleepString;
 
-      if (Toolbox::IsInteger(sleepString))
+      if (that->db_.LookupGlobalProperty(sleepString, GlobalProperty_FlushSleep) &&
+          Toolbox::IsInteger(sleepString))
       {
         sleep = boost::lexical_cast<unsigned int>(sleepString);
       }
@@ -371,7 +369,7 @@
       }
 
       boost::mutex::scoped_lock lock(that->mutex_);
-      that->db_->FlushToDisk();
+      that->db_.FlushToDisk();
       count = 0;
     }
 
@@ -379,7 +377,7 @@
   }
 
 
-  static void ComputeExpectedNumberOfInstances(DatabaseWrapper& db,
+  static void ComputeExpectedNumberOfInstances(IDatabaseWrapper& db,
                                                int64_t series,
                                                const DicomMap& dicomSummary)
   {
@@ -419,40 +417,147 @@
   }
 
 
+
+
+  bool ServerIndex::GetMetadataAsInteger(int64_t& result,
+                                         int64_t id,
+                                         MetadataType type)
+  {
+    std::string s;
+    if (!db_.LookupMetadata(s, id, type))
+    {
+      return false;
+    }
+
+    try
+    {
+      result = boost::lexical_cast<int64_t>(s);
+      return true;
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      return false;
+    }
+  }
+
+
+  void ServerIndex::LogChange(int64_t internalId,
+                              ChangeType changeType,
+                              ResourceType resourceType,
+                              const std::string& publicId)
+  {
+    ServerIndexChange change(changeType, resourceType, publicId);
+
+    if (changeType <= ChangeType_INTERNAL_LastLogged)
+    {
+      db_.LogChange(internalId, change);
+    }
+
+    assert(listener_.get() != NULL);
+    listener_->SignalChange(change);
+  }
+
+
+  uint64_t ServerIndex::IncrementGlobalSequenceInternal(GlobalProperty property)
+  {
+    std::string oldValue;
+
+    if (db_.LookupGlobalProperty(oldValue, property))
+    {
+      uint64_t oldNumber;
+
+      try
+      {
+        oldNumber = boost::lexical_cast<uint64_t>(oldValue);
+        db_.SetGlobalProperty(property, boost::lexical_cast<std::string>(oldNumber + 1));
+        return oldNumber + 1;
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+    else
+    {
+      // Initialize the sequence at "1"
+      db_.SetGlobalProperty(property, "1");
+      return 1;
+    }
+  }
+
+
+
+  void ServerIndex::SetMainDicomTags(int64_t resource,
+                                     const DicomMap& tags)
+  {
+    DicomArray flattened(tags);
+    for (size_t i = 0; i < flattened.GetSize(); i++)
+    {
+      const DicomElement& element = flattened.GetElement(i);
+      db_.SetMainDicomTag(resource, element.GetTag(), element.GetValue().AsString());
+    }
+  }
+
+
+  int64_t ServerIndex::CreateResource(const std::string& publicId,
+                                      ResourceType type)
+  {
+    int64_t id = db_.CreateResource(publicId, type);
+
+    ChangeType changeType;
+    switch (type)
+    {
+    case ResourceType_Patient: 
+      changeType = ChangeType_NewPatient; 
+      break;
+
+    case ResourceType_Study: 
+      changeType = ChangeType_NewStudy; 
+      break;
+
+    case ResourceType_Series: 
+      changeType = ChangeType_NewSeries; 
+      break;
+
+    case ResourceType_Instance: 
+      changeType = ChangeType_NewInstance; 
+      break;
+
+    default:
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    ServerIndexChange change(changeType, type, publicId);
+    db_.LogChange(id, change);
+
+    assert(listener_.get() != NULL);
+    listener_->SignalChange(change);
+
+    return id;
+  }
+
+
   ServerIndex::ServerIndex(ServerContext& context,
-                           const std::string& dbPath) : 
+                           IDatabaseWrapper& db) : 
     done_(false),
+    db_(db),
     maximumStorageSize_(0),
     maximumPatients_(0)
   {
     listener_.reset(new Internals::ServerIndexListener(context));
-
-    if (dbPath == ":memory:")
-    {
-      db_.reset(new DatabaseWrapper(*listener_));
-    }
-    else
-    {
-      boost::filesystem::path p = dbPath;
+    db_.SetListener(*listener_);
 
-      try
-      {
-        boost::filesystem::create_directories(p);
-      }
-      catch (boost::filesystem::filesystem_error)
-      {
-      }
-
-      db_.reset(new DatabaseWrapper(p.string() + "/index", *listener_));
-    }
-
-    currentStorageSize_ = db_->GetTotalCompressedSize();
+    currentStorageSize_ = db_.GetTotalCompressedSize();
 
     // Initial recycling if the parameters have changed since the last
     // execution of Orthanc
     StandaloneRecycling();
 
-    flushThread_ = boost::thread(FlushThread, this);
+    if (db.HasFlushToDisk())
+    {
+      flushThread_ = boost::thread(FlushThread, this);
+    }
+
     unstableResourcesMonitorThread_ = boost::thread(UnstableResourcesMonitorThread, this);
   }
 
@@ -461,7 +566,8 @@
   {
     done_ = true;
 
-    if (flushThread_.joinable())
+    if (db_.HasFlushToDisk() &&
+        flushThread_.joinable())
     {
       flushThread_.join();
     }
@@ -493,10 +599,10 @@
       {
         ResourceType type;
         int64_t tmp;
-        if (db_->LookupResource(hasher.HashInstance(), tmp, type))
+        if (db_.LookupResource(tmp, type, hasher.HashInstance()))
         {
           assert(type == ResourceType_Instance);
-          db_->GetAllMetadata(instanceMetadata, tmp);
+          db_.GetAllMetadata(instanceMetadata, tmp);
           return StoreStatus_AlreadyStored;
         }
       }
@@ -512,11 +618,11 @@
       Recycle(instanceSize, hasher.HashPatient());
 
       // Create the instance
-      int64_t instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance);
+      int64_t instance = CreateResource(hasher.HashInstance(), ResourceType_Instance);
 
       DicomMap dicom;
       dicomSummary.ExtractInstanceInformation(dicom);
-      db_->SetMainDicomTags(instance, dicom);
+      SetMainDicomTags(instance, dicom);
 
       // Detect up to which level the patient/study/series/instance
       // hierarchy must be created
@@ -528,26 +634,26 @@
       {
         ResourceType dummy;
 
-        if (db_->LookupResource(hasher.HashSeries(), series, dummy))
+        if (db_.LookupResource(series, dummy, hasher.HashSeries()))
         {
           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));
+          bool ok = (db_.LookupResource(patient, dummy, hasher.HashPatient()) &&
+                     db_.LookupResource(study, dummy, hasher.HashStudy()));
           assert(ok);
         }
-        else if (db_->LookupResource(hasher.HashStudy(), study, dummy))
+        else if (db_.LookupResource(study, dummy, hasher.HashStudy()))
         {
           assert(dummy == ResourceType_Study);
 
           // New series: The patient and the study already exist
           isNewSeries = true;
 
-          bool ok = db_->LookupResource(hasher.HashPatient(), patient, dummy);
+          bool ok = db_.LookupResource(patient, dummy, hasher.HashPatient());
           assert(ok);
         }
-        else if (db_->LookupResource(hasher.HashPatient(), patient, dummy))
+        else if (db_.LookupResource(patient, dummy, hasher.HashPatient()))
         {
           assert(dummy == ResourceType_Patient);
 
@@ -567,38 +673,38 @@
       // Create the series if needed
       if (isNewSeries)
       {
-        series = db_->CreateResource(hasher.HashSeries(), ResourceType_Series);
+        series = CreateResource(hasher.HashSeries(), ResourceType_Series);
         dicomSummary.ExtractSeriesInformation(dicom);
-        db_->SetMainDicomTags(series, dicom);
+        SetMainDicomTags(series, dicom);
       }
 
       // Create the study if needed
       if (isNewStudy)
       {
-        study = db_->CreateResource(hasher.HashStudy(), ResourceType_Study);
+        study = CreateResource(hasher.HashStudy(), ResourceType_Study);
         dicomSummary.ExtractStudyInformation(dicom);
-        db_->SetMainDicomTags(study, dicom);
+        SetMainDicomTags(study, dicom);
       }
 
       // Create the patient if needed
       if (isNewPatient)
       {
-        patient = db_->CreateResource(hasher.HashPatient(), ResourceType_Patient);
+        patient = CreateResource(hasher.HashPatient(), ResourceType_Patient);
         dicomSummary.ExtractPatientInformation(dicom);
-        db_->SetMainDicomTags(patient, dicom);
+        SetMainDicomTags(patient, dicom);
       }
 
       // Create the parent-to-child links
-      db_->AttachChild(series, instance);
+      db_.AttachChild(series, instance);
 
       if (isNewSeries)
       {
-        db_->AttachChild(study, series);
+        db_.AttachChild(study, series);
       }
 
       if (isNewStudy)
       {
-        db_->AttachChild(patient, study);
+        db_.AttachChild(patient, study);
       }
 
       // Sanity checks
@@ -611,7 +717,7 @@
       for (Attachments::const_iterator it = attachments.begin();
            it != attachments.end(); ++it)
       {
-        db_->AddAttachment(instance, *it);
+        db_.AddAttachment(instance, *it);
       }
 
       // Attach the user-specified metadata
@@ -621,19 +727,19 @@
         switch (it->first.first)
         {
           case ResourceType_Patient:
-            db_->SetMetadata(patient, it->first.second, it->second);
+            db_.SetMetadata(patient, it->first.second, it->second);
             break;
 
           case ResourceType_Study:
-            db_->SetMetadata(study, it->first.second, it->second);
+            db_.SetMetadata(study, it->first.second, it->second);
             break;
 
           case ResourceType_Series:
-            db_->SetMetadata(series, it->first.second, it->second);
+            db_.SetMetadata(series, it->first.second, it->second);
             break;
 
           case ResourceType_Instance:
-            db_->SetMetadata(instance, it->first.second, it->second);
+            db_.SetMetadata(instance, it->first.second, it->second);
             instanceMetadata[it->first.second] = it->second;
             break;
 
@@ -644,36 +750,36 @@
 
       // Attach the auto-computed metadata for the patient/study/series levels
       std::string now = Toolbox::GetNowIsoString();
-      db_->SetMetadata(series, MetadataType_LastUpdate, now);
-      db_->SetMetadata(study, MetadataType_LastUpdate, now);
-      db_->SetMetadata(patient, MetadataType_LastUpdate, now);
+      db_.SetMetadata(series, MetadataType_LastUpdate, now);
+      db_.SetMetadata(study, MetadataType_LastUpdate, now);
+      db_.SetMetadata(patient, MetadataType_LastUpdate, now);
 
       // Attach the auto-computed metadata for the instance level,
       // reflecting these additions into the input metadata map
-      db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, now);
+      db_.SetMetadata(instance, MetadataType_Instance_ReceptionDate, now);
       instanceMetadata[MetadataType_Instance_ReceptionDate] = now;
 
-      db_->SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet);
+      db_.SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet);
       instanceMetadata[MetadataType_Instance_RemoteAet] = remoteAet;
 
       const DicomValue* value;
       if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
           (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
       {
-        db_->SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString());
+        db_.SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString());
         instanceMetadata[MetadataType_Instance_IndexInSeries] = value->AsString();
       }
 
       // Check whether the series of this new instance is now completed
       if (isNewSeries)
       {
-        ComputeExpectedNumberOfInstances(*db_, series, dicomSummary);
+        ComputeExpectedNumberOfInstances(db_, series, dicomSummary);
       }
 
       SeriesStatus seriesStatus = GetSeriesStatus(series);
       if (seriesStatus == SeriesStatus_Complete)
       {
-        db_->LogChange(series, ChangeType_CompletedSeries, ResourceType_Series, hasher.HashSeries());
+        LogChange(series, ChangeType_CompletedSeries, ResourceType_Series, hasher.HashSeries());
       }
 
       // Mark the parent resources of this instance as unstable
@@ -687,8 +793,7 @@
     }
     catch (OrthancException& e)
     {
-      LOG(ERROR) << "EXCEPTION [" << e.What() << "]" 
-                 << " (SQLite status: " << db_->GetErrorMessage() << ")";
+      LOG(ERROR) << "EXCEPTION [" << e.What() << "]";
     }
 
     return StoreStatus_Failure;
@@ -701,17 +806,17 @@
     target = Json::objectValue;
 
     uint64_t cs = currentStorageSize_;
-    assert(cs == db_->GetTotalCompressedSize());
-    uint64_t us = db_->GetTotalUncompressedSize();
+    assert(cs == db_.GetTotalCompressedSize());
+    uint64_t us = db_.GetTotalUncompressedSize();
     target["TotalDiskSize"] = boost::lexical_cast<std::string>(cs);
     target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us);
     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));
-    target["CountSeries"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Series));
-    target["CountInstances"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Instance));
+    target["CountPatients"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Patient));
+    target["CountStudies"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Study));
+    target["CountSeries"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Series));
+    target["CountInstances"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Instance));
   }          
 
 
@@ -719,34 +824,23 @@
   SeriesStatus ServerIndex::GetSeriesStatus(int64_t id)
   {
     // Get the expected number of instances in this series (from the metadata)
-    std::string s = db_->GetMetadata(id, MetadataType_Series_ExpectedNumberOfInstances);
-
-    size_t expected;
-    try
-    {
-      expected = boost::lexical_cast<size_t>(s);
-    }
-    catch (boost::bad_lexical_cast&)
+    int64_t expected;
+    if (!GetMetadataAsInteger(expected, id, MetadataType_Series_ExpectedNumberOfInstances))
     {
       return SeriesStatus_Unknown;
     }
 
     // Loop over the instances of this series
     std::list<int64_t> children;
-    db_->GetChildrenInternalId(children, id);
+    db_.GetChildrenInternalId(children, id);
 
-    std::set<size_t> instances;
+    std::set<int64_t> instances;
     for (std::list<int64_t>::const_iterator 
            it = children.begin(); it != children.end(); ++it)
     {
       // Get the index of this instance in the series
-      s = db_->GetMetadata(*it, MetadataType_Instance_IndexInSeries);
-      size_t index;
-      try
-      {
-        index = boost::lexical_cast<size_t>(s);
-      }
-      catch (boost::bad_lexical_cast&)
+      int64_t index;
+      if (!GetMetadataAsInteger(index, *it, MetadataType_Instance_IndexInSeries))
       {
         return SeriesStatus_Unknown;
       }
@@ -766,7 +860,7 @@
       instances.insert(index);
     }
 
-    if (instances.size() == expected)
+    if (static_cast<int64_t>(instances.size()) == expected)
     {
       return SeriesStatus_Complete;
     }
@@ -782,7 +876,7 @@
                                         int64_t resourceId)
   {
     DicomMap tags;
-    db_->GetMainDicomTags(tags, resourceId);
+    db_.GetMainDicomTags(tags, resourceId);
     target["MainDicomTags"] = Json::objectValue;
     FromDcmtkBridge::ToJson(target["MainDicomTags"], tags);
   }
@@ -798,7 +892,7 @@
     // Lookup for the requested resource
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(publicId, id, type) ||
+    if (!db_.LookupResource(id, type, publicId) ||
         type != expectedType)
     {
       return false;
@@ -808,12 +902,12 @@
     if (type != ResourceType_Patient)
     {
       int64_t parentId;
-      if (!db_->LookupParent(parentId, id))
+      if (!db_.LookupParent(parentId, id))
       {
         throw OrthancException(ErrorCode_InternalError);
       }
 
-      std::string parent = db_->GetPublicId(parentId);
+      std::string parent = db_.GetPublicId(parentId);
 
       switch (type)
       {
@@ -836,7 +930,7 @@
 
     // List the children resources
     std::list<std::string> children;
-    db_->GetChildrenPublicId(children, id);
+    db_.GetChildrenPublicId(children, id);
 
     if (type != ResourceType_Instance)
     {
@@ -883,9 +977,9 @@
         result["Type"] = "Series";
         result["Status"] = EnumerationToString(GetSeriesStatus(id));
 
-        int i;
-        if (db_->GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances))
-          result["ExpectedNumberOfInstances"] = i;
+        int64_t i;
+        if (GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances))
+          result["ExpectedNumberOfInstances"] = static_cast<int>(i);
         else
           result["ExpectedNumberOfInstances"] = Json::nullValue;
 
@@ -897,7 +991,7 @@
         result["Type"] = "Instance";
 
         FileInfo attachment;
-        if (!db_->LookupAttachment(attachment, id, FileContentType_Dicom))
+        if (!db_.LookupAttachment(attachment, id, FileContentType_Dicom))
         {
           throw OrthancException(ErrorCode_InternalError);
         }
@@ -905,9 +999,9 @@
         result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize());
         result["FileUuid"] = attachment.GetUuid();
 
-        int i;
-        if (db_->GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries))
-          result["IndexInSeries"] = i;
+        int64_t i;
+        if (GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries))
+          result["IndexInSeries"] = static_cast<int>(i);
         else
           result["IndexInSeries"] = Json::nullValue;
 
@@ -924,13 +1018,15 @@
 
     std::string tmp;
 
-    tmp = db_->GetMetadata(id, MetadataType_AnonymizedFrom);
-    if (tmp.size() != 0)
+    if (db_.LookupMetadata(tmp, id, MetadataType_AnonymizedFrom))
+    {
       result["AnonymizedFrom"] = tmp;
+    }
 
-    tmp = db_->GetMetadata(id, MetadataType_ModifiedFrom);
-    if (tmp.size() != 0)
+    if (db_.LookupMetadata(tmp, id, MetadataType_ModifiedFrom))
+    {
       result["ModifiedFrom"] = tmp;
+    }
 
     if (type == ResourceType_Patient ||
         type == ResourceType_Study ||
@@ -938,9 +1034,10 @@
     {
       result["IsStable"] = !unstableResources_.Contains(id);
 
-      tmp = db_->GetMetadata(id, MetadataType_LastUpdate);
-      if (tmp.size() != 0)
+      if (db_.LookupMetadata(tmp, id, MetadataType_LastUpdate))
+      {
         result["LastUpdate"] = tmp;
+      }
     }
 
     return true;
@@ -955,12 +1052,12 @@
 
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(instanceUuid, id, type))
+    if (!db_.LookupResource(id, type, instanceUuid))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    if (db_->LookupAttachment(attachment, id, contentType))
+    if (db_.LookupAttachment(attachment, id, contentType))
     {
       assert(attachment.GetContentType() == contentType);
       return true;
@@ -976,27 +1073,76 @@
   void ServerIndex::GetAllUuids(Json::Value& target,
                                 ResourceType resourceType)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-    db_->GetAllPublicIds(target, resourceType);
+    std::list<std::string> lst;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      db_.GetAllPublicIds(lst, resourceType);
+    }
+
+    target = Json::arrayValue;
+    for (std::list<std::string>::const_iterator
+           it = lst.begin(); it != lst.end(); ++it)
+    {
+      target.append(*it);
+    }
   }
 
 
-  bool ServerIndex::GetChanges(Json::Value& target,
+  template <typename T>
+  static void FormatLog(Json::Value& target,
+                        const std::list<T>& log,
+                        const std::string& name,
+                        bool done,
+                        int64_t since)
+  {
+    Json::Value items = Json::arrayValue;
+    for (typename std::list<T>::const_iterator
+           it = log.begin(); it != log.end(); ++it)
+    {
+      Json::Value item;
+      it->Format(item);
+      items.append(item);
+    }
+
+    target = Json::objectValue;
+    target[name] = items;
+    target["Done"] = done;
+
+    int64_t last = (log.empty() ? since : log.back().GetSeq());
+    target["Last"] = static_cast<int>(last);
+  }
+
+
+  void ServerIndex::GetChanges(Json::Value& target,
                                int64_t since,                               
                                unsigned int maxResults)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-    db_->GetChanges(target, since, maxResults);
-    return true;
+    std::list<ServerIndexChange> changes;
+    bool done;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      db_.GetChanges(changes, done, since, maxResults);
+    }
+
+    FormatLog(target, changes, "Changes", done, since);
   }
 
-  bool ServerIndex::GetLastChange(Json::Value& target)
+
+  void ServerIndex::GetLastChange(Json::Value& target)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-    db_->GetLastChange(target);
-    return true;
+    std::list<ServerIndexChange> changes;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      db_.GetLastChange(changes);
+    }
+
+    FormatLog(target, changes, "Changes", true, 0);
   }
 
+
   void ServerIndex::LogExportedResource(const std::string& publicId,
                                         const std::string& remoteModality)
   {
@@ -1004,7 +1150,7 @@
 
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(publicId, id, type))
+    if (!db_.LookupResource(id, type, publicId))
     {
       throw OrthancException(ErrorCode_InternalError);
     }
@@ -1022,7 +1168,7 @@
     while (!done)
     {
       DicomMap map;
-      db_->GetMainDicomTags(map, currentId);
+      db_.GetMainDicomTags(map, currentId);
 
       switch (currentType)
       {
@@ -1054,36 +1200,51 @@
       // the current resource
       if (!done)
       {
-        bool ok = db_->LookupParent(currentId, currentId);
+        bool ok = db_.LookupParent(currentId, currentId);
         assert(ok);
       }
     }
 
-    // No need for a SQLite::Transaction here, as we only insert 1 record
-    db_->LogExportedResource(type,
-                             publicId,
-                             remoteModality,
-                             patientId,
-                             studyInstanceUid,
-                             seriesInstanceUid,
-                             sopInstanceUid);
+    // No need for a SQLite::ITransaction here, as we only insert 1 record
+    ExportedResource resource(-1, 
+                              type,
+                              publicId,
+                              remoteModality,
+                              Toolbox::GetNowIsoString(),
+                              patientId,
+                              studyInstanceUid,
+                              seriesInstanceUid,
+                              sopInstanceUid);
+    db_.LogExportedResource(resource);
   }
 
 
-  bool ServerIndex::GetExportedResources(Json::Value& target,
+  void ServerIndex::GetExportedResources(Json::Value& target,
                                          int64_t since,
                                          unsigned int maxResults)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-    db_->GetExportedResources(target, since, maxResults);
-    return true;
+    std::list<ExportedResource> exported;
+    bool done;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      db_.GetExportedResources(exported, done, since, maxResults);
+    }
+
+    FormatLog(target, exported, "Exports", done, since);
   }
 
-  bool ServerIndex::GetLastExportedResource(Json::Value& target)
+
+  void ServerIndex::GetLastExportedResource(Json::Value& target)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-    db_->GetLastExportedResource(target);
-    return true;
+    std::list<ExportedResource> exported;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      db_.GetLastExportedResource(exported);
+    }
+
+    FormatLog(target, exported, "Exports", true, 0);
   }
 
 
@@ -1092,7 +1253,7 @@
     if (maximumStorageSize_ != 0)
     {
       uint64_t currentSize = currentStorageSize_ - listener_->GetSizeOfFilesToRemove();
-      assert(db_->GetTotalCompressedSize() == currentSize);
+      assert(db_.GetTotalCompressedSize() == currentSize);
 
       if (currentSize + instanceSize > maximumStorageSize_)
       {
@@ -1102,7 +1263,7 @@
 
     if (maximumPatients_ != 0)
     {
-      uint64_t patientCount = db_->GetResourceCount(ResourceType_Patient);
+      uint64_t patientCount = db_.GetResourceCount(ResourceType_Patient);
       if (patientCount > maximumPatients_)
       {
         return true;
@@ -1125,7 +1286,7 @@
     // already stored
     int64_t patientToAvoid;
     ResourceType type;
-    bool hasPatientToAvoid = db_->LookupResource(newPatientId, patientToAvoid, type);
+    bool hasPatientToAvoid = db_.LookupResource(patientToAvoid, type, newPatientId);
 
     if (hasPatientToAvoid && type != ResourceType_Patient)
     {
@@ -1140,8 +1301,8 @@
       // If other instances of this patient are already in the store,
       // we must avoid to recycle them
       bool ok = hasPatientToAvoid ?
-        db_->SelectPatientToRecycle(patientToRecycle, patientToAvoid) :
-        db_->SelectPatientToRecycle(patientToRecycle);
+        db_.SelectPatientToRecycle(patientToRecycle, patientToAvoid) :
+        db_.SelectPatientToRecycle(patientToRecycle);
         
       if (!ok)
       {
@@ -1149,7 +1310,7 @@
       }
       
       LOG(INFO) << "Recycling one patient";
-      db_->DeleteResource(patientToRecycle);
+      db_.DeleteResource(patientToRecycle);
 
       if (!IsRecyclingNeeded(instanceSize))
       {
@@ -1209,13 +1370,13 @@
     // Lookup for the requested resource
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(publicId, id, type) ||
+    if (!db_.LookupResource(id, type, publicId) ||
         type != ResourceType_Patient)
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    return db_->IsProtectedPatient(id);
+    return db_.IsProtectedPatient(id);
   }
      
 
@@ -1227,14 +1388,14 @@
     // Lookup for the requested resource
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(publicId, id, type) ||
+    if (!db_.LookupResource(id, type, publicId) ||
         type != ResourceType_Patient)
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    // No need for a SQLite::Transaction here, as we only make 1 write to the DB
-    db_->SetProtectedPatient(id, isProtected);
+    // No need for a SQLite::ITransaction here, as we only make 1 write to the DB
+    db_.SetProtectedPatient(id, isProtected);
 
     if (isProtected)
       LOG(INFO) << "Patient " << publicId << " has been protected";
@@ -1252,7 +1413,7 @@
 
     ResourceType type;
     int64_t resource;
-    if (!db_->LookupResource(publicId, resource, type))
+    if (!db_.LookupResource(resource, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
@@ -1264,12 +1425,12 @@
     }
 
     std::list<int64_t> tmp;
-    db_->GetChildrenInternalId(tmp, resource);
+    db_.GetChildrenInternalId(tmp, resource);
 
     for (std::list<int64_t>::const_iterator 
            it = tmp.begin(); it != tmp.end(); ++it)
     {
-      result.push_back(db_->GetPublicId(*it));
+      result.push_back(db_.GetPublicId(*it));
     }
   }
 
@@ -1283,7 +1444,7 @@
 
     ResourceType type;
     int64_t top;
-    if (!db_->LookupResource(publicId, top, type))
+    if (!db_.LookupResource(top, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
@@ -1306,14 +1467,14 @@
       int64_t resource = toExplore.top();
       toExplore.pop();
 
-      if (db_->GetResourceType(resource) == ResourceType_Instance)
+      if (db_.GetResourceType(resource) == ResourceType_Instance)
       {
-        result.push_back(db_->GetPublicId(resource));
+        result.push_back(db_.GetPublicId(resource));
       }
       else
       {
         // Tag all the children of this resource as to be explored
-        db_->GetChildrenInternalId(tmp, resource);
+        db_.GetChildrenInternalId(tmp, resource);
         for (std::list<int64_t>::const_iterator 
                it = tmp.begin(); it != tmp.end(); ++it)
         {
@@ -1332,12 +1493,12 @@
 
     ResourceType rtype;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, rtype))
+    if (!db_.LookupResource(id, rtype, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->SetMetadata(id, type, value);
+    db_.SetMetadata(id, type, value);
   }
 
 
@@ -1348,12 +1509,12 @@
 
     ResourceType rtype;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, rtype))
+    if (!db_.LookupResource(id, rtype, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->DeleteMetadata(id, type);
+    db_.DeleteMetadata(id, type);
   }
 
 
@@ -1365,12 +1526,12 @@
 
     ResourceType rtype;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, rtype))
+    if (!db_.LookupResource(id, rtype, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    return db_->LookupMetadata(target, id, type);
+    return db_.LookupMetadata(target, id, type);
   }
 
 
@@ -1381,12 +1542,12 @@
 
     ResourceType rtype;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, rtype))
+    if (!db_.LookupResource(id, rtype, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->ListAvailableMetadata(target, id);
+    db_.ListAvailableMetadata(target, id);
   }
 
 
@@ -1398,13 +1559,13 @@
 
     ResourceType type;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, type) ||
+    if (!db_.LookupResource(id, type, publicId) ||
         expectedType != type)
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->ListAvailableAttachments(target, id);
+    db_.ListAvailableAttachments(target, id);
   }
 
 
@@ -1415,15 +1576,15 @@
 
     ResourceType type;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, type))
+    if (!db_.LookupResource(id, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
     int64_t parentId;
-    if (db_->LookupParent(parentId, id))
+    if (db_.LookupParent(parentId, id))
     {
-      target = db_->GetPublicId(parentId);
+      target = db_.GetPublicId(parentId);
       return true;
     }
     else
@@ -1437,10 +1598,10 @@
   {
     boost::mutex::scoped_lock lock(mutex_);
 
-    std::auto_ptr<SQLite::Transaction> transaction(db_->StartTransaction());
+    std::auto_ptr<SQLite::ITransaction> transaction(db_.StartTransaction());
 
     transaction->Begin();
-    uint64_t seq = db_->IncrementGlobalSequence(sequence);
+    uint64_t seq = IncrementGlobalSequenceInternal(sequence);
     transaction->Commit();
 
     return seq;
@@ -1452,17 +1613,17 @@
                               const std::string& publicId)
   {
     boost::mutex::scoped_lock lock(mutex_);
-    std::auto_ptr<SQLite::Transaction> transaction(db_->StartTransaction());
+    std::auto_ptr<SQLite::ITransaction> transaction(db_.StartTransaction());
     transaction->Begin();
 
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(publicId, id, type))
+    if (!db_.LookupResource(id, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->LogChange(id, changeType, type, publicId);
+    LogChange(id, changeType, type, publicId);
 
     transaction->Commit();
   }
@@ -1471,13 +1632,13 @@
   void ServerIndex::DeleteChanges()
   {
     boost::mutex::scoped_lock lock(mutex_);
-    db_->ClearTable("Changes");
+    db_.ClearChanges();
   }
 
   void ServerIndex::DeleteExportedResources()
   {
     boost::mutex::scoped_lock lock(mutex_);
-    db_->ClearTable("ExportedResources");
+    db_.ClearExportedResources();
   }
 
 
@@ -1504,16 +1665,16 @@
       int64_t resource = toExplore.top();
       toExplore.pop();
 
-      ResourceType thisType = db_->GetResourceType(resource);
+      ResourceType thisType = db_.GetResourceType(resource);
 
       std::list<FileContentType> f;
-      db_->ListAvailableAttachments(f, resource);
+      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))
+        if (db_.LookupAttachment(attachment, resource, *it))
         {
           compressedSize += attachment.GetCompressedSize();
           uncompressedSize += attachment.GetUncompressedSize();
@@ -1542,7 +1703,7 @@
 
         // Tag all the children of this resource as to be explored
         std::list<int64_t> tmp;
-        db_->GetChildrenInternalId(tmp, resource);
+        db_.GetChildrenInternalId(tmp, resource);
         for (std::list<int64_t>::const_iterator 
                it = tmp.begin(); it != tmp.end(); ++it)
         {
@@ -1571,7 +1732,7 @@
 
     ResourceType type;
     int64_t top;
-    if (!db_->LookupResource(publicId, top, type))
+    if (!db_.LookupResource(top, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
@@ -1620,7 +1781,7 @@
 
     ResourceType type;
     int64_t top;
-    if (!db_->LookupResource(publicId, top, type))
+    if (!db_.LookupResource(top, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
@@ -1657,20 +1818,20 @@
         int64_t id = that->unstableResources_.RemoveOldest(payload);
 
         // Ensure that the resource is still existing before logging the change
-        if (that->db_->IsExistingResource(id))
+        if (that->db_.IsExistingResource(id))
         {
           switch (payload.GetResourceType())
           {
             case ResourceType_Patient:
-              that->db_->LogChange(id, ChangeType_StablePatient, ResourceType_Patient, payload.GetPublicId());
+              that->LogChange(id, ChangeType_StablePatient, ResourceType_Patient, payload.GetPublicId());
               break;
 
             case ResourceType_Study:
-              that->db_->LogChange(id, ChangeType_StableStudy, ResourceType_Study, payload.GetPublicId());
+              that->LogChange(id, ChangeType_StableStudy, ResourceType_Study, payload.GetPublicId());
               break;
 
             case ResourceType_Series:
-              that->db_->LogChange(id, ChangeType_StableSeries, ResourceType_Series, payload.GetPublicId());
+              that->LogChange(id, ChangeType_StableSeries, ResourceType_Series, payload.GetPublicId());
               break;
 
             default:
@@ -1700,7 +1861,7 @@
     unstableResources_.AddOrMakeMostRecent(id, payload);
     //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id;
 
-    db_->LogChange(id, ChangeType_NewChildInstance, type, publicId);
+    LogChange(id, ChangeType_NewChildInstance, type, publicId);
   }
 
 
@@ -1715,14 +1876,14 @@
     boost::mutex::scoped_lock lock(mutex_);
 
     std::list<int64_t> id;
-    db_->LookupIdentifier(id, tag, value);
+    db_.LookupIdentifier(id, tag, value);
 
     for (std::list<int64_t>::const_iterator 
            it = id.begin(); it != id.end(); ++it)
     {
-      if (db_->GetResourceType(*it) == type)
+      if (db_.GetResourceType(*it) == type)
       {
-        result.push_back(db_->GetPublicId(*it));
+        result.push_back(db_.GetPublicId(*it));
       }
     }
   }
@@ -1737,12 +1898,12 @@
     boost::mutex::scoped_lock lock(mutex_);
 
     std::list<int64_t> id;
-    db_->LookupIdentifier(id, tag, value);
+    db_.LookupIdentifier(id, tag, value);
 
     for (std::list<int64_t>::const_iterator 
            it = id.begin(); it != id.end(); ++it)
     {
-      result.push_back(db_->GetPublicId(*it));
+      result.push_back(db_.GetPublicId(*it));
     }
   }
 
@@ -1755,13 +1916,13 @@
     boost::mutex::scoped_lock lock(mutex_);
 
     std::list<int64_t> id;
-    db_->LookupIdentifier(id, value);
+    db_.LookupIdentifier(id, value);
 
     for (std::list<int64_t>::const_iterator 
            it = id.begin(); it != id.end(); ++it)
     {
-      result.push_back(std::make_pair(db_->GetResourceType(*it),
-                                      db_->GetPublicId(*it)));
+      result.push_back(std::make_pair(db_.GetResourceType(*it),
+                                      db_.GetPublicId(*it)));
     }
   }
 
@@ -1775,20 +1936,20 @@
 
     ResourceType resourceType;
     int64_t resourceId;
-    if (!db_->LookupResource(publicId, resourceId, resourceType))
+    if (!db_.LookupResource(resourceId, resourceType, publicId))
     {
       return StoreStatus_Failure;  // Inexistent resource
     }
 
     // Remove possible previous attachment
-    db_->DeleteAttachment(resourceId, attachment.GetContentType());
+    db_.DeleteAttachment(resourceId, attachment.GetContentType());
 
     // Locate the patient of the target resource
     int64_t patientId = resourceId;
     for (;;)
     {
       int64_t parent;
-      if (db_->LookupParent(parent, patientId))
+      if (db_.LookupParent(parent, patientId))
       {
         // We have not reached the patient level yet
         patientId = parent;
@@ -1801,10 +1962,10 @@
     }
 
     // Possibly apply the recycling mechanism while preserving this patient
-    assert(db_->GetResourceType(patientId) == ResourceType_Patient);
-    Recycle(attachment.GetCompressedSize(), db_->GetPublicId(patientId));
+    assert(db_.GetResourceType(patientId) == ResourceType_Patient);
+    Recycle(attachment.GetCompressedSize(), db_.GetPublicId(patientId));
 
-    db_->AddAttachment(resourceId, attachment);
+    db_.AddAttachment(resourceId, attachment);
 
     t.Commit(attachment.GetCompressedSize());
 
@@ -1821,12 +1982,12 @@
 
     ResourceType rtype;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, rtype))
+    if (!db_.LookupResource(id, rtype, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->DeleteAttachment(id, type);
+    db_.DeleteAttachment(id, type);
 
     t.Commit(0);
   }
@@ -1841,22 +2002,54 @@
 
     ResourceType type;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, type))
+    if (!db_.LookupResource(id, type, publicId))
     {
       return false;
     }
 
     std::list<MetadataType> metadata;
-    db_->ListAvailableMetadata(metadata, id);
+    db_.ListAvailableMetadata(metadata, id);
 
     for (std::list<MetadataType>::const_iterator
-           it = metadata.begin(); it != metadata.end(); it++)
+           it = metadata.begin(); it != metadata.end(); ++it)
     {
       std::string key = EnumerationToString(*it);
-      std::string value = db_->GetMetadata(id, *it);
+
+      std::string value;
+      if (!db_.LookupMetadata(value, id, *it))
+      {
+        value.clear();
+      }
+
       target[key] = value;
     }
 
     return true;
   }
+
+
+  void ServerIndex::SetGlobalProperty(GlobalProperty property,
+                                      const std::string& value)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    db_.SetGlobalProperty(property, value);
+  }
+
+
+  std::string ServerIndex::GetGlobalProperty(GlobalProperty property,
+                                             const std::string& defaultValue)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    std::string value;
+    if (db_.LookupGlobalProperty(value, property))
+    {
+      return value;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
 }
--- a/OrthancServer/ServerIndex.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ServerIndex.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -40,7 +40,7 @@
 #include "../Core/DicomFormat/DicomInstanceHasher.h"
 #include "ServerEnumerations.h"
 
-#include "DatabaseWrapper.h"
+#include "IDatabaseWrapper.h"
 
 
 namespace Orthanc
@@ -68,7 +68,7 @@
     boost::thread unstableResourcesMonitorThread_;
 
     std::auto_ptr<Internals::ServerIndexListener> listener_;
-    std::auto_ptr<DatabaseWrapper> db_;
+    IDatabaseWrapper& db_;
     LeastRecentlyUsedIndex<int64_t, UnstableResourcePayload>  unstableResources_;
 
     uint64_t currentStorageSize_;
@@ -103,9 +103,26 @@
                                /* in  */ int64_t id,
                                /* in  */ ResourceType type);
 
+    bool GetMetadataAsInteger(int64_t& result,
+                              int64_t id,
+                              MetadataType type);
+
+    void LogChange(int64_t internalId,
+                   ChangeType changeType,
+                   ResourceType resourceType,
+                   const std::string& publicId);
+
+    uint64_t IncrementGlobalSequenceInternal(GlobalProperty property);
+
+    void SetMainDicomTags(int64_t resource,
+                          const DicomMap& tags);
+
+    int64_t CreateResource(const std::string& publicId,
+                           ResourceType type);
+
   public:
     ServerIndex(ServerContext& context,
-                const std::string& dbPath);
+                IDatabaseWrapper& database);
 
     ~ServerIndex();
 
@@ -148,20 +165,20 @@
                         const std::string& uuid,
                         ResourceType expectedType);
 
-    bool GetChanges(Json::Value& target,
+    void GetChanges(Json::Value& target,
                     int64_t since,
                     unsigned int maxResults);
 
-    bool GetLastChange(Json::Value& target);
+    void GetLastChange(Json::Value& target);
 
     void LogExportedResource(const std::string& publicId,
                              const std::string& remoteModality);
 
-    bool GetExportedResources(Json::Value& target,
+    void GetExportedResources(Json::Value& target,
                               int64_t since,
                               unsigned int maxResults);
 
-    bool GetLastExportedResource(Json::Value& target);
+    void GetLastExportedResource(Json::Value& target);
 
     bool IsProtectedPatient(const std::string& publicId);
 
@@ -234,5 +251,11 @@
 
     void DeleteAttachment(const std::string& publicId,
                           FileContentType type);
+
+    void SetGlobalProperty(GlobalProperty property,
+                           const std::string& value);
+
+    std::string GetGlobalProperty(GlobalProperty property,
+                                  const std::string& defaultValue);
   };
 }
--- a/OrthancServer/ServerIndexChange.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ServerIndexChange.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -33,28 +33,52 @@
 #pragma once
 
 #include "ServerEnumerations.h"
+#include "../Core/Toolbox.h"
 
 #include <string>
+#include <json/value.h>
 
 namespace Orthanc
 {
   struct ServerIndexChange
   {
   private:
+    int64_t      seq_;
     ChangeType   changeType_;
     ResourceType resourceType_;
     std::string  publicId_;
+    std::string  date_;
 
   public:
     ServerIndexChange(ChangeType changeType,
                       ResourceType resourceType,
-                      const std::string&  publicId) :
+                      const std::string& publicId) :
+      seq_(-1),
       changeType_(changeType),
       resourceType_(resourceType),
-      publicId_(publicId)
+      publicId_(publicId),
+      date_(Toolbox::GetNowIsoString())
     {
     }
 
+    ServerIndexChange(int64_t seq,
+                      ChangeType changeType,
+                      ResourceType resourceType,
+                      const std::string& publicId,
+                      const std::string& date) :
+      seq_(seq),
+      changeType_(changeType),
+      resourceType_(resourceType),
+      publicId_(publicId),
+      date_(date)
+    {
+    }
+
+    int64_t  GetSeq() const
+    {
+      return seq_;
+    }
+
     ChangeType  GetChangeType() const
     {
       return changeType_;
@@ -69,5 +93,21 @@
     {
       return publicId_;
     }
+
+    const std::string& GetDate() const
+    {
+      return date_;
+    }
+
+    void Format(Json::Value& item) const
+    {
+      item = Json::objectValue;
+      item["Seq"] = static_cast<int>(seq_);
+      item["ChangeType"] = EnumerationToString(changeType_);
+      item["ResourceType"] = EnumerationToString(resourceType_);
+      item["ID"] = publicId_;
+      item["Path"] = GetBasePath(resourceType_, publicId_);
+      item["Date"] = date_;
+    }
   };
 }
--- a/OrthancServer/ServerToolbox.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ServerToolbox.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/ServerToolbox.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ServerToolbox.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/ToDcmtkBridge.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ToDcmtkBridge.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/ToDcmtkBridge.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/ToDcmtkBridge.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/OrthancServer/main.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/OrthancServer/main.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -38,7 +38,6 @@
 #include <boost/algorithm/string/predicate.hpp>
 
 #include "../Core/Uuid.h"
-#include "../Core/FileStorage/FilesystemStorage.h"
 #include "../Core/HttpServer/EmbeddedResourceHttpHandler.h"
 #include "../Core/HttpServer/FilesystemHttpHandler.h"
 #include "../Core/Lua/LuaFunctionCall.h"
@@ -73,7 +72,8 @@
   virtual void Handle(const std::string& dicomFile,
                       const DicomMap& dicomSummary,
                       const Json::Value& dicomJson,
-                      const std::string& remoteAet)
+                      const std::string& remoteAet,
+                      const std::string& calledAet)
   {
     if (dicomFile.size() > 0)
     {
@@ -82,6 +82,7 @@
       toStore.SetSummary(dicomSummary);
       toStore.SetJson(dicomJson);
       toStore.SetRemoteAet(remoteAet);
+      toStore.SetCalledAet(calledAet);
 
       std::string id;
       server_.Store(id, toStore);
@@ -339,7 +340,7 @@
 {
   std::cout
     << path << " " << ORTHANC_VERSION << std::endl
-    << "Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege (Belgium) " << std::endl
+    << "Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics Department, University Hospital of Liege (Belgium)" << std::endl
     << "Licensing GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>, with OpenSSL exception." << std::endl
     << "This is free software: you are free to change and redistribute it." << std::endl
     << "There is NO WARRANTY, to the extent permitted by law." << std::endl
@@ -382,64 +383,17 @@
 
 
 
-class FilesystemStorageWithoutDicom : public IStorageArea
+static bool StartOrthanc(int argc, char *argv[])
 {
-private:
-  FilesystemStorage storage_;
-
-public:
-  FilesystemStorageWithoutDicom(const std::string& path) : storage_(path)
-  {
-  }
-
-  virtual void Create(const std::string& uuid,
-                      const void* content, 
-                      size_t size,
-                      FileContentType type)
-  {
-    if (type != FileContentType_Dicom)
-    {
-      storage_.Create(uuid, content, size, type);
-    }
-  }
+  std::auto_ptr<IDatabaseWrapper> database;
+  database.reset(Configuration::CreateDatabaseWrapper());
 
-  virtual void Read(std::string& content,
-                    const std::string& uuid,
-                    FileContentType type) const
-  {
-    if (type != FileContentType_Dicom)
-    {
-      storage_.Read(content, uuid, type);
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
-  }
-
-  virtual void Remove(const std::string& uuid,
-                      FileContentType type) 
-  {
-    if (type != FileContentType_Dicom)
-    {
-      storage_.Remove(uuid, type);
-    }
-  }
-};
-
-
-static bool StartOrthanc()
-{
-  std::string storageDirectoryStr = Configuration::GetGlobalStringParameter("StorageDirectory", "OrthancStorage");
-  boost::filesystem::path indexDirectory = Configuration::InterpretStringParameterAsPath(
-    Configuration::GetGlobalStringParameter("IndexDirectory", storageDirectoryStr));
 
   // "storage" must be declared BEFORE "ServerContext context", to
   // avoid mess in the invokation order of the destructors.
   std::auto_ptr<IStorageArea>  storage;
-  ServerContext context(indexDirectory);
 
-  LOG(WARNING) << "Index directory: " << indexDirectory;
+  ServerContext context(*database);
 
   context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false));
   context.SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true));
@@ -513,13 +467,14 @@
 
 #if ENABLE_PLUGINS == 1
     OrthancPlugins orthancPlugins(context);
+    orthancPlugins.SetCommandLineArguments(argc, argv);
     orthancPlugins.SetOrthancRestApi(restApi);
 
     PluginsManager pluginsManager;
     pluginsManager.RegisterServiceProvider(orthancPlugins);
     LoadPlugins(pluginsManager);
     httpServer.RegisterHandler(orthancPlugins);
-    context.SetOrthancPlugins(orthancPlugins);
+    context.SetOrthancPlugins(pluginsManager, orthancPlugins);
 #endif
 
     httpServer.RegisterHandler(staticResources);
@@ -536,17 +491,7 @@
     else
 #endif
     {
-      boost::filesystem::path storageDirectory = Configuration::InterpretStringParameterAsPath(storageDirectoryStr);
-      LOG(WARNING) << "Storage directory: " << storageDirectory;
-      if (Configuration::GetGlobalBoolParameter("StoreDicom", true))
-      {
-        storage.reset(new FilesystemStorage(storageDirectory.string()));
-      }
-      else
-      {
-        LOG(WARNING) << "The DICOM files will not be stored, Orthanc running in index-only mode";
-        storage.reset(new FilesystemStorageWithoutDicom(storageDirectory.string()));
-      }
+      storage.reset(Configuration::CreateStorageArea());
     }
     
     context.SetStorageArea(*storage);
@@ -586,6 +531,7 @@
     LOG(WARNING) << "Orthanc is stopping";
 
 #if ENABLE_PLUGINS == 1
+    context.ResetOrthancPlugins();
     orthancPlugins.Stop();
     LOG(WARNING) << "    Plugins have stopped";
 #endif
@@ -683,7 +629,7 @@
     {
       OrthancInitialize(configurationFile);
 
-      bool reset = StartOrthanc();
+      bool reset = StartOrthanc(argc, argv);
       if (reset)
       {
         OrthancFinalize();
@@ -694,14 +640,24 @@
       }
     }
   }
-  catch (OrthancException& e)
+  catch (const OrthancException& e)
   {
     LOG(ERROR) << "Uncaught exception, stopping now: [" << e.What() << "]";
     status = -1;
   }
+  catch (const std::exception& e) 
+  {
+    LOG(ERROR) << "Uncaught exception, stopping now: [" << e.what() << "]";
+    status = -1;
+  }
+  catch (const std::string& s) 
+  {
+    LOG(ERROR) << "Uncaught exception, stopping now: [" << s << "]";
+    status = -1;
+  }
   catch (...)
   {
-    LOG(ERROR) << "Native exception, stopping now";
+    LOG(ERROR) << "Native exception, stopping now. Check your plugins, if any.";
     status = -1;
   }
 
--- a/Plugins/Engine/IPluginServiceProvider.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Engine/IPluginServiceProvider.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -32,7 +32,7 @@
 
 #pragma once
 
-#include "../OrthancCPlugin/OrthancCPlugin.h"
+#include "../Include/OrthancCPlugin.h"
 
 #include <boost/noncopyable.hpp>
 
--- a/Plugins/Engine/OrthancPlugins.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Engine/OrthancPlugins.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -172,10 +172,13 @@
 
   struct OrthancPlugins::PImpl
   {
+    typedef std::pair<std::string, _OrthancPluginProperty>  Property;
+
     typedef std::pair<boost::regex*, OrthancPluginRestCallback> RestCallback;
     typedef std::list<RestCallback>  RestCallbacks;
     typedef std::list<OrthancPluginOnStoredInstanceCallback>  OnStoredCallbacks;
     typedef std::list<OrthancPluginOnChangeCallback>  OnChangeCallbacks;
+    typedef std::map<Property, std::string>  Properties;
 
     ServerContext& context_;
     RestCallbacks restCallbacks_;
@@ -184,16 +187,21 @@
     OnChangeCallbacks  onChangeCallbacks_;
     bool hasStorageArea_;
     _OrthancPluginRegisterStorageArea storageArea_;
-    boost::mutex callbackMutex_;
+    boost::recursive_mutex callbackMutex_;
     SharedMessageQueue  pendingChanges_;
     boost::thread  changeThread_;
     bool done_;
+    Properties properties_;
+    int argc_;
+    char** argv_;
 
     PImpl(ServerContext& context) : 
       context_(context), 
       restApi_(NULL),
       hasStorageArea_(false),
-      done_(false)
+      done_(false),
+      argc_(1),
+      argv_(NULL)
     {
       memset(&storageArea_, 0, sizeof(storageArea_));
     }
@@ -207,7 +215,7 @@
         
         if (obj.get() != NULL)
         {
-          boost::mutex::scoped_lock lock(that->callbackMutex_);
+          boost::recursive_mutex::scoped_lock lock(that->callbackMutex_);
           PendingChange& change = *dynamic_cast<PendingChange*>(obj.get());
           change.Submit(that->onChangeCallbacks_);
         }
@@ -389,7 +397,7 @@
     int32_t error;
 
     {
-      boost::mutex::scoped_lock lock(pimpl_->callbackMutex_);
+      boost::recursive_mutex::scoped_lock lock(pimpl_->callbackMutex_);
       error = callback(reinterpret_cast<OrthancPluginRestOutput*>(&output), 
                        flatUri.c_str(), 
                        &request);
@@ -415,7 +423,7 @@
   void OrthancPlugins::SignalStoredInstance(DicomInstanceToStore& instance,
                                             const std::string& instanceId)                                                  
   {
-    boost::mutex::scoped_lock lock(pimpl_->callbackMutex_);
+    boost::recursive_mutex::scoped_lock lock(pimpl_->callbackMutex_);
 
     for (PImpl::OnStoredCallbacks::const_iterator
            callback = pimpl_->onStoredCallbacks_.begin(); 
@@ -643,7 +651,8 @@
   }
 
 
-  void OrthancPlugins::RestApiGet(const void* parameters)
+  void OrthancPlugins::RestApiGet(const void* parameters,
+                                  bool afterPlugins)
   {
     const _OrthancPluginRestApiGet& p = 
       *reinterpret_cast<const _OrthancPluginRestApiGet*>(parameters);
@@ -658,12 +667,25 @@
     StringHttpOutput stream;
     HttpOutput http(stream, false /* no keep alive */);
 
-    LOG(INFO) << "Plugin making REST GET call on URI " << p.uri;
+    LOG(INFO) << "Plugin making REST GET call on URI " << p.uri
+              << (afterPlugins ? " (after plugins)" : " (built-in API)");
+
+    bool ok = false;
+    std::string result;
 
-    if (pimpl_->restApi_ != NULL &&
-        pimpl_->restApi_->Handle(http, HttpMethod_Get, uri, headers, getArguments, body))
+    if (afterPlugins)
+    {
+      ok = Handle(http, HttpMethod_Get, uri, headers, getArguments, body);
+    }
+
+    if (!ok)
     {
-      std::string result;
+      ok = (pimpl_->restApi_ != NULL &&
+            pimpl_->restApi_->Handle(http, HttpMethod_Get, uri, headers, getArguments, body));
+    }
+
+    if (ok)
+    {
       stream.GetOutput(result);
       CopyToMemoryBuffer(*p.target, result);
     }
@@ -674,7 +696,9 @@
   }
 
 
-  void OrthancPlugins::RestApiPostPut(bool isPost, const void* parameters)
+  void OrthancPlugins::RestApiPostPut(bool isPost, 
+                                      const void* parameters,
+                                      bool afterPlugins)
   {
     const _OrthancPluginRestApiPostPut& p = 
       *reinterpret_cast<const _OrthancPluginRestApiPostPut*>(parameters);
@@ -692,12 +716,25 @@
     HttpOutput http(stream, false /* no keep alive */);
 
     HttpMethod method = (isPost ? HttpMethod_Post : HttpMethod_Put);
-    LOG(INFO) << "Plugin making REST " << EnumerationToString(method) << " call on URI " << p.uri;
+    LOG(INFO) << "Plugin making REST " << EnumerationToString(method) << " call on URI " << p.uri
+              << (afterPlugins ? " (after plugins)" : " (built-in API)");
+
+    bool ok = false;
+    std::string result;
 
-    if (pimpl_->restApi_ != NULL &&
-        pimpl_->restApi_->Handle(http, method, uri, headers, getArguments, body))
+    if (afterPlugins)
+    {
+      ok = Handle(http, method, uri, headers, getArguments, body);
+    }
+    
+    if (!ok)
     {
-      std::string result;
+      ok = (pimpl_->restApi_ != NULL &&
+            pimpl_->restApi_->Handle(http, method, uri, headers, getArguments, body));
+    }
+
+    if (ok)
+    {
       stream.GetOutput(result);
       CopyToMemoryBuffer(*p.target, result);
     }
@@ -708,7 +745,8 @@
   }
 
 
-  void OrthancPlugins::RestApiDelete(const void* parameters)
+  void OrthancPlugins::RestApiDelete(const void* parameters,
+                                     bool afterPlugins)
   {
     // The "parameters" point to the URI
     UriComponents uri;
@@ -722,10 +760,23 @@
     HttpOutput http(stream, false /* no keep alive */);
 
     LOG(INFO) << "Plugin making REST DELETE call on URI " 
-              << reinterpret_cast<const char*>(parameters);
+              << reinterpret_cast<const char*>(parameters)
+              << (afterPlugins ? " (after plugins)" : " (built-in API)");
+
+    bool ok = false;
 
-    if (pimpl_->restApi_ == NULL ||
-        !pimpl_->restApi_->Handle(http, HttpMethod_Delete, uri, headers, getArguments, body))
+    if (afterPlugins)
+    {
+      ok = Handle(http, HttpMethod_Delete, uri, headers, getArguments, body);
+    }
+
+    if (!ok)
+    {
+      ok = (pimpl_->restApi_ != NULL &&
+            pimpl_->restApi_->Handle(http, HttpMethod_Delete, uri, headers, getArguments, body));
+    }
+
+    if (!ok)
     {
       throw OrthancException(ErrorCode_BadRequest);
     }
@@ -955,19 +1006,35 @@
         return true;
 
       case _OrthancPluginService_RestApiGet:
-        RestApiGet(parameters);
+        RestApiGet(parameters, false);
+        return true;
+
+      case _OrthancPluginService_RestApiGetAfterPlugins:
+        RestApiGet(parameters, true);
         return true;
 
       case _OrthancPluginService_RestApiPost:
-        RestApiPostPut(true, parameters);
+        RestApiPostPut(true, parameters, false);
+        return true;
+
+      case _OrthancPluginService_RestApiPostAfterPlugins:
+        RestApiPostPut(true, parameters, true);
         return true;
 
       case _OrthancPluginService_RestApiDelete:
-        RestApiDelete(parameters);
+        RestApiDelete(parameters, false);
+        return true;
+
+      case _OrthancPluginService_RestApiDeleteAfterPlugins:
+        RestApiDelete(parameters, true);
         return true;
 
       case _OrthancPluginService_RestApiPut:
-        RestApiPostPut(false, parameters);
+        RestApiPostPut(false, parameters, false);
+        return true;
+
+      case _OrthancPluginService_RestApiPutAfterPlugins:
+        RestApiPostPut(false, parameters, true);
         return true;
 
       case _OrthancPluginService_Redirect:
@@ -1022,6 +1089,63 @@
         return true;
       }
 
+      case _OrthancPluginService_SetPluginProperty:
+      {
+        const _OrthancPluginSetPluginProperty& p = 
+          *reinterpret_cast<const _OrthancPluginSetPluginProperty*>(parameters);
+        pimpl_->properties_[std::make_pair(p.plugin, p.property)] = p.value;
+        return true;
+      }
+
+      case _OrthancPluginService_SetGlobalProperty:
+      {
+        const _OrthancPluginGlobalProperty& p = 
+          *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
+        if (p.property < 1024)
+        {
+          return false;
+        }
+        else
+        {
+          pimpl_->context_.GetIndex().SetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value);
+          return true;
+        }
+      }
+
+      case _OrthancPluginService_GetGlobalProperty:
+      {
+        const _OrthancPluginGlobalProperty& p = 
+          *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
+        std::string result = pimpl_->context_.GetIndex().GetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value);
+        *(p.result) = CopyString(result);
+        return true;
+      }
+
+      case _OrthancPluginService_GetCommandLineArgumentsCount:
+      {
+        const _OrthancPluginReturnSingleValue& p =
+          *reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters);
+        *(p.resultUint32) = pimpl_->argc_ - 1;
+        return true;
+      }
+
+      case _OrthancPluginService_GetCommandLineArgument:
+      {
+        const _OrthancPluginGlobalProperty& p =
+          *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
+        
+        if (p.property + 1 > pimpl_->argc_)
+        {
+          return false;
+        }
+        else
+        {
+          std::string arg = std::string(pimpl_->argv_[p.property + 1]);
+          *(p.result) = CopyString(arg);
+          return true;
+        }
+      }
+
       default:
         return false;
     }
@@ -1051,7 +1175,7 @@
       {
         if (buffer != NULL)
         {
-          params_.free_(buffer);
+          params_.free(buffer);
         }
       }
 
@@ -1080,7 +1204,7 @@
                           size_t size,
                           FileContentType type)
       {
-        if (params_.create_(uuid.c_str(), content, size, Convert(type)) != 0)
+        if (params_.create(uuid.c_str(), content, size, Convert(type)) != 0)
         {
           throw OrthancException(ErrorCode_Plugin);
         }
@@ -1088,12 +1212,12 @@
 
       virtual void Read(std::string& content,
                         const std::string& uuid,
-                        FileContentType type) const
+                        FileContentType type)
       {
         void* buffer = NULL;
         int64_t size = 0;
 
-        if (params_.read_(&buffer, &size, uuid.c_str(), Convert(type)) != 0)
+        if (params_.read(&buffer, &size, uuid.c_str(), Convert(type)) != 0)
         {
           throw OrthancException(ErrorCode_Plugin);
         }        
@@ -1119,7 +1243,7 @@
       virtual void Remove(const std::string& uuid,
                           FileContentType type) 
       {
-        if (params_.remove_(uuid.c_str(), Convert(type)) != 0)
+        if (params_.remove(uuid.c_str(), Convert(type)) != 0)
         {
           throw OrthancException(ErrorCode_Plugin);
         }        
@@ -1135,6 +1259,36 @@
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
 
-    return new PluginStorageArea(pimpl_->storageArea_);;
+    return new PluginStorageArea(pimpl_->storageArea_);
+  }
+
+
+
+  const char* OrthancPlugins::GetProperty(const char* plugin,
+                                          _OrthancPluginProperty property) const
+  {
+    PImpl::Property p = std::make_pair(plugin, property);
+    PImpl::Properties::const_iterator it = pimpl_->properties_.find(p);
+
+    if (it == pimpl_->properties_.end())
+    {
+      return NULL;
+    }
+    else
+    {
+      return it->second.c_str();
+    }
+  }
+
+
+  void OrthancPlugins::SetCommandLineArguments(int argc, char* argv[])
+  {
+    if (argc < 1 || argv == NULL)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    pimpl_->argc_ = argc;
+    pimpl_->argv_ = argv;
   }
 }
--- a/Plugins/Engine/OrthancPlugins.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Engine/OrthancPlugins.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -36,7 +36,7 @@
 #include "../../Core/HttpServer/HttpHandler.h"
 #include "../../OrthancServer/ServerContext.h"
 #include "../../OrthancServer/OrthancRestApi/OrthancRestApi.h"
-#include "../OrthancCPlugin/OrthancCPlugin.h"
+#include "../Include/OrthancCPlugin.h"
 
 #include <list>
 #include <boost/shared_ptr.hpp>
@@ -63,11 +63,15 @@
 
     void GetDicomForInstance(const void* parameters);
 
-    void RestApiGet(const void* parameters);
+    void RestApiGet(const void* parameters,
+                    bool afterPlugins);
 
-    void RestApiPostPut(bool isPost, const void* parameters);
+    void RestApiPostPut(bool isPost, 
+                        const void* parameters,
+                        bool afterPlugins);
 
-    void RestApiDelete(const void* parameters);
+    void RestApiDelete(const void* parameters,
+                       bool afterPlugins);
 
     void LookupResource(_OrthancPluginService service,
                         const void* parameters);
@@ -109,5 +113,10 @@
     IStorageArea* GetStorageArea();
 
     void Stop();
+
+    const char* GetProperty(const char* plugin,
+                            _OrthancPluginProperty property) const;
+
+    void SetCommandLineArguments(int argc, char* argv[]);
   };
 }
--- a/Plugins/Engine/PluginsManager.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Engine/PluginsManager.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -205,10 +205,10 @@
     {
       if (it->second != NULL)
       {
-        LOG(WARNING) << "Unregistering plugin '" << CallGetName(*it->second)
-                     << "' (version " << CallGetVersion(*it->second) << ")";
+        LOG(WARNING) << "Unregistering plugin '" << it->first
+                     << "' (version " << it->second->GetVersion() << ")";
 
-        CallFinalize(*(it->second));
+        CallFinalize(it->second->GetLibrary());
         delete it->second;
       }
     }
@@ -226,26 +226,39 @@
   
   void PluginsManager::RegisterPlugin(const std::string& path)
   {
-    std::auto_ptr<SharedLibrary> plugin(new SharedLibrary(path));
+    if (!boost::filesystem::exists(path))
+    {
+      LOG(ERROR) << "Inexistent path to plugins: " << path;
+      return;
+    }
 
-    if (!IsOrthancPlugin(*plugin))
+    if (boost::filesystem::is_directory(path))
     {
-      LOG(ERROR) << "Plugin " << plugin->GetPath()
+      ScanFolderForPlugins(path, false);
+      return;
+    }
+
+    std::auto_ptr<Plugin> plugin(new Plugin(path));
+
+    if (!IsOrthancPlugin(plugin->GetLibrary()))
+    {
+      LOG(ERROR) << "Plugin " << plugin->GetLibrary().GetPath()
                  << " does not declare the proper entry functions";
       throw OrthancException(ErrorCode_SharedLibrary);
     }
 
-    std::string name(CallGetName(*plugin));
+    std::string name(CallGetName(plugin->GetLibrary()));
     if (plugins_.find(name) != plugins_.end())
     {
       LOG(ERROR) << "Plugin '" << name << "' already registered";
       throw OrthancException(ErrorCode_SharedLibrary);
     }
 
+    plugin->SetVersion(CallGetVersion(plugin->GetLibrary()));
     LOG(WARNING) << "Registering plugin '" << name
-                 << "' (version " << CallGetVersion(*plugin) << ")";
+                 << "' (version " << plugin->GetVersion() << ")";
 
-    CallInitialize(*plugin, context_);
+    CallInitialize(plugin->GetLibrary(), context_);
 
     plugins_[name] = plugin.release();
   }
@@ -283,19 +296,46 @@
         {
           LOG(INFO) << "Found a shared library: " << it->path();
 
-          try
+          SharedLibrary plugin(path);
+          if (IsOrthancPlugin(plugin))
           {
-            SharedLibrary plugin(path);
-            if (IsOrthancPlugin(plugin))
-            {
-              RegisterPlugin(path);
-            }
-          }
-          catch (OrthancException&)
-          {
+            RegisterPlugin(path);
           }
         }
       }
     }
   }
+
+
+  void PluginsManager::ListPlugins(std::list<std::string>& result) const
+  {
+    result.clear();
+
+    for (Plugins::const_iterator it = plugins_.begin(); 
+         it != plugins_.end(); ++it)
+    {
+      result.push_back(it->first);
+    }
+  }
+
+
+  bool PluginsManager::HasPlugin(const std::string& name) const
+  {
+    return plugins_.find(name) != plugins_.end();
+  }
+
+
+  const std::string& PluginsManager::GetPluginVersion(const std::string& name) const
+  {
+    Plugins::const_iterator it = plugins_.find(name);
+    if (it == plugins_.end())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return it->second->GetVersion();
+    }
+  }
+
 }
--- a/Plugins/Engine/PluginsManager.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Engine/PluginsManager.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -43,7 +43,34 @@
   class PluginsManager : boost::noncopyable
   {
   private:
-    typedef std::map<std::string, SharedLibrary*>  Plugins;
+    class Plugin
+    {
+    private:
+      SharedLibrary library_;
+      std::string  version_;
+
+    public:
+      Plugin(const std::string& path) : library_(path)
+      {
+      }
+
+      SharedLibrary& GetLibrary()
+      {
+        return library_;
+      }
+
+      void SetVersion(const std::string& version)
+      {
+        version_ = version;
+      }
+
+      const std::string& GetVersion() const
+      {
+        return version_;
+      }
+    };
+
+    typedef std::map<std::string, Plugin*>  Plugins;
 
     OrthancPluginContext  context_;
     Plugins  plugins_;
@@ -67,5 +94,11 @@
     {
       serviceProviders_.push_back(&provider);
     }
+
+    void ListPlugins(std::list<std::string>& result) const;
+
+    bool HasPlugin(const std::string& name) const;
+
+    const std::string& GetPluginVersion(const std::string& name) const;
   };
 }
--- a/Plugins/Engine/SharedLibrary.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Engine/SharedLibrary.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Plugins/Engine/SharedLibrary.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Engine/SharedLibrary.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Include/OrthancCPlugin.h	Wed Feb 11 10:32:14 2015 +0100
@@ -0,0 +1,2087 @@
+/**
+ * \mainpage
+ *
+ * This C/C++ SDK allows external developers to create plugins that
+ * can be loaded into Orthanc to extend its functionality. Each
+ * Orthanc plugin must expose 4 public functions with the following
+ * signatures:
+ * 
+ * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
+ *    This function is invoked by Orthanc when it loads the plugin on startup.
+ *    The plugin must:
+ *    - Check its compatibility with the Orthanc version using
+ *      ::OrthancPluginCheckVersion().
+ *    - Store the context pointer so that it can use the plugin 
+ *      services of Orthanc.
+ *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
+ *    - Register all its callbacks for received instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
+ *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
+ * -# <tt>void OrthancPluginFinalize()</tt>:
+ *    This function is invoked by Orthanc during its shutdown. The plugin
+ *    must free all its memory.
+ * -# <tt>const char* OrthancPluginGetName()</tt>:
+ *    The plugin must return a short string to identify itself.
+ * -# <tt>const char* OrthancPluginGetVersion()</tt>:
+ *    The plugin must return a string containing its version number.
+ *
+ * The name and the version of a plugin is only used to prevent it
+ * from being loaded twice.
+ * 
+ * The various callbacks are guaranteed to be executed in mutual
+ * exclusion since Orthanc 0.8.5.
+ **/
+
+
+
+/**
+ * @defgroup CInterface C Interface 
+ * @brief The C interface to create Orthanc plugins.
+ * 
+ * These functions must be used to create C plugins for Orthanc.
+ **/
+
+
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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 <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#define ORTHANC_PLUGINS_API __declspec(dllexport)
+#else
+#define ORTHANC_PLUGINS_API
+#endif
+
+#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     0
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     8
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  6
+
+
+
+/********************************************************************
+ ** Check that function inlining is properly supported. The use of
+ ** inlining is required, to avoid the duplication of object code
+ ** between two compilation modules that would use the Orthanc Plugin
+ ** API.
+ ********************************************************************/
+
+/* If the auto-detection of the "inline" keyword below does not work
+   automatically and that your compiler is known to properly support
+   inlining, uncomment the following #define and adapt the definition
+   of "static inline". */
+
+/* #define ORTHANC_PLUGIN_INLINE static inline */
+
+#ifndef ORTHANC_PLUGIN_INLINE
+#  if __STDC_VERSION__ >= 199901L
+/*   This is C99 or above: http://predef.sourceforge.net/prestd.html */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__cplusplus)
+/*   This is C++ */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__GNUC__)
+/*   This is GCC running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  elif defined(_MSC_VER)
+/*   This is Visual Studio running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  else
+#    error Your compiler is not known to support the "inline" keyword
+#  endif
+#endif
+
+
+
+/********************************************************************
+ ** Inclusion of standard libraries.
+ ********************************************************************/
+
+#ifdef _MSC_VER
+#include "../../Resources/ThirdParty/VisualStudio/stdint.h"
+#else
+#include <stdint.h>
+#endif
+
+#include <stdlib.h>
+
+
+
+/********************************************************************
+ ** Definition of the Orthanc Plugin API.
+ ********************************************************************/
+
+/** @{ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+  /**
+   * Forward declaration of one of the mandatory functions for Orthanc
+   * plugins.
+   **/
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
+
+
+  /**
+   * The various HTTP methods for a REST call.
+   **/
+  typedef enum
+  {
+    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
+    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
+    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
+    OrthancPluginHttpMethod_Delete = 4  /*!< DELETE request */
+  } OrthancPluginHttpMethod;
+
+
+  /**
+   * @brief The parameters of a REST request.
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The HTTP method.
+     **/
+    OrthancPluginHttpMethod method;    
+
+    /**
+     * @brief The number of groups of the regular expression.
+     **/
+    uint32_t                groupsCount;
+
+    /**
+     * @brief The matched values for the groups of the regular expression.
+     **/
+    const char* const*      groups;
+
+    /**
+     * @brief For a GET request, the number of GET parameters.
+     **/
+    uint32_t                getCount;
+
+    /**
+     * @brief For a GET request, the keys of the GET parameters.
+     **/
+    const char* const*      getKeys;
+
+    /**
+     * @brief For a GET request, the values of the GET parameters.
+     **/
+    const char* const*      getValues;
+
+    /**
+     * @brief For a PUT or POST request, the content of the body.
+     **/
+    const char*             body;
+
+    /**
+     * @brief For a PUT or POST request, the number of bytes of the body.
+     **/
+    uint32_t                bodySize;
+
+
+    /* --------------------------------------------------
+       New in version 0.8.1
+       -------------------------------------------------- */
+
+    /**
+     * @brief The number of HTTP headers.
+     **/
+    uint32_t                headersCount;
+
+    /**
+     * @brief The keys of the HTTP headers (always converted to low-case).
+     **/
+    const char* const*      headersKeys;
+
+    /**
+     * @brief The values of the HTTP headers.
+     **/
+    const char* const*      headersValues;
+
+  } OrthancPluginHttpRequest;
+
+
+  typedef enum 
+  {
+    /* Generic services */
+    _OrthancPluginService_LogInfo = 1,
+    _OrthancPluginService_LogWarning = 2,
+    _OrthancPluginService_LogError = 3,
+    _OrthancPluginService_GetOrthancPath = 4,
+    _OrthancPluginService_GetOrthancDirectory = 5,
+    _OrthancPluginService_GetConfigurationPath = 6,
+    _OrthancPluginService_SetPluginProperty = 7,
+    _OrthancPluginService_GetGlobalProperty = 8,
+    _OrthancPluginService_SetGlobalProperty = 9,
+    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
+    _OrthancPluginService_GetCommandLineArgument = 11,
+
+    /* Registration of callbacks */
+    _OrthancPluginService_RegisterRestCallback = 1000,
+    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
+    _OrthancPluginService_RegisterStorageArea = 1002,
+    _OrthancPluginService_RegisterOnChangeCallback = 1003,
+
+    /* Sending answers to REST calls */
+    _OrthancPluginService_AnswerBuffer = 2000,
+    _OrthancPluginService_CompressAndAnswerPngImage = 2001,
+    _OrthancPluginService_Redirect = 2002,
+    _OrthancPluginService_SendHttpStatusCode = 2003,
+    _OrthancPluginService_SendUnauthorized = 2004,
+    _OrthancPluginService_SendMethodNotAllowed = 2005,
+    _OrthancPluginService_SetCookie = 2006,
+    _OrthancPluginService_SetHttpHeader = 2007,
+
+    /* Access to the Orthanc database and API */
+    _OrthancPluginService_GetDicomForInstance = 3000,
+    _OrthancPluginService_RestApiGet = 3001,
+    _OrthancPluginService_RestApiPost = 3002,
+    _OrthancPluginService_RestApiDelete = 3003,
+    _OrthancPluginService_RestApiPut = 3004,
+    _OrthancPluginService_LookupPatient = 3005,
+    _OrthancPluginService_LookupStudy = 3006,
+    _OrthancPluginService_LookupSeries = 3007,
+    _OrthancPluginService_LookupInstance = 3008,
+    _OrthancPluginService_LookupStudyWithAccessionNumber = 3009,
+    _OrthancPluginService_RestApiGetAfterPlugins = 3010,
+    _OrthancPluginService_RestApiPostAfterPlugins = 3011,
+    _OrthancPluginService_RestApiDeleteAfterPlugins = 3012,
+    _OrthancPluginService_RestApiPutAfterPlugins = 3013,
+
+    /* Access to DICOM instances */
+    _OrthancPluginService_GetInstanceRemoteAet = 4000,
+    _OrthancPluginService_GetInstanceSize = 4001,
+    _OrthancPluginService_GetInstanceData = 4002,
+    _OrthancPluginService_GetInstanceJson = 4003,
+    _OrthancPluginService_GetInstanceSimplifiedJson = 4004,
+    _OrthancPluginService_HasInstanceMetadata = 4005,
+    _OrthancPluginService_GetInstanceMetadata = 4006
+  } _OrthancPluginService;
+
+
+  typedef enum
+  {
+    _OrthancPluginProperty_Description = 1,
+    _OrthancPluginProperty_RootUri = 2,
+    _OrthancPluginProperty_OrthancExplorer = 3
+  } _OrthancPluginProperty;
+
+
+
+  /**
+   * The memory layout of the pixels of an image.
+   **/
+  typedef enum
+  {
+    /**
+     * @brief Graylevel 8bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * one byte.
+     **/
+    OrthancPluginPixelFormat_Grayscale8 = 1,
+
+    /**
+     * @brief Graylevel, unsigned 16bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * two bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale16 = 2,
+
+    /**
+     * @brief Graylevel, signed 16bpp image.
+     *
+     * The image is graylevel. Each pixel is signed and stored in two
+     * bytes.
+     **/
+    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
+
+    /**
+     * @brief Color image in RGB24 format.
+     *
+     * This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.
+     **/
+    OrthancPluginPixelFormat_RGB24 = 4,
+
+    /**
+     * @brief Color image in RGBA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.
+     **/
+    OrthancPluginPixelFormat_RGBA32 = 5
+  } OrthancPluginPixelFormat;
+
+
+
+  /**
+   * The content types that are supported by Orthanc plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
+    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
+    OrthancPluginContentType_DicomAsJson = 2   /*!< JSON summary of a DICOM file */
+  } OrthancPluginContentType;
+
+
+
+  /**
+   * The supported type of DICOM resources.
+   **/
+  typedef enum
+  {
+    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
+    OrthancPluginResourceType_Study = 1,       /*!< Study */
+    OrthancPluginResourceType_Series = 2,      /*!< Series */
+    OrthancPluginResourceType_Instance = 3     /*!< Instance */
+  } OrthancPluginResourceType;
+
+
+
+  /**
+   * The supported type of changes that can happen to DICOM resources.
+   **/
+  typedef enum
+  {
+    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
+    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
+    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
+    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
+    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
+    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
+    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
+    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
+    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
+    OrthancPluginChangeType_StableStudy = 9         /*!< Timeout: No new instance in this study */
+  } OrthancPluginChangeType;
+
+
+
+  /**
+   * @brief A memory buffer allocated by the core system of Orthanc.
+   *
+   * A memory buffer allocated by the core system of Orthanc. When the
+   * content of the buffer is not useful anymore, it must be free by a
+   * call to ::OrthancPluginFreeMemoryBuffer().
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The content of the buffer.
+     **/
+    void*      data;
+
+    /**
+     * @brief The number of bytes in the buffer.
+     **/
+    uint32_t   size;
+  } OrthancPluginMemoryBuffer;
+
+
+
+
+  /**
+   * @brief Opaque structure that represents the HTTP connection to the client application.
+   **/
+  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
+
+
+
+  /**
+   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
+   **/
+  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
+
+
+
+  /**
+   * @brief Signature of a callback function that answers to a REST request.
+   **/
+  typedef int32_t (*OrthancPluginRestCallback) (
+    OrthancPluginRestOutput* output,
+    const char* url,
+    const OrthancPluginHttpRequest* request);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
+   **/
+  typedef int32_t (*OrthancPluginOnStoredInstanceCallback) (
+    OrthancPluginDicomInstance* instance,
+    const char* instanceId);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
+   **/
+  typedef int32_t (*OrthancPluginOnChangeCallback) (
+    OrthancPluginChangeType changeType,
+    OrthancPluginResourceType resourceType,
+    const char* resourceId);
+
+
+
+  /**
+   * @brief Signature of a function to free dynamic memory.
+   **/
+  typedef void (*OrthancPluginFree) (void* buffer);
+
+
+
+  /**
+   * @brief Callback for writing to the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
+   *
+   * @param uuid The UUID of the file.
+   * @param content The content of the file.
+   * @param size The size of the file.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   **/
+  typedef int32_t (*OrthancPluginStorageCreate) (
+    const char* uuid,
+    const void* content,
+    int64_t size,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
+   *
+   * @param content The content of the file (output).
+   * @param size The size of the file (output).
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   **/
+  typedef int32_t (*OrthancPluginStorageRead) (
+    void** content,
+    int64_t* size,
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for removing a file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
+   *
+   * @param uuid The UUID of the file to be removed.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   **/
+  typedef int32_t (*OrthancPluginStorageRemove) (
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Data structure that contains information about the Orthanc core.
+   **/
+  typedef struct _OrthancPluginContext_t
+  {
+    void*              pluginsManager;
+    const char*        orthancVersion;
+    OrthancPluginFree  Free;
+    int32_t          (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                       _OrthancPluginService service,
+                                       const void* params);
+  } OrthancPluginContext;
+
+
+
+  /**
+   * @brief Free a string.
+   * 
+   * Free a string that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param str The string to be freed.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
+    OrthancPluginContext* context, 
+    char* str)
+  {
+    if (str != NULL)
+    {
+      context->Free(str);
+    }
+  }
+
+
+  /**
+   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
+   * 
+   * This function checks whether the version of this C header is
+   * compatible with the current version of Orthanc. The result of
+   * this function should always be checked in the
+   * OrthancPluginInitialize() entry point of the plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return 1 if and only if the versions are compatible. If the
+   * result is 0, the initialization of the plugin should fail.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
+    OrthancPluginContext* context)
+  {
+    int major, minor, revision;
+
+    /* Assume compatibility with the mainline */
+    if (!strcmp(context->orthancVersion, "mainline"))
+    {
+      return 1;
+    }
+
+    /* Parse the version of the Orthanc core */
+    if ( 
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
+    {
+      return 0;
+    }
+
+    /* Check the major number of the version */
+
+    if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the minor number of the version */
+
+    if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the revision number of the version */
+
+    if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER)
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Free a memory buffer.
+   * 
+   * Free a memory buffer that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer to release.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
+    OrthancPluginContext* context, 
+    OrthancPluginMemoryBuffer* buffer)
+  {
+    context->Free(buffer->data);
+  }
+
+
+  /**
+   * @brief Log an error.
+   *
+   * Log an error message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogError, message);
+  }
+
+
+  /**
+   * @brief Log a warning.
+   *
+   * Log a warning message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
+  }
+
+
+  /**
+   * @brief Log an information.
+   *
+   * Log an information message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
+  }
+
+
+
+  typedef struct
+  {
+    const char* pathRegularExpression;
+    OrthancPluginRestCallback callback;
+  } _OrthancPluginRestCallback;
+
+  /**
+   * @brief Register a REST callback.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnStoredInstanceCallback callback;
+  } _OrthancPluginOnStoredInstanceCallback;
+
+  /**
+   * @brief Register a callback for received instances.
+   *
+   * This function registers a callback function that is called
+   * whenever a new DICOM instance is stored into the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
+    OrthancPluginContext*                  context,
+    OrthancPluginOnStoredInstanceCallback  callback)
+  {
+    _OrthancPluginOnStoredInstanceCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    const char*              mimeType;
+  } _OrthancPluginAnswerBuffer;
+
+  /**
+   * @brief Answer to a REST request.
+   *
+   * This function answers to a REST request with the content of a memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the answer.
+   * @param answerSize Number of bytes of the answer.
+   * @param mimeType The MIME type of the answer.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    const char*              mimeType)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = mimeType;
+    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginPixelFormat  format;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+  } _OrthancPluginCompressAndAnswerPngImage;
+
+  /**
+   * @brief Answer to a REST request with a PNG image.
+   *
+   * This function answers to a REST request with a PNG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a PNG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer.
+   * @param buffer The memory buffer containing the uncompressed image.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer)
+  {
+    _OrthancPluginCompressAndAnswerPngImage params;
+    params.output = output;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerPngImage, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 instanceId;
+  } _OrthancPluginGetDicomForInstance;
+
+  /**
+   * @brief Retrieve a DICOM instance using its Orthanc identifier.
+   * 
+   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
+   * file is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginGetDicomForInstance(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 instanceId)
+  {
+    _OrthancPluginGetDicomForInstance params;
+    params.target = target;
+    params.instanceId = instanceId;
+    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+  } _OrthancPluginRestApiGet;
+
+  /**
+   * @brief Make a GET call to the built-in Orthanc REST API.
+   * 
+   * Make a GET call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
+  }
+
+
+
+  /**
+   * @brief Make a GET call to the REST API, as tainted by the plugins.
+   * 
+   * Make a GET call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiGetAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginRestApiPostPut;
+
+  /**
+   * @brief Make a POST call to the built-in Orthanc REST API.
+   * 
+   * Make a POST call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
+  }
+
+
+  /**
+   * @brief Make a POST call to the REST API, as tainted by the plugins.
+   * 
+   * Make a POST call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiPostAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
+  }
+
+
+
+  /**
+   * @brief Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiDelete(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
+  }
+
+
+  /**
+   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
+   * 
+   * Make a DELETE call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. 
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiDeleteAfterPlugins(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the built-in Orthanc REST API.
+   * 
+   * Make a PUT call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the REST API, as tainted by the plugins.
+   * 
+   * Make a PUT call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiPutAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              argument;
+  } _OrthancPluginOutputPlusArgument;
+
+  /**
+   * @brief Redirect a REST request.
+   *
+   * This function answers to a REST request by redirecting the user
+   * to another URI using HTTP status 301.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param redirection Where to redirect.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              redirection)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = redirection;
+    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const char*  argument;
+  } _OrthancPluginRetrieveDynamicString;
+
+  /**
+   * @brief Look for a patient.
+   *
+   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored patients).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param patientID The Patient ID of interest.
+   * @return The NULL value if the patient is non-existent, or a string containing the 
+   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
+    OrthancPluginContext*  context,
+    const char*            patientID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = patientID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study.
+   *
+   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param studyUID The Study Instance UID of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
+    OrthancPluginContext*  context,
+    const char*            studyUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = studyUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study, using the accession number.
+   *
+   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param accessionNumber The Accession Number of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
+    OrthancPluginContext*  context,
+    const char*            accessionNumber)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = accessionNumber;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a series.
+   *
+   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored series).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param seriesUID The Series Instance UID of interest.
+   * @return The NULL value if the series is non-existent, or a string containing the 
+   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
+    OrthancPluginContext*  context,
+    const char*            seriesUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = seriesUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for an instance.
+   *
+   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored instances).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param sopInstanceUID The SOP Instance UID of interest.
+   * @return The NULL value if the instance is non-existent, or a string containing the 
+   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
+    OrthancPluginContext*  context,
+    const char*            sopInstanceUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = sopInstanceUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+  } _OrthancPluginSendHttpStatusCode;
+
+  /**
+   * @brief Send a HTTP status code.
+   *
+   * This function answers to a REST request by sending a HTTP status
+   * code (such as "400 - Bad Request"). Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status)
+  {
+    _OrthancPluginSendHttpStatusCode params;
+    params.output = output;
+    params.status = status;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
+  }
+
+
+  /**
+   * @brief Signal that a REST request is not authorized.
+   *
+   * This function answers to a REST request by signaling that it is
+   * not authorized.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param realm The realm for the authorization process.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              realm)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = realm;
+    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
+  }
+
+
+  /**
+   * @brief Signal that this URI does not support this HTTP method.
+   *
+   * This function answers to a REST request by signaling that the
+   * queried URI does not support this method.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              allowedMethods)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = allowedMethods;
+    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              key;
+    const char*              value;
+  } _OrthancPluginSetHttpHeader;
+
+  /**
+   * @brief Set a cookie.
+   *
+   * This function sets a cookie in the HTTP client.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param cookie The cookie to be set.
+   * @param value The value of the cookie.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              cookie,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = cookie;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
+  }
+
+
+  /**
+   * @brief Set some HTTP header.
+   *
+   * This function sets a HTTP header in the HTTP answer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param key The HTTP header to be set.
+   * @param value The value of the HTTP header.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              key,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = key;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                      resultStringToFree;
+    const char**                resultString;
+    int64_t*                    resultInt64;
+    const char*                 key;
+    OrthancPluginDicomInstance* instance;
+  } _OrthancPluginAccessDicomInstance;
+
+
+  /**
+   * @brief Get the AET of a DICOM instance.
+   *
+   * This function returns the Application Entity Title (AET) of the
+   * DICOM modality from which a DICOM instance originates.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The AET if success, NULL if error.
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the size of a DICOM file.
+   *
+   * This function returns the number of bytes of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The size of the file, -1 in case of error.
+   **/
+  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    int64_t size;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &size;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params))
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return size;
+    }
+  }
+
+
+  /**
+   * @brief Get the data of a DICOM file.
+   *
+   * This function returns a pointer to the content of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The pointer to the DICOM data, NULL in case of error.
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file.
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance. In contrast with
+   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
+   * its simplified version.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Check whether a DICOM instance is associated with some metadata.
+   *
+   * This function checks whether the DICOM instance of interest is
+   * associated with some metadata. As of Orthanc 0.8.1, in the
+   * callbacks registered by
+   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
+   * possibly available metadata are "ReceptionDate", "RemoteAET" and
+   * "IndexInSeries".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    int64_t result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params))
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+
+
+  /**
+   * @brief Get the value of some metadata associated with a given DICOM instance.
+   *
+   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
+   * Before calling this function, the existence of the metadata must have been checked with
+   * ::OrthancPluginHasInstanceMetadata().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return The metadata value if success, NULL if error.
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageCreate  create;
+    OrthancPluginStorageRead    read;
+    OrthancPluginStorageRemove  remove;
+    OrthancPluginFree           free;
+  } _OrthancPluginRegisterStorageArea;
+
+  /**
+   * @brief Register a custom storage area.
+   *
+   * This function registers a custom storage area, to replace the
+   * built-in way Orthanc stores its files on the filesystem. This
+   * function must be called during the initialization of the plugin,
+   * i.e. inside the OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param create The callback function to store a file on the custom storage area.
+   * @param read The callback function to read a file from the custom storage area.
+   * @param remove The callback function to remove a file from the custom storage area.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageCreate  create,
+    OrthancPluginStorageRead    read,
+    OrthancPluginStorageRemove  remove)
+  {
+    _OrthancPluginRegisterStorageArea params;
+    params.create = create;
+    params.read = read;
+    params.remove = remove;
+
+#ifdef  __cplusplus
+    params.free = ::free;
+#else
+    params.free = free;
+#endif
+
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
+  }
+
+
+
+  /**
+   * @brief Return the path to the Orthanc executable.
+   *
+   * This function returns the path to the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the directory containing the Orthanc.
+   *
+   * This function returns the path to the directory containing the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the path to the configuration file.
+   *
+   * This function returns the path to the configuration file that was
+   * specified when starting Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnChangeCallback callback;
+  } _OrthancPluginOnChangeCallback;
+
+  /**
+   * @brief Register a callback to monitor changes.
+   *
+   * This function registers a callback function that is called
+   * whenever a change happens to some DICOM resource.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginOnChangeCallback  callback)
+  {
+    _OrthancPluginOnChangeCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char* plugin;
+    _OrthancPluginProperty property;
+    const char* value;
+  } _OrthancPluginSetPluginProperty;
+
+
+  /**
+   * @brief Set the URI where the plugin provides its Web interface.
+   *
+   * For plugins that come with a Web interface, this function
+   * declares the entry path where to find this interface. This
+   * information is notably used in the "Plugins" page of Orthanc
+   * Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The root URI for this plugin.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
+    OrthancPluginContext*  context,
+    const char*            uri)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_RootUri;
+    params.value = uri;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Set a description for this plugin.
+   *
+   * Set a description for this plugin. It is displayed in the
+   * "Plugins" page of Orthanc Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param description The description.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
+    OrthancPluginContext*  context,
+    const char*            description)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_Description;
+    params.value = description;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Extend the JavaScript code of Orthanc Explorer.
+   *
+   * Add JavaScript code to customize the default behavior of Orthanc
+   * Explorer. This can for instance be used to add new buttons.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param javascript The custom JavaScript code.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
+    OrthancPluginContext*  context,
+    const char*            javascript)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_OrthancExplorer;
+    params.value = javascript;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  typedef struct
+  {
+    char**       result;
+    int32_t      property;
+    const char*  value;
+  } _OrthancPluginGlobalProperty;
+
+
+  /**
+   * @brief Get the value of a global property.
+   *
+   * Get the value of a global property that is stored in the Orthanc database. Global
+   * properties whose index is below 1024 are reserved by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param defaultValue The value to return, if the global property is unset.
+   * @return The value of the global property, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            defaultValue)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = property;
+    params.value = defaultValue;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Set the value of a global property.
+   *
+   * Set the value of a global property into the Orthanc
+   * database. Setting a global property can be used by plugins to
+   * save their internal parameters. Plugins are only allowed to set
+   * properties whose index are above or equal to 1024 (properties
+   * below 1024 are read-only and reserved by Orthanc).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param value The value to be set in the global property.
+   * @return 0 if success, -1 in case of error.
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t OrthancPluginSetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            value)
+  {
+    _OrthancPluginGlobalProperty params;
+    params.result = NULL;
+    params.property = property;
+    params.value = value;
+
+    if (context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params))
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    int32_t   *resultInt32;
+    uint32_t  *resultUint32;
+    int64_t   *resultInt64;
+    uint64_t  *resultUint64;
+  } _OrthancPluginReturnSingleValue;
+
+  /**
+   * @brief Get the number of command-line arguments.
+   *
+   * Retrieve the number of command-line arguments that were used to launch Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of arguments.
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params))
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Get the value of a command-line argument.
+   *
+   * Get the value of one of the command-line arguments that were used
+   * to launch Orthanc. The number of available arguments can be
+   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param argument The index of the argument.
+   * @return The value of the argument, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
+    OrthancPluginContext*  context,
+    uint32_t               argument)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = (int32_t) argument;
+    params.value = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params))
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- a/Plugins/OrthancCPlugin/OrthancCPlugin.h	Tue Nov 04 13:56:17 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1722 +0,0 @@
-/**
- * \mainpage
- *
- * This C/C++ SDK allows external developers to create plugins that
- * can be loaded into Orthanc to extend its functionality. Each
- * Orthanc plugin must expose 4 public functions with the following
- * signatures:
- * 
- * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
- *    This function is invoked by Orthanc when it loads the plugin on startup.
- *    The plugin must:
- *    - Check its compatibility with the Orthanc version using
- *      ::OrthancPluginCheckVersion().
- *    - Store the context pointer so that it can use the plugin 
- *      services of Orthanc.
- *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
- *    - Register all its callbacks for received instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
- *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
- * -# <tt>void OrthancPluginFinalize()</tt>:
- *    This function is invoked by Orthanc during its shutdown. The plugin
- *    must free all its memory.
- * -# <tt>const char* OrthancPluginGetName()</tt>:
- *    The plugin must return a short string to identify itself.
- * -# <tt>const char* OrthancPluginGetVersion()</tt>:
- *    The plugin must return a string containing its version number.
- *
- * The name and the version of a plugin is only used to prevent it
- * from being loaded twice.
- * 
- * The various callbacks are guaranteed to be executed in mutual
- * exclusion since Orthanc 0.8.5.
- **/
-
-
-
-/**
- * @defgroup CInterface C Interface 
- * @brief The C interface to create Orthanc plugins.
- * 
- * These functions must be used to create C plugins for Orthanc.
- **/
-
-
-
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-
-#pragma once
-
-
-#include <stdio.h>
-#include <string.h>
-
-#ifdef WIN32
-#define ORTHANC_PLUGINS_API __declspec(dllexport)
-#else
-#define ORTHANC_PLUGINS_API
-#endif
-
-#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     0
-#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     8
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  5
-
-
-
-/********************************************************************
- ** Check that function inlining is properly supported. The use of
- ** inlining is required, to avoid the duplication of object code
- ** between two compilation modules that would use the Orthanc Plugin
- ** API.
- ********************************************************************/
-
-/* If the auto-detection of the "inline" keyword below does not work
-   automatically and that your compiler is known to properly support
-   inlining, uncomment the following #define and adapt the definition
-   of "static inline". */
-
-/* #define ORTHANC_PLUGIN_INLINE static inline */
-
-#ifndef ORTHANC_PLUGIN_INLINE
-#  if __STDC_VERSION__ >= 199901L
-/*   This is C99 or above: http://predef.sourceforge.net/prestd.html */
-#    define ORTHANC_PLUGIN_INLINE static inline
-#  elif defined(__cplusplus)
-/*   This is C++ */
-#    define ORTHANC_PLUGIN_INLINE static inline
-#  elif defined(__GNUC__)
-/*   This is GCC running in C89 mode */
-#    define ORTHANC_PLUGIN_INLINE static __inline
-#  elif defined(_MSC_VER)
-/*   This is Visual Studio running in C89 mode */
-#    define ORTHANC_PLUGIN_INLINE static __inline
-#  else
-#    error Your compiler is not known to support the "inline" keyword
-#  endif
-#endif
-
-
-
-/********************************************************************
- ** Inclusion of standard libraries.
- ********************************************************************/
-
-#ifdef _MSC_VER
-#include "../../Resources/ThirdParty/VisualStudio/stdint.h"
-#else
-#include <stdint.h>
-#endif
-
-#include <stdlib.h>
-
-
-
-/********************************************************************
- ** Definition of the Orthanc Plugin API.
- ********************************************************************/
-
-/** @{ */
-
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-
-  /**
-   * The various HTTP methods for a REST call.
-   **/
-  typedef enum
-  {
-    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
-    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
-    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
-    OrthancPluginHttpMethod_Delete = 4  /*!< DELETE request */
-  } OrthancPluginHttpMethod;
-
-
-  /**
-   * @brief The parameters of a REST request.
-   **/
-  typedef struct
-  {
-    /**
-     * @brief The HTTP method.
-     **/
-    OrthancPluginHttpMethod method;    
-
-    /**
-     * @brief The number of groups of the regular expression.
-     **/
-    uint32_t                groupsCount;
-
-    /**
-     * @brief The matched values for the groups of the regular expression.
-     **/
-    const char* const*      groups;
-
-    /**
-     * @brief For a GET request, the number of GET parameters.
-     **/
-    uint32_t                getCount;
-
-    /**
-     * @brief For a GET request, the keys of the GET parameters.
-     **/
-    const char* const*      getKeys;
-
-    /**
-     * @brief For a GET request, the values of the GET parameters.
-     **/
-    const char* const*      getValues;
-
-    /**
-     * @brief For a PUT or POST request, the content of the body.
-     **/
-    const char*             body;
-
-    /**
-     * @brief For a PUT or POST request, the number of bytes of the body.
-     **/
-    uint32_t                bodySize;
-
-
-    /* --------------------------------------------------
-       New in version 0.8.1
-       -------------------------------------------------- */
-
-    /**
-     * @brief The number of HTTP headers.
-     **/
-    uint32_t                headersCount;
-
-    /**
-     * @brief The keys of the HTTP headers (always converted to low-case).
-     **/
-    const char* const*      headersKeys;
-
-    /**
-     * @brief The values of the HTTP headers.
-     **/
-    const char* const*      headersValues;
-
-  } OrthancPluginHttpRequest;
-
-
-  typedef enum 
-  {
-    /* Generic services */
-    _OrthancPluginService_LogInfo = 1,
-    _OrthancPluginService_LogWarning = 2,
-    _OrthancPluginService_LogError = 3,
-    _OrthancPluginService_GetOrthancPath = 4,
-    _OrthancPluginService_GetOrthancDirectory = 5,
-    _OrthancPluginService_GetConfigurationPath = 6,
-
-    /* Registration of callbacks */
-    _OrthancPluginService_RegisterRestCallback = 1000,
-    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
-    _OrthancPluginService_RegisterStorageArea = 1002,
-    _OrthancPluginService_RegisterOnChangeCallback = 1003,
-
-    /* Sending answers to REST calls */
-    _OrthancPluginService_AnswerBuffer = 2000,
-    _OrthancPluginService_CompressAndAnswerPngImage = 2001,
-    _OrthancPluginService_Redirect = 2002,
-    _OrthancPluginService_SendHttpStatusCode = 2003,
-    _OrthancPluginService_SendUnauthorized = 2004,
-    _OrthancPluginService_SendMethodNotAllowed = 2005,
-    _OrthancPluginService_SetCookie = 2006,
-    _OrthancPluginService_SetHttpHeader = 2007,
-
-    /* Access to the Orthanc database and API */
-    _OrthancPluginService_GetDicomForInstance = 3000,
-    _OrthancPluginService_RestApiGet = 3001,
-    _OrthancPluginService_RestApiPost = 3002,
-    _OrthancPluginService_RestApiDelete = 3003,
-    _OrthancPluginService_RestApiPut = 3004,
-    _OrthancPluginService_LookupPatient = 3005,
-    _OrthancPluginService_LookupStudy = 3006,
-    _OrthancPluginService_LookupSeries = 3007,
-    _OrthancPluginService_LookupInstance = 3008,
-    _OrthancPluginService_LookupStudyWithAccessionNumber = 3009,
-
-    /* Access to DICOM instances */
-    _OrthancPluginService_GetInstanceRemoteAet = 4000,
-    _OrthancPluginService_GetInstanceSize = 4001,
-    _OrthancPluginService_GetInstanceData = 4002,
-    _OrthancPluginService_GetInstanceJson = 4003,
-    _OrthancPluginService_GetInstanceSimplifiedJson = 4004,
-    _OrthancPluginService_HasInstanceMetadata = 4005,
-    _OrthancPluginService_GetInstanceMetadata = 4006
-  } _OrthancPluginService;
-
-
-
-  /**
-   * The memory layout of the pixels of an image.
-   **/
-  typedef enum
-  {
-    /**
-     * @brief Graylevel 8bpp image.
-     *
-     * The image is graylevel. Each pixel is unsigned and stored in
-     * one byte.
-     **/
-    OrthancPluginPixelFormat_Grayscale8 = 1,
-
-    /**
-     * @brief Graylevel, unsigned 16bpp image.
-     *
-     * The image is graylevel. Each pixel is unsigned and stored in
-     * two bytes.
-     **/
-    OrthancPluginPixelFormat_Grayscale16 = 2,
-
-    /**
-     * @brief Graylevel, signed 16bpp image.
-     *
-     * The image is graylevel. Each pixel is signed and stored in two
-     * bytes.
-     **/
-    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
-
-    /**
-     * @brief Color image in RGB24 format.
-     *
-     * This format describes a color image. The pixels are stored in 3
-     * consecutive bytes. The memory layout is RGB.
-     **/
-    OrthancPluginPixelFormat_RGB24 = 4,
-
-    /**
-     * @brief Color image in RGBA32 format.
-     *
-     * This format describes a color image. The pixels are stored in 4
-     * consecutive bytes. The memory layout is RGBA.
-     **/
-    OrthancPluginPixelFormat_RGBA32 = 5
-  } OrthancPluginPixelFormat;
-
-
-
-  /**
-   * The content types that are supported by Orthanc plugins.
-   **/
-  typedef enum
-  {
-    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
-    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
-    OrthancPluginContentType_DicomAsJson = 2   /*!< JSON summary of a DICOM file */
-  } OrthancPluginContentType;
-
-
-
-  /**
-   * The supported type of DICOM resources.
-   **/
-  typedef enum
-  {
-    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
-    OrthancPluginResourceType_Study = 1,       /*!< Study */
-    OrthancPluginResourceType_Series = 2,      /*!< Series */
-    OrthancPluginResourceType_Instance = 3     /*!< Instance */
-  } OrthancPluginResourceType;
-
-
-
-  /**
-   * The supported type of changes that can happen to DICOM resources.
-   **/
-  typedef enum
-  {
-    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
-    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
-    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
-    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
-    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
-    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
-    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
-    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
-    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
-    OrthancPluginChangeType_StableStudy = 9         /*!< Timeout: No new instance in this study */
-  } OrthancPluginChangeType;
-
-
-
-  /**
-   * @brief A memory buffer allocated by the core system of Orthanc.
-   *
-   * A memory buffer allocated by the core system of Orthanc. When the
-   * content of the buffer is not useful anymore, it must be free by a
-   * call to ::OrthancPluginFreeMemoryBuffer().
-   **/
-  typedef struct
-  {
-    /**
-     * @brief The content of the buffer.
-     **/
-    void*      data;
-
-    /**
-     * @brief The number of bytes in the buffer.
-     **/
-    uint32_t   size;
-  } OrthancPluginMemoryBuffer;
-
-
-
-
-  /**
-   * @brief Opaque structure that represents the HTTP connection to the client application.
-   **/
-  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
-
-
-
-  /**
-   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
-   **/
-  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
-
-
-
-  /**
-   * @brief Signature of a callback function that answers to a REST request.
-   **/
-  typedef int32_t (*OrthancPluginRestCallback) (
-    OrthancPluginRestOutput* output,
-    const char* url,
-    const OrthancPluginHttpRequest* request);
-
-
-
-  /**
-   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
-   **/
-  typedef int32_t (*OrthancPluginOnStoredInstanceCallback) (
-    OrthancPluginDicomInstance* instance,
-    const char* instanceId);
-
-
-
-  /**
-   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
-   **/
-  typedef int32_t (*OrthancPluginOnChangeCallback) (
-    OrthancPluginChangeType changeType,
-    OrthancPluginResourceType resourceType,
-    const char* resourceId);
-
-
-
-  /**
-   * @brief Signature of a function to free dynamic memory.
-   **/
-  typedef void (*OrthancPluginFree) (void* buffer);
-
-
-
-  /**
-   * @brief Callback for writing to the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
-   *
-   * @param uuid The UUID of the file.
-   * @param content The content of the file.
-   * @param size The size of the file.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   **/
-  typedef int32_t (*OrthancPluginStorageCreate) (
-    const char* uuid,
-    const void* content,
-    int64_t size,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Callback for reading from the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
-   *
-   * @param content The content of the file (output).
-   * @param size The size of the file (output).
-   * @param uuid The UUID of the file of interest.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   **/
-  typedef int32_t (*OrthancPluginStorageRead) (
-    void** content,
-    int64_t* size,
-    const char* uuid,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Callback for removing a file from the storage area.
-   *
-   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
-   *
-   * @param uuid The UUID of the file to be removed.
-   * @param type The content type corresponding to this file. 
-   * @return 0 if success, other value if error.
-   **/
-  typedef int32_t (*OrthancPluginStorageRemove) (
-    const char* uuid,
-    OrthancPluginContentType type);
-
-
-
-  /**
-   * @brief Opaque structure that contains information about the Orthanc core.
-   **/
-  typedef struct _OrthancPluginContext_t
-  {
-    void*              pluginsManager;
-    const char*        orthancVersion;
-    OrthancPluginFree  Free;
-    int32_t          (*InvokeService) (struct _OrthancPluginContext_t* context,
-                                       _OrthancPluginService service,
-                                       const void* params);
-  } OrthancPluginContext;
-
-
-
-  /**
-   * @brief Free a string.
-   * 
-   * Free a string that was allocated by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param str The string to be freed.
-   **/
-  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
-    OrthancPluginContext* context, 
-    char* str)
-  {
-    if (str != NULL)
-    {
-      context->Free(str);
-    }
-  }
-
-
-  /**
-   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
-   * 
-   * This function checks whether the version of this C header is
-   * compatible with the current version of Orthanc. The result of
-   * this function should always be checked in the
-   * OrthancPluginInitialize() entry point of the plugin.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return 1 if and only if the versions are compatible. If the
-   * result is 0, the initialization of the plugin should fail.
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
-    OrthancPluginContext* context)
-  {
-    int major, minor, revision;
-
-    /* Assume compatibility with the mainline */
-    if (!strcmp(context->orthancVersion, "mainline"))
-    {
-      return 1;
-    }
-
-    /* Parse the version of the Orthanc core */
-    if ( 
-#ifdef _MSC_VER
-      sscanf_s
-#else
-      sscanf
-#endif
-      (context->orthancVersion, "%d.%d.%d", &major, &minor, &revision) != 3)
-    {
-      return 0;
-    }
-
-    /* Check the major number of the version */
-
-    if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
-    {
-      return 1;
-    }
-
-    if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
-    {
-      return 0;
-    }
-
-    /* Check the minor number of the version */
-
-    if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
-    {
-      return 1;
-    }
-
-    if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
-    {
-      return 0;
-    }
-
-    /* Check the revision number of the version */
-
-    if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER)
-    {
-      return 1;
-    }
-    else
-    {
-      return 0;
-    }
-  }
-
-
-  /**
-   * @brief Free a memory buffer.
-   * 
-   * Free a memory buffer that was allocated by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param buffer The memory buffer to release.
-   **/
-  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
-    OrthancPluginContext* context, 
-    OrthancPluginMemoryBuffer* buffer)
-  {
-    context->Free(buffer->data);
-  }
-
-
-  /**
-   * @brief Log an error.
-   *
-   * Log an error message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogError, message);
-  }
-
-
-  /**
-   * @brief Log a warning.
-   *
-   * Log a warning message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
-  }
-
-
-  /**
-   * @brief Log an information.
-   *
-   * Log an information message using the Orthanc logging system.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param message The message to be logged.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
-    OrthancPluginContext* context,
-    const char* message)
-  {
-    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
-  }
-
-
-
-  typedef struct
-  {
-    const char* pathRegularExpression;
-    OrthancPluginRestCallback callback;
-  } _OrthancPluginRestCallback;
-
-  /**
-   * @brief Register a REST callback.
-   *
-   * This function registers a REST callback against a regular
-   * expression for a URI. This function must be called during the
-   * initialization of the plugin, i.e. inside the
-   * OrthancPluginInitialize() public function.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param pathRegularExpression Regular expression for the URI. May contain groups.
-   * @param callback The callback function to handle the REST call.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
-    OrthancPluginContext*     context,
-    const char*               pathRegularExpression,
-    OrthancPluginRestCallback callback)
-  {
-    _OrthancPluginRestCallback params;
-    params.pathRegularExpression = pathRegularExpression;
-    params.callback = callback;
-    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginOnStoredInstanceCallback callback;
-  } _OrthancPluginOnStoredInstanceCallback;
-
-  /**
-   * @brief Register a callback for received instances.
-   *
-   * This function registers a callback function that is called
-   * whenever a new DICOM instance is stored into the Orthanc core.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback function.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
-    OrthancPluginContext*                  context,
-    OrthancPluginOnStoredInstanceCallback  callback)
-  {
-    _OrthancPluginOnStoredInstanceCallback params;
-    params.callback = callback;
-
-    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              answer;
-    uint32_t                 answerSize;
-    const char*              mimeType;
-  } _OrthancPluginAnswerBuffer;
-
-  /**
-   * @brief Answer to a REST request.
-   *
-   * This function answers to a REST request with the content of a memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param answer Pointer to the memory buffer containing the answer.
-   * @param answerSize Number of bytes of the answer.
-   * @param mimeType The MIME type of the answer.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              answer,
-    uint32_t                 answerSize,
-    const char*              mimeType)
-  {
-    _OrthancPluginAnswerBuffer params;
-    params.output = output;
-    params.answer = answer;
-    params.answerSize = answerSize;
-    params.mimeType = mimeType;
-    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput*  output;
-    OrthancPluginPixelFormat  format;
-    uint32_t                  width;
-    uint32_t                  height;
-    uint32_t                  pitch;
-    const void*               buffer;
-  } _OrthancPluginCompressAndAnswerPngImage;
-
-  /**
-   * @brief Answer to a REST request with a PNG image.
-   *
-   * This function answers to a REST request with a PNG image. The
-   * parameters of this function describe a memory buffer that
-   * contains an uncompressed image. The image will be automatically compressed
-   * as a PNG image by the core system of Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param format The memory layout of the uncompressed image.
-   * @param width The width of the image.
-   * @param height The height of the image.
-   * @param pitch The pitch of the image (i.e. the number of bytes
-   * between 2 successive lines of the image in the memory buffer.
-   * @param buffer The memory buffer containing the uncompressed image.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
-    OrthancPluginContext*     context,
-    OrthancPluginRestOutput*  output,
-    OrthancPluginPixelFormat  format,
-    uint32_t                  width,
-    uint32_t                  height,
-    uint32_t                  pitch,
-    const void*               buffer)
-  {
-    _OrthancPluginCompressAndAnswerPngImage params;
-    params.output = output;
-    params.format = format;
-    params.width = width;
-    params.height = height;
-    params.pitch = pitch;
-    params.buffer = buffer;
-    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerPngImage, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 instanceId;
-  } _OrthancPluginGetDicomForInstance;
-
-  /**
-   * @brief Retrieve a DICOM instance using its Orthanc identifier.
-   * 
-   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
-   * file is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
-   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
-   * @return 0 if success, other value if error.
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginGetDicomForInstance(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 instanceId)
-  {
-    _OrthancPluginGetDicomForInstance params;
-    params.target = target;
-    params.instanceId = instanceId;
-    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 uri;
-  } _OrthancPluginRestApiGet;
-
-  /**
-   * @brief Make a GET call to the built-in Orthanc REST API.
-   * 
-   * Make a GET call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
-   * @param uri The URI in the built-in Orthanc API.
-   * @return 0 if success, other value if error.
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiGet(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri)
-  {
-    _OrthancPluginRestApiGet params;
-    params.target = target;
-    params.uri = uri;
-    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginMemoryBuffer*  target;
-    const char*                 uri;
-    const char*                 body;
-    uint32_t                    bodySize;
-  } _OrthancPluginRestApiPostPut;
-
-  /**
-   * @brief Make a POST call to the built-in Orthanc REST API.
-   * 
-   * Make a POST call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the POST request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, other value if error.
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiPost(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
-  }
-
-
-
-  /**
-   * @brief Make a DELETE call to the built-in Orthanc REST API.
-   * 
-   * Make a DELETE call to the built-in Orthanc REST API.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param uri The URI to delete in the built-in Orthanc API.
-   * @return 0 if success, other value if error.
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiDelete(
-    OrthancPluginContext*       context,
-    const char*                 uri)
-  {
-    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
-  }
-
-
-
-  /**
-   * @brief Make a PUT call to the built-in Orthanc REST API.
-   * 
-   * Make a PUT call to the built-in Orthanc REST API. The result to
-   * the query is stored into a newly allocated memory buffer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
-   * @param uri The URI in the built-in Orthanc API.
-   * @param body The body of the PUT request.
-   * @param bodySize The size of the body.
-   * @return 0 if success, other value if error.
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiPut(
-    OrthancPluginContext*       context,
-    OrthancPluginMemoryBuffer*  target,
-    const char*                 uri,
-    const char*                 body,
-    uint32_t                    bodySize)
-  {
-    _OrthancPluginRestApiPostPut params;
-    params.target = target;
-    params.uri = uri;
-    params.body = body;
-    params.bodySize = bodySize;
-    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              argument;
-  } _OrthancPluginOutputPlusArgument;
-
-  /**
-   * @brief Redirect a REST request.
-   *
-   * This function answers to a REST request by redirecting the user
-   * to another URI using HTTP status 301.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param redirection Where to redirect.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              redirection)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = redirection;
-    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
-  }
-
-
-
-  typedef struct
-  {
-    char**       result;
-    const char*  argument;
-  } _OrthancPluginRetrieveDynamicString;
-
-  /**
-   * @brief Look for a patient.
-   *
-   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored patients).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param patientID The Patient ID of interest.
-   * @return The NULL value if the patient is non-existent, or a string containing the 
-   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
-    OrthancPluginContext*  context,
-    const char*            patientID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = patientID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a study.
-   *
-   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored studies).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param studyUID The Study Instance UID of interest.
-   * @return The NULL value if the study is non-existent, or a string containing the 
-   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
-    OrthancPluginContext*  context,
-    const char*            studyUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = studyUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a study, using the accession number.
-   *
-   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored studies).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param accessionNumber The Accession Number of interest.
-   * @return The NULL value if the study is non-existent, or a string containing the 
-   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
-    OrthancPluginContext*  context,
-    const char*            accessionNumber)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = accessionNumber;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for a series.
-   *
-   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored series).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param seriesUID The Series Instance UID of interest.
-   * @return The NULL value if the series is non-existent, or a string containing the 
-   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
-    OrthancPluginContext*  context,
-    const char*            seriesUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = seriesUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Look for an instance.
-   *
-   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
-   * This function uses the database index to run as fast as possible (it does not loop
-   * over all the stored instances).
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param sopInstanceUID The SOP Instance UID of interest.
-   * @return The NULL value if the instance is non-existent, or a string containing the 
-   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
-    OrthancPluginContext*  context,
-    const char*            sopInstanceUID)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = sopInstanceUID;
-
-    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    uint16_t                 status;
-  } _OrthancPluginSendHttpStatusCode;
-
-  /**
-   * @brief Send a HTTP status code.
-   *
-   * This function answers to a REST request by sending a HTTP status
-   * code (such as "400 - Bad Request"). Note that:
-   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
-   * - Redirections (status 301) must use ::OrthancPluginRedirect().
-   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
-   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param status The HTTP status code to be sent.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    uint16_t                 status)
-  {
-    _OrthancPluginSendHttpStatusCode params;
-    params.output = output;
-    params.status = status;
-    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
-  }
-
-
-  /**
-   * @brief Signal that a REST request is not authorized.
-   *
-   * This function answers to a REST request by signaling that it is
-   * not authorized.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param realm The realm for the authorization process.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              realm)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = realm;
-    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
-  }
-
-
-  /**
-   * @brief Signal that this URI does not support this HTTP method.
-   *
-   * This function answers to a REST request by signaling that the
-   * queried URI does not support this method.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              allowedMethods)
-  {
-    _OrthancPluginOutputPlusArgument params;
-    params.output = output;
-    params.argument = allowedMethods;
-    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
-  }
-
-
-  typedef struct
-  {
-    OrthancPluginRestOutput* output;
-    const char*              key;
-    const char*              value;
-  } _OrthancPluginSetHttpHeader;
-
-  /**
-   * @brief Set a cookie.
-   *
-   * This function sets a cookie in the HTTP client.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param cookie The cookie to be set.
-   * @param value The value of the cookie.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              cookie,
-    const char*              value)
-  {
-    _OrthancPluginSetHttpHeader params;
-    params.output = output;
-    params.key = cookie;
-    params.value = value;
-    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
-  }
-
-
-  /**
-   * @brief Set some HTTP header.
-   *
-   * This function sets a HTTP header in the HTTP answer.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param output The HTTP connection to the client application.
-   * @param key The HTTP header to be set.
-   * @param value The value of the HTTP header.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
-    OrthancPluginContext*    context,
-    OrthancPluginRestOutput* output,
-    const char*              key,
-    const char*              value)
-  {
-    _OrthancPluginSetHttpHeader params;
-    params.output = output;
-    params.key = key;
-    params.value = value;
-    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
-  }
-
-
-  typedef struct
-  {
-    char**                      resultStringToFree;
-    const char**                resultString;
-    int64_t*                    resultInt64;
-    const char*                 key;
-    OrthancPluginDicomInstance* instance;
-  } _OrthancPluginAccessDicomInstance;
-
-
-  /**
-   * @brief Get the AET of a DICOM instance.
-   *
-   * This function returns the Application Entity Title (AET) of the
-   * DICOM modality from which a DICOM instance originates.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The AET if success, NULL if error.
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the size of a DICOM file.
-   *
-   * This function returns the number of bytes of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The size of the file, -1 in case of error.
-   **/
-  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
-    OrthancPluginContext*       context,
-    OrthancPluginDicomInstance* instance)
-  {
-    int64_t size;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultInt64 = &size;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params))
-    {
-      /* Error */
-      return -1;
-    }
-    else
-    {
-      return size;
-    }
-  }
-
-
-  /**
-   * @brief Get the data of a DICOM file.
-   *
-   * This function returns a pointer to the content of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The pointer to the DICOM data, NULL in case of error.
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the DICOM tag hierarchy as a JSON file.
-   *
-   * This function returns a pointer to a newly created string
-   * containing a JSON file. This JSON file encodes the tag hierarchy
-   * of the given DICOM instance.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The NULL value in case of error, or a string containing the JSON file.
-   * This string must be freed by OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultStringToFree = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
-   *
-   * This function returns a pointer to a newly created string
-   * containing a JSON file. This JSON file encodes the tag hierarchy
-   * of the given DICOM instance. In contrast with
-   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
-   * its simplified version.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @return The NULL value in case of error, or a string containing the JSON file.
-   * This string must be freed by OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance)
-  {
-    char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultStringToFree = &result;
-    params.instance = instance;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Check whether a DICOM instance is associated with some metadata.
-   *
-   * This function checks whether the DICOM instance of interest is
-   * associated with some metadata. As of Orthanc 0.8.1, in the
-   * callbacks registered by
-   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
-   * possibly available metadata are "ReceptionDate", "RemoteAET" and
-   * "IndexInSeries".
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @param metadata The metadata of interest.
-   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
-   **/
-  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance,
-    const char*                  metadata)
-  {
-    int64_t result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultInt64 = &result;
-    params.instance = instance;
-    params.key = metadata;
-
-    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params))
-    {
-      /* Error */
-      return -1;
-    }
-    else
-    {
-      return (result != 0);
-    }
-  }
-
-
-  /**
-   * @brief Get the value of some metadata associated with a given DICOM instance.
-   *
-   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
-   * Before calling this function, the existence of the metadata must have been checked with
-   * ::OrthancPluginHasInstanceMetadata().
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param instance The instance of interest.
-   * @param metadata The metadata of interest.
-   * @return The metadata value if success, NULL if error.
-   **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
-    OrthancPluginContext*        context,
-    OrthancPluginDicomInstance*  instance,
-    const char*                  metadata)
-  {
-    const char* result;
-
-    _OrthancPluginAccessDicomInstance params;
-    memset(&params, 0, sizeof(params));
-    params.resultString = &result;
-    params.instance = instance;
-    params.key = metadata;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginStorageCreate  create_;
-    OrthancPluginStorageRead    read_;
-    OrthancPluginStorageRemove  remove_;
-    OrthancPluginFree           free_;
-  } _OrthancPluginRegisterStorageArea;
-
-  /**
-   * @brief Register a custom storage area.
-   *
-   * This function registers a custom storage area, to replace the
-   * built-in way Orthanc stores its files on the filesystem. This
-   * function must be called during the initialization of the plugin,
-   * i.e. inside the OrthancPluginInitialize() public function.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param create The callback function to store a file on the custom storage area.
-   * @param read The callback function to read a file from the custom storage area.
-   * @param remove The callback function to remove a file from the custom storage area.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
-    OrthancPluginContext*       context,
-    OrthancPluginStorageCreate  create,
-    OrthancPluginStorageRead    read,
-    OrthancPluginStorageRemove  remove)
-  {
-    _OrthancPluginRegisterStorageArea params;
-    params.create_ = create;
-    params.read_ = read;
-    params.remove_ = remove;
-
-#ifdef  __cplusplus
-    params.free_ = ::free;
-#else
-    params.free_ = free;
-#endif
-
-    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
-  }
-
-
-
-  /**
-   * @brief Return the path to the Orthanc executable.
-   *
-   * This function returns the path to the Orthanc executable.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Return the directory containing the Orthanc.
-   *
-   * This function returns the path to the directory containing the Orthanc executable.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-  /**
-   * @brief Return the path to the configuration file.
-   *
-   * This function returns the path to the configuration file that was
-   * specified when starting Orthanc.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @return NULL in the case of an error, or a newly allocated string
-   * containing the path. This string must be freed by
-   * OrthancPluginFreeString().
-   **/
-  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
-  {
-    char* result;
-
-    _OrthancPluginRetrieveDynamicString params;
-    params.result = &result;
-    params.argument = NULL;
-
-    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params))
-    {
-      /* Error */
-      return NULL;
-    }
-    else
-    {
-      return result;
-    }
-  }
-
-
-
-  typedef struct
-  {
-    OrthancPluginOnChangeCallback callback;
-  } _OrthancPluginOnChangeCallback;
-
-  /**
-   * @brief Register a callback to monitor changes.
-   *
-   * This function registers a callback function that is called
-   * whenever a change happens to some DICOM resource.
-   * 
-   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param callback The callback function.
-   **/
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
-    OrthancPluginContext*          context,
-    OrthancPluginOnChangeCallback  callback)
-  {
-    _OrthancPluginOnChangeCallback params;
-    params.callback = callback;
-
-    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
-  }
-
-
-
-
-#ifdef  __cplusplus
-}
-#endif
-
-
-/** @} */
-
--- a/Plugins/Samples/Basic/Plugin.c	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Samples/Basic/Plugin.c	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
@@ -165,6 +165,58 @@
 }
 
 
+ORTHANC_PLUGINS_API int32_t Callback5(OrthancPluginRestOutput* output,
+                                      const char* url,
+                                      const OrthancPluginHttpRequest* request)
+{
+  /**
+   * Demonstration the difference between the
+   * "OrthancPluginRestApiXXX()" and the
+   * "OrthancPluginRestApiXXXAfterPlugins()" mechanisms to forward
+   * REST calls.
+   *
+   * # curl http://localhost:8042/forward/built-in/system
+   * # curl http://localhost:8042/forward/plugins/system
+   * # curl http://localhost:8042/forward/built-in/plugin/image
+   *   => FAILURE (because the "/plugin/image" URI is implemented by this plugin)
+   * # curl http://localhost:8042/forward/plugins/plugin/image  => SUCCESS
+   **/
+
+  OrthancPluginMemoryBuffer tmp;
+  int isBuiltIn, error;
+
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+    return 0;
+  }
+
+  isBuiltIn = strcmp("plugins", request->groups[0]);
+ 
+  if (isBuiltIn)
+  {
+    error = OrthancPluginRestApiGet(context, &tmp, request->groups[1]);
+  }
+  else
+  {
+    printf("ICI1\n");
+    error = OrthancPluginRestApiGetAfterPlugins(context, &tmp, request->groups[1]);
+    printf("ICI2\n");
+  }
+
+  if (error)
+  {
+    return -1;
+  }
+  else
+  {
+    OrthancPluginAnswerBuffer(context, output, tmp.data, tmp.size, "application/octet-stream");
+    OrthancPluginFreeMemoryBuffer(context, &tmp);
+    return 0;
+  }
+}
+
+
 ORTHANC_PLUGINS_API int32_t CallbackCreateDicom(OrthancPluginRestOutput* output,
                                                 const char* url,
                                                 const OrthancPluginHttpRequest* request)
@@ -280,6 +332,7 @@
 {
   OrthancPluginMemoryBuffer tmp;
   char info[1024], *s;
+  int counter, i;
 
   context = c;
   OrthancPluginLogWarning(context, "Sample plugin is initializing");
@@ -315,17 +368,34 @@
   OrthancPluginLogWarning(context, info);
   OrthancPluginFreeString(context, s);
 
+  /* Print the command-line arguments of Orthanc */
+  counter = OrthancPluginGetCommandLineArgumentsCount(context);
+  for (i = 0; i < counter; i++)
+  {
+    s = OrthancPluginGetCommandLineArgument(context, i);
+    sprintf(info, "  Command-line argument %d: \"%s\"", i, s);
+    OrthancPluginLogWarning(context, info);
+    OrthancPluginFreeString(context, s);    
+  }
+
   /* Register the callbacks */
   OrthancPluginRegisterRestCallback(context, "/(plu.*)/hello", Callback1);
   OrthancPluginRegisterRestCallback(context, "/plu.*/image", Callback2);
   OrthancPluginRegisterRestCallback(context, "/plugin/instances/([^/]+)/info", Callback3);
   OrthancPluginRegisterRestCallback(context, "/instances/([^/]+)/preview", Callback4);
+  OrthancPluginRegisterRestCallback(context, "/forward/(built-in)(/.+)", Callback5);
+  OrthancPluginRegisterRestCallback(context, "/forward/(plugins)(/.+)", Callback5);
   OrthancPluginRegisterRestCallback(context, "/plugin/create", CallbackCreateDicom);
 
   OrthancPluginRegisterOnStoredInstanceCallback(context, OnStoredCallback);
 
   OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
 
+  /* Declare several properties of the plugin */
+  OrthancPluginSetRootUri(context, "/plugin/hello");
+  OrthancPluginSetDescription(context, "This is the description of the sample plugin that can be seen in Orthanc Explorer.");
+  OrthancPluginExtendOrthancExplorer(context, "alert('Hello Orthanc! From sample plugin with love.');");
+
   /* Make REST requests to the built-in Orthanc API */
   OrthancPluginRestApiGet(context, &tmp, "/changes");
   OrthancPluginFreeMemoryBuffer(context, &tmp);
@@ -335,7 +405,18 @@
   /* Play with PUT by defining a new target modality. */
   sprintf(info, "[ \"STORESCP\", \"localhost\", 2000 ]");
   OrthancPluginRestApiPut(context, &tmp, "/modalities/demo", info, strlen(info));
- 
+
+  /* Play with global properties: A global counter is incremented 
+     each time the plugin starts. */
+  s = OrthancPluginGetGlobalProperty(context, 1024, "0");
+  sscanf(s, "%d", &counter);
+  sprintf(info, "Number of times this plugin was started: %d", counter);
+  OrthancPluginLogWarning(context, info);
+  counter++;
+  sprintf(info, "%d", counter);
+  OrthancPluginSetGlobalProperty(context, 1024, info);
+  OrthancPluginFreeString(context, s);
+
   return 0;
 }
 
--- a/Plugins/Samples/GdcmDecoding/OrthancContext.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Samples/GdcmDecoding/OrthancContext.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
--- a/Plugins/Samples/GdcmDecoding/OrthancContext.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Samples/GdcmDecoding/OrthancContext.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
--- a/Plugins/Samples/GdcmDecoding/Plugin.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Samples/GdcmDecoding/Plugin.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
--- a/Plugins/Samples/StorageArea/Plugin.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Samples/StorageArea/Plugin.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
@@ -96,7 +96,7 @@
   else
   {
     *content = malloc(*size);
-    if (content == NULL ||
+    if (*content == NULL ||
         fread(*content, *size, 1, fp) != 1)
     {
       ok = false;
@@ -121,14 +121,13 @@
 {
   ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
   {
-    char info[1024];
-
     context = c;
     OrthancPluginLogWarning(context, "Storage plugin is initializing");
 
     /* Check the version of the Orthanc core */
     if (OrthancPluginCheckVersion(c) == 0)
     {
+      char info[1024];
       sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
               c->orthancVersion,
               ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
--- a/Plugins/Samples/WebSkeleton/Configuration.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Samples/WebSkeleton/Configuration.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
--- a/Plugins/Samples/WebSkeleton/Framework/EmbedResources.py	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Samples/WebSkeleton/Framework/EmbedResources.py	Wed Feb 11 10:32:14 2015 +0100
@@ -1,6 +1,6 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
-# Belgium
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital 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
--- a/Plugins/Samples/WebSkeleton/Framework/Framework.cmake	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Samples/WebSkeleton/Framework/Framework.cmake	Wed Feb 11 10:32:14 2015 +0100
@@ -1,6 +1,6 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
-# Belgium
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital 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
--- a/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
--- a/README	Tue Nov 04 13:56:17 2014 +0100
+++ b/README	Wed Feb 11 10:32:14 2015 +0100
@@ -41,11 +41,11 @@
 exception:
 http://people.gnome.org/~markmc/openssl-and-the-gpl.html
 
-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 ask scientific works and clinical studies that make
+use of Orthanc to cite Orthanc in their associated publications.
+Similarly, we ask 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:
 
 @inproceedings{Jodogne:ISBI2013,
   author = {Jodogne, S. and Bernard, C. and Devillers, M. and Lenaerts, E. and Coucke, P.},
@@ -78,8 +78,9 @@
 * OrthancCppClient/   - Code of the C++ client
 * OrthancExplorer/    - Code of the Web application (HTML5/Javascript)
 * OrthancServer/      - Code of the Orthanc server (depends on DCMTK)
+* Plugins/            - Code of the plugin framework
 * Resources/          - Scripts, resources for building third-party code
-* UnitTests/          - Unit tests
+* UnitTestsSources/   - Unit tests
 
 This archive contains the following files:
 
--- a/Resources/CMake/LibCurlConfiguration.cmake	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/CMake/LibCurlConfiguration.cmake	Wed Feb 11 10:32:14 2015 +0100
@@ -26,7 +26,7 @@
     -DCURL_DISABLE_LDAP=1
     -DCURL_DISABLE_LDAPS=1
     -DCURL_DISABLE_POP3=1
-    -DCURL_DISABLE_PROXY=1
+    #-DCURL_DISABLE_PROXY=1
     -DCURL_DISABLE_RTSP=1
     -DCURL_DISABLE_TELNET=1
     -DCURL_DISABLE_TFTP=1
--- a/Resources/CMake/PlustacheConfiguration.cmake	Tue Nov 04 13:56:17 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-if (USE_PLUSTACHE)
-  add_definitions(-DORTHANC_PLUSTACHE_ENABLED=1)
-
-  if (STATIC_BUILD OR NOT USE_SYSTEM_PLUSTACHE)
-    set(PLUSTACHE_SOURCES_DIR ${CMAKE_BINARY_DIR}/plustache-0.3.0)
-    DownloadPackage(
-      "6162946bdb3dccf3b2185fcf149671ee"
-      "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/plustache-0.3.0.tar.gz"
-      "${PLUSTACHE_SOURCES_DIR}")
-
-    list(APPEND THIRD_PARTY_SOURCES
-      ${PLUSTACHE_SOURCES_DIR}/src/context.cpp
-      ${PLUSTACHE_SOURCES_DIR}/src/template.cpp
-      )
-
-    include_directories(
-      ${PLUSTACHE_SOURCES_DIR}
-      )
-
-    execute_process(
-      COMMAND patch -p0 -i ${ORTHANC_ROOT}/Resources/CMake/PlustacheConfiguration.patch
-      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-      )
-
-    source_group(ThirdParty\\Plustache REGULAR_EXPRESSION ${PLUSTACHE_SOURCES_DIR}/.*)
-
-  else()
-    message(FATAL_ERROR "Dynamic linking against plustache not implemented (a patch is required)")
-  endif()
-
-else()
-  add_definitions(-DORTHANC_PLUSTACHE_ENABLED=0)
-
-endif()
--- a/Resources/CMake/PlustacheConfiguration.patch	Tue Nov 04 13:56:17 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-diff -r -u plustache-0.3.0.orig/include/template.hpp plustache-0.3.0/include/template.hpp
---- plustache-0.3.0.orig/include/template.hpp	2014-01-29 13:26:52.000000000 +0100
-+++ plustache-0.3.0/include/template.hpp	2014-05-28 17:51:51.623305914 +0200
-@@ -21,7 +21,7 @@
-     public:
-         template_t ();
-         template_t (std::string& tmpl_path);
--        ~template_t ();
-+        virtual ~template_t ();
-         std::string render(const std::string& tmplate, const Context& ctx);
-         std::string render(const std::string& tmplate, const ObjectType& ctx);
- 
-@@ -42,11 +42,13 @@
-         std::string render_sections(const std::string& tmplate,
-                                     const Context& ctx);
-         std::string html_escape(const std::string& s);
--        std::string get_partial(const std::string& partial) const;
-         void change_delimiter(const std::string& opentag,
-                               const std::string& closetag);
-         void compile_data();
--        std::string get_template(const std::string& tmpl);
-+
-+    protected:
-+        virtual std::string get_partial(const std::string& partial) const;
-+        virtual std::string get_template(const std::string& tmpl);
-     };
- } // namespace Plustache
- #endif
-Only in plustache-0.3.0/include: template.hpp~
-diff -r -u plustache-0.3.0.orig/src/template.cpp plustache-0.3.0/src/template.cpp
---- plustache-0.3.0.orig/src/template.cpp	2014-01-29 13:26:52.000000000 +0100
-+++ plustache-0.3.0/src/template.cpp	2014-05-28 17:51:32.599306393 +0200
-@@ -126,7 +126,7 @@
-         // found a partial
-         else if (modifier == ">")
-         {
--            std::string partial = template_t::get_partial(key);
-+            std::string partial = get_partial(key);
-             repl.assign(template_t::render(partial, ctx));
-         }
-         // normal tag
-Only in plustache-0.3.0/src: template.cpp~
--- a/Resources/Configuration.json	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Configuration.json	Wed Feb 11 10:32:14 2015 +0100
@@ -35,7 +35,9 @@
 
   // List of paths to the plugins that are to be loaded into this
   // instance of Orthanc (e.g. "./libPluginTest.so" for Linux, or
-  // "./PluginTest.dll" for Windows).
+  // "./PluginTest.dll" for Windows). These paths can refer to
+  // folders, in which case they will be scanned non-recursively to
+  // find shared libraries.
   "Plugins" : [
   ],
 
@@ -138,6 +140,11 @@
     // "peer2" : [ "http://localhost:8044/" ]
   },
 
+  // Parameters of the HTTP proxy to be used by Orthanc. If set to the
+  // empty string, no HTTP proxy is used. For instance:
+  //   "HttpProxy" : "192.168.0.1:3128"
+  //   "HttpProxy" : "proxyUser:proxyPassword@192.168.0.1:3128"
+  "HttpProxy" : "",
 
 
   /**
@@ -203,11 +210,17 @@
   // to grow indefinitely in auto-routing tasks.
   "LogExportedResources" : true,
 
-  // Enable or disable HTTP Keep-Alive (experimental). Set this option
+  // Enable or disable HTTP Keep-Alive (deprecated). Set this option
   // to "true" only in the case of high HTTP loads.
   "KeepAlive" : false,
 
   // If this option is set to "false", Orthanc will run in index-only
   // mode. The DICOM files will not be stored on the drive.
-  "StoreDicom" : true
+  "StoreDicom" : true,
+
+  // DICOM associations are kept open as long as new DICOM commands
+  // are issued. This option sets the number of seconds of inactivity
+  // to wait before automatically closing a DICOM association. If set
+  // to 0, the connection is closed immediately.
+  "DicomAssociationCloseDelay" : 5
 }
--- a/Resources/EmbedResources.py	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/EmbedResources.py	Wed Feb 11 10:32:14 2015 +0100
@@ -1,6 +1,6 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
-# Belgium
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital 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
--- a/Resources/OrthancPlugin.doxygen	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/OrthancPlugin.doxygen	Wed Feb 11 10:32:14 2015 +0100
@@ -655,7 +655,7 @@
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = @CMAKE_SOURCE_DIR@/Plugins/OrthancCPlugin/OrthancCPlugin.h
+INPUT                  = @CMAKE_SOURCE_DIR@/Plugins/Include/OrthancCPlugin.h
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
@@ -709,7 +709,7 @@
 # wildcard * is used, a substring. Examples: ANamespace, AClass,
 # AClass::ANamespace, ANamespace::*Test
 
-EXCLUDE_SYMBOLS        = _OrthancPlugin*
+EXCLUDE_SYMBOLS        = _OrthancPlugin* OrthancPluginGetName
 
 # The EXAMPLE_PATH tag can be used to specify one or more files or
 # directories that contain example code fragments that are included (see
--- a/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Wed Feb 11 10:32:14 2015 +0100
@@ -1,8 +1,8 @@
 #!/usr/bin/python
 
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
-# Belgium
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital 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
--- a/Resources/Samples/Lua/AutoroutingConditional.lua	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/Lua/AutoroutingConditional.lua	Wed Feb 11 10:32:14 2015 +0100
@@ -1,4 +1,10 @@
-function OnStoredInstance(instanceId, tags, metadata)
+function OnStoredInstance(instanceId, tags, metadata, remoteAet, calledAet)
+   -- The "remoteAet" and "calledAet" arguments are only available
+   -- since Orthanc 0.8.6
+   if remoteAet ~=nil and calledAet ~= nil then
+      print ("Source AET: " .. remoteAet .. " => Called AET: " .. calledAet)
+   end
+
    -- Extract the value of the "PatientName" DICOM tag
    local patientName = string.lower(tags['PatientName'])
 
--- a/Resources/Samples/Lua/AutoroutingModification.lua	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/Lua/AutoroutingModification.lua	Wed Feb 11 10:32:14 2015 +0100
@@ -7,6 +7,7 @@
       -- The tags to be replaced
       local replace = {}
       replace['StationName'] = 'My Medical Device'
+      replace['0031-1020'] = 'Some private tag'
 
       -- The tags to be removed
       local remove = { 'MilitaryRank' }
--- a/Resources/Samples/Lua/CallWebService.js	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/Lua/CallWebService.js	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
--- a/Resources/Samples/OrthancClient/Basic/main.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/OrthancClient/Basic/main.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
--- a/Resources/Samples/OrthancClient/Vtk/main.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/OrthancClient/Vtk/main.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
--- a/Resources/Samples/Python/AnonymizeAllPatients.py	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/Python/AnonymizeAllPatients.py	Wed Feb 11 10:32:14 2015 +0100
@@ -2,8 +2,8 @@
 # -*- coding: utf-8 -*-
 
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
-# Belgium
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital 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
--- a/Resources/Samples/Python/AutoClassify.py	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/Python/AutoClassify.py	Wed Feb 11 10:32:14 2015 +0100
@@ -2,8 +2,8 @@
 # -*- coding: utf-8 -*-
 
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
-# Belgium
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital 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
--- a/Resources/Samples/Python/ChangesLoop.py	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/Python/ChangesLoop.py	Wed Feb 11 10:32:14 2015 +0100
@@ -1,8 +1,8 @@
 #!/usr/bin/python
 
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
-# Belgium
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital 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
--- a/Resources/Samples/Python/DownloadAnonymized.py	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/Python/DownloadAnonymized.py	Wed Feb 11 10:32:14 2015 +0100
@@ -2,8 +2,8 @@
 # -*- coding: utf-8 -*-
 
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
-# Belgium
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital 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
--- a/Resources/Samples/Python/HighPerformanceAutoRouting.py	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/Python/HighPerformanceAutoRouting.py	Wed Feb 11 10:32:14 2015 +0100
@@ -2,8 +2,8 @@
 # -*- coding: utf-8 -*-
 
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
-# Belgium
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital 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
--- a/Resources/Samples/Python/RestToolbox.py	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/Python/RestToolbox.py	Wed Feb 11 10:32:14 2015 +0100
@@ -1,6 +1,6 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
-# Belgium
+# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+# Department, University Hospital 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
--- a/Resources/Samples/Tools/RecoverCompressedFile.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/Tools/RecoverCompressedFile.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/Resources/Samples/WebApplications/DrawingDicomizer.js	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/WebApplications/DrawingDicomizer.js	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
--- a/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
--- a/Resources/Samples/WebApplications/NodeToolbox.js	Tue Nov 04 13:56:17 2014 +0100
+++ b/Resources/Samples/WebApplications/NodeToolbox.js	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
--- a/UnitTestsSources/DicomMapTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/DicomMapTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -133,16 +133,17 @@
 
 
 
-static void TestModule(ResourceType level)
+static void TestModule(ResourceType level,
+                       DicomModule module)
 {
-  std::set<DicomTag> module, main;
-  DicomTag::GetTagsForModule(module, level);
+  std::set<DicomTag> moduleTags, main;
+  DicomTag::GetTagsForModule(moduleTags, module);
   DicomMap::GetMainDicomTags(main, level);
   
   // The main dicom tags are a subset of the module
-  for (std::set<DicomTag>::const_iterator it = main.begin(); it != main.end(); it++)
+  for (std::set<DicomTag>::const_iterator it = main.begin(); it != main.end(); ++it)
   {
-    bool ok = module.find(*it) != module.end();
+    bool ok = moduleTags.find(*it) != moduleTags.end();
 
     // Exceptions for the Series level
     /*if ((//
@@ -182,8 +183,8 @@
 
 TEST(DicomMap, Modules)
 {
-  TestModule(ResourceType_Patient);
-  TestModule(ResourceType_Study);
-  //TestModule(ResourceType_Series);   // TODO
-  TestModule(ResourceType_Instance);
+  TestModule(ResourceType_Patient, DicomModule_Patient);
+  TestModule(ResourceType_Study, DicomModule_Study);
+  //TestModule(ResourceType_Series, DicomModule_Series);   // TODO
+  TestModule(ResourceType_Instance, DicomModule_Instance);
 }
--- a/UnitTestsSources/FileStorageTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/FileStorageTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/FromDcmtkTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -89,14 +89,35 @@
 
 TEST(DicomModification, Anonymization)
 {
+  ASSERT_EQ(DICOM_TAG_PATIENT_NAME, FromDcmtkBridge::ParseTag("PatientName"));
+
   const DicomTag privateTag(0x0045, 0x0010);
+  const DicomTag privateTag2(FromDcmtkBridge::ParseTag("0031-1020"));
   ASSERT_TRUE(FromDcmtkBridge::IsPrivateTag(privateTag));
+  ASSERT_TRUE(FromDcmtkBridge::IsPrivateTag(privateTag2));
+  ASSERT_EQ(0x0031, privateTag2.GetGroup());
+  ASSERT_EQ(0x1020, privateTag2.GetElement());
 
+  std::string s;
   ParsedDicomFile o;
   o.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
-  o.Replace(privateTag, "private tag");
+  ASSERT_FALSE(o.GetTagValue(s, privateTag));
+  o.Insert(privateTag, "private tag");
+  ASSERT_TRUE(o.GetTagValue(s, privateTag));
+  ASSERT_STREQ("private tag", s.c_str());
 
-  std::string s;
+  ASSERT_FALSE(o.GetTagValue(s, privateTag2));
+  ASSERT_THROW(o.Replace(privateTag2, "hello", DicomReplaceMode_ThrowIfAbsent), OrthancException);
+  ASSERT_FALSE(o.GetTagValue(s, privateTag2));
+  o.Replace(privateTag2, "hello", DicomReplaceMode_IgnoreIfAbsent);
+  ASSERT_FALSE(o.GetTagValue(s, privateTag2));
+  o.Replace(privateTag2, "hello", DicomReplaceMode_InsertIfAbsent);
+  ASSERT_TRUE(o.GetTagValue(s, privateTag2));
+  ASSERT_STREQ("hello", s.c_str());
+  o.Replace(privateTag2, "hello world");
+  ASSERT_TRUE(o.GetTagValue(s, privateTag2));
+  ASSERT_STREQ("hello world", s.c_str());
+
   ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
   ASSERT_FALSE(Toolbox::IsUuid(s));
 
@@ -109,7 +130,7 @@
   ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
   ASSERT_TRUE(Toolbox::IsUuid(s));
   ASSERT_TRUE(o.GetTagValue(s, privateTag));
-  ASSERT_EQ("private tag", s);
+  ASSERT_STREQ("private tag", s.c_str());
   
   m.SetupAnonymization();
   m.Apply(o);
--- a/UnitTestsSources/ImageProcessingTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/ImageProcessingTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/JpegLosslessTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/JpegLosslessTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/LuaTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/LuaTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/MemoryCacheTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/MemoryCacheTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/MultiThreadingTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -291,7 +291,7 @@
                      const ListOfStrings& inputs)
   {
     for (ListOfStrings::const_iterator 
-           it = inputs.begin(); it != inputs.end(); it++)
+           it = inputs.begin(); it != inputs.end(); ++it)
     {
       int a = boost::lexical_cast<int>(*it);
       int b = factor_ * a;
@@ -318,8 +318,10 @@
   {
     ListOfStrings l;
     s->GetListOfJobs(l);
-    for (ListOfStrings::iterator i = l.begin(); i != l.end(); i++)
-      printf(">> %s: %0.1f\n", i->c_str(), 100.0f * s->GetProgress(*i));
+    for (ListOfStrings::iterator it = l.begin(); it != l.end(); ++it)
+    {
+      printf(">> %s: %0.1f\n", it->c_str(), 100.0f * s->GetProgress(*it));
+    }
     Toolbox::USleep(10000);
   }
 }
--- a/UnitTestsSources/PluginsTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/PluginsTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/PlustacheTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,120 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#if ORTHANC_PLUSTACHE_ENABLED == 1
-
-#include <include/template.hpp>
-
-class OrthancPlustache : public Plustache::template_t
-{
-public:
-protected:
-  virtual std::string get_template(const std::string& tmpl)
-  {
-    //printf("OK [%s]\n", tmpl.c_str());
-    return Plustache::template_t::get_template(tmpl);
-  }
-
-  virtual std::string get_partial(const std::string& partial) const
-  {
-    //printf("OK2 [%s]\n", partial.c_str());
-    //return Plustache::template_t::get_partial(partial);
-    return "<li>{{name}}</li>";
-  }
-};
-
-
-TEST(Plustache, Basic1)
-{
-  PlustacheTypes::ObjectType ctx;
-  ctx["title"] = "About";
-
-  OrthancPlustache t;
-  ASSERT_EQ("<h1>About</h1>", t.render("<h1>{{title}}</h1>", ctx));
-}
-
-
-TEST(Plustache, Basic2)
-{
-  Plustache::Context ctx;
-  ctx.add("title", "About");
-
-  OrthancPlustache t;
-  ASSERT_EQ("<h1>About</h1>", t.render("<h1>{{title}}</h1>", ctx));
-}
-
-
-TEST(Plustache, Context)
-{
-  PlustacheTypes::ObjectType a;
-  a["name"] = "Orthanc";
-
-  PlustacheTypes::ObjectType b;
-  b["name"] = "Jodogne";
-
-  PlustacheTypes::CollectionType c;
-  c.push_back(a);
-  c.push_back(b);
-
-  Plustache::Context ctx;
-  ctx.add("items", c);
-
-  OrthancPlustache t;
-  ASSERT_EQ("<ul><li>Orthanc</li><li>Jodogne</li></ul>",
-            t.render("<ul>{{#items}}<li>{{name}}</li>{{/items}}</ul>", ctx));
-}
-
-
-TEST(Plustache, Partials)
-{
-  PlustacheTypes::ObjectType a;
-  a["name"] = "Orthanc";
-
-  PlustacheTypes::ObjectType b;
-  b["name"] = "Jodogne";
-
-  PlustacheTypes::CollectionType c;
-  c.push_back(a);
-  c.push_back(b);
-
-  Plustache::Context ctx;
-  ctx.add("items", c);
-
-  OrthancPlustache t;
-  ASSERT_EQ("<ul><li>Orthanc</li><li>Jodogne</li></ul>",
-            t.render("<ul>{{#items}}{{>partial}}{{/items}}</ul>", ctx));
-}
-
-#endif
--- a/UnitTestsSources/PngTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/PngTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/PrecompiledHeadersUnitTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/PrecompiledHeadersUnitTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/PrecompiledHeadersUnitTests.h	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/PrecompiledHeadersUnitTests.h	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/RestApiTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/RestApiTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/SQLiteChromiumTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/SQLiteChromiumTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/SQLiteTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/SQLiteTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/ServerIndexTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/ServerIndexTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -101,7 +101,7 @@
   {
   protected:
     std::auto_ptr<ServerIndexListener> listener_;
-    std::auto_ptr<DatabaseWrapper> index_;
+    std::auto_ptr<IDatabaseWrapper> index_;
 
     DatabaseWrapperTest()
     {
@@ -110,7 +110,18 @@
     virtual void SetUp() 
     {
       listener_.reset(new ServerIndexListener);
-      index_.reset(new DatabaseWrapper(*listener_));
+
+      switch (GetParam())
+      {
+        case DatabaseWrapperClass_SQLite:
+          index_.reset(new DatabaseWrapper());
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      index_->SetListener(*listener_);
     }
 
     virtual void TearDown()
@@ -118,6 +129,121 @@
       index_.reset(NULL);
       listener_.reset(NULL);
     }
+
+    void CheckTableRecordCount(uint32_t expected, const char* table)
+    {
+      switch (GetParam())
+      {
+        case DatabaseWrapperClass_SQLite:
+        {
+          DatabaseWrapper* sqlite = dynamic_cast<DatabaseWrapper*>(index_.get());
+          ASSERT_EQ(expected, sqlite->GetTableRecordCount(table));
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    void CheckNoParent(int64_t id)
+    {
+      std::string s;
+
+      switch (GetParam())
+      {
+        case DatabaseWrapperClass_SQLite:
+        {
+          DatabaseWrapper* sqlite = dynamic_cast<DatabaseWrapper*>(index_.get());
+          ASSERT_FALSE(sqlite->GetParentPublicId(s, id));
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    void CheckParentPublicId(const char* expected, int64_t id)
+    {
+      std::string s;
+
+      switch (GetParam())
+      {
+        case DatabaseWrapperClass_SQLite:
+        {
+          DatabaseWrapper* sqlite = dynamic_cast<DatabaseWrapper*>(index_.get());
+          ASSERT_TRUE(sqlite->GetParentPublicId(s, id));
+          ASSERT_EQ(expected, s);
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    void CheckNoChild(int64_t id)
+    {
+      std::list<std::string> j;
+
+      switch (GetParam())
+      {
+        case DatabaseWrapperClass_SQLite:
+        {
+          DatabaseWrapper* sqlite = dynamic_cast<DatabaseWrapper*>(index_.get());
+          sqlite->GetChildren(j, id);
+          ASSERT_EQ(0, j.size());
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    void CheckOneChild(const char* expected, int64_t id)
+    {
+      std::list<std::string> j;
+
+      switch (GetParam())
+      {
+        case DatabaseWrapperClass_SQLite:
+        {
+          DatabaseWrapper* sqlite = dynamic_cast<DatabaseWrapper*>(index_.get());
+          sqlite->GetChildren(j, id);
+          ASSERT_EQ(1, j.size());
+          ASSERT_EQ(expected, j.front());
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    void CheckTwoChildren(const char* expected1,
+                          const char* expected2,
+                          int64_t id)
+    {
+      std::list<std::string> j;
+
+      switch (GetParam())
+      {
+        case DatabaseWrapperClass_SQLite:
+        {
+          DatabaseWrapper* sqlite = dynamic_cast<DatabaseWrapper*>(index_.get());
+          sqlite->GetChildren(j, id);
+          ASSERT_EQ(2, j.size());
+          ASSERT_TRUE((expected1 == j.front() && expected2 == j.back()) ||
+                      (expected1 == j.back() && expected2 == j.front()));                    
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
   };
 }
 
@@ -156,15 +282,15 @@
   ASSERT_EQ(ResourceType_Study, index_->GetResourceType(a[6]));
 
   {
-    Json::Value t;
+    std::list<std::string> t;
     index_->GetAllPublicIds(t, ResourceType_Patient);
 
     ASSERT_EQ(1u, t.size());
-    ASSERT_EQ("a", t[0u].asString());
+    ASSERT_EQ("a", t.front());
 
     index_->GetAllPublicIds(t, ResourceType_Series);
     ASSERT_EQ(1u, t.size());
-    ASSERT_EQ("c", t[0u].asString());
+    ASSERT_EQ("c", t.front());
 
     index_->GetAllPublicIds(t, ResourceType_Study);
     ASSERT_EQ(2u, t.size());
@@ -191,14 +317,14 @@
   ASSERT_FALSE(index_->LookupParent(parent, a[6]));
 
   std::string s;
-  
-  ASSERT_FALSE(index_->GetParentPublicId(s, a[0]));
-  ASSERT_FALSE(index_->GetParentPublicId(s, a[6]));
-  ASSERT_TRUE(index_->GetParentPublicId(s, a[1])); ASSERT_EQ("a", s);
-  ASSERT_TRUE(index_->GetParentPublicId(s, a[2])); ASSERT_EQ("b", s);
-  ASSERT_TRUE(index_->GetParentPublicId(s, a[3])); ASSERT_EQ("c", s);
-  ASSERT_TRUE(index_->GetParentPublicId(s, a[4])); ASSERT_EQ("c", s);
-  ASSERT_TRUE(index_->GetParentPublicId(s, a[5])); ASSERT_EQ("g", s);
+
+  CheckNoParent(a[0]);
+  CheckNoParent(a[6]);
+  CheckParentPublicId("a", a[1]);
+  CheckParentPublicId("b", a[2]);
+  CheckParentPublicId("c", a[3]);
+  CheckParentPublicId("c", a[4]);
+  CheckParentPublicId("g", a[5]);
 
   std::list<std::string> l;
   index_->GetChildrenPublicId(l, a[0]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("b", l.front());
@@ -224,7 +350,7 @@
   ASSERT_EQ(0u, md.size());
 
   index_->AddAttachment(a[4], FileInfo("my json file", FileContentType_DicomAsJson, 42, "md5", 
-                                     CompressionType_Zlib, 21, "compressedMD5"));
+                                       CompressionType_Zlib, 21, "compressedMD5"));
   index_->AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42, "md5"));
   index_->AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44, "md5"));
   index_->SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE");
@@ -255,27 +381,26 @@
   ASSERT_EQ(21u + 42u + 44u, index_->GetTotalCompressedSize());
   ASSERT_EQ(42u + 42u + 44u, index_->GetTotalUncompressedSize());
 
-  DicomMap m;
-  m.SetValue(0x0010, 0x0010, "PatientName");
-  index_->SetMainDicomTags(a[3], m);
+  index_->SetMainDicomTag(a[3], DicomTag(0x0010, 0x0010), "PatientName");
 
   int64_t b;
   ResourceType t;
-  ASSERT_TRUE(index_->LookupResource("g", b, t));
+  ASSERT_TRUE(index_->LookupResource(b, t, "g"));
   ASSERT_EQ(7, b);
   ASSERT_EQ(ResourceType_Study, t);
 
   ASSERT_TRUE(index_->LookupMetadata(s, a[4], MetadataType_Instance_RemoteAet));
   ASSERT_FALSE(index_->LookupMetadata(s, a[4], MetadataType_Instance_IndexInSeries));
   ASSERT_EQ("PINNACLE", s);
-  ASSERT_EQ("PINNACLE", index_->GetMetadata(a[4], MetadataType_Instance_RemoteAet));
-  ASSERT_EQ("None", index_->GetMetadata(a[4], MetadataType_Instance_IndexInSeries, "None"));
+
+  std::string u;
+  ASSERT_TRUE(index_->LookupMetadata(u, a[4], MetadataType_Instance_RemoteAet));
+  ASSERT_EQ("PINNACLE", u);
+  ASSERT_FALSE(index_->LookupMetadata(u, a[4], MetadataType_Instance_IndexInSeries));
 
   ASSERT_TRUE(index_->LookupGlobalProperty(s, GlobalProperty_FlushSleep));
   ASSERT_FALSE(index_->LookupGlobalProperty(s, static_cast<GlobalProperty>(42)));
   ASSERT_EQ("World", s);
-  ASSERT_EQ("World", index_->GetGlobalProperty(GlobalProperty_FlushSleep));
-  ASSERT_EQ("None", index_->GetGlobalProperty(static_cast<GlobalProperty>(42), "None"));
 
   FileInfo att;
   ASSERT_TRUE(index_->LookupAttachment(att, a[4], FileContentType_DicomAsJson));
@@ -296,10 +421,11 @@
 
   ASSERT_EQ(0u, listener_->deletedFiles_.size());
   ASSERT_EQ(0u, listener_->deletedResources_.size());
-  ASSERT_EQ(7u, index_->GetTableRecordCount("Resources")); 
-  ASSERT_EQ(3u, index_->GetTableRecordCount("AttachedFiles"));
-  ASSERT_EQ(1u, index_->GetTableRecordCount("Metadata"));
-  ASSERT_EQ(1u, index_->GetTableRecordCount("MainDicomTags"));
+
+  CheckTableRecordCount(7, "Resources");
+  CheckTableRecordCount(3, "AttachedFiles");
+  CheckTableRecordCount(1, "Metadata");
+  CheckTableRecordCount(1, "MainDicomTags");
 
   index_->DeleteResource(a[0]);
   ASSERT_EQ(5u, listener_->deletedResources_.size());
@@ -311,15 +437,17 @@
                          listener_->deletedFiles_.end(),
                          "my dicom file") == listener_->deletedFiles_.end());
 
-  ASSERT_EQ(2u, index_->GetTableRecordCount("Resources"));
-  ASSERT_EQ(0u, index_->GetTableRecordCount("Metadata"));
-  ASSERT_EQ(1u, index_->GetTableRecordCount("AttachedFiles"));
-  ASSERT_EQ(0u, index_->GetTableRecordCount("MainDicomTags"));
+  CheckTableRecordCount(2, "Resources");
+  CheckTableRecordCount(0, "Metadata");
+  CheckTableRecordCount(1, "AttachedFiles");
+  CheckTableRecordCount(0, "MainDicomTags");
+
   index_->DeleteResource(a[5]);
   ASSERT_EQ(7u, listener_->deletedResources_.size());
-  ASSERT_EQ(0u, index_->GetTableRecordCount("Resources"));
-  ASSERT_EQ(0u, index_->GetTableRecordCount("AttachedFiles"));
-  ASSERT_EQ(2u, index_->GetTableRecordCount("GlobalProperties"));
+
+  CheckTableRecordCount(0, "Resources");
+  CheckTableRecordCount(0, "AttachedFiles");
+  CheckTableRecordCount(2, "GlobalProperties");
 
   ASSERT_EQ(3u, listener_->deletedFiles_.size());
   ASSERT_FALSE(std::find(listener_->deletedFiles_.begin(), 
@@ -351,29 +479,14 @@
   index_->AttachChild(a[0], a[5]);
   index_->AttachChild(a[5], a[7]);
 
-  {
-    Json::Value j;
-    index_->GetChildren(j, a[0]);
-    ASSERT_EQ(2u, j.size());
-    ASSERT_TRUE((j[0u] == "b" && j[1u] == "f") ||
-                (j[1u] == "b" && j[0u] == "f"));
-
-    index_->GetChildren(j, a[1]);
-    ASSERT_EQ(2u, j.size());
-    ASSERT_TRUE((j[0u] == "c" && j[1u] == "g") ||
-                (j[1u] == "c" && j[0u] == "g"));
-
-    index_->GetChildren(j, a[2]);
-    ASSERT_EQ(2u, j.size());
-    ASSERT_TRUE((j[0u] == "d" && j[1u] == "e") ||
-                (j[1u] == "d" && j[0u] == "e"));
-
-    index_->GetChildren(j, a[3]); ASSERT_EQ(0u, j.size());
-    index_->GetChildren(j, a[4]); ASSERT_EQ(0u, j.size());
-    index_->GetChildren(j, a[5]); ASSERT_EQ(1u, j.size()); ASSERT_EQ("h", j[0u].asString());
-    index_->GetChildren(j, a[6]); ASSERT_EQ(0u, j.size());
-    index_->GetChildren(j, a[7]); ASSERT_EQ(0u, j.size());
-  }
+  CheckTwoChildren("b", "f", a[0]);
+  CheckTwoChildren("c", "g", a[1]);
+  CheckTwoChildren("d", "e", a[2]);
+  CheckNoChild(a[3]);
+  CheckNoChild(a[4]);
+  CheckOneChild("h", a[5]);
+  CheckNoChild(a[6]);
+  CheckNoChild(a[7]);
 
   listener_->Reset();
   index_->DeleteResource(a[3]);
@@ -404,12 +517,12 @@
     std::string p = "Patient " + boost::lexical_cast<std::string>(i);
     patients.push_back(index_->CreateResource(p, ResourceType_Patient));
     index_->AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10, 
-                                              "md5-" + boost::lexical_cast<std::string>(i)));
+                                                "md5-" + boost::lexical_cast<std::string>(i)));
     ASSERT_FALSE(index_->IsProtectedPatient(patients[i]));
   }
 
-  ASSERT_EQ(10u, index_->GetTableRecordCount("Resources")); 
-  ASSERT_EQ(10u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
+  CheckTableRecordCount(10u, "Resources");
+  CheckTableRecordCount(10u, "PatientRecyclingOrder");
 
   listener_->Reset();
   ASSERT_EQ(0u, listener_->deletedResources_.size());
@@ -417,8 +530,9 @@
   index_->DeleteResource(patients[5]);
   index_->DeleteResource(patients[0]);
   ASSERT_EQ(2u, listener_->deletedResources_.size());
-  ASSERT_EQ(8u, index_->GetTableRecordCount("Resources")); 
-  ASSERT_EQ(8u, index_->GetTableRecordCount("PatientRecyclingOrder"));
+
+  CheckTableRecordCount(8u, "Resources");
+  CheckTableRecordCount(8u, "PatientRecyclingOrder");
 
   ASSERT_EQ(2u, listener_->deletedFiles_.size());
   ASSERT_EQ("Patient 5", listener_->deletedFiles_[0]);
@@ -450,8 +564,9 @@
   ASSERT_EQ(10u, listener_->deletedResources_.size());
 
   ASSERT_EQ(10u, listener_->deletedFiles_.size());
-  ASSERT_EQ(0u, index_->GetTableRecordCount("Resources")); 
-  ASSERT_EQ(0u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
+
+  CheckTableRecordCount(0, "Resources");
+  CheckTableRecordCount(0, "PatientRecyclingOrder");
 }
 
 
@@ -463,42 +578,40 @@
     std::string p = "Patient " + boost::lexical_cast<std::string>(i);
     patients.push_back(index_->CreateResource(p, ResourceType_Patient));
     index_->AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10,
-                                              "md5-" + boost::lexical_cast<std::string>(i)));
+                                                "md5-" + boost::lexical_cast<std::string>(i)));
     ASSERT_FALSE(index_->IsProtectedPatient(patients[i]));
   }
 
-  ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); 
-  ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
+  CheckTableRecordCount(5, "Resources");
+  CheckTableRecordCount(5, "PatientRecyclingOrder");
 
   ASSERT_FALSE(index_->IsProtectedPatient(patients[2]));
   index_->SetProtectedPatient(patients[2], true);
   ASSERT_TRUE(index_->IsProtectedPatient(patients[2]));
-  ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder"));
-  ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); 
+  CheckTableRecordCount(5, "Resources");
+  CheckTableRecordCount(4, "PatientRecyclingOrder");
 
   index_->SetProtectedPatient(patients[2], true);
   ASSERT_TRUE(index_->IsProtectedPatient(patients[2]));
-  ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
+  CheckTableRecordCount(4, "PatientRecyclingOrder");
   index_->SetProtectedPatient(patients[2], false);
   ASSERT_FALSE(index_->IsProtectedPatient(patients[2]));
-  ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
+  CheckTableRecordCount(5, "PatientRecyclingOrder");
   index_->SetProtectedPatient(patients[2], false);
   ASSERT_FALSE(index_->IsProtectedPatient(patients[2]));
-  ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
-
-  ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); 
-  ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
+  CheckTableRecordCount(5, "PatientRecyclingOrder");
+  CheckTableRecordCount(5, "Resources");
   index_->SetProtectedPatient(patients[2], true);
   ASSERT_TRUE(index_->IsProtectedPatient(patients[2]));
-  ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder"));
+  CheckTableRecordCount(4, "PatientRecyclingOrder");
   index_->SetProtectedPatient(patients[2], false);
   ASSERT_FALSE(index_->IsProtectedPatient(patients[2]));
-  ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
+  CheckTableRecordCount(5, "PatientRecyclingOrder");
   index_->SetProtectedPatient(patients[3], true);
   ASSERT_TRUE(index_->IsProtectedPatient(patients[3]));
-  ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
+  CheckTableRecordCount(4, "PatientRecyclingOrder");
 
-  ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); 
+  CheckTableRecordCount(5, "Resources");
   ASSERT_EQ(0u, listener_->deletedFiles_.size());
 
   // Unprotecting a patient puts it at the last position in the recycling queue
@@ -522,11 +635,11 @@
   ASSERT_FALSE(index_->SelectPatientToRecycle(p));
 
   ASSERT_EQ(4u, listener_->deletedFiles_.size());
-  ASSERT_EQ(1u, index_->GetTableRecordCount("Resources")); 
-  ASSERT_EQ(0u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
+  CheckTableRecordCount(1, "Resources");
+  CheckTableRecordCount(0, "PatientRecyclingOrder");
 
   index_->SetProtectedPatient(patients[3], false);
-  ASSERT_EQ(1u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
+  CheckTableRecordCount(1, "PatientRecyclingOrder");
   ASSERT_FALSE(index_->SelectPatientToRecycle(p, patients[3]));
   ASSERT_TRUE(index_->SelectPatientToRecycle(p, patients[2]));
   ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
@@ -534,18 +647,27 @@
   ASSERT_EQ(5u, listener_->deletedResources_.size());
 
   ASSERT_EQ(5u, listener_->deletedFiles_.size());
-  ASSERT_EQ(0u, index_->GetTableRecordCount("Resources")); 
-  ASSERT_EQ(0u, index_->GetTableRecordCount("PatientRecyclingOrder")); 
+  CheckTableRecordCount(0, "Resources");
+  CheckTableRecordCount(0, "PatientRecyclingOrder");
 }
 
 
 
-TEST_P(DatabaseWrapperTest, Sequence)
+TEST(ServerIndex, Sequence)
 {
-  ASSERT_EQ(1u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
-  ASSERT_EQ(2u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
-  ASSERT_EQ(3u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
-  ASSERT_EQ(4u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
+  const std::string path = "UnitTestsStorage";
+
+  Toolbox::RemoveFile(path + "/index");
+  FilesystemStorage storage(path);
+  DatabaseWrapper db;   // The SQLite DB is in memory
+  ServerContext context(db);
+  context.SetStorageArea(storage);
+  ServerIndex& index = context.GetIndex();
+
+  ASSERT_EQ(1u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
+  ASSERT_EQ(2u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
+  ASSERT_EQ(3u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
+  ASSERT_EQ(4u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
 }
 
 
@@ -559,11 +681,10 @@
     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);
+  index_->SetMainDicomTag(a[0], DICOM_TAG_STUDY_INSTANCE_UID, "0");
+  index_->SetMainDicomTag(a[1], DICOM_TAG_STUDY_INSTANCE_UID, "1");
+  index_->SetMainDicomTag(a[2], DICOM_TAG_STUDY_INSTANCE_UID, "0");
+  index_->SetMainDicomTag(a[3], DICOM_TAG_SERIES_INSTANCE_UID, "0");
 
   std::list<int64_t> s;
 
@@ -588,13 +709,13 @@
 
 
   /*{
-      std::list<std::string> s;
-      context.GetIndex().LookupIdentifier(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;;
-      }      
-      }*/
+    std::list<std::string> s;
+    context.GetIndex().LookupIdentifier(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;;
+    }      
+    }*/
 }
 
 
@@ -605,7 +726,8 @@
 
   Toolbox::RemoveFile(path + "/index");
   FilesystemStorage storage(path);
-  ServerContext context(":memory:");   // The SQLite DB is in memory
+  DatabaseWrapper db;   // The SQLite DB is in memory
+  ServerContext context(db);
   context.SetStorageArea(storage);
   ServerIndex& index = context.GetIndex();
 
--- a/UnitTestsSources/UnitTestsMain.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/UnitTestsMain.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/VersionsTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/VersionsTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
--- a/UnitTestsSources/ZipTests.cpp	Tue Nov 04 13:56:17 2014 +0100
+++ b/UnitTestsSources/ZipTests.cpp	Wed Feb 11 10:32:14 2015 +0100
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital 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
@@ -72,6 +72,28 @@
 }
 
 
+TEST(ZipWriter, Append)
+{
+  {
+    Orthanc::ZipWriter w;
+    w.SetAppendToExisting(false);
+    w.SetOutputPath("UnitTestsResults/append.zip");
+    w.Open();
+    w.OpenFile("world/hello");
+    w.Write("Hello world 1");
+  }
+
+  {
+    Orthanc::ZipWriter w;
+    w.SetAppendToExisting(true);
+    w.SetOutputPath("UnitTestsResults/append.zip");
+    w.Open();
+    w.OpenFile("world/appended");
+    w.Write("Hello world 2");
+  }
+}
+
+