changeset 289:ffd98d2f0b91

merge
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 14 Dec 2012 11:22:29 +0100
parents 40d3bf6cc8d9 (diff) 9cd240cfd3a6 (current diff)
children b3322636b06d
files CMakeLists.txt
diffstat 41 files changed, 2044 insertions(+), 345 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Mon Dec 10 11:00:50 2012 +0100
+++ b/CMakeLists.txt	Fri Dec 14 11:22:29 2012 +0100
@@ -4,7 +4,7 @@
 
 # Version of the build, should always be "mainline" except in release branches
 add_definitions(
-  -DORTHANC_VERSION="0.3.1"
+  -DORTHANC_VERSION="mainline"
   )
 
 # Parameters of the build
@@ -99,6 +99,7 @@
   ${AUTOGENERATED_SOURCES}
   ${THIRD_PARTY_SOURCES}
 
+  Core/Cache/MemoryCache.cpp
   Core/ChunkedBuffer.cpp
   Core/Compression/BufferCompressor.cpp
   Core/Compression/ZlibCompressor.cpp
@@ -182,7 +183,6 @@
     include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake)
     add_executable(UnitTests
       ${GTEST_SOURCES}
-      UnitTests/MessageWithDestination.cpp
       UnitTests/RestApi.cpp
       UnitTests/SQLite.cpp
       UnitTests/SQLiteChromium.cpp
@@ -190,6 +190,7 @@
       UnitTests/Versions.cpp
       UnitTests/Zip.cpp
       UnitTests/FileStorage.cpp
+      UnitTests/MemoryCache.cpp
       UnitTests/main.cpp
       )
     target_link_libraries(UnitTests ServerLibrary CoreLibrary)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/CacheIndex.h	Fri Dec 14 11:22:29 2012 +0100
@@ -0,0 +1,250 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <list>
+#include <map>
+#include <boost/noncopyable.hpp>
+#include <cassert>
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  /**
+   * This class implements the index of a cache with least recently
+   * used (LRU) recycling policy. All the items of the cache index
+   * can be associated with a payload.
+   * Reference: http://stackoverflow.com/a/2504317
+   **/
+  template <typename T, typename Payload = NullType>
+  class CacheIndex : public boost::noncopyable
+  {
+  private:
+    typedef std::list< std::pair<T, Payload> >  Queue;
+    typedef std::map<T, typename Queue::iterator>  Index;
+
+    Index  index_;
+    Queue  queue_;
+
+    /**
+     * Internal method for debug builds to check whether the internal
+     * data structures are not corrupted.
+     **/
+    void CheckInvariants() const;
+
+  public:
+    /**
+     * Add a new element to the cache index, and make it the most
+     * recent element.
+     * \param id The ID of the element.
+     * \param payload The payload of the element.
+     **/
+    void Add(T id, Payload payload = Payload());
+
+    /**
+     * When accessing an element of the cache, this method tags the
+     * element as the most recently used.
+     * \param id The most recently accessed item.
+     **/
+    void TagAsMostRecent(T id);
+
+    /**
+     * Remove an element from the cache index.
+     * \param id The item to remove.
+     **/
+    Payload Invalidate(T id);
+
+    /**
+     * Get the oldest element in the cache and remove it.
+     * \return The oldest item.
+     **/
+    T RemoveOldest()
+    {
+      Payload p;
+      return RemoveOldest(p);
+    }
+
+    /**
+     * Get the oldest element in the cache, remove it and return the
+     * associated payload.
+     * \param payload Where to store the associated payload.
+     * \return The oldest item.
+     **/
+    T RemoveOldest(Payload& payload);
+
+    /**
+     * Check whether an element is contained in the cache.
+     * \param id The item.
+     * \return \c true iff the item is indexed by the cache.
+     **/
+    bool Contains(T id) const
+    {
+      return index_.find(id) != index_.end();
+    }
+
+    bool Contains(T id, Payload& payload) const
+    {
+      typename Index::const_iterator it = index_.find(id);
+      if (it == index_.end())
+      {
+        return false;
+      }
+      else
+      {
+        payload = it->second->second;
+        return true;
+      }
+    }
+
+    /**
+     * Return the number of elements in the cache.
+     * \return The number of elements.
+     **/
+    size_t GetSize() const
+    {
+      assert(index_.size() == queue_.size());
+      return queue_.size();
+    }
+
+    /**
+     * Check whether the cache index is empty.
+     * \return \c true iff the cache is empty.
+     **/
+    bool IsEmpty() const
+    {
+      return index_.empty();
+    }
+  };
+
+
+
+
+  /******************************************************************
+   ** Implementation of the template
+   ******************************************************************/
+
+  template <typename T, typename Payload>
+  void CacheIndex<T, Payload>::CheckInvariants() const
+  {
+#ifndef NDEBUG
+    assert(index_.size() == queue_.size());
+
+    for (typename Index::const_iterator 
+           it = index_.begin(); it != index_.end(); it++)
+    {
+      assert(it->second != queue_.end());
+      assert(it->second->first == it->first);
+    }
+#endif
+  }
+
+
+  template <typename T, typename Payload>
+  void CacheIndex<T, Payload>::Add(T id, Payload payload)
+  {
+    if (Contains(id))
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    queue_.push_front(std::make_pair(id, payload));
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void CacheIndex<T, Payload>::TagAsMostRecent(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  Payload CacheIndex<T, Payload>::Invalidate(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    Payload payload = it->second->second;
+    queue_.erase(it->second);
+    index_.erase(it);
+
+    CheckInvariants();
+    return payload;
+  }
+
+
+  template <typename T, typename Payload>
+  T CacheIndex<T, Payload>::RemoveOldest(Payload& payload)
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+    payload = item.second;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/ICachePageProvider.h	Fri Dec 14 11:22:29 2012 +0100
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include "../IDynamicObject.h"
+
+namespace Orthanc
+{
+  class ICachePageProvider
+  {
+  public:
+    virtual ~ICachePageProvider()
+    {
+    }
+
+    virtual IDynamicObject* Provide(const std::string& id) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/MemoryCache.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -0,0 +1,94 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MemoryCache.h"
+
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  MemoryCache::Page& MemoryCache::Load(const std::string& id)
+  {
+    // Reuse the cache entry if it already exists
+    Page* p = NULL;
+    if (index_.Contains(id, p))
+    {
+      VLOG(1) << "Reusing a cache page";
+      assert(p != NULL);
+      index_.TagAsMostRecent(id);
+      return *p;
+    }
+
+    // The id is not in the cache yet. Make some room if the cache
+    // is full.
+    if (index_.GetSize() == cacheSize_)
+    {
+      VLOG(1) << "Dropping the oldest cache page";
+      index_.RemoveOldest(p);
+      delete p;
+    }
+
+    // Create a new cache page
+    std::auto_ptr<Page> result(new Page);
+    result->id_ = id;
+    result->content_.reset(provider_.Provide(id));
+
+    // Add the newly create page to the cache
+    VLOG(1) << "Registering new data in a cache page";
+    p = result.release();
+    index_.Add(id, p);
+    return *p;
+  }
+
+  MemoryCache::MemoryCache(ICachePageProvider& provider,
+                           size_t cacheSize) : 
+    provider_(provider),
+    cacheSize_(cacheSize)
+  {
+  }
+
+  MemoryCache::~MemoryCache()
+  {
+    while (!index_.IsEmpty())
+    {
+      Page* element = NULL;
+      index_.RemoveOldest(element);
+      assert(element != NULL);
+      delete element;
+    }
+  }
+
+  IDynamicObject& MemoryCache::Access(const std::string& id)
+  {
+    return *Load(id).content_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/MemoryCache.h	Fri Dec 14 11:22:29 2012 +0100
@@ -0,0 +1,67 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <memory>
+#include "CacheIndex.h"
+#include "ICachePageProvider.h"
+
+namespace Orthanc
+{
+  /**
+   * WARNING: This class is NOT thread-safe.
+   **/
+  class MemoryCache
+  {
+  private:
+    struct Page
+    {
+      std::string id_;
+      std::auto_ptr<IDynamicObject> content_;
+    };
+
+    ICachePageProvider& provider_;
+    size_t cacheSize_;
+    CacheIndex<std::string, Page*>  index_;
+
+    Page& Load(const std::string& id);
+
+  public:
+    MemoryCache(ICachePageProvider& provider,
+                size_t cacheSize);
+
+    ~MemoryCache();
+
+    IDynamicObject& Access(const std::string& id);
+  };
+}
--- a/Core/Enumerations.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/Core/Enumerations.h	Fri Dec 14 11:22:29 2012 +0100
@@ -47,6 +47,7 @@
     ErrorCode_NotEnoughMemory,
     ErrorCode_BadParameterType,
     ErrorCode_BadSequenceOfCalls,
+    ErrorCode_InexistentItem,
 
     // Specific error codes
     ErrorCode_UriSyntax,
@@ -55,7 +56,8 @@
     ErrorCode_BadFileFormat,
     ErrorCode_Timeout,
     ErrorCode_UnknownResource,
-    ErrorCode_IncompatibleDatabaseVersion
+    ErrorCode_IncompatibleDatabaseVersion,
+    ErrorCode_FullStorage
   };
 
   enum PixelFormat
--- a/Core/HttpServer/HttpOutput.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/Core/HttpServer/HttpOutput.h	Fri Dec 14 11:22:29 2012 +0100
@@ -43,11 +43,6 @@
   private:
     void SendHeaderInternal(Orthanc_HttpStatus status);
 
-    void SendOkHeader(const char* contentType,
-                      bool hasContentLength,
-                      uint64_t contentLength,
-                      const char* contentFilename);
-
   public:
     virtual ~HttpOutput()
     {
@@ -55,6 +50,11 @@
 
     virtual void Send(const void* buffer, size_t length) = 0;
 
+    void SendOkHeader(const char* contentType,
+                      bool hasContentLength,
+                      uint64_t contentLength,
+                      const char* contentFilename);
+
     void SendCustomOkHeader(const std::string& customHeader);
 
     void SendString(const std::string& s);
--- a/Core/OrthancException.cpp	Mon Dec 10 11:00:50 2012 +0100
+++ b/Core/OrthancException.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -93,6 +93,12 @@
       case ErrorCode_IncompatibleDatabaseVersion:
         return "Incompatible version of the database";
 
+      case ErrorCode_FullStorage:
+        return "The file storage is full";
+
+      case ErrorCode_InexistentItem:
+        return "Accessing an inexistent item";
+
       case ErrorCode_Custom:
       default:
         return "???";
--- a/Core/RestApi/RestApi.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/Core/RestApi/RestApi.h	Fri Dec 14 11:22:29 2012 +0100
@@ -70,6 +70,11 @@
       {
         return *fullUri_;
       }
+    
+      const UriComponents& GetTrailingUri() const
+      {
+        return *trailing_;
+      }
 
       std::string GetUriComponent(const std::string& name,
                                   const std::string& defaultValue) const
--- a/Core/RestApi/RestApiOutput.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/Core/RestApi/RestApiOutput.h	Fri Dec 14 11:22:29 2012 +0100
@@ -52,6 +52,16 @@
 
     ~RestApiOutput();
 
+    HttpOutput& GetLowLevelOutput()
+    {
+      return output_;
+    }
+
+    void MarkLowLevelOutputDone()
+    {
+      alreadySent_ = true;
+    }
+
     void AnswerFile(HttpFileSender& sender);
 
     void AnswerJson(const Json::Value& value);
--- a/Core/SQLite/FunctionContext.cpp	Mon Dec 10 11:00:50 2012 +0100
+++ b/Core/SQLite/FunctionContext.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -72,6 +72,12 @@
       return sqlite3_value_int(argv_[index]);
     }
 
+    int64_t FunctionContext::GetInt64Value(unsigned int index) const
+    {
+      CheckIndex(index);
+      return sqlite3_value_int64(argv_[index]);
+    }
+
     double FunctionContext::GetDoubleValue(unsigned int index) const
     {
       CheckIndex(index);
--- a/Core/SQLite/FunctionContext.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/Core/SQLite/FunctionContext.h	Fri Dec 14 11:22:29 2012 +0100
@@ -69,6 +69,8 @@
 
       int GetIntValue(unsigned int index) const;
 
+      int64_t GetInt64Value(unsigned int index) const;
+
       double GetDoubleValue(unsigned int index) const;
 
       std::string GetStringValue(unsigned int index) const;
--- a/Core/Toolbox.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/Core/Toolbox.h	Fri Dec 14 11:22:29 2012 +0100
@@ -40,6 +40,10 @@
 {
   typedef std::vector<std::string> UriComponents;
 
+  class NullType
+  {
+  };
+
   namespace Toolbox
   {
     void ServerBarrier();
--- a/INSTALL	Mon Dec 10 11:00:50 2012 +0100
+++ b/INSTALL	Fri Dec 14 11:22:29 2012 +0100
@@ -67,7 +67,7 @@
 # cmake -DSTATIC_BUILD=OFF -DCMAKE_BUILD_TYPE=DEBUG ~/Orthanc
 # make
 
- 
+
 
 Cross-Compilation for Windows under Linux
 -----------------------------------------
@@ -104,3 +104,17 @@
   Visual Studio 2005:
   http://en.wikipedia.org/wiki/Microsoft_Windows_SDK.
   Read the CMake FAQ: http://goo.gl/By90B 
+
+
+
+Debian/Ubuntu specific
+----------------------
+
+When dynamically linking against the system libraries, you have to
+manually add the "wrap" and "oflog" libraries at the configuration
+time (because of a packaging error in "libdcmtk"):
+
+# cd ~/OrthancBuild
+# cmake "-DDCMTK_LIBRARIES=wrap;oflog" -DSTATIC_BUILD=OFF -DCMAKE_BUILD_TYPE=DEBUG ~/Orthanc
+# make
+
--- a/NEWS	Mon Dec 10 11:00:50 2012 +0100
+++ b/NEWS	Fri Dec 14 11:22:29 2012 +0100
@@ -1,6 +1,9 @@
 Pending changes in the mainline
 ===============================
 
+* Recycling of disk space
+* Protection of patients against recycling (also in Orthanc Explorer)
+* Raw access to the value of the DICOM tags in the REST API
 
 
 Version 0.3.1 (2012/12/05)
--- a/OrthancExplorer/explorer.css	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancExplorer/explorer.css	Fri Dec 14 11:22:29 2012 +0100
@@ -37,3 +37,7 @@
     text-decoration: none;
     color: white !important;
 }
+
+.switch-container .ui-slider-switch {
+    width: 100%;
+}
\ No newline at end of file
--- a/OrthancExplorer/explorer.html	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancExplorer/explorer.html	Fri Dec 14 11:22:29 2012 +0100
@@ -82,7 +82,13 @@
               <ul data-role="listview" data-inset="true" data-theme="a"  id="patient-info">
               </ul>
               <p>
-                <a href="#find-patients" data-role="button" data-icon="search">Go to patient finder</a>
+                <div class="switch-container">
+                  <select name="protection" id="protection" data-role="slider">
+	            <option value="off">Unprotected</option>
+	            <option value="on">Protected</option>
+                  </select>
+                </div>
+                <!--a href="#find-patients" data-role="button" data-icon="search">Go to patient finder</a-->
                 <a href="#" data-role="button" data-icon="delete" id="patient-delete">Delete this patient</a>
                 <a href="#" data-role="button" data-icon="gear" id="patient-archive">Download ZIP</a>
               </p>
--- a/OrthancExplorer/explorer.js	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancExplorer/explorer.js	Fri Dec 14 11:22:29 2012 +0100
@@ -378,6 +378,18 @@
         }
 
         target.listview('refresh');
+
+        // Check whether this patient is protected
+        $.ajax({
+          url: '../patients/' + $.mobile.pageData.uuid + '/protected',
+          type: 'GET',
+          dataType: 'text',
+          async: false,
+          success: function (s) {
+            var v = (s == '1') ? 'on' : 'off';
+            $('#protection').val(v).slider('refresh');
+          }
+        });
       });
     });
   }
@@ -786,3 +798,13 @@
   window.location.href = '../series/' + $.mobile.pageData.uuid + '/archive';
 });
 
+$('#protection').live('change', function(e) {
+  var isProtected = e.target.value == "on";
+  $.ajax({
+    url: '../patients/' + $.mobile.pageData.uuid + '/protected',
+    type: 'PUT',
+    dataType: 'text',
+    data: isProtected ? '1' : '0',
+    async: false
+  });
+});
--- a/OrthancServer/DatabaseWrapper.cpp	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/DatabaseWrapper.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -33,6 +33,7 @@
 #include "DatabaseWrapper.h"
 
 #include "../Core/DicomFormat/DicomArray.h"
+#include "../Core/Uuid.h"
 #include "EmbeddedResources.h"
 
 #include <glog/logging.h>
@@ -61,12 +62,18 @@
 
       virtual unsigned int GetCardinality() const
       {
-        return 1;
+        return 5;
       }
 
       virtual void Compute(SQLite::FunctionContext& context)
       {
-        listener_.SignalFileDeleted(context.GetStringValue(0));
+        FileInfo info(context.GetStringValue(0),
+                      static_cast<FileContentType>(context.GetIntValue(1)),
+                      static_cast<uint64_t>(context.GetInt64Value(2)),
+                      static_cast<CompressionType>(context.GetIntValue(3)),
+                      static_cast<uint64_t>(context.GetInt64Value(4)));
+        
+        listener_.SignalFileDeleted(info);
       }
     };
 
@@ -743,9 +750,9 @@
       LOG(INFO) << "Version of the Orthanc database: " << version;
       unsigned int v = boost::lexical_cast<unsigned int>(version);
 
-      // This version of Orthanc is only compatible with version 2 of
-      // the DB schema (since Orthanc 0.3.1)
-      ok = (v == 2); 
+      // This version of Orthanc is only compatible with version 3 of
+      // the DB schema (since Orthanc 0.3.2)
+      ok = (v == 3); 
     }
     catch (boost::bad_lexical_cast&)
     {
@@ -777,4 +784,70 @@
 
     return c;
   }
+
+  bool DatabaseWrapper::SelectPatientToRecycle(int64_t& internalId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                        "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1");
+   
+    if (!s.Step())
+    {
+      // No patient remaining or all the patients are protected
+      return false;
+    }
+    else
+    {
+      internalId = s.ColumnInt(0);
+      return true;
+    }    
+  }
+
+  bool DatabaseWrapper::SelectPatientToRecycle(int64_t& internalId,
+                                               int64_t patientIdToAvoid)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                        "SELECT patientId FROM PatientRecyclingOrder "
+                        "WHERE patientId != ? ORDER BY seq ASC LIMIT 1");
+    s.BindInt(0, patientIdToAvoid);
+
+    if (!s.Step())
+    {
+      // No patient remaining or all the patients are protected
+      return false;
+    }
+    else
+    {
+      internalId = s.ColumnInt(0);
+      return true;
+    }   
+  }
+
+  bool DatabaseWrapper::IsProtectedPatient(int64_t internalId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                        "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?");
+    s.BindInt(0, internalId);
+    return !s.Step();
+  }
+
+  void DatabaseWrapper::SetProtectedPatient(int64_t internalId, 
+                                            bool isProtected)
+  {
+    if (isProtected)
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
+      s.BindInt(0, internalId);
+      s.Run();
+    }
+    else if (IsProtectedPatient(internalId))
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
+      s.BindInt(0, internalId);
+      s.Run();
+    }
+    else
+    {
+      // Nothing to do: The patient is already unprotected
+    }
+  }
 }
--- a/OrthancServer/DatabaseWrapper.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/DatabaseWrapper.h	Fri Dec 14 11:22:29 2012 +0100
@@ -179,6 +179,16 @@
     void GetAllPublicIds(Json::Value& target,
                          ResourceType resourceType);
 
+    bool SelectPatientToRecycle(int64_t& internalId);
+
+    bool SelectPatientToRecycle(int64_t& internalId,
+                                int64_t patientIdToAvoid);
+
+    bool IsProtectedPatient(int64_t internalId);
+
+    void SetProtectedPatient(int64_t internalId, 
+                             bool isProtected);
+
     DatabaseWrapper(const std::string& path,
                     IServerIndexListener& listener);
 
--- a/OrthancServer/FromDcmtkBridge.cpp	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -63,6 +63,193 @@
 
 namespace Orthanc
 {
+  ParsedDicomFile::ParsedDicomFile(const std::string& content)
+  {
+    DcmInputBufferStream is;
+    if (content.size() > 0)
+    {
+      is.setBuffer(&content[0], content.size());
+    }
+    is.setEos();
+
+    file_.reset(new DcmFileFormat);
+    if (!file_->read(is).good())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  static void SendPathValueForDictionary(RestApiOutput& output,
+                                       DcmItem& dicom)
+  {
+    Json::Value v = Json::arrayValue;
+
+    for (unsigned long i = 0; i < dicom.card(); i++)
+    {
+      DcmElement* element = dicom.getElement(i);
+      if (element)
+      {
+        char buf[16];
+        sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag());
+        v.append(buf);
+      }
+    }
+
+    output.AnswerJson(v);
+  }
+
+  static inline uint16_t GetCharValue(char c)
+  {
+    if (c >= '0' && c <= '9')
+      return c - '0';
+    else if (c >= 'a' && c <= 'f')
+      return c - 'a' + 10;
+    else if (c >= 'A' && c <= 'F')
+      return c - 'A' + 10;
+    else
+      return 0;
+  }
+
+  static inline uint16_t GetTagValue(const char* c)
+  {
+    return ((GetCharValue(c[0]) << 12) + 
+            (GetCharValue(c[1]) << 8) + 
+            (GetCharValue(c[2]) << 4) + 
+            GetCharValue(c[3]));
+  }
+
+  static bool ParseTagAndGroup(DcmTagKey& key,
+                               const std::string& tag)
+  {
+    if (tag.size() != 9 ||
+        !isxdigit(tag[0]) ||
+        !isxdigit(tag[1]) ||
+        !isxdigit(tag[2]) ||
+        !isxdigit(tag[3]) ||
+        tag[4] != '-' ||
+        !isxdigit(tag[5]) ||
+        !isxdigit(tag[6]) ||
+        !isxdigit(tag[7]) ||
+        !isxdigit(tag[8]))        
+    {
+      return false;
+    }
+
+    uint16_t group = GetTagValue(tag.c_str());
+    uint16_t element = GetTagValue(tag.c_str() + 5);
+
+    key = DcmTagKey(group, element);
+
+    return true;
+  }
+
+  static void SendPathValueForLeaf(RestApiOutput& output,
+                                   const std::string& tag,
+                                   DcmItem& dicom)
+  {
+    DcmTagKey k;
+    if (!ParseTagAndGroup(k, tag))
+    {
+      return;
+    }
+
+    DcmElement* element = NULL;
+    if (dicom.findAndGetElement(k, element).good() && element != NULL)
+    {
+      if (element->getVR() == EVR_SQ)
+      {
+        // This element is a sequence
+        Json::Value v = Json::arrayValue;
+        DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
+
+        for (unsigned long i = 0; i < sequence.card(); i++)
+        {
+          v.append(boost::lexical_cast<std::string>(i));
+        }
+
+        output.AnswerJson(v);
+      }
+      else
+      {
+        // This element is not a sequence
+        std::string buffer;
+        buffer.resize(65536);
+        Uint32 length = element->getLength();
+        Uint32 offset = 0;
+
+        output.GetLowLevelOutput().SendOkHeader("application/octet-stream", true, length, NULL);
+
+        while (offset < length)
+        {
+          Uint32 nbytes;
+          if (length - offset < buffer.size())
+          {
+            nbytes = length - offset;
+          }
+          else
+          {
+            nbytes = buffer.size();
+          }
+
+          if (element->getPartialValue(&buffer[0], offset, nbytes).good())
+          {
+            output.GetLowLevelOutput().Send(&buffer[0], nbytes);
+            offset += nbytes;
+          }
+          else
+          {
+            return;
+          }
+        }
+
+        output.MarkLowLevelOutputDone();
+      }
+    }
+  }
+
+  void ParsedDicomFile::SendPathValue(RestApiOutput& output,
+                                      const UriComponents& uri)
+  {
+    DcmItem* dicom = file_->getDataset();
+
+    // Go down in the tag hierarchy according to the URI
+    for (size_t pos = 0; pos < uri.size() / 2; pos++)
+    {
+      size_t index;
+      try
+      {
+        index = boost::lexical_cast<size_t>(uri[2 * pos + 1]);
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return;
+      }
+
+      DcmTagKey k;
+      DcmItem *child = NULL;
+      if (!ParseTagAndGroup(k, uri[2 * pos]) ||
+          !dicom->findAndGetSequenceItem(k, child, index).good() ||
+          child == NULL)
+      {
+        return;
+      }
+
+      dicom = child;
+    }
+
+    // We have reached the end of the URI
+    if (uri.size() % 2 == 0)
+    {
+      SendPathValueForDictionary(output, *dicom);
+    }
+    else
+    {
+      SendPathValueForLeaf(output, uri.back(), *dicom);
+    }
+  }
+
+
   void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset)
   {
     target.Clear();
--- a/OrthancServer/FromDcmtkBridge.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/FromDcmtkBridge.h	Fri Dec 14 11:22:29 2012 +0100
@@ -33,8 +33,13 @@
 #pragma once
 
 #include "../Core/DicomFormat/DicomMap.h"
+#include "../Core/RestApi/RestApiOutput.h"
+#include "../Core/Toolbox.h"
+
 #include <dcmtk/dcmdata/dcdatset.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
 #include <json/json.h>
+#include <memory>
 
 namespace Orthanc
 {
@@ -52,6 +57,23 @@
     DicomRootLevel_Instance
   };
 
+  class ParsedDicomFile : public IDynamicObject
+  {
+  private:
+    std::auto_ptr<DcmFileFormat> file_;
+
+  public:
+    ParsedDicomFile(const std::string& content);
+
+    DcmFileFormat& GetDicom()
+    {
+      return *file_;
+    }
+
+    void SendPathValue(RestApiOutput& output,
+                       const UriComponents& uri);
+  };
+
   class FromDcmtkBridge
   {
   public:
--- a/OrthancServer/IServerIndexListener.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/IServerIndexListener.h	Fri Dec 14 11:22:29 2012 +0100
@@ -47,7 +47,6 @@
     virtual void SignalRemainingAncestor(ResourceType parentType,
                                          const std::string& publicId) = 0;
 
-    virtual void SignalFileDeleted(const std::string& fileUuid) = 0;                     
-                                 
+    virtual void SignalFileDeleted(const FileInfo& info) = 0;
   };
 }
--- a/OrthancServer/OrthancInitialization.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/OrthancInitialization.h	Fri Dec 14 11:22:29 2012 +0100
@@ -35,6 +35,7 @@
 #include <string>
 #include <set>
 #include <json/json.h>
+#include <stdint.h>
 #include "../Core/HttpServer/MongooseServer.h"
 
 namespace Orthanc
--- a/OrthancServer/OrthancRestApi.cpp	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/OrthancRestApi.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -607,6 +607,40 @@
   }
 
   
+  // Get information about a single patient -----------------------------------
+ 
+  static void IsProtectedPatient(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+    std::string publicId = call.GetUriComponent("id", "");
+    bool isProtected = context.GetIndex().IsProtectedPatient(publicId);
+    call.GetOutput().AnswerBuffer(isProtected ? "1" : "0", "text/plain");
+  }
+
+
+  static void SetPatientProtection(RestApi::PutCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+    std::string publicId = call.GetUriComponent("id", "");
+    std::string s = Toolbox::StripSpaces(call.GetPutBody());
+
+    if (s == "0")
+    {
+      context.GetIndex().SetProtectedPatient(publicId, false);
+      call.GetOutput().AnswerBuffer("", "text/plain");
+    }
+    else if (s == "1")
+    {
+      context.GetIndex().SetProtectedPatient(publicId, true);
+      call.GetOutput().AnswerBuffer("", "text/plain");
+    }
+    else
+    {
+      // Bad request
+    }
+  }
+
+
   // Get information about a single instance ----------------------------------
  
   static void GetInstanceFile(RestApi::GetCall& call)
@@ -813,6 +847,23 @@
 
 
 
+  // Raw access to the DICOM tags of an instance ------------------------------
+
+  static void GetRawContent(RestApi::GetCall& call)
+  {
+    // TODO IMPROVE MULTITHREADING
+    static boost::mutex mutex_;
+    boost::mutex::scoped_lock lock(mutex_);
+
+    RETRIEVE_CONTEXT(call);
+    std::string id = call.GetUriComponent("id", "");
+    ParsedDicomFile& dicom = context.GetDicomFile(id);
+    dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri());
+  }
+
+
+
+
   // Registration of the various REST handlers --------------------------------
 
   OrthancRestApi::OrthancRestApi(ServerContext& context) : 
@@ -845,10 +896,13 @@
     Register("/studies/{id}/archive", GetArchive<ResourceType_Study>);
     Register("/series/{id}/archive", GetArchive<ResourceType_Series>);
 
+    Register("/patients/{id}/protected", IsProtectedPatient);
+    Register("/patients/{id}/protected", SetPatientProtection);
     Register("/instances/{id}/file", GetInstanceFile);
     Register("/instances/{id}/tags", GetInstanceTags<false>);
     Register("/instances/{id}/simplified-tags", GetInstanceTags<true>);
     Register("/instances/{id}/frames", ListFrames);
+    Register("/instances/{id}/content/*", GetRawContent);
 
     Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>);
     Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
--- a/OrthancServer/PrepareDatabase.sql	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/PrepareDatabase.sql	Fri Dec 14 11:22:29 2012 +0100
@@ -55,9 +55,15 @@
        date TEXT
        ); 
 
+CREATE TABLE PatientRecyclingOrder(
+       seq INTEGER PRIMARY KEY AUTOINCREMENT,
+       patientId INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
 CREATE INDEX ChildrenIndex ON Resources(parentId);
 CREATE INDEX PublicIndex ON Resources(publicId);
 CREATE INDEX ResourceTypeIndex ON Resources(resourceType);
+CREATE INDEX PatientRecyclingIndex ON PatientRecyclingOrder(patientId);
 
 CREATE INDEX MainDicomTagsIndex1 ON MainDicomTags(id);
 CREATE INDEX MainDicomTagsIndex2 ON MainDicomTags(tagGroup, tagElement);
@@ -68,7 +74,8 @@
 CREATE TRIGGER AttachedFileDeleted
 AFTER DELETE ON AttachedFiles
 BEGIN
-  SELECT SignalFileDeleted(old.uuid);
+  SELECT SignalFileDeleted(old.uuid, old.fileType, old.uncompressedSize, 
+                           old.compressionType, old.compressedSize);
 END;
 
 CREATE TRIGGER ResourceDeleted
@@ -86,6 +93,14 @@
   DELETE FROM Resources WHERE internalId = old.parentId;
 END;
 
+CREATE TRIGGER PatientAdded
+AFTER INSERT ON Resources
+FOR EACH ROW WHEN new.resourceType = 1  -- "1" corresponds to "ResourceType_Patient" in C++
+BEGIN
+  INSERT INTO PatientRecyclingOrder VALUES (NULL, new.internalId);
+END;
+
+
 -- Set the version of the database schema
 -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration
-INSERT INTO GlobalProperties VALUES (1, "2");
+INSERT INTO GlobalProperties VALUES (1, "3");
--- a/OrthancServer/ServerContext.cpp	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/ServerContext.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -37,6 +37,8 @@
 #include <glog/logging.h>
 
 
+static const size_t DICOM_CACHE_SIZE = 2;
+
 /**
  * IMPORTANT: We make the assumption that the same instance of
  * FileStorage can be accessed from multiple threads. This seems OK
@@ -51,7 +53,9 @@
   ServerContext::ServerContext(const boost::filesystem::path& path) :
     storage_(path.string()),
     index_(*this, path.string()),
-    accessor_(storage_)
+    accessor_(storage_),
+    provider_(*this),
+    dicomCache_(provider_, DICOM_CACHE_SIZE)
   {
   }
 
@@ -162,4 +166,18 @@
     accessor_.SetCompressionForNextOperations(attachment.GetCompressionType());
     accessor_.Read(result, attachment.GetUuid());
   }
+
+
+  IDynamicObject* ServerContext::DicomCacheProvider::Provide(const std::string& instancePublicId)
+  {
+    std::string content;
+    context_.ReadFile(content, instancePublicId, FileContentType_Dicom);
+    return new ParsedDicomFile(content);
+  }
+
+
+  ParsedDicomFile& ServerContext::GetDicomFile(const std::string& instancePublicId)
+  {
+    return dynamic_cast<ParsedDicomFile&>(dicomCache_.Access(instancePublicId));
+  }
 }
--- a/OrthancServer/ServerContext.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/ServerContext.h	Fri Dec 14 11:22:29 2012 +0100
@@ -32,10 +32,12 @@
 
 #pragma once
 
-#include "ServerIndex.h"
+#include "../Core/Cache/MemoryCache.h"
 #include "../Core/FileStorage/CompressedFileStorageAccessor.h"
 #include "../Core/FileStorage/FileStorage.h"
 #include "../Core/RestApi/RestApiOutput.h"
+#include "ServerIndex.h"
+#include "FromDcmtkBridge.h"
 
 namespace Orthanc
 {
@@ -47,10 +49,26 @@
   class ServerContext
   {
   private:
+    class DicomCacheProvider : public ICachePageProvider
+    {
+    private:
+      ServerContext& context_;
+
+    public:
+      DicomCacheProvider(ServerContext& context) : context_(context)
+      {
+      }
+      
+      virtual IDynamicObject* Provide(const std::string& id);
+    };
+
     FileStorage storage_;
     ServerIndex index_;
     CompressedFileStorageAccessor accessor_;
     bool compressionEnabled_;
+    
+    DicomCacheProvider provider_;
+    MemoryCache dicomCache_;
 
   public:
     ServerContext(const boost::filesystem::path& path);
@@ -86,5 +104,8 @@
     void ReadFile(std::string& result,
                   const std::string& instancePublicId,
                   FileContentType content);
+
+    // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD
+    ParsedDicomFile& GetDicomFile(const std::string& instancePublicId);
   };
 }
--- a/OrthancServer/ServerIndex.cpp	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/ServerIndex.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -59,12 +59,14 @@
       bool hasRemainingLevel_;
       ResourceType remainingType_;
       std::string remainingPublicId_;
+      std::list<std::string> pendingFilesToRemove_;
+      uint64_t sizeOfFilesToRemove_;
 
     public:
       ServerIndexListener(ServerContext& context) : 
-        context_(context),
-        hasRemainingLevel_(false)
+        context_(context)
       {
+        Reset();
         assert(ResourceType_Patient < ResourceType_Study &&
                ResourceType_Study < ResourceType_Series &&
                ResourceType_Series < ResourceType_Instance);
@@ -72,7 +74,24 @@
 
       void Reset()
       {
+        sizeOfFilesToRemove_ = 0;
         hasRemainingLevel_ = false;
+        pendingFilesToRemove_.clear();
+      }
+
+      uint64_t GetSizeOfFilesToRemove()
+      {
+        return sizeOfFilesToRemove_;
+      }
+
+      void CommitFilesToRemove()
+      {
+        for (std::list<std::string>::iterator 
+               it = pendingFilesToRemove_.begin();
+             it != pendingFilesToRemove_.end(); it++)
+        {
+          context_.RemoveFile(*it);
+        }
       }
 
       virtual void SignalRemainingAncestor(ResourceType parentType,
@@ -96,10 +115,11 @@
         }        
       }
 
-      virtual void SignalFileDeleted(const std::string& fileUuid)
+      virtual void SignalFileDeleted(const FileInfo& info)
       {
-        assert(Toolbox::IsUuid(fileUuid));
-        context_.RemoveFile(fileUuid);
+        assert(Toolbox::IsUuid(info.GetUuid()));
+        pendingFilesToRemove_.push_back(info.GetUuid());
+        sizeOfFilesToRemove_ += info.GetCompressedSize();
       }
 
       bool HasRemainingLevel() const
@@ -122,16 +142,57 @@
   }
 
 
+  class ServerIndex::Transaction
+  {
+  private:
+    ServerIndex& index_;
+    std::auto_ptr<SQLite::Transaction> transaction_;
+    bool isCommitted_;
+
+  public:
+    Transaction(ServerIndex& index) : 
+      index_(index),
+      isCommitted_(false)
+    {
+      assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize());
+
+      index_.listener_->Reset();
+      transaction_.reset(index_.db_->StartTransaction());
+      transaction_->Begin();
+    }
+
+    void Commit(uint64_t sizeOfAddedFiles)
+    {
+      if (!isCommitted_)
+      {
+        transaction_->Commit();
+
+        // We can remove the files once the SQLite transaction has
+        // been successfully committed. Some files might have to be
+        // deleted because of recycling.
+        index_.listener_->CommitFilesToRemove();
+
+        index_.currentStorageSize_ += sizeOfAddedFiles;
+
+        assert(index_.currentStorageSize_ >= index_.listener_->GetSizeOfFilesToRemove());
+        index_.currentStorageSize_ -= index_.listener_->GetSizeOfFilesToRemove();
+
+        assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize());
+
+        isCommitted_ = true;
+      }
+    }
+  };
+
+
   bool ServerIndex::DeleteResource(Json::Value& target,
                                    const std::string& uuid,
                                    ResourceType expectedType)
   {
     boost::mutex::scoped_lock lock(mutex_);
-
     listener_->Reset();
 
-    std::auto_ptr<SQLite::Transaction> t(db_->StartTransaction());
-    t->Begin();
+    Transaction t(*this);
 
     int64_t id;
     ResourceType type;
@@ -158,7 +219,7 @@
       target["RemainingAncestor"] = Json::nullValue;
     }
 
-    t->Commit();
+    t.Commit(0);
 
     return true;
   }
@@ -180,7 +241,9 @@
 
 
   ServerIndex::ServerIndex(ServerContext& context,
-                           const std::string& dbPath) : mutex_()
+                           const std::string& dbPath) : 
+    maximumStorageSize_(0),
+    maximumPatients_(0)
   {
     listener_.reset(new Internals::ServerIndexListener(context));
 
@@ -203,6 +266,12 @@
       db_.reset(new DatabaseWrapper(p.string() + "/index", *listener_));
     }
 
+    currentStorageSize_ = db_->GetTotalCompressedSize();
+
+    // Initial recycling if the parameters have changed since the last
+    // execution of Orthanc
+    StandaloneRecycling();
+
     unsigned int sleep;
     try
     {
@@ -232,13 +301,13 @@
                                  const std::string& remoteAet)
   {
     boost::mutex::scoped_lock lock(mutex_);
+    listener_->Reset();
 
     DicomInstanceHasher hasher(dicomSummary);
 
     try
     {
-      std::auto_ptr<SQLite::Transaction> t(db_->StartTransaction());
-      t->Begin();
+      Transaction t(*this);
 
       int64_t patient, study, series, instance;
       ResourceType type;
@@ -251,6 +320,16 @@
         return StoreStatus_AlreadyStored;
       }
 
+      // Ensure there is enough room in the storage for the new instance
+      uint64_t instanceSize = 0;
+      for (Attachments::const_iterator it = attachments.begin();
+           it != attachments.end(); it++)
+      {
+        instanceSize += it->GetCompressedSize();
+      }
+
+      Recycle(instanceSize, hasher.HashPatient());
+
       // Create the instance
       instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance);
 
@@ -337,13 +416,14 @@
         db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series);
       }
 
-      t->Commit();
+      t.Commit(instanceSize);
 
       return StoreStatus_Success;
     }
     catch (OrthancException& e)
     {
-      LOG(ERROR) << "EXCEPTION2 [" << e.What() << "]" << " " << db_->GetErrorMessage();  
+      LOG(ERROR) << "EXCEPTION [" << e.What() << "]" 
+                 << " (SQLite status: " << db_->GetErrorMessage() << ")";
     }
 
     return StoreStatus_Failure;
@@ -357,7 +437,8 @@
     boost::mutex::scoped_lock lock(mutex_);
     target = Json::objectValue;
 
-    uint64_t cs = db_->GetTotalCompressedSize();
+    uint64_t cs = currentStorageSize_;
+    assert(cs == db_->GetTotalCompressedSize());
     uint64_t us = db_->GetTotalUncompressedSize();
     target["TotalDiskSpace"] = boost::lexical_cast<std::string>(cs);
     target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us);
@@ -477,20 +558,20 @@
 
       switch (type)
       {
-      case ResourceType_Study:
-        result["ParentPatient"] = parent;
-        break;
+        case ResourceType_Study:
+          result["ParentPatient"] = parent;
+          break;
 
-      case ResourceType_Series:
-        result["ParentStudy"] = parent;
-        break;
+        case ResourceType_Series:
+          result["ParentStudy"] = parent;
+          break;
 
-      case ResourceType_Instance:
-        result["ParentSeries"] = parent;
-        break;
+        case ResourceType_Instance:
+          result["ParentSeries"] = parent;
+          break;
 
-      default:
-        throw OrthancException(ErrorCode_InternalError);
+        default:
+          throw OrthancException(ErrorCode_InternalError);
       }
     }
 
@@ -510,72 +591,72 @@
 
       switch (type)
       {
-      case ResourceType_Patient:
-        result["Studies"] = c;
-        break;
+        case ResourceType_Patient:
+          result["Studies"] = c;
+          break;
 
-      case ResourceType_Study:
-        result["Series"] = c;
-        break;
+        case ResourceType_Study:
+          result["Series"] = c;
+          break;
 
-      case ResourceType_Series:
-        result["Instances"] = c;
-        break;
+        case ResourceType_Series:
+          result["Instances"] = c;
+          break;
 
-      default:
-        throw OrthancException(ErrorCode_InternalError);
+        default:
+          throw OrthancException(ErrorCode_InternalError);
       }
     }
 
     // Set the resource type
     switch (type)
     {
-    case ResourceType_Patient:
-      result["Type"] = "Patient";
-      break;
+      case ResourceType_Patient:
+        result["Type"] = "Patient";
+        break;
 
-    case ResourceType_Study:
-      result["Type"] = "Study";
-      break;
-
-    case ResourceType_Series:
-    {
-      result["Type"] = "Series";
-      result["Status"] = ToString(GetSeriesStatus(id));
+      case ResourceType_Study:
+        result["Type"] = "Study";
+        break;
 
-      int i;
-      if (db_->GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances))
-        result["ExpectedNumberOfInstances"] = i;
-      else
-        result["ExpectedNumberOfInstances"] = Json::nullValue;
-
-      break;
-    }
+      case ResourceType_Series:
+      {
+        result["Type"] = "Series";
+        result["Status"] = ToString(GetSeriesStatus(id));
 
-    case ResourceType_Instance:
-    {
-      result["Type"] = "Instance";
+        int i;
+        if (db_->GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances))
+          result["ExpectedNumberOfInstances"] = i;
+        else
+          result["ExpectedNumberOfInstances"] = Json::nullValue;
 
-      FileInfo attachment;
-      if (!db_->LookupAttachment(attachment, id, FileContentType_Dicom))
-      {
-        throw OrthancException(ErrorCode_InternalError);
+        break;
       }
 
-      result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize());
-      result["FileUuid"] = attachment.GetUuid();
+      case ResourceType_Instance:
+      {
+        result["Type"] = "Instance";
+
+        FileInfo attachment;
+        if (!db_->LookupAttachment(attachment, id, FileContentType_Dicom))
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
 
-      int i;
-      if (db_->GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries))
-        result["IndexInSeries"] = i;
-      else
-        result["IndexInSeries"] = Json::nullValue;
+        result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize());
+        result["FileUuid"] = attachment.GetUuid();
 
-      break;
-    }
+        int i;
+        if (db_->GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries))
+          result["IndexInSeries"] = i;
+        else
+          result["IndexInSeries"] = Json::nullValue;
 
-    default:
-      throw OrthancException(ErrorCode_InternalError);
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
     }
 
     // Record the remaining information
@@ -666,28 +747,28 @@
 
       switch (currentType)
       {
-      case ResourceType_Patient:
-        patientId = map.GetValue(DICOM_TAG_PATIENT_ID).AsString();
-        done = true;
-        break;
+        case ResourceType_Patient:
+          patientId = map.GetValue(DICOM_TAG_PATIENT_ID).AsString();
+          done = true;
+          break;
 
-      case ResourceType_Study:
-        studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
-        currentType = ResourceType_Patient;
-        break;
+        case ResourceType_Study:
+          studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
+          currentType = ResourceType_Patient;
+          break;
 
-      case ResourceType_Series:
-        seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
-        currentType = ResourceType_Study;
-        break;
+        case ResourceType_Series:
+          seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
+          currentType = ResourceType_Study;
+          break;
 
-      case ResourceType_Instance:
-        sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
-        currentType = ResourceType_Series;
-        break;
+        case ResourceType_Instance:
+          sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
+          currentType = ResourceType_Series;
+          break;
 
-      default:
-        throw OrthancException(ErrorCode_InternalError);
+        default:
+          throw OrthancException(ErrorCode_InternalError);
       }
 
       // If we have not reached the Patient level, find the parent of
@@ -724,4 +805,161 @@
     db_->GetLastExportedResource(target);
     return true;
   }
+
+
+  bool ServerIndex::IsRecyclingNeeded(uint64_t instanceSize)
+  {
+    if (maximumStorageSize_ != 0)
+    {
+      uint64_t currentSize = currentStorageSize_ - listener_->GetSizeOfFilesToRemove();
+      assert(db_->GetTotalCompressedSize() == currentSize);
+
+      if (currentSize + instanceSize > maximumStorageSize_)
+      {
+        return true;
+      }
+    }
+
+    if (maximumPatients_ != 0)
+    {
+      uint64_t patientCount = db_->GetResourceCount(ResourceType_Patient);
+      if (patientCount > maximumPatients_)
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  
+  void ServerIndex::Recycle(uint64_t instanceSize,
+                            const std::string& newPatientId)
+  {
+    if (!IsRecyclingNeeded(instanceSize))
+    {
+      return;
+    }
+
+    // Check whether other DICOM instances from this patient are
+    // already stored
+    int64_t patientToAvoid;
+    ResourceType type;
+    bool hasPatientToAvoid = db_->LookupResource(newPatientId, patientToAvoid, type);
+
+    if (hasPatientToAvoid && type != ResourceType_Patient)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Iteratively select patient to remove until there is enough
+    // space in the DICOM store
+    int64_t patientToRecycle;
+    while (true)
+    {
+      // If other instances of this patient are already in the store,
+      // we must avoid to recycle them
+      bool ok = hasPatientToAvoid ?
+        db_->SelectPatientToRecycle(patientToRecycle, patientToAvoid) :
+        db_->SelectPatientToRecycle(patientToRecycle);
+        
+      if (!ok)
+      {
+        throw OrthancException(ErrorCode_FullStorage);
+      }
+      
+      LOG(INFO) << "Recycling one patient";
+      db_->DeleteResource(patientToRecycle);
+
+      if (!IsRecyclingNeeded(instanceSize))
+      {
+        // OK, we're done
+        break;
+      }
+    }
+  }  
+
+  void ServerIndex::SetMaximumPatientCount(unsigned int count) 
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    maximumPatients_ = count;
+
+    if (count == 0)
+    {
+      LOG(WARNING) << "No limit on the number of stored patients";
+    }
+    else
+    {
+      LOG(WARNING) << "At most " << count << " patients will be stored";
+    }
+
+    StandaloneRecycling();
+  }
+
+  void ServerIndex::SetMaximumStorageSize(uint64_t size) 
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    maximumStorageSize_ = size;
+
+    if (size == 0)
+    {
+      LOG(WARNING) << "No limit on the size of the storage area";
+    }
+    else
+    {
+      LOG(WARNING) << "At most " << (size / (1024 * 1024)) << "MB will be used for the storage area";
+    }
+
+    StandaloneRecycling();
+  }
+
+  void ServerIndex::StandaloneRecycling()
+  {
+    // WARNING: No mutex here, do not include this as a public method
+    Transaction t(*this);
+    Recycle(0, "");
+    t.Commit(0);
+  }
+
+
+  bool ServerIndex::IsProtectedPatient(const std::string& publicId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    // Lookup for the requested resource
+    int64_t id;
+    ResourceType type;
+    if (!db_->LookupResource(publicId, id, type) ||
+        type != ResourceType_Patient)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    return db_->IsProtectedPatient(id);
+  }
+     
+
+  void ServerIndex::SetProtectedPatient(const std::string& publicId,
+                                        bool isProtected)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    // Lookup for the requested resource
+    int64_t id;
+    ResourceType type;
+    if (!db_->LookupResource(publicId, id, type) ||
+        type != ResourceType_Patient)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    // No need for a SQLite::Transaction here, as we only make 1 write to the DB
+    db_->SetProtectedPatient(id, isProtected);
+
+    if (isProtected)
+      LOG(INFO) << "Patient " << publicId << " has been protected";
+    else
+      LOG(INFO) << "Patient " << publicId << " has been unprotected";
+  }
+
 }
--- a/OrthancServer/ServerIndex.h	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/ServerIndex.h	Fri Dec 14 11:22:29 2012 +0100
@@ -51,22 +51,33 @@
     class ServerIndexListener;
   }
 
-
-
   class ServerIndex : public boost::noncopyable
   {
   private:
+    class Transaction;
+
     boost::mutex mutex_;
     boost::thread flushThread_;
 
     std::auto_ptr<Internals::ServerIndexListener> listener_;
     std::auto_ptr<DatabaseWrapper> db_;
 
+    uint64_t currentStorageSize_;
+    uint64_t maximumStorageSize_;
+    unsigned int maximumPatients_;
+
     void MainDicomTagsToJson(Json::Value& result,
                              int64_t resourceId);
 
     SeriesStatus GetSeriesStatus(int id);
 
+    bool IsRecyclingNeeded(uint64_t instanceSize);
+
+    void Recycle(uint64_t instanceSize,
+                 const std::string& newPatientId);
+
+    void StandaloneRecycling();
+
   public:
     typedef std::list<FileInfo> Attachments;
 
@@ -75,6 +86,22 @@
 
     ~ServerIndex();
 
+    uint64_t GetMaximumStorageSize() const
+    {
+      return maximumStorageSize_;
+    }
+
+    uint64_t GetMaximumPatientCount() const
+    {
+      return maximumPatients_;
+    }
+
+    // "size == 0" means no limit on the storage size
+    void SetMaximumStorageSize(uint64_t size);
+
+    // "count == 0" means no limit on the number of patients
+    void SetMaximumPatientCount(unsigned int count);
+
     StoreStatus Store(const DicomMap& dicomSummary,
                       const Attachments& attachments,
                       const std::string& remoteAet);
@@ -111,5 +138,9 @@
 
     bool GetLastExportedResource(Json::Value& target);
 
+    bool IsProtectedPatient(const std::string& publicId);
+
+    void SetProtectedPatient(const std::string& publicId,
+                             bool isProtected);
   };
 }
--- a/OrthancServer/main.cpp	Mon Dec 10 11:00:50 2012 +0100
+++ b/OrthancServer/main.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -214,6 +214,25 @@
     ServerContext context(storageDirectory);
     context.SetCompressionEnabled(GetGlobalBoolParameter("StorageCompression", false));
 
+    try
+    {
+      context.GetIndex().SetMaximumPatientCount(GetGlobalIntegerParameter("MaximumPatientCount", 0));
+    }
+    catch (...)
+    {
+      context.GetIndex().SetMaximumPatientCount(0);
+    }
+
+    try
+    {
+      uint64_t size = GetGlobalIntegerParameter("MaximumStorageSize", 0);
+      context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024);
+    }
+    catch (...)
+    {
+      context.GetIndex().SetMaximumStorageSize(0);
+    }
+
     MyDicomStoreFactory storeScp(context);
 
     {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Archives/MessageWithDestination.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -0,0 +1,171 @@
+#include "../Core/IDynamicObject.h"
+
+#include "../Core/OrthancException.h"
+
+#include <stdint.h>
+#include <memory>
+#include <map>
+#include <gtest/gtest.h>
+#include <string>
+#include <boost/thread.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+namespace Orthanc
+{
+  class SharedMessageQueue
+  {
+  private:
+    typedef std::list<IDynamicObject*>  Queue;
+
+    unsigned int maxSize_;
+    Queue queue_;
+    boost::mutex mutex_;
+    boost::condition_variable elementAvailable_;
+
+  public:
+    SharedMessageQueue(unsigned int maxSize = 0)
+    {
+      maxSize_ = maxSize;
+    }
+
+    ~SharedMessageQueue()
+    {
+      for (Queue::iterator it = queue_.begin(); it != queue_.end(); it++)
+      {
+        delete *it;
+      }
+    }
+
+    void Enqueue(IDynamicObject* message)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (maxSize_ != 0 && queue_.size() > maxSize_)
+      {
+        // Too many elements in the queue: First remove the oldest
+        delete queue_.front();
+        queue_.pop_front();
+      }
+
+      queue_.push_back(message);
+      elementAvailable_.notify_one();
+    }
+
+    IDynamicObject* Dequeue(int32_t timeout)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      // Wait for a message to arrive in the FIFO queue
+      while (queue_.empty())
+      {
+        if (timeout == 0)
+        {
+          elementAvailable_.wait(lock);
+        }
+        else
+        {
+          bool success = elementAvailable_.timed_wait
+            (lock, boost::posix_time::milliseconds(timeout));
+          if (!success)
+          {
+            throw OrthancException(ErrorCode_Timeout);
+          }
+        }
+      }
+
+      std::auto_ptr<IDynamicObject> message(queue_.front());
+      queue_.pop_front();
+
+      return message.release();
+    }
+
+    IDynamicObject* Dequeue()
+    {
+      return Dequeue(0);
+    }
+  };
+
+
+  /**
+   * This class represents a message that is to be sent to some destination.
+   **/
+  class MessageToDispatch : public boost::noncopyable
+  {
+  private:
+    IDynamicObject* message_;
+    std::string destination_;
+
+  public:
+    /**
+     * Create a new message with a destination.
+     * \param message The content of the message (takes the ownership)
+     * \param destination The destination of the message
+     **/
+    MessageToDispatch(IDynamicObject* message,
+                      const char* destination)
+    {
+      message_ = message;
+      destination_ = destination;
+    }
+
+    ~MessageToDispatch()
+    {
+      if (message_)
+      {
+        delete message_;
+      }
+    }
+  };
+
+
+  class IDestinationContext : public IDynamicObject
+  {
+  public:
+    virtual void Handle(const IDynamicObject& message) = 0;
+  };
+
+
+  class IDestinationContextFactory : public IDynamicObject
+  {
+  public:
+    virtual IDestinationContext* Construct(const char* destination) = 0;
+  };
+
+
+  class MessageDispatcher
+  {
+  private:
+    typedef std::map<std::string, IDestinationContext*>  ActiveContexts;
+
+    std::auto_ptr<IDestinationContextFactory> factory_;
+    ActiveContexts activeContexts_;
+    SharedMessageQueue queue_;
+
+  public:
+    MessageDispatcher(IDestinationContextFactory* factory)  // takes the ownership
+    {
+      factory_.reset(factory);
+    }
+
+    ~MessageDispatcher()
+    {
+      for (ActiveContexts::iterator it = activeContexts_.begin(); 
+           it != activeContexts_.end(); it++)
+      {
+        delete it->second;
+      }
+    }
+  };
+}
+
+
+
+#include "../Core/DicomFormat/DicomString.h"
+
+using namespace Orthanc;
+
+TEST(MessageToDispatch, A)
+{
+  MessageToDispatch a(new DicomString("coucou"), "pukkaj");
+}
+
--- a/Resources/CMake/DcmtkConfiguration.cmake	Mon Dec 10 11:00:50 2012 +0100
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Fri Dec 14 11:22:29 2012 +0100
@@ -71,14 +71,14 @@
   set(DCMTK_BUNDLES_LOG4CPLUS 1)
 
 else()
-  #include(FindDCMTK)
-  set(DCMTK_DIR /usr/include/dcmtk)
-  set(DCMTK_INCLUDE_DIR ${DCMTK_DIR})
-
-  #message(${DCMTK_LIBRARIES})
+  # The following line allows to manually add libraries at the
+  # command-line, which is necessary for Ubuntu/Debian packages
+  set(tmp "${DCMTK_LIBRARIES}")
+  include(FindDCMTK)
+  list(APPEND DCMTK_LIBRARIES "${tmp}")
 
   include_directories(${DCMTK_INCLUDE_DIR})
-  link_libraries(dcmdata dcmnet wrap ofstd)
+  link_libraries(${DCMTK_LIBRARIES})
 
   add_definitions(
     -DHAVE_CONFIG_H=1
@@ -93,17 +93,17 @@
   endif()
 
   # Autodetection of the version of DCMTK
-  file(STRINGS "${DCMTK_CONFIGURATION_FILE}" DCMTK_VERSION_NUMBER1 REGEX ".*PACKAGE_VERSION .*")    
-  string(REGEX REPLACE ".*PACKAGE_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$" "\\1\\2\\3" DCMTK_VERSION_NUMBER ${DCMTK_VERSION_NUMBER1})
+  file(STRINGS
+    "${DCMTK_CONFIGURATION_FILE}" 
+    DCMTK_VERSION_NUMBER1 REGEX
+    ".*PACKAGE_VERSION .*")    
 
-  IF (EXISTS "${DCMTK_DIR}/oflog")
-    set(DCMTK_BUNDLES_LOG4CPLUS 1)
-    link_libraries(oflog)
-  else()
-    set(DCMTK_BUNDLES_LOG4CPLUS 0)
-  endif()
+  string(REGEX REPLACE
+    ".*PACKAGE_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$"
+    "\\1\\2\\3" 
+    DCMTK_VERSION_NUMBER 
+    ${DCMTK_VERSION_NUMBER1})
 endif()
 
 add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER})
 message("DCMTK version: ${DCMTK_VERSION_NUMBER}")
-message("Does DCMTK includes its own copy of Log4Cplus: ${DCMTK_BUNDLES_LOG4CPLUS}")
--- a/Resources/Configuration.json	Mon Dec 10 11:00:50 2012 +0100
+++ b/Resources/Configuration.json	Fri Dec 14 11:22:29 2012 +0100
@@ -13,6 +13,14 @@
     // Enable the transparent compression of the DICOM instances
     "StorageCompression" : false,
 
+    // Maximum size of the storage in MB (a value of "0" indicates no
+    // limit on the storage size)
+    "MaximumStorageSize" : 0,
+
+    // Maximum number of patients that can be stored at a given time
+    // in the storage (a value of "0" indicates no limit on the number
+    // of patients)
+    "MaximumPatientCount" : 0,
 
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Fri Dec 14 11:22:29 2012 +0100
@@ -0,0 +1,61 @@
+#!/usr/bin/python
+
+import os
+import sys
+import os.path
+import httplib2
+
+if len(sys.argv) != 4:
+    print("""
+Sample script to recursively import in Orthanc all the DICOM files
+that are stored in some path. Please make sure that Orthanc is running
+before starting this script. The files are uploaded through the REST
+API.
+
+Usage: %s [hostname] [HTTP port] [path]
+For instance: %s localhost 8042 .
+""" % (sys.argv[0], sys.argv[0]))
+    exit(-1)
+
+URL = 'http://%s:%d/instances' % (sys.argv[1], int(sys.argv[2]))
+
+success = 0
+
+
+# This function will upload a single file to Orthanc through the REST API
+def UploadFile(path):
+    global success
+
+    f = open(path, "r")
+    content = f.read()
+    f.close()
+
+    try:
+        sys.stdout.write("Importing %s" % path)
+
+        h = httplib2.Http()
+        resp, content = h.request(URL, 'POST', 
+                                  body = content,
+                                  headers = { 'content-type' : 'application/dicom' })
+
+        if resp.status == 200:
+            sys.stdout.write(" => success\n")
+            success += 1
+        else:
+            sys.stdout.write(" => failure (is it a DICOM file?)\n")
+
+    except:
+        sys.stdout.write(" => unable to connect\n")
+
+
+if os.path.isfile(sys.argv[3]):
+    # Upload a single file
+    UploadFile(sys.argv[3])
+else:
+    # Recursively upload a directory
+    for root, dirs, files in os.walk(sys.argv[3]):
+        for f in files:
+            UploadFile(os.path.join(root, f))
+        
+
+print("\nSummary: %d DICOM file(s) have been imported" % success)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/RestApi/CMakeLists.txt	Fri Dec 14 11:22:29 2012 +0100
@@ -0,0 +1,62 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(RestApiSample)
+
+include(ExternalProject)
+
+# Send the toolchain information to the Orthanc 
+if (CMAKE_TOOLCHAIN_FILE)
+  set(TOOLCHAIN "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}")
+endif()
+
+ExternalProject_Add(
+  ORTHANC_CORE
+
+  # We use the Orthanc-0.3.1 branch for this sample
+  DOWNLOAD_COMMAND hg clone https://code.google.com/p/orthanc/ -r Orthanc-0.3.1
+
+  # Optional step, to reuse the third-party downloads
+  PATCH_COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/../../../ThirdPartyDownloads ThirdPartyDownloads
+
+  PREFIX ${CMAKE_BINARY_DIR}/Orthanc/
+  UPDATE_COMMAND ""
+  SOURCE_DIR ${CMAKE_BINARY_DIR}/Orthanc/src/orthanc/
+  CMAKE_COMMAND ${CMAKE_COMMAND}
+  CMAKE_ARGS -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DUSE_DYNAMIC_GOOGLE_LOG=OFF -DUSE_DYNAMIC_SQLITE=OFF -DONLY_CORE_LIBRARY=ON -DENABLE_SSL=OFF ${TOOLCHAIN}
+  BUILD_COMMAND $(MAKE)
+  INSTALL_COMMAND ""
+  BUILD_IN_SOURCE 0
+  )
+
+ExternalProject_Get_Property(ORTHANC_CORE source_dir)
+include_directories(${source_dir})
+
+ExternalProject_Get_Property(ORTHANC_CORE binary_dir)
+link_directories(${binary_dir})
+include_directories(${binary_dir}/jsoncpp-src-0.5.0/include)
+include_directories(${binary_dir}/glog-0.3.2/src)
+include_directories(${binary_dir}/boost_1_49_0)
+
+
+add_executable(RestApiSample
+  Sample.cpp
+  )
+
+add_dependencies(RestApiSample ORTHANC_CORE)
+
+target_link_libraries(RestApiSample 
+  # From Orthanc
+  CoreLibrary
+  GoogleLog
+
+  # These two libraries are not necessary
+  #OpenSSL
+  #Curl
+  )
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+  target_link_libraries(RestApiSample pthread)
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  add_definitions(-DGOOGLE_GLOG_DLL_DECL=)
+  target_link_libraries(RestApiSample wsock32)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/RestApi/Sample.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -0,0 +1,105 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <Core/HttpServer/MongooseServer.h>
+#include <Core/RestApi/RestApi.h>
+#include <Core/Toolbox.h>
+#include <glog/logging.h>
+#include <stdio.h>
+
+
+/**
+ * This is a demo program that shows how to setup a REST server with
+ * the Orthanc Core API. Once the server is running, here are some 
+ * sample command lines to interact with it:
+ * 
+ *  # curl http://localhost:8042
+ *  # curl 'http://localhost:8042?name=Hide'
+ *  # curl http://localhost:8042 -X DELETE
+ *  # curl http://localhost:8042 -X PUT -d "PutBody"
+ *  # curl http://localhost:8042 -X POST -d "PostBody"
+ **/
+
+static void GetRoot(Orthanc::RestApi::GetCall& call)
+{
+  std::string answer = "Hello world\n";
+  answer += "Glad to meet you, Mr. " + call.GetArgument("name", "Nobody") + "\n";
+  call.GetOutput().AnswerBuffer(answer, "text/plain");
+}
+ 
+static void DeleteRoot(Orthanc::RestApi::DeleteCall& call)
+{
+  call.GetOutput().AnswerBuffer("Hey, you have just deleted the server!\n",
+                                "text/plain");
+}
+ 
+static void PostRoot(Orthanc::RestApi::PostCall& call)
+{
+  call.GetOutput().AnswerBuffer("I have received a POST with body: [" +
+                                call.GetPostBody() + "]\n", "text/plain");
+}
+ 
+static void PutRoot(Orthanc::RestApi::PutCall& call)
+{
+  call.GetOutput().AnswerBuffer("I have received a PUT with body: [" +
+                                call.GetPutBody() + "]\n", "text/plain");
+}
+ 
+int main()
+{
+  // Initialize the logging mechanism
+  google::InitGoogleLogging("Orthanc");
+  FLAGS_logtostderr = true;
+  FLAGS_minloglevel = 0;                      // Use the verbose mode
+  FLAGS_v = 0;
+  
+  // Define the callbacks of the REST API
+  std::auto_ptr<Orthanc::RestApi> rest(new Orthanc::RestApi);
+  rest->Register("/", GetRoot);
+  rest->Register("/", PostRoot);
+  rest->Register("/", PutRoot);
+  rest->Register("/", DeleteRoot);
+
+  // Setup the embedded HTTP server
+  Orthanc::MongooseServer httpServer;
+  httpServer.SetPortNumber(8042);             // Use TCP port 8042
+  httpServer.SetRemoteAccessAllowed(true);    // Do not block remote requests
+  httpServer.RegisterHandler(rest.release()); // The REST API is the handler
+
+  // Start the server and wait for the user to hit "Ctrl-C"
+  httpServer.Start();
+  LOG(WARNING) << "REST server has started";
+  Orthanc::Toolbox::ServerBarrier();
+  LOG(WARNING) << "REST server has stopped";
+
+  return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTests/MemoryCache.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -0,0 +1,128 @@
+#include "gtest/gtest.h"
+
+#include <glog/logging.h>
+#include <memory>
+#include <boost/thread.hpp>
+#include <boost/lexical_cast.hpp>
+#include "../Core/IDynamicObject.h"
+#include "../Core/Cache/MemoryCache.h"
+
+
+TEST(CacheIndex, Basic)
+{
+  Orthanc::CacheIndex<std::string> r;
+  
+  r.Add("d");
+  r.Add("a");
+  r.Add("c");
+  r.Add("b");
+
+  r.TagAsMostRecent("a");
+  r.TagAsMostRecent("d");
+  r.TagAsMostRecent("b");
+  r.TagAsMostRecent("c");
+  r.TagAsMostRecent("d");
+  r.TagAsMostRecent("c");
+
+  ASSERT_EQ("a", r.RemoveOldest());
+  ASSERT_EQ("b", r.RemoveOldest());
+  ASSERT_EQ("d", r.RemoveOldest());
+  ASSERT_EQ("c", r.RemoveOldest());
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
+TEST(CacheIndex, Payload)
+{
+  Orthanc::CacheIndex<std::string, int> r;
+  
+  r.Add("a", 420);
+  r.Add("b", 421);
+  r.Add("c", 422);
+  r.Add("d", 423);
+
+  r.TagAsMostRecent("a");
+  r.TagAsMostRecent("d");
+  r.TagAsMostRecent("b");
+  r.TagAsMostRecent("c");
+  r.TagAsMostRecent("d");
+  r.TagAsMostRecent("c");
+
+  ASSERT_TRUE(r.Contains("b"));
+  ASSERT_EQ(421, r.Invalidate("b"));
+  ASSERT_FALSE(r.Contains("b"));
+
+  int p;
+  ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p);
+  ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p);
+  ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p);
+
+  ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p);
+  ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p);
+  ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p);
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
+
+
+namespace
+{
+  class Integer : public Orthanc::IDynamicObject
+  {
+  private:
+    std::string& log_;
+    int value_;
+
+  public:
+    Integer(std::string& log, int v) : log_(log), value_(v)
+    {
+    }
+
+    virtual ~Integer()
+    {
+      LOG(INFO) << "Removing cache entry for " << value_;
+      log_ += boost::lexical_cast<std::string>(value_) + " ";
+    }
+
+    int GetValue() const 
+    {
+      return value_;
+    }
+  };
+
+  class IntegerProvider : public Orthanc::ICachePageProvider
+  {
+  public:
+    std::string log_;
+
+    Orthanc::IDynamicObject* Provide(const std::string& s)
+    {
+      LOG(INFO) << "Providing " << s;
+      return new Integer(log_, boost::lexical_cast<int>(s));
+    }
+  };
+}
+
+
+TEST(MemoryCache, Basic)
+{
+  IntegerProvider provider;
+
+  {
+    Orthanc::MemoryCache cache(provider, 3);
+    cache.Access("42");  // 42 -> exit
+    cache.Access("43");  // 43, 42 -> exit
+    cache.Access("45");  // 45, 43, 42 -> exit
+    cache.Access("42");  // 42, 45, 43 -> exit
+    cache.Access("43");  // 43, 42, 45 -> exit
+    cache.Access("47");  // 45 is removed; 47, 43, 42 -> exit 
+    cache.Access("44");  // 42 is removed; 44, 47, 43 -> exit
+    cache.Access("42");  // 43 is removed; 42, 44, 47 -> exit
+    // Closing the cache: 47, 44, 42 are successively removed
+  }
+
+  ASSERT_EQ("45 42 43 47 44 42 ", provider.log_);
+}
--- a/UnitTests/MessageWithDestination.cpp	Mon Dec 10 11:00:50 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,171 +0,0 @@
-#include "../Core/IDynamicObject.h"
-
-#include "../Core/OrthancException.h"
-
-#include <stdint.h>
-#include <memory>
-#include <map>
-#include <gtest/gtest.h>
-#include <string>
-#include <boost/thread.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-
-namespace Orthanc
-{
-  class SharedMessageQueue
-  {
-  private:
-    typedef std::list<IDynamicObject*>  Queue;
-
-    unsigned int maxSize_;
-    Queue queue_;
-    boost::mutex mutex_;
-    boost::condition_variable elementAvailable_;
-
-  public:
-    SharedMessageQueue(unsigned int maxSize = 0)
-    {
-      maxSize_ = maxSize;
-    }
-
-    ~SharedMessageQueue()
-    {
-      for (Queue::iterator it = queue_.begin(); it != queue_.end(); it++)
-      {
-        delete *it;
-      }
-    }
-
-    void Enqueue(IDynamicObject* message)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (maxSize_ != 0 && queue_.size() > maxSize_)
-      {
-        // Too many elements in the queue: First remove the oldest
-        delete queue_.front();
-        queue_.pop_front();
-      }
-
-      queue_.push_back(message);
-      elementAvailable_.notify_one();
-    }
-
-    IDynamicObject* Dequeue(int32_t timeout)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      // Wait for a message to arrive in the FIFO queue
-      while (queue_.empty())
-      {
-        if (timeout == 0)
-        {
-          elementAvailable_.wait(lock);
-        }
-        else
-        {
-          bool success = elementAvailable_.timed_wait
-            (lock, boost::posix_time::milliseconds(timeout));
-          if (!success)
-          {
-            throw OrthancException(ErrorCode_Timeout);
-          }
-        }
-      }
-
-      std::auto_ptr<IDynamicObject> message(queue_.front());
-      queue_.pop_front();
-
-      return message.release();
-    }
-
-    IDynamicObject* Dequeue()
-    {
-      return Dequeue(0);
-    }
-  };
-
-
-  /**
-   * This class represents a message that is to be sent to some destination.
-   **/
-  class MessageToDispatch : public boost::noncopyable
-  {
-  private:
-    IDynamicObject* message_;
-    std::string destination_;
-
-  public:
-    /**
-     * Create a new message with a destination.
-     * \param message The content of the message (takes the ownership)
-     * \param destination The destination of the message
-     **/
-    MessageToDispatch(IDynamicObject* message,
-                      const char* destination)
-    {
-      message_ = message;
-      destination_ = destination;
-    }
-
-    ~MessageToDispatch()
-    {
-      if (message_)
-      {
-        delete message_;
-      }
-    }
-  };
-
-
-  class IDestinationContext : public IDynamicObject
-  {
-  public:
-    virtual void Handle(const IDynamicObject& message) = 0;
-  };
-
-
-  class IDestinationContextFactory : public IDynamicObject
-  {
-  public:
-    virtual IDestinationContext* Construct(const char* destination) = 0;
-  };
-
-
-  class MessageDispatcher
-  {
-  private:
-    typedef std::map<std::string, IDestinationContext*>  ActiveContexts;
-
-    std::auto_ptr<IDestinationContextFactory> factory_;
-    ActiveContexts activeContexts_;
-    SharedMessageQueue queue_;
-
-  public:
-    MessageDispatcher(IDestinationContextFactory* factory)  // takes the ownership
-    {
-      factory_.reset(factory);
-    }
-
-    ~MessageDispatcher()
-    {
-      for (ActiveContexts::iterator it = activeContexts_.begin(); 
-           it != activeContexts_.end(); it++)
-      {
-        delete it->second;
-      }
-    }
-  };
-}
-
-
-
-#include "../Core/DicomFormat/DicomString.h"
-
-using namespace Orthanc;
-
-TEST(MessageToDispatch, A)
-{
-  MessageToDispatch a(new DicomString("coucou"), "pukkaj");
-}
-
--- a/UnitTests/RestApi.cpp	Mon Dec 10 11:00:50 2012 +0100
+++ b/UnitTests/RestApi.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -50,41 +50,3 @@
     ASSERT_EQ("c", trail[2]);
   }
 }
-
-
-
-#if 0
-
-#include "../Core/HttpServer/MongooseServer.h"
-
-struct Tutu : public IDynamicObject
-{
-  static void Toto(RestApi::GetCall& call)
-  {
-    printf("DONE\n");
-    Json::Value a = Json::objectValue;
-    a["Tutu"] = "Toto";
-    a["Youpie"] = call.GetArgument("coucou", "nope");
-    a["Toto"] = call.GetUriComponent("test", "nope");
-    call.GetOutput().AnswerJson(a);
-  }
-};
-
-
-
-TEST(RestApi, Tutu)
-{
-  MongooseServer httpServer;
-  httpServer.SetPortNumber(8042);
-  httpServer.Start();
-
-  RestApi* api = new RestApi;
-  httpServer.RegisterHandler(api);
-  api->Register("/coucou/{test}/a/*", Tutu::Toto);
-
-  httpServer.Start();
-  /*LOG(WARNING) << "REST has started";
-    Toolbox::ServerBarrier();*/
-}
-
-#endif
--- a/UnitTests/ServerIndex.cpp	Mon Dec 10 11:00:50 2012 +0100
+++ b/UnitTests/ServerIndex.cpp	Fri Dec 14 11:22:29 2012 +0100
@@ -1,6 +1,7 @@
 #include "gtest/gtest.h"
 
 #include "../OrthancServer/DatabaseWrapper.h"
+#include "../Core/Uuid.h"
 
 #include <ctype.h>
 #include <glog/logging.h>
@@ -12,7 +13,7 @@
   class ServerIndexListener : public IServerIndexListener
   {
   public:
-    std::set<std::string> deletedFiles_;
+    std::vector<std::string> deletedFiles_;
     std::string ancestorId_;
     ResourceType ancestorType_;
 
@@ -29,9 +30,10 @@
       ancestorType_ = type;
     }
 
-    virtual void SignalFileDeleted(const std::string& fileUuid)
+    virtual void SignalFileDeleted(const FileInfo& info)
     {
-      deletedFiles_.insert(fileUuid);
+      const std::string fileUuid = info.GetUuid();
+      deletedFiles_.push_back(fileUuid);
       LOG(INFO) << "A file must be removed: " << fileUuid;
     }                                
   };
@@ -170,8 +172,12 @@
   index.DeleteResource(a[0]);
 
   ASSERT_EQ(2u, listener.deletedFiles_.size());
-  ASSERT_FALSE(listener.deletedFiles_.find("my json file") == listener.deletedFiles_.end());
-  ASSERT_FALSE(listener.deletedFiles_.find("my dicom file") == listener.deletedFiles_.end());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "my json file") == listener.deletedFiles_.end());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "my dicom file") == listener.deletedFiles_.end());
 
   ASSERT_EQ(2u, index.GetTableRecordCount("Resources"));
   ASSERT_EQ(0u, index.GetTableRecordCount("Metadata"));
@@ -183,7 +189,9 @@
   ASSERT_EQ(2u, index.GetTableRecordCount("GlobalProperties"));
 
   ASSERT_EQ(3u, listener.deletedFiles_.size());
-  ASSERT_FALSE(listener.deletedFiles_.find("world") == listener.deletedFiles_.end());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "world") == listener.deletedFiles_.end());
 }
 
 
@@ -256,3 +264,135 @@
   index.DeleteResource(a[6]);
   ASSERT_EQ("", listener.ancestorId_);  // No more ancestor
 }
+
+
+TEST(DatabaseWrapper, PatientRecycling)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  std::vector<int64_t> patients;
+  for (int i = 0; i < 10; i++)
+  {
+    std::string p = "Patient " + boost::lexical_cast<std::string>(i);
+    patients.push_back(index.CreateResource(p, ResourceType_Patient));
+    index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10));
+    ASSERT_FALSE(index.IsProtectedPatient(patients[i]));
+  }
+
+  ASSERT_EQ(10u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(10u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  listener.Reset();
+
+  index.DeleteResource(patients[5]);
+  index.DeleteResource(patients[0]);
+  ASSERT_EQ(8u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(8u, index.GetTableRecordCount("PatientRecyclingOrder"));
+
+  ASSERT_EQ(2u, listener.deletedFiles_.size());
+  ASSERT_EQ("Patient 5", listener.deletedFiles_[0]);
+  ASSERT_EQ("Patient 0", listener.deletedFiles_[1]);
+
+  int64_t p;
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]);
+  index.DeleteResource(p);
+  index.DeleteResource(patients[8]);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]);
+  index.DeleteResource(p);
+  ASSERT_FALSE(index.SelectPatientToRecycle(p));
+
+  ASSERT_EQ(10u, listener.deletedFiles_.size());
+  ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+}
+
+
+TEST(DatabaseWrapper, PatientProtection)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  std::vector<int64_t> patients;
+  for (int i = 0; i < 5; i++)
+  {
+    std::string p = "Patient " + boost::lexical_cast<std::string>(i);
+    patients.push_back(index.CreateResource(p, ResourceType_Patient));
+    index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10));
+    ASSERT_FALSE(index.IsProtectedPatient(patients[i]));
+  }
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder"));
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder"));
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[3], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[3]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, listener.deletedFiles_.size());
+
+  // Unprotecting a patient puts it at the last position in the recycling queue
+  int64_t p;
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[1])); ASSERT_EQ(p, patients[4]);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
+  index.DeleteResource(p);
+  ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[2]));
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
+  index.DeleteResource(p);
+  // "patients[3]" is still protected
+  ASSERT_FALSE(index.SelectPatientToRecycle(p));
+
+  ASSERT_EQ(4u, listener.deletedFiles_.size());
+  ASSERT_EQ(1u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  index.SetProtectedPatient(patients[3], false);
+  ASSERT_EQ(1u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[3]));
+  ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[2]));
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
+  index.DeleteResource(p);
+
+  ASSERT_EQ(5u, listener.deletedFiles_.size());
+  ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+}