changeset 546:0e510ea3de31 laaw

merge mainline -> laaw
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 13 Sep 2013 11:25:08 +0200
parents fe796b053863 (current diff) c0a841f282a9 (diff)
children ffedcc8f0938
files CMakeLists.txt Core/Cache/CacheIndex.h
diffstat 30 files changed, 1426 insertions(+), 489 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Fri Sep 13 11:10:58 2013 +0200
+++ b/CMakeLists.txt	Fri Sep 13 11:25:08 2013 +0200
@@ -20,24 +20,20 @@
 SET(BUILD_CLIENT_LIBRARY ON CACHE BOOL "Build the client library")
 SET(DCMTK_DICTIONARY_DIR "/usr/share/dcmtk" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (ignored in standalone builds)") 
 
-# Advanced parameters (for Debian packaging)
-SET(USE_DYNAMIC_JSONCPP OFF CACHE BOOL "Use the dynamic version of JsonCpp (only for Debian sid)")
+# Advanced parameters to fine-tune linking against system libraries
+SET(USE_DYNAMIC_JSONCPP OFF CACHE BOOL "Use the dynamic version of JsonCpp")
 SET(USE_DYNAMIC_GOOGLE_LOG ON CACHE BOOL "Use the dynamic version of Google Log")
-SET(USE_DYNAMIC_GOOGLE_TEST ON CACHE BOOL "Use the dynamic version of Google Test (not for Debian sid)")
+SET(USE_DYNAMIC_GOOGLE_TEST ON CACHE BOOL "Use the dynamic version of Google Test")
 SET(USE_DYNAMIC_SQLITE ON CACHE BOOL "Use the dynamic version of SQLite")
 SET(USE_DYNAMIC_MONGOOSE OFF CACHE BOOL "Use the dynamic version of Mongoose")
 SET(USE_DYNAMIC_LUA OFF CACHE BOOL "Use the dynamic version of Lua")
-SET(DEBIAN_FORCE_HARDENING OFF CACHE BOOL "Force the injection of Debian hardening flags (unrecommended)")
-SET(DEBIAN_USE_GTEST_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (only for Debian sid)")
-SET(ONLY_CORE_LIBRARY OFF CACHE BOOL "Only build the core library")
+SET(DEBIAN_USE_GTEST_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
 
 mark_as_advanced(USE_DYNAMIC_JSONCPP)
 mark_as_advanced(USE_DYNAMIC_GOOGLE_LOG)
 mark_as_advanced(USE_DYNAMIC_GOOGLE_TEST)
 mark_as_advanced(USE_DYNAMIC_SQLITE)
-mark_as_advanced(DEBIAN_FORCE_HARDENING)
-mark_as_advanced(DEBIAN_USE_STATIC_GOOGLE_TEST)
-mark_as_advanced(ONLY_CORE_LIBRARY)
+mark_as_advanced(DEBIAN_USE_GTEST_SOURCE_PACKAGE)
 
 # Some basic inclusions
 include(CheckIncludeFiles)
@@ -79,11 +75,7 @@
 endif()
 
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/BoostConfiguration.cmake)
-
-if(NOT ONLY_CORE_LIBRARY)
-  include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake)
-endif()
-
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/MongooseConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/ZlibConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/SQLiteConfiguration.cmake)
@@ -193,83 +185,81 @@
   )  
 
 
+#####################################################################
+## Build the Orthanc server
+#####################################################################
 
-if(NOT ONLY_CORE_LIBRARY)
-  #####################################################################
-  ## Build the Orthanc server
-  #####################################################################
+add_library(ServerLibrary
+  STATIC
+  ${DCMTK_SOURCES}
+  OrthancServer/DicomProtocol/DicomFindAnswers.cpp
+  OrthancServer/DicomProtocol/DicomServer.cpp
+  OrthancServer/DicomProtocol/DicomUserConnection.cpp
+  OrthancServer/FromDcmtkBridge.cpp
+  OrthancServer/Internals/CommandDispatcher.cpp
+  OrthancServer/Internals/FindScp.cpp
+  OrthancServer/Internals/MoveScp.cpp
+  OrthancServer/Internals/StoreScp.cpp
+  OrthancServer/OrthancInitialization.cpp
+  OrthancServer/OrthancRestApi.cpp
+  OrthancServer/ServerIndex.cpp
+  OrthancServer/ToDcmtkBridge.cpp
+  OrthancServer/DatabaseWrapper.cpp
+  OrthancServer/ServerContext.cpp
+  OrthancServer/ServerEnumerations.cpp
+  OrthancServer/ServerToolbox.cpp
+  )
+
+# Ensure autogenerated code is built before building ServerLibrary
+add_dependencies(ServerLibrary CoreLibrary)
+
+add_executable(Orthanc
+  OrthancServer/main.cpp
+  )
+
+target_link_libraries(Orthanc ServerLibrary CoreLibrary)
 
-  add_library(ServerLibrary
-    STATIC
-    ${DCMTK_SOURCES}
-    OrthancServer/DicomProtocol/DicomFindAnswers.cpp
-    OrthancServer/DicomProtocol/DicomServer.cpp
-    OrthancServer/DicomProtocol/DicomUserConnection.cpp
-    OrthancServer/FromDcmtkBridge.cpp
-    OrthancServer/Internals/CommandDispatcher.cpp
-    OrthancServer/Internals/FindScp.cpp
-    OrthancServer/Internals/MoveScp.cpp
-    OrthancServer/Internals/StoreScp.cpp
-    OrthancServer/OrthancInitialization.cpp
-    OrthancServer/OrthancRestApi.cpp
-    OrthancServer/ServerIndex.cpp
-    OrthancServer/ToDcmtkBridge.cpp
-    OrthancServer/DatabaseWrapper.cpp
-    OrthancServer/ServerContext.cpp
-    OrthancServer/ServerEnumerations.cpp
-    OrthancServer/ServerToolbox.cpp
+if (${OPENSSL_SOURCES_LENGTH} GREATER 0)
+  target_link_libraries(Orthanc OpenSSL)
+endif()
+
+install(
+  TARGETS Orthanc
+  RUNTIME DESTINATION bin
+  )
+
+
+
+#####################################################################
+## Build the unit tests if required
+#####################################################################
+
+if (BUILD_UNIT_TESTS)
+  add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1)
+  include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake)
+  add_executable(UnitTests
+    ${GTEST_SOURCES}
+    UnitTests/FileStorage.cpp
+    UnitTests/MemoryCache.cpp
+    UnitTests/Png.cpp
+    UnitTests/RestApi.cpp
+    UnitTests/SQLite.cpp
+    UnitTests/SQLiteChromium.cpp
+    UnitTests/ServerIndex.cpp
+    UnitTests/Versions.cpp
+    UnitTests/Zip.cpp
+    UnitTests/Lua.cpp
+    UnitTests/main.cpp
     )
-
-  # Ensure autogenerated code is built before building ServerLibrary
-  add_dependencies(ServerLibrary CoreLibrary)
-
-  add_executable(Orthanc
-    OrthancServer/main.cpp
-    )
-
-  target_link_libraries(Orthanc ServerLibrary CoreLibrary)
+  target_link_libraries(UnitTests ServerLibrary CoreLibrary)
 
   if (${OPENSSL_SOURCES_LENGTH} GREATER 0)
-    target_link_libraries(Orthanc OpenSSL)
-  endif()
-
-  install(
-    TARGETS Orthanc
-    RUNTIME DESTINATION bin
-    )
-
-
-  #####################################################################
-  ## Build the unit tests if required
-  #####################################################################
-
-  if (BUILD_UNIT_TESTS)
-    add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1)
-    include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake)
-    add_executable(UnitTests
-      ${GTEST_SOURCES}
-      UnitTests/FileStorage.cpp
-      UnitTests/MemoryCache.cpp
-      UnitTests/Png.cpp
-      UnitTests/RestApi.cpp
-      UnitTests/SQLite.cpp
-      UnitTests/SQLiteChromium.cpp
-      UnitTests/ServerIndex.cpp
-      UnitTests/Versions.cpp
-      UnitTests/Zip.cpp
-      UnitTests/Lua.cpp
-      UnitTests/main.cpp
-      )
-    target_link_libraries(UnitTests ServerLibrary CoreLibrary)
-
-    if (${OPENSSL_SOURCES_LENGTH} GREATER 0)
-      target_link_libraries(UnitTests OpenSSL)
-    endif()
-
+    target_link_libraries(UnitTests OpenSSL)
   endif()
 endif()
 
 
+
 #####################################################################
 ## Create the standalone DLL containing the Orthanc Client API
 #####################################################################
@@ -332,20 +322,13 @@
     message(FATAL_ERROR "Support your platform here")
   endif()
 
-  # Copy the header file of the client library to the build directory
-  add_custom_command(
-    TARGET OrthancClient PRE_BUILD
-    COMMAND ${CMAKE_COMMAND} -E copy
-    ${ORTHANC_ROOT}/OrthancCppClient/Package/AUTOGENERATED/OrthancCppClient.h 
-    ${CMAKE_BINARY_DIR})
-
   install(
     TARGETS OrthancClient
     LIBRARY DESTINATION lib
     )
 
   install(
-    FILES ${CMAKE_BINARY_DIR}/OrthancCppClient.h
+    FILES ${ORTHANC_ROOT}/OrthancCppClient/Package/AUTOGENERATED/OrthancCppClient.h 
     DESTINATION include/orthanc
     )
 endif()
--- a/Core/Cache/CacheIndex.h	Fri Sep 13 11:10:58 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,250 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <list>
-#include <map>
-#include <boost/noncopyable.hpp>
-#include <cassert>
-
-#include "../OrthancException.h"
-#include "../Toolbox.h"
-
-namespace Orthanc
-{
-  /**
-   * This class implements the index of a cache with least recently
-   * used (LRU) recycling policy. All the items of the cache index
-   * can be associated with a payload.
-   * Reference: http://stackoverflow.com/a/2504317
-   **/
-  template <typename T, typename Payload = NullType>
-  class 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/LeastRecentlyUsedIndex.h	Fri Sep 13 11:25:08 2013 +0200
@@ -0,0 +1,346 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <list>
+#include <map>
+#include <boost/noncopyable.hpp>
+#include <cassert>
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  /**
+   * This class implements the index of a cache with least recently
+   * used (LRU) recycling policy. All the items of the cache index
+   * can be associated with a payload.
+   * Reference: http://stackoverflow.com/a/2504317
+   **/
+  template <typename T, typename Payload = NullType>
+  class LeastRecentlyUsedIndex : public boost::noncopyable
+  {
+  private:
+    typedef std::list< std::pair<T, Payload> >  Queue;
+    typedef std::map<T, typename Queue::iterator>  Index;
+
+    Index  index_;
+    Queue  queue_;
+
+    /**
+     * Internal method for debug builds to check whether the internal
+     * data structures are not corrupted.
+     **/
+    void CheckInvariants() const;
+
+  public:
+    /**
+     * Add a new element to the cache index, and make it the most
+     * recent element.
+     * \param id The ID of the element.
+     * \param payload The payload of the element.
+     **/
+    void Add(T id, Payload payload = Payload());
+
+    void AddOrMakeMostRecent(T id, Payload payload = Payload());
+
+    /**
+     * When accessing an element of the cache, this method tags the
+     * element as the most recently used.
+     * \param id The most recently accessed item.
+     **/
+    void MakeMostRecent(T id);
+
+    void MakeMostRecent(T id, Payload updatedPayload);
+
+    /**
+     * Remove an element from the cache index.
+     * \param id The item to remove.
+     **/
+    Payload Invalidate(T id);
+
+    /**
+     * Get the oldest element in the cache and remove it.
+     * \return The oldest item.
+     **/
+    T RemoveOldest();
+
+    /**
+     * Get the oldest element in the cache, remove it and return the
+     * associated payload.
+     * \param payload Where to store the associated payload.
+     * \return The oldest item.
+     **/
+    T RemoveOldest(Payload& payload);
+
+    /**
+     * Check whether an element is contained in the cache.
+     * \param id The item.
+     * \return \c true iff the item is indexed by the cache.
+     **/
+    bool Contains(T id) const
+    {
+      return index_.find(id) != index_.end();
+    }
+
+    bool Contains(T id, Payload& payload) const
+    {
+      typename Index::const_iterator it = index_.find(id);
+      if (it == index_.end())
+      {
+        return false;
+      }
+      else
+      {
+        payload = it->second->second;
+        return true;
+      }
+    }
+
+    /**
+     * Return the number of elements in the cache.
+     * \return The number of elements.
+     **/
+    size_t GetSize() const
+    {
+      assert(index_.size() == queue_.size());
+      return queue_.size();
+    }
+
+    /**
+     * Check whether the cache index is empty.
+     * \return \c true iff the cache is empty.
+     **/
+    bool IsEmpty() const
+    {
+      return index_.empty();
+    }
+
+    const T& GetOldest() const;
+    
+    const Payload& GetOldestPayload() const;
+  };
+
+
+
+
+  /******************************************************************
+   ** Implementation of the template
+   ******************************************************************/
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::CheckInvariants() const
+  {
+#ifndef NDEBUG
+    assert(index_.size() == queue_.size());
+
+    for (typename Index::const_iterator 
+           it = index_.begin(); it != index_.end(); it++)
+    {
+      assert(it->second != queue_.end());
+      assert(it->second->first == it->first);
+    }
+#endif
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::Add(T id, Payload payload)
+  {
+    if (Contains(id))
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    queue_.push_front(std::make_pair(id, payload));
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::AddOrMakeMostRecent(T id, Payload payload)
+  {
+    typename Index::iterator it = index_.find(id);
+
+    if (it != index_.end())
+    {
+      // Already existing. Make it most recent.
+      std::pair<T, Payload> item = *(it->second);
+      item.second = payload;
+      queue_.erase(it->second);
+      queue_.push_front(item);
+    }
+    else
+    {
+      // New item
+      queue_.push_front(std::make_pair(id, payload));
+    }
+
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id, Payload updatedPayload)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    item.second = updatedPayload;
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  Payload LeastRecentlyUsedIndex<T, Payload>::Invalidate(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    Payload payload = it->second->second;
+    queue_.erase(it->second);
+    index_.erase(it);
+
+    CheckInvariants();
+    return payload;
+  }
+
+
+  template <typename T, typename Payload>
+  T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest(Payload& payload)
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+    payload = item.second;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+
+
+  template <typename T, typename Payload>
+  T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest()
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+
+
+  template <typename T, typename Payload>
+  const T& LeastRecentlyUsedIndex<T, Payload>::GetOldest() const
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return queue_.back().first;
+  }
+
+
+  template <typename T, typename Payload>
+  const Payload& LeastRecentlyUsedIndex<T, Payload>::GetOldestPayload() const
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return queue_.back().second;
+  }
+}
--- a/Core/Cache/MemoryCache.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/Core/Cache/MemoryCache.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -46,7 +46,7 @@
     {
       VLOG(1) << "Reusing a cache page";
       assert(p != NULL);
-      index_.TagAsMostRecent(id);
+      index_.MakeMostRecent(id);
       return *p;
     }
 
--- a/Core/Cache/MemoryCache.h	Fri Sep 13 11:10:58 2013 +0200
+++ b/Core/Cache/MemoryCache.h	Fri Sep 13 11:25:08 2013 +0200
@@ -33,7 +33,7 @@
 #pragma once
 
 #include <memory>
-#include "CacheIndex.h"
+#include "LeastRecentlyUsedIndex.h"
 #include "ICachePageProvider.h"
 
 namespace Orthanc
@@ -52,7 +52,7 @@
 
     ICachePageProvider& provider_;
     size_t cacheSize_;
-    CacheIndex<std::string, Page*>  index_;
+    LeastRecentlyUsedIndex<std::string, Page*>  index_;
 
     Page& Load(const std::string& id);
 
--- a/NEWS	Fri Sep 13 11:10:58 2013 +0200
+++ b/NEWS	Fri Sep 13 11:25:08 2013 +0200
@@ -1,6 +1,11 @@
 Pending changes in the mainline
 ===============================
 
+* Detection of stable patients/studies/series
+* C-Find SCU at the instance level
+* Link from modified to original resource in Orthanc Explorer
+* Fix of issue #8
+* Anonymization of the medical alerts tag (0010,2000)
 
 
 Version 0.6.0 (2013/07/16)
--- a/OrthancExplorer/explorer.html	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancExplorer/explorer.html	Fri Sep 13 11:25:08 2013 +0200
@@ -101,6 +101,9 @@
                 <li data-icon="info" data-theme="e" style="display:none">
                   <a href="#" id="patient-anonymized-from">Before anonymization</a>
                 </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="patient-modified-from">Before modification</a>
+                </li>
                 <li data-icon="gear"><a href="#" id="patient-archive">Download ZIP</a></li>
               </ul>
             </div>
@@ -144,6 +147,9 @@
                 <li data-icon="info" data-theme="e" style="display:none">
                   <a href="#" id="study-anonymized-from">Before anonymization</a>
                 </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="study-modified-from">Before modification</a>
+                </li>
                 <li data-icon="gear"><a href="#" id="study-archive">Download ZIP</a></li>
               </ul>
             </div>
@@ -189,6 +195,9 @@
                 <li data-icon="info" data-theme="e" style="display:none">
                   <a href="#" id="series-anonymized-from">Before anonymization</a>
                 </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="series-modified-from">Before modification</a>
+                </li>
                 <li data-icon="search"><a href="#" id="series-preview">Preview this series</a></li>
                 <li data-icon="gear"><a href="#" id="series-archive">Download ZIP</a></li>
               </ul>
@@ -231,6 +240,12 @@
 
               <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c">
                 <li data-role="list-divider">Access</li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="instance-anonymized-from">Before anonymization</a>
+                </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="instance-modified-from">Before modification</a>
+                </li>
                 <li data-icon="arrow-d"><a href="#" id="instance-download-dicom">Download the DICOM file</a></li>
                 <li data-icon="arrow-d"><a href="#" id="instance-download-json">Download the JSON file</a></li>
                 <li data-icon="search"><a href="#" id="instance-preview">Preview the instance</a></li>
--- a/OrthancExplorer/explorer.js	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancExplorer/explorer.js	Fri Sep 13 11:25:08 2013 +0200
@@ -361,14 +361,13 @@
 
 
 
-function SetupAnonymizedFrom(buttonSelector, resource, resourceType)
+function SetupAnonymizedOrModifiedFrom(buttonSelector, resource, resourceType, field)
 {
-  if ('AnonymizedFrom' in resource)
+  if (field in resource)
   {
     $(buttonSelector).closest('li').show();
     $(buttonSelector).click(function(e) {
-      window.location.assign('explorer.html#' + resourceType + '?uuid=' + resource.AnonymizedFrom);
-      //window.location.reload();
+      window.location.assign('explorer.html#' + resourceType + '?uuid=' + resource[field]);
     });
   }
   else
@@ -378,6 +377,7 @@
 }
 
 
+
 function RefreshPatient()
 {
   if ($.mobile.pageData) {
@@ -404,7 +404,8 @@
           target.append(FormatStudy(studies[i], '#study?uuid=' + studies[i].ID));
         }
 
-        SetupAnonymizedFrom('#patient-anonymized-from', patient, 'patient');
+        SetupAnonymizedOrModifiedFrom('#patient-anonymized-from', patient, 'patient', 'AnonymizedFrom');
+        SetupAnonymizedOrModifiedFrom('#patient-modified-from', patient, 'patient', 'ModifiedFrom');
 
         target.listview('refresh');
 
@@ -446,7 +447,8 @@
             .append(FormatStudy(study))
             .listview('refresh');
 
-          SetupAnonymizedFrom('#study-anonymized-from', study, 'study');
+          SetupAnonymizedOrModifiedFrom('#study-anonymized-from', study, 'study', 'AnonymizedFrom');
+          SetupAnonymizedOrModifiedFrom('#study-modified-from', study, 'study', 'ModifiedFrom');
 
           var target = $('#list-series');
           $('li', target).remove();
@@ -491,7 +493,8 @@
               .append(FormatSeries(series))
               .listview('refresh');
 
-            SetupAnonymizedFrom('#series-anonymized-from', series, 'series');
+            SetupAnonymizedOrModifiedFrom('#series-anonymized-from', series, 'series', 'AnonymizedFrom');
+            SetupAnonymizedOrModifiedFrom('#series-modified-from', series, 'series', 'ModifiedFrom');
 
             var target = $('#list-instances');
             $('li', target).remove();
@@ -595,6 +598,9 @@
               }
             });
 
+            SetupAnonymizedOrModifiedFrom('#instance-anonymized-from', instance, 'instance', 'AnonymizedFrom');
+            SetupAnonymizedOrModifiedFrom('#instance-modified-from', instance, 'instance', 'ModifiedFrom');
+
             currentPage = 'instance';
             currentUuid = $.mobile.pageData.uuid;
           });
--- a/OrthancServer/DatabaseWrapper.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/DatabaseWrapper.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -949,4 +949,50 @@
   {
     db_.Execute("DELETE FROM " + tableName);    
   }
+
+
+  bool DatabaseWrapper::IsExistingResource(int64_t internalId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT * FROM Resources WHERE internalId=?");
+    s.BindInt(0, internalId);
+    return s.Step();
+  }
+
+
+  void  DatabaseWrapper::LookupTagValue(std::list<int64_t>& result,
+                                        DicomTag tag,
+                                        const std::string& value)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT id FROM MainDicomTags WHERE tagGroup=? AND tagElement=? and value=?");
+
+    s.BindInt(0, tag.GetGroup());
+    s.BindInt(1, tag.GetElement());
+    s.BindString(2, value);
+
+    result.clear();
+
+    while (s.Step())
+    {
+      result.push_back(s.ColumnInt64(0));
+    }
+  }
+
+
+  void  DatabaseWrapper::LookupTagValue(std::list<int64_t>& result,
+                                        const std::string& value)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT id FROM MainDicomTags WHERE value=?");
+
+    s.BindString(0, value);
+
+    result.clear();
+
+    while (s.Step())
+    {
+      result.push_back(s.ColumnInt64(0));
+    }
+  }
 }
--- a/OrthancServer/DatabaseWrapper.h	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/DatabaseWrapper.h	Fri Sep 13 11:25:08 2013 +0200
@@ -223,5 +223,14 @@
     uint64_t IncrementGlobalSequence(GlobalProperty property);
 
     void ClearTable(const std::string& tableName);
+
+    bool IsExistingResource(int64_t internalId);
+
+    void LookupTagValue(std::list<int64_t>& result,
+                        DicomTag tag,
+                        const std::string& value);
+
+    void LookupTagValue(std::list<int64_t>& result,
+                        const std::string& value);
   };
 }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -294,7 +294,20 @@
       break;
 
     case FindRootModel_Instance:
-      DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE");
+      if (manufacturer_ == ModalityManufacturer_ClearCanvas)
+      {
+        // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
+        // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
+        // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
+        printf("CLEAR CANVAS\n");
+        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE");
+      }
+      else
+      {
+        printf("GENERIC\n");
+        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE");
+      }
+
       sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
 
       // Accession number
@@ -453,6 +466,7 @@
     distantAet_ = "ANY-SCP";
     distantPort_ = 104;
     distantHost_ = "127.0.0.1";
+    manufacturer_ = ModalityManufacturer_Generic;
 
     pimpl_->net_ = NULL;
     pimpl_->params_ = NULL;
@@ -476,6 +490,12 @@
     distantAet_ = aet;
   }
 
+  void DicomUserConnection::SetDistantManufacturer(ModalityManufacturer manufacturer)
+  {
+    Close();
+    manufacturer_ = manufacturer;
+  }
+
 
   void DicomUserConnection::SetDistantHost(const std::string& host)
   {
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Fri Sep 13 11:25:08 2013 +0200
@@ -33,6 +33,7 @@
 #pragma once
 
 #include "DicomFindAnswers.h"
+#include "../ServerEnumerations.h"
 
 #include <stdint.h>
 #include <boost/shared_ptr.hpp>
@@ -59,6 +60,7 @@
     std::string distantAet_;
     std::string distantHost_;
     uint16_t distantPort_;
+    ModalityManufacturer manufacturer_;
 
     void CheckIsOpen() const;
 
@@ -106,6 +108,13 @@
       return distantPort_;
     }
 
+    void SetDistantManufacturer(ModalityManufacturer manufacturer);
+
+    ModalityManufacturer GetDistantManufacturer() const
+    {
+      return manufacturer_;
+    }
+
     void Open();
 
     void Close();
--- a/OrthancServer/Internals/CommandDispatcher.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -41,9 +41,149 @@
 #include <boost/lexical_cast.hpp>
 #include <glog/logging.h>
 
+#define ORTHANC_PROMISCUOUS 1
+
 static OFBool    opt_rejectWithoutImplementationUID = OFFalse;
 
 
+
+#if ORTHANC_PROMISCUOUS == 1
+static
+DUL_PRESENTATIONCONTEXT *
+findPresentationContextID(LST_HEAD * head,
+                          T_ASC_PresentationContextID presentationContextID)
+{
+  DUL_PRESENTATIONCONTEXT *pc;
+  LST_HEAD **l;
+  OFBool found = OFFalse;
+
+  if (head == NULL)
+    return NULL;
+
+  l = &head;
+  if (*l == NULL)
+    return NULL;
+
+  pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l));
+  (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc));
+
+  while (pc && !found) {
+    if (pc->presentationContextID == presentationContextID) {
+      found = OFTrue;
+    } else {
+      pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l));
+    }
+  }
+  return pc;
+}
+
+
+/** accept all presenstation contexts for unknown SOP classes,
+ *  i.e. UIDs appearing in the list of abstract syntaxes
+ *  where no corresponding name is defined in the UID dictionary.
+ *  @param params pointer to association parameters structure
+ *  @param transferSyntax transfer syntax to accept
+ *  @param acceptedRole SCU/SCP role to accept
+ */
+static OFCondition acceptUnknownContextsWithTransferSyntax(
+  T_ASC_Parameters * params,
+  const char* transferSyntax,
+  T_ASC_SC_ROLE acceptedRole)
+{
+  OFCondition cond = EC_Normal;
+  int n, i, k;
+  DUL_PRESENTATIONCONTEXT *dpc;
+  T_ASC_PresentationContext pc;
+  OFBool accepted = OFFalse;
+  OFBool abstractOK = OFFalse;
+
+  n = ASC_countPresentationContexts(params);
+  for (i = 0; i < n; i++)
+  {
+    cond = ASC_getPresentationContext(params, i, &pc);
+    if (cond.bad()) return cond;
+    abstractOK = OFFalse;
+    accepted = OFFalse;
+
+    if (dcmFindNameOfUID(pc.abstractSyntax) == NULL)
+    {
+      abstractOK = OFTrue;
+
+      /* check the transfer syntax */
+      for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++)
+      {
+        if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0)
+        {
+          accepted = OFTrue;
+        }
+      }
+    }
+
+    if (accepted)
+    {
+      cond = ASC_acceptPresentationContext(
+        params, pc.presentationContextID,
+        transferSyntax, acceptedRole);
+      if (cond.bad()) return cond;
+    } else {
+      T_ASC_P_ResultReason reason;
+
+      /* do not refuse if already accepted */
+      dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext,
+                                      pc.presentationContextID);
+      if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE)))
+      {
+
+        if (abstractOK) {
+          reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;
+        } else {
+          reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED;
+        }
+        /*
+         * If previously this presentation context was refused
+         * because of bad transfer syntax let it stay that way.
+         */
+        if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED))
+          reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;
+
+        cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason);
+        if (cond.bad()) return cond;
+      }
+    }
+  }
+  return EC_Normal;
+}
+
+
+/** accept all presenstation contexts for unknown SOP classes,
+ *  i.e. UIDs appearing in the list of abstract syntaxes
+ *  where no corresponding name is defined in the UID dictionary.
+ *  This method is passed a list of "preferred" transfer syntaxes.
+ *  @param params pointer to association parameters structure
+ *  @param transferSyntax transfer syntax to accept
+ *  @param acceptedRole SCU/SCP role to accept
+ */
+static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes(
+  T_ASC_Parameters * params,
+  const char* transferSyntaxes[], int transferSyntaxCount,
+  T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT)
+{
+  OFCondition cond = EC_Normal;
+  /*
+  ** Accept in the order "least wanted" to "most wanted" transfer
+  ** syntax.  Accepting a transfer syntax will override previously
+  ** accepted transfer syntaxes.
+  */
+  for (int i = transferSyntaxCount - 1; i >= 0; i--)
+  {
+    cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole);
+    if (cond.bad()) return cond;
+  }
+  return cond;
+}
+#endif
+
+
 namespace Orthanc
 {
   namespace Internals
@@ -150,6 +290,18 @@
         return NULL;
       }
 
+#if ORTHANC_PROMISCUOUS == 1
+      /* accept everything not known not to be a storage SOP class */
+      cond = acceptUnknownContextsWithPreferredTransferSyntaxes(
+        assoc->params, transferSyntaxes, numTransferSyntaxes);
+      if (cond.bad())
+      {
+        LOG(INFO) << cond.text();
+        AssociationCleanup(assoc);
+        return NULL;
+      }
+#endif
+
       /* set our app title */
       ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str());
 
--- a/OrthancServer/Internals/StoreScp.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -117,7 +117,7 @@
             FromDcmtkBridge::Convert(summary, **imageDataSet);
             FromDcmtkBridge::ToJson(dicomJson, **imageDataSet);       
 
-            if (FromDcmtkBridge::SaveToMemoryBuffer(buffer, *imageDataSet) < 0)
+            if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, *imageDataSet))
             {
               LOG(ERROR) << "cannot write DICOM file to memory";
               rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
--- a/OrthancServer/OrthancInitialization.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -230,7 +230,8 @@
   void GetDicomModality(const std::string& name,
                         std::string& aet,
                         std::string& address,
-                        int& port)
+                        int& port,
+                        ModalityManufacturer& manufacturer)
   {
     boost::mutex::scoped_lock lock(globalMutex_);
 
@@ -241,7 +242,8 @@
 
     const Json::Value& modalities = (*configuration_) ["DicomModalities"];
     if (modalities.type() != Json::objectValue ||
-        !modalities.isMember(name))
+        !modalities.isMember(name) ||
+        (modalities[name].size() != 3 && modalities[name].size() != 4))
     {
       throw OrthancException("");
     }
@@ -251,6 +253,15 @@
       aet = modalities[name].get(0u, "").asString();
       address = modalities[name].get(1u, "").asString();
       port = modalities[name].get(2u, "").asInt();
+
+      if (modalities[name].size() == 4)
+      {
+        manufacturer = StringToModalityManufacturer(modalities[name].get(3u, "").asString());
+      }
+      else
+      {
+        manufacturer = ModalityManufacturer_Generic;
+      }
     }
     catch (...)
     {
--- a/OrthancServer/OrthancInitialization.h	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/OrthancInitialization.h	Fri Sep 13 11:25:08 2013 +0200
@@ -37,6 +37,7 @@
 #include <json/json.h>
 #include <stdint.h>
 #include "../Core/HttpServer/MongooseServer.h"
+#include "ServerEnumerations.h"
 
 namespace Orthanc
 {
@@ -56,7 +57,8 @@
   void GetDicomModality(const std::string& name,
                         std::string& aet,
                         std::string& address,
-                        int& port);
+                        int& port,
+                        ModalityManufacturer& manufacturer);
 
   void GetOrthancPeer(const std::string& name,
                       std::string& url,
--- a/OrthancServer/OrthancRestApi.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/OrthancRestApi.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -76,11 +76,13 @@
   {
     std::string aet, address;
     int port;
-    GetDicomModality(name, aet, address, port);
+    ModalityManufacturer manufacturer;
+    GetDicomModality(name, aet, address, port, manufacturer);
     connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC"));
     connection.SetDistantApplicationEntityTitle(aet);
     connection.SetDistantHost(address);
     connection.SetDistantPort(port);
+    connection.SetDistantManufacturer(manufacturer);
     connection.Open();
   }
 
@@ -179,6 +181,34 @@
     call.GetOutput().AnswerJson(result);
   }
 
+  static void DicomFindInstance(RestApi::PostCall& call)
+  {
+    DicomMap m;
+    DicomMap::SetupFindInstanceTemplate(m);
+    if (!MergeQueryAndTemplate(m, call.GetPostBody()))
+    {
+      return;
+    }
+
+    if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 &&
+         m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) ||
+        m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 ||
+        m.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2)
+    {
+      return;
+    }        
+         
+    DicomUserConnection connection;
+    ConnectToModality(connection, call.GetUriComponent("id", ""));
+  
+    DicomFindAnswers answers;
+    connection.FindInstance(answers, m);
+
+    Json::Value result;
+    answers.ToJson(result);
+    call.GetOutput().AnswerJson(result);
+  }
+
   static void DicomFind(RestApi::PostCall& call)
   {
     DicomMap m;
@@ -934,6 +964,7 @@
       result.append("find-patient");
       result.append("find-study");
       result.append("find-series");
+      result.append("find-instance");
       result.append("find");
       result.append("store");
       call.GetOutput().AnswerJson(result);
@@ -1002,8 +1033,6 @@
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    target.clear();
-
     for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++)
     {
       std::string name = removals[i].asString();
@@ -1023,8 +1052,6 @@
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    target.clear();
-
     Json::Value::Members members = replacements.getMemberNames();
     for (size_t i = 0; i < members.size(); i++)
     {
@@ -1070,7 +1097,7 @@
     removals.insert(DicomTag(0x0008, 0x1155));  // Referenced SOP Instance UID 
     removals.insert(DicomTag(0x0008, 0x2111));  // Derivation Description 
     removals.insert(DicomTag(0x0010, 0x0010));  // Patient's Name 
-    removals.insert(DicomTag(0x0010, 0x0020));  // Patient ID
+    //removals.insert(DicomTag(0x0010, 0x0020));  // Patient ID => cf. below (*)
     removals.insert(DicomTag(0x0010, 0x0030));  // Patient's Birth Date 
     removals.insert(DicomTag(0x0010, 0x0032));  // Patient's Birth Time 
     removals.insert(DicomTag(0x0010, 0x0040));  // Patient's Sex 
@@ -1086,8 +1113,8 @@
     removals.insert(DicomTag(0x0010, 0x4000));  // Patient Comments 
     removals.insert(DicomTag(0x0018, 0x1000));  // Device Serial Number 
     removals.insert(DicomTag(0x0018, 0x1030));  // Protocol Name 
-    //removals.insert(DicomTag(0x0020, 0x000d));  // Study Instance UID => generated below
-    //removals.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => generated below
+    //removals.insert(DicomTag(0x0020, 0x000d));  // Study Instance UID => cf. below (*)
+    //removals.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => cf. below (*)
     removals.insert(DicomTag(0x0020, 0x0010));  // Study ID 
     removals.insert(DicomTag(0x0020, 0x0052));  // Frame of Reference UID 
     removals.insert(DicomTag(0x0020, 0x0200));  // Synchronization Frame of Reference UID 
@@ -1099,30 +1126,25 @@
     removals.insert(DicomTag(0x3006, 0x0024));  // Referenced Frame of Reference UID 
     removals.insert(DicomTag(0x3006, 0x00c2));  // Related Frame of Reference UID 
 
+    /**
+     *   (*) Patient ID, Study Instance UID and Series Instance UID
+     * are modified by "AnonymizeInstance()" if anonymizing a single
+     * instance, or by "RetrieveMappedUid()" if anonymizing a
+     * patient/study/series.
+     **/
+
+
     // Some more removals (from the experience of DICOM files at the CHU of Liege)
     removals.insert(DicomTag(0x0010, 0x1040));  // Patient's Address
     removals.insert(DicomTag(0x0032, 0x1032));  // Requesting Physician
     removals.insert(DicomTag(0x0010, 0x2154));  // PatientTelephoneNumbers
+    removals.insert(DicomTag(0x0010, 0x2000));  // Medical Alerts
 
     // Set the DeidentificationMethod tag
     replacements.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"));
 
-    // Set the PatientIdentityRemoved
+    // Set the PatientIdentityRemoved tag
     replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES"));
-
-    // Generate random study UID if not specified
-    if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end())
-    {
-      replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, 
-                                         FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study)));
-    }
-
-    // Generate random series UID if not specified
-    if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end())
-    {
-      replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, 
-                                         FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series)));
-    }
   }
 
 
@@ -1225,13 +1247,6 @@
         replacements.insert(std::make_pair(DicomTag(0x0010, 0x0010), GeneratePatientName(context)));
       }
 
-      // Generate random Patient's ID if none is specified
-      if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end())
-      {
-        replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, 
-                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient)));
-      }
-
       return true;
     }
     else
@@ -1264,14 +1279,23 @@
                                 UidMap& uidMap)
   {
     std::auto_ptr<DicomTag> tag;
-    if (level == DicomRootLevel_Series)
+
+    switch (level)
     {
-      tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID));
-    }
-    else
-    {
-      assert(level == DicomRootLevel_Study);
-      tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID));
+      case DicomRootLevel_Series:
+        tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID));
+        break;
+
+      case DicomRootLevel_Study:
+        tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID));
+        break;
+
+      case DicomRootLevel_Patient:
+        tag.reset(new DicomTag(DICOM_TAG_PATIENT_ID));
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
     }
 
     std::string original;
@@ -1301,7 +1325,8 @@
   }
 
 
-  static void AnonymizeOrModifyResource(Removals& removals,
+  static void AnonymizeOrModifyResource(bool isAnonymization,
+                                        Removals& removals,
                                         Replacements& replacements,
                                         bool removePrivateTags,
                                         MetadataType metadataType,
@@ -1338,15 +1363,20 @@
       LOG(INFO) << "Modifying instance " << *it;
       ParsedDicomFile& original = context.GetDicomFile(*it);
 
-      bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap);
+      DicomInstanceHasher originalHasher = original.GetHasher();
 
-      bool isNewStudy = false;
-      if (resourceType == ResourceType_Study ||
-          resourceType == ResourceType_Patient)
+      if (isFirst && !isAnonymization)
       {
-        isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap);
+        // If modifying a study or a series, keep the original patient ID.
+        std::string patientId = originalHasher.GetPatientId();
+        uidMap[std::make_pair(DicomRootLevel_Patient, patientId)] = patientId;
       }
 
+      bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap);
+      bool isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap);
+      bool isNewPatient = RetrieveMappedUid(original, DicomRootLevel_Patient, replacements, uidMap);
+
+
       /**
        * Compute the resulting DICOM instance and store it into the Orthanc store.
        **/
@@ -1367,7 +1397,6 @@
        **/
 
       DicomInstanceHasher modifiedHasher = modified->GetHasher();
-      DicomInstanceHasher originalHasher = original.GetHasher();
 
       if (isNewSeries)
       {
@@ -1381,6 +1410,12 @@
                                        metadataType, originalHasher.HashStudy());
       }
 
+      if (isNewPatient)
+      {
+        context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), 
+                                       metadataType, originalHasher.HashPatient());
+      }
+
       assert(*it == originalHasher.HashInstance());
       assert(modifiedInstance == modifiedHasher.HashInstance());
       context.GetIndex().SetMetadata(modifiedInstance, metadataType, *it);
@@ -1392,9 +1427,6 @@
 
       if (isFirst)
       {
-        context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), 
-                                       metadataType, originalHasher.HashPatient());
-
         std::string newId;
 
         switch (resourceType)
@@ -1449,6 +1481,27 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
+      // Generate random patient ID if not specified
+      if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end())
+      {
+        replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, 
+                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient)));
+      }
+
+      // Generate random study UID if not specified
+      if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end())
+      {
+        replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, 
+                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study)));
+      }
+
+      // Generate random series UID if not specified
+      if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end())
+      {
+        replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, 
+                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series)));
+      }
+
       AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call);
     }
   }
@@ -1462,7 +1515,7 @@
 
     if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, 
                                 MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, 
                                 ResourceType_Series, call);
     }
@@ -1477,7 +1530,7 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, 
                                 MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, 
                                 ResourceType_Series, call);
     }
@@ -1492,7 +1545,7 @@
 
     if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, 
                                 MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, 
                                 ResourceType_Study, call);
     }
@@ -1507,14 +1560,14 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, 
                                 MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, 
                                 ResourceType_Study, call);
     }
   }
 
 
-  static void ModifyPatientInplace(RestApi::PostCall& call)
+  /*static void ModifyPatientInplace(RestApi::PostCall& call)
   {
     Removals removals;
     Replacements replacements;
@@ -1522,11 +1575,11 @@
 
     if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, 
                                 MetadataType_ModifiedFrom, ChangeType_ModifiedPatient, 
                                 ResourceType_Patient, call);
     }
-  }
+    }*/
 
 
   static void AnonymizePatientInplace(RestApi::PostCall& call)
@@ -1537,7 +1590,7 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, 
                                 MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient, 
                                 ResourceType_Patient, call);
     }
@@ -1794,6 +1847,7 @@
     Register("/modalities/{id}/find-patient", DicomFindPatient);
     Register("/modalities/{id}/find-study", DicomFindStudy);
     Register("/modalities/{id}/find-series", DicomFindSeries);
+    Register("/modalities/{id}/find-instance", DicomFindInstance);
     Register("/modalities/{id}/find", DicomFind);
     Register("/modalities/{id}/store", DicomStore);
 
@@ -1804,7 +1858,7 @@
     Register("/instances/{id}/modify", ModifyInstance);
     Register("/series/{id}/modify", ModifySeriesInplace);
     Register("/studies/{id}/modify", ModifyStudyInplace);
-    Register("/patients/{id}/modify", ModifyPatientInplace);
+    //Register("/patients/{id}/modify", ModifyPatientInplace);
 
     Register("/instances/{id}/anonymize", AnonymizeInstance);
     Register("/series/{id}/anonymize", AnonymizeSeriesInplace);
--- a/OrthancServer/ServerEnumerations.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -205,6 +205,15 @@
       case ChangeType_ModifiedPatient:
         return "ModifiedPatient";
 
+      case ChangeType_StablePatient:
+        return "StablePatient";
+
+      case ChangeType_StableStudy:
+        return "StableStudy";
+
+      case ChangeType_StableSeries:
+        return "StableSeries";
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -247,4 +256,39 @@
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
+
+
+  const char* EnumerationToString(ModalityManufacturer manufacturer)
+  {
+    switch (manufacturer)
+    {
+      case ModalityManufacturer_Generic:
+        return "Generic";
+
+      case ModalityManufacturer_ClearCanvas:
+        return "ClearCanvas";
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer)
+  {
+    if (manufacturer == "Generic")
+    {
+      return ModalityManufacturer_Generic;
+    }
+    else if (manufacturer == "ClearCanvas")
+    {
+      return ModalityManufacturer_ClearCanvas;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
 }
--- a/OrthancServer/ServerEnumerations.h	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/ServerEnumerations.h	Fri Sep 13 11:25:08 2013 +0200
@@ -51,6 +51,12 @@
     StoreStatus_FilteredOut     // Removed by NewInstanceFilter
   };
 
+  enum ModalityManufacturer
+  {
+    ModalityManufacturer_Generic,
+    ModalityManufacturer_ClearCanvas
+  };
+
 
   /**
    * WARNING: Do not change the explicit values in the enumerations
@@ -100,7 +106,10 @@
     ChangeType_ModifiedStudy = 8,
     ChangeType_ModifiedSeries = 9,
     ChangeType_AnonymizedPatient = 10,
-    ChangeType_ModifiedPatient = 11
+    ChangeType_ModifiedPatient = 11,
+    ChangeType_StablePatient = 12,
+    ChangeType_StableStudy = 13,
+    ChangeType_StableSeries = 14
   };
 
   void InitializeServerEnumerations();
@@ -123,6 +132,10 @@
 
   const char* EnumerationToString(ChangeType type);
 
+  const char* EnumerationToString(ModalityManufacturer manufacturer);
+
+  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer);
+
   ResourceType GetParentResourceType(ResourceType type);
 
   ResourceType GetChildResourceType(ResourceType type);
--- a/OrthancServer/ServerIndex.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/ServerIndex.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -37,6 +37,7 @@
 #endif
 
 #include "EmbeddedResources.h"
+#include "OrthancInitialization.h"
 #include "../Core/Toolbox.h"
 #include "../Core/Uuid.h"
 #include "../Core/DicomFormat/DicomArray.h"
@@ -187,6 +188,27 @@
   };
 
 
+  struct ServerIndex::UnstableResourcePayload
+  {
+    Orthanc::ResourceType type_;
+    boost::posix_time::ptime time_;
+
+    UnstableResourcePayload() : type_(Orthanc::ResourceType_Instance)
+    {
+    }
+
+    UnstableResourcePayload(Orthanc::ResourceType type) : type_(type)
+    {
+      time_ = boost::posix_time::second_clock::local_time();
+    }
+
+    unsigned int GetAge() const
+    {
+      return (boost::posix_time::second_clock::local_time() - time_).total_seconds();
+    }
+  };
+
+
   bool ServerIndex::DeleteResource(Json::Value& target,
                                    const std::string& uuid,
                                    ResourceType expectedType)
@@ -227,18 +249,40 @@
   }
 
 
-  static void FlushThread(DatabaseWrapper* db,
-                          boost::mutex* mutex,
-                          unsigned int sleep)
+  void ServerIndex::FlushThread(ServerIndex* that)
   {
+    unsigned int sleep;
+
+    try
+    {
+      std::string sleepString = that->db_->GetGlobalProperty(GlobalProperty_FlushSleep);
+      sleep = boost::lexical_cast<unsigned int>(sleepString);
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      // By default, wait for 10 seconds before flushing
+      sleep = 10;
+    }
+
     LOG(INFO) << "Starting the database flushing thread (sleep = " << sleep << ")";
 
-    while (1)
+    unsigned int count = 0;
+
+    while (!that->done_)
     {
-      boost::this_thread::sleep(boost::posix_time::seconds(sleep));
-      boost::mutex::scoped_lock lock(*mutex);
-      db->FlushToDisk();
+      boost::this_thread::sleep(boost::posix_time::seconds(1));
+      count++;
+      if (count < sleep)
+      {
+        continue;
+      }
+
+      boost::mutex::scoped_lock lock(that->mutex_);
+      that->db_->FlushToDisk();
+      count = 0;
     }
+
+    LOG(INFO) << "Stopping the database flushing thread";
   }
 
 
@@ -284,6 +328,7 @@
 
   ServerIndex::ServerIndex(ServerContext& context,
                            const std::string& dbPath) : 
+    done_(false),
     maximumStorageSize_(0),
     maximumPatients_(0)
   {
@@ -314,25 +359,24 @@
     // execution of Orthanc
     StandaloneRecycling();
 
-    unsigned int sleep;
-    try
-    {
-      std::string sleepString = db_->GetGlobalProperty(GlobalProperty_FlushSleep);
-      sleep = boost::lexical_cast<unsigned int>(sleepString);
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      // By default, wait for 10 seconds before flushing
-      sleep = 10;
-    }
-
-    flushThread_ = boost::thread(FlushThread, db_.get(), &mutex_, sleep);
+    flushThread_ = boost::thread(FlushThread, this);
+    unstableResourcesMonitorThread_ = boost::thread(UnstableResourcesMonitorThread, this);
   }
 
 
   ServerIndex::~ServerIndex()
   {
-    LOG(INFO) << "Stopping the database flushing thread";
+    done_ = true;
+
+    if (flushThread_.joinable())
+    {
+      flushThread_.join();
+    }
+
+    if (unstableResourcesMonitorThread_.joinable())
+    {
+      unstableResourcesMonitorThread_.join();
+    }
   }
 
 
@@ -479,7 +523,6 @@
       db_->SetMetadata(series, MetadataType_LastUpdate, now);
       db_->SetMetadata(study, MetadataType_LastUpdate, now);
       db_->SetMetadata(patient, MetadataType_LastUpdate, now);
-
       db_->SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet);
 
       const DicomValue* value;
@@ -501,6 +544,11 @@
         db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series);
       }
 
+      // Mark the parent resources of this instance as unstable
+      MarkAsUnstable(patient, ResourceType_Patient);
+      MarkAsUnstable(study, ResourceType_Study);
+      MarkAsUnstable(series, ResourceType_Series);
+
       t.Commit(instanceSize);
 
       return StoreStatus_Success;
@@ -756,6 +804,13 @@
     if (tmp.size() != 0)
       result["ModifiedFrom"] = tmp;
 
+    if (type == ResourceType_Patient ||
+        type == ResourceType_Study ||
+        type == ResourceType_Series)
+    {
+      result["IsStable"] = !unstableResources_.Contains(id);
+    }
+
     return true;
   }
 
@@ -1343,4 +1398,112 @@
         break;
     }
   }
+
+
+  void ServerIndex::UnstableResourcesMonitorThread(ServerIndex* that)
+  {
+    int stableAge = GetGlobalIntegerParameter("StableAge", 60);
+    if (stableAge <= 0)
+    {
+      stableAge = 60;
+    }
+
+    LOG(INFO) << "Starting the monitor for stable resources (stable age = " << stableAge << ")";
+
+    while (!that->done_)
+    {
+      // Check for stable resources each second
+      boost::this_thread::sleep(boost::posix_time::seconds(1));
+
+      boost::mutex::scoped_lock lock(that->mutex_);
+
+      while (!that->unstableResources_.IsEmpty() &&
+             that->unstableResources_.GetOldestPayload().GetAge() > static_cast<unsigned int>(stableAge))
+      {
+        // This DICOM resource has not received any new instance for
+        // some time. It can be considered as stable.
+          
+        UnstableResourcePayload payload;
+        int64_t id = that->unstableResources_.RemoveOldest(payload);
+
+        // Ensure that the resource is still existing before logging the change
+        if (that->db_->IsExistingResource(id))
+        {
+          switch (payload.type_)
+          {
+            case Orthanc::ResourceType_Patient:
+              that->db_->LogChange(ChangeType_StablePatient, id, ResourceType_Patient);
+              break;
+
+            case Orthanc::ResourceType_Study:
+              that->db_->LogChange(ChangeType_StableStudy, id, ResourceType_Study);
+              break;
+
+            case Orthanc::ResourceType_Series:
+              that->db_->LogChange(ChangeType_StableSeries, id, ResourceType_Series);
+              break;
+
+            default:
+              throw OrthancException(ErrorCode_InternalError);
+          }
+
+          //LOG(INFO) << "Stable resource: " << EnumerationToString(payload.type_) << " " << id;
+        }
+      }
+    }
+
+    LOG(INFO) << "Closing the monitor thread for stable resources";
+  }
+  
+
+  void ServerIndex::MarkAsUnstable(int64_t id,
+                                   Orthanc::ResourceType type)
+  {
+    // WARNING: Before calling this method, "mutex_" must be locked.
+
+    assert(type == Orthanc::ResourceType_Patient ||
+           type == Orthanc::ResourceType_Study ||
+           type == Orthanc::ResourceType_Series);
+
+    unstableResources_.AddOrMakeMostRecent(id, type);
+    //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id;
+  }
+
+
+
+  void ServerIndex::LookupTagValue(std::list<std::string>& result,
+                                   DicomTag tag,
+                                   const std::string& value)
+  {
+    result.clear();
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    std::list<int64_t> id;
+    db_->LookupTagValue(id, tag, value);
+
+    for (std::list<int64_t>::const_iterator 
+           it = id.begin(); it != id.end(); it++)
+    {
+      result.push_back(db_->GetPublicId(*it));
+    }
+  }
+
+
+  void ServerIndex::LookupTagValue(std::list<std::string>& result,
+                                   const std::string& value)
+  {
+    result.clear();
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    std::list<int64_t> id;
+    db_->LookupTagValue(id, value);
+
+    for (std::list<int64_t>::const_iterator 
+           it = id.begin(); it != id.end(); it++)
+    {
+      result.push_back(db_->GetPublicId(*it));
+    }
+  }
 }
--- a/OrthancServer/ServerIndex.h	Fri Sep 13 11:10:58 2013 +0200
+++ b/OrthancServer/ServerIndex.h	Fri Sep 13 11:25:08 2013 +0200
@@ -34,6 +34,7 @@
 
 #include <boost/thread.hpp>
 #include <boost/noncopyable.hpp>
+#include "../Core/Cache/LeastRecentlyUsedIndex.h"
 #include "../Core/SQLite/Connection.h"
 #include "../Core/DicomFormat/DicomMap.h"
 #include "../Core/DicomFormat/DicomInstanceHasher.h"
@@ -55,17 +56,25 @@
   {
   private:
     class Transaction;
+    struct UnstableResourcePayload;
 
+    bool done_;
     boost::mutex mutex_;
     boost::thread flushThread_;
+    boost::thread unstableResourcesMonitorThread_;
 
     std::auto_ptr<Internals::ServerIndexListener> listener_;
     std::auto_ptr<DatabaseWrapper> db_;
+    LeastRecentlyUsedIndex<int64_t, UnstableResourcePayload>  unstableResources_;
 
     uint64_t currentStorageSize_;
     uint64_t maximumStorageSize_;
     unsigned int maximumPatients_;
 
+    static void FlushThread(ServerIndex* that);
+
+    static void UnstableResourcesMonitorThread(ServerIndex* that);
+
     void MainDicomTagsToJson(Json::Value& result,
                              int64_t resourceId);
 
@@ -78,6 +87,9 @@
 
     void StandaloneRecycling();
 
+    void MarkAsUnstable(int64_t id,
+                        Orthanc::ResourceType type);
+
   public:
     typedef std::list<FileInfo> Attachments;
 
@@ -174,5 +186,12 @@
 
     void GetStatistics(Json::Value& target,
                        const std::string& publicId);
+
+    void LookupTagValue(std::list<std::string>& result,
+                        DicomTag tag,
+                        const std::string& value);
+
+    void LookupTagValue(std::list<std::string>& result,
+                        const std::string& value);
   };
 }
--- a/Resources/CMake/Compiler.cmake	Fri Sep 13 11:10:58 2013 +0200
+++ b/Resources/CMake/Compiler.cmake	Fri Sep 13 11:25:08 2013 +0200
@@ -29,28 +29,6 @@
 
 
 if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
-  if (DEBIAN_FORCE_HARDENING)
-    execute_process(
-      COMMAND dpkg-buildflags --get CPPFLAGS 
-      OUTPUT_VARIABLE DEBIAN_CPP_FLAGS
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
-    execute_process(
-      COMMAND dpkg-buildflags --get CFLAGS 
-      OUTPUT_VARIABLE DEBIAN_C_FLAGS
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
-    execute_process(
-      COMMAND dpkg-buildflags --get CXXFLAGS 
-      OUTPUT_VARIABLE DEBIAN_CXX_FLAGS
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
-    execute_process(
-      COMMAND dpkg-buildflags --get LDFLAGS 
-      OUTPUT_VARIABLE DEBIAN_LD_FLAGS
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
-
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DEBIAN_C_FLAGS} ${DEBIAN_CPP_FLAGS}")
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEBIAN_CXX_FLAGS} ${DEBIAN_CPP_FLAGS}")
-  endif()
-
   if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
     SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive")
   endif()
@@ -59,9 +37,9 @@
     -D_LARGEFILE64_SOURCE=1 
     -D_FILE_OFFSET_BITS=64
     )
-  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed ${DEBIAN_LD_FLAGS}")
-  set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined ${DEBIAN_LD_FLAGS}")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined ${DEBIAN_LD_FLAGS}")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
+  set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
 
   # Remove the "-rdynamic" option
   # http://www.mail-archive.com/cmake@cmake.org/msg08837.html
--- a/Resources/CMake/MongooseConfiguration.cmake	Fri Sep 13 11:10:58 2013 +0200
+++ b/Resources/CMake/MongooseConfiguration.cmake	Fri Sep 13 11:25:08 2013 +0200
@@ -35,9 +35,11 @@
   endif()
 
 
-  if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND ${CMAKE_COMPILER_IS_GNUCXX})
-    # This is a patch for MinGW64
-    add_definitions(-D_TIMESPEC_DEFINED=1)
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    if (${CMAKE_COMPILER_IS_GNUCXX})
+      # This is a patch for MinGW64
+      add_definitions(-D_TIMESPEC_DEFINED=1)
+    endif()
   endif()
 
   source_group(ThirdParty\\Mongoose REGULAR_EXPRESSION ${MONGOOSE_SOURCES_DIR}/.*)
--- a/Resources/Configuration.json	Fri Sep 13 11:10:58 2013 +0200
+++ b/Resources/Configuration.json	Fri Sep 13 11:25:08 2013 +0200
@@ -33,13 +33,6 @@
   "LuaScripts" : [
   ],
 
-  // Dictionary of symbolic names for the user-defined metadata. Each
-  // entry must map a number between 1024 and 65535 to an unique
-  // string.
-  "UserMetadata" : {
-    // "Sample" : 1024
-  },
-
 
 
   /**
@@ -103,6 +96,14 @@
      * command line "storescp 2000".
      **/
     // "sample" : [ "STORESCP", "localhost", 2000 ]
+
+    /**
+     * A fourth parameter is available to enable patches for a
+     * specific PACS manufacturer. The allowed values are currently
+     * "Generic" (default value) and "ClearCanvas". This parameter is
+     * case-sensitive.
+     **/
+    // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ]
   },
 
   // The list of the known Orthanc peers
@@ -114,5 +115,22 @@
      **/
     // "peer"  : [ "http://localhost:8043/", "alice", "alicePassword" ]
     // "peer2" : [ "http://localhost:8044/" ]
-  }
+  },
+
+
+
+  /**
+   * Advanced options
+   **/
+
+  // Dictionary of symbolic names for the user-defined metadata. Each
+  // entry must map a number between 1024 and 65535 to an unique
+  // string.
+  "UserMetadata" : {
+    // "Sample" : 1024
+  },
+
+  // Number of seconds without receiving any instance before a
+  // patient, a study or a series is considered as stable.
+  "StableAge" : 60
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/EclipseCodingStyle.xml	Fri Sep 13 11:25:08 2013 +0200
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<profiles version="1">
+<profile kind="CodeFormatterProfile" name="Orthanc" version="1">
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.lineSplit" value="80"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_member_access" value="0"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_base_types" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_constructor_initializer_list" value="0"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_exception_specification" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_base_types" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_access_specifier" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_exception_specification" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_arguments" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.comment.min_distance_between_code_and_line_comment" value="1"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_expressions_in_array_initializer" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_declarator_list" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_bracket" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.tabulation.size" value="2"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_enumerator_list" value="49"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_declarator_list" value="16"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_empty_lines" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_method_declaration" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_arguments" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_base_clause" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.join_wrapped_lines" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_declarator_list" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_arguments_in_method_invocation" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.comment.never_indent_line_comments_on_first_column" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_brackets" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_bracket" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_parameters_in_method_declaration" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_type_declaration" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_arguments" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_expression_list" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_parameters" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.continuation_indentation" value="2"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_expression_list" value="0"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_parameters" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression" value="34"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_extra_spaces" value="0"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_compare_to_type_header" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_namespace_header" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_compact_if" value="16"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_assignment" value="16"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression_chain" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_parameters" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_expression_list" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_exception_specification" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_identifier_in_function_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_base_clause_in_type_declaration" value="80"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_exception_specification" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_declaration_compare_to_template_header" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_body" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_binary_expression" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_block" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_arguments" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_parameters" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.tabulation.char" value="space"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_parameters" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_colon_in_constructor_initializer_list" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block_in_case" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.compact_else_if" value="true"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_template_declaration" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_base_clause" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_switch" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.alignment_for_overloaded_left_shift_chain" value="18"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.indentation.size" value="2"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_namespace_declaration" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_arguments" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.brace_position_for_array_initializer" value="next_line"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_namespace_declaration" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_bracket" value="do not insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_while_in_do_statement" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_parameters" value="insert"/>
+<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_arguments" value="do not insert"/>
+</profile>
+</profiles>
--- a/Resources/Samples/RestApi/CMakeLists.txt	Fri Sep 13 11:10:58 2013 +0200
+++ b/Resources/Samples/RestApi/CMakeLists.txt	Fri Sep 13 11:25:08 2013 +0200
@@ -22,8 +22,8 @@
   UPDATE_COMMAND ""
   SOURCE_DIR ${CMAKE_BINARY_DIR}/Orthanc/src/orthanc/
   CMAKE_COMMAND ${CMAKE_COMMAND}
-  CMAKE_ARGS -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DUSE_DYNAMIC_GOOGLE_LOG=OFF -DUSE_DYNAMIC_SQLITE=OFF -DONLY_CORE_LIBRARY=ON -DENABLE_SSL=OFF ${TOOLCHAIN}
-  BUILD_COMMAND $(MAKE)
+  CMAKE_ARGS -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DUSE_DYNAMIC_GOOGLE_LOG=OFF -DUSE_DYNAMIC_SQLITE=OFF -DENABLE_SSL=OFF ${TOOLCHAIN}
+  BUILD_COMMAND $(MAKE) CoreLibrary
   INSTALL_COMMAND ""
   BUILD_IN_SOURCE 0
   )
--- a/THANKS	Fri Sep 13 11:10:58 2013 +0200
+++ b/THANKS	Fri Sep 13 11:25:08 2013 +0200
@@ -16,6 +16,7 @@
 * Will Ryder (will.ryder@sydney.edu.au), for improvements with the
   handling of series with temporal positions (fMRI and dynamic PET).
 * Ryan Walklin (ryanwalklin@gmail.com), for Mac OS X build.
+* Peter Somlo (peter.somlo@gmail.com), for ClearCanvas support.
 
 
 Artwork
--- a/UnitTests/MemoryCache.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/UnitTests/MemoryCache.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -8,46 +8,53 @@
 #include "../Core/Cache/MemoryCache.h"
 
 
-TEST(CacheIndex, Basic)
+TEST(LRU, Basic)
 {
-  Orthanc::CacheIndex<std::string> r;
+  Orthanc::LeastRecentlyUsedIndex<std::string> r;
   
   r.Add("d");
   r.Add("a");
   r.Add("c");
   r.Add("b");
 
-  r.TagAsMostRecent("a");
-  r.TagAsMostRecent("d");
-  r.TagAsMostRecent("b");
-  r.TagAsMostRecent("c");
-  r.TagAsMostRecent("d");
-  r.TagAsMostRecent("c");
+  r.MakeMostRecent("a");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("b");
+  r.MakeMostRecent("c");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("c");
 
+  ASSERT_EQ("a", r.GetOldest());
   ASSERT_EQ("a", r.RemoveOldest());
+  ASSERT_EQ("b", r.GetOldest());
   ASSERT_EQ("b", r.RemoveOldest());
+  ASSERT_EQ("d", r.GetOldest());
   ASSERT_EQ("d", r.RemoveOldest());
+  ASSERT_EQ("c", r.GetOldest());
   ASSERT_EQ("c", r.RemoveOldest());
 
   ASSERT_TRUE(r.IsEmpty());
+
+  ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException);
+  ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException);
 }
 
 
-TEST(CacheIndex, Payload)
+TEST(LRU, Payload)
 {
-  Orthanc::CacheIndex<std::string, int> r;
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
   
   r.Add("a", 420);
   r.Add("b", 421);
   r.Add("c", 422);
   r.Add("d", 423);
 
-  r.TagAsMostRecent("a");
-  r.TagAsMostRecent("d");
-  r.TagAsMostRecent("b");
-  r.TagAsMostRecent("c");
-  r.TagAsMostRecent("d");
-  r.TagAsMostRecent("c");
+  r.MakeMostRecent("a");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("b");
+  r.MakeMostRecent("c");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("c");
 
   ASSERT_TRUE(r.Contains("b"));
   ASSERT_EQ(421, r.Invalidate("b"));
@@ -58,14 +65,76 @@
   ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p);
   ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p);
 
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(420, r.GetOldestPayload());
   ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p);
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(423, r.GetOldestPayload());
   ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p);
+
+  ASSERT_EQ("c", r.GetOldest());
+  ASSERT_EQ(422, r.GetOldestPayload());
   ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p);
 
   ASSERT_TRUE(r.IsEmpty());
 }
 
 
+TEST(LRU, PayloadUpdate)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
+  
+  r.Add("a", 420);
+  r.Add("b", 421);
+  r.Add("d", 423);
+
+  r.MakeMostRecent("a", 424);
+  r.MakeMostRecent("d", 421);
+
+  ASSERT_EQ("b", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(424, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
+
+TEST(LRU, PayloadUpdateBis)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
+  
+  r.AddOrMakeMostRecent("a", 420);
+  r.AddOrMakeMostRecent("b", 421);
+  r.AddOrMakeMostRecent("d", 423);
+  r.AddOrMakeMostRecent("a", 424);
+  r.AddOrMakeMostRecent("d", 421);
+
+  ASSERT_EQ("b", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(424, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
 
 
 namespace
--- a/UnitTests/ServerIndex.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/UnitTests/ServerIndex.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -5,6 +5,7 @@
 
 #include <ctype.h>
 #include <glog/logging.h>
+#include <algorithm>
 
 using namespace Orthanc;
 
@@ -432,3 +433,57 @@
   ASSERT_EQ(3u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
   ASSERT_EQ(4u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
 }
+
+
+
+TEST(DatabaseWrapper, LookupTagValue)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  int64_t a[] = {
+    index.CreateResource("a", ResourceType_Study),   // 0
+    index.CreateResource("b", ResourceType_Study),   // 1
+    index.CreateResource("c", ResourceType_Study),   // 2
+    index.CreateResource("d", ResourceType_Series)   // 3
+  };
+
+  DicomMap m;
+  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[0], m);
+  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "1"); index.SetMainDicomTags(a[1], m);
+  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[2], m);
+  m.Clear(); m.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "0"); index.SetMainDicomTags(a[3], m);
+
+  std::list<int64_t> s;
+
+  index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "0");
+  ASSERT_EQ(2u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end());
+
+  index.LookupTagValue(s, "0");
+  ASSERT_EQ(3u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[3]) != s.end());
+
+  index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1");
+  ASSERT_EQ(1u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end());
+
+  index.LookupTagValue(s, "1");
+  ASSERT_EQ(1u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end());
+
+
+  /*{
+      std::list<std::string> s;
+      context.GetIndex().LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1.2.250.1.74.20130819132500.29000036381059");
+      for (std::list<std::string>::iterator i = s.begin(); i != s.end(); i++)
+      {
+        std::cout << "*** " << *i << std::endl;;
+      }      
+      }*/
+
+
+}
--- a/UnitTests/main.cpp	Fri Sep 13 11:10:58 2013 +0200
+++ b/UnitTests/main.cpp	Fri Sep 13 11:25:08 2013 +0200
@@ -445,7 +445,7 @@
     q.Enqueue(new DynamicInteger(20));  
     throw OrthancException("Nope");
   }
-  catch (OrthancException)
+  catch (OrthancException&)
   {
   }
 }