diff OrthancServer/ServerIndex.cpp @ 1364:111e23bb4904 query-retrieve

integration mainline->query-retrieve
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 21 May 2015 16:58:30 +0200
parents c2c28dd17e87 216db29c5aa9
children b22ba8c5edbe
line wrap: on
line diff
--- a/OrthancServer/ServerIndex.cpp	Wed Jun 25 15:34:40 2014 +0200
+++ b/OrthancServer/ServerIndex.cpp	Thu May 21 16:58:30 2015 +0200
@@ -1,7 +1,7 @@
 /**
  * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
- * Belgium
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -37,12 +37,12 @@
 #define NOMINMAX
 #endif
 
+#include "ServerIndexChange.h"
 #include "EmbeddedResources.h"
 #include "OrthancInitialization.h"
 #include "../Core/Toolbox.h"
 #include "../Core/Uuid.h"
 #include "../Core/DicomFormat/DicomArray.h"
-#include "../Core/SQLite/Transaction.h"
 #include "FromDcmtkBridge.h"
 #include "ServerContext.h"
 
@@ -59,16 +59,49 @@
     class ServerIndexListener : public IServerIndexListener
     {
     private:
+      struct FileToRemove
+      {
+      private:
+        std::string  uuid_;
+        FileContentType  type_;
+
+      public:
+        FileToRemove(const FileInfo& info) : uuid_(info.GetUuid()), 
+                                             type_(info.GetContentType())
+        {
+        }
+
+        const std::string& GetUuid() const
+        {
+          return uuid_;
+        }
+
+        FileContentType GetContentType() const 
+        {
+          return type_;
+        }
+      };
+
       ServerContext& context_;
       bool hasRemainingLevel_;
       ResourceType remainingType_;
       std::string remainingPublicId_;
-      std::list<std::string> pendingFilesToRemove_;
+      std::list<FileToRemove> pendingFilesToRemove_;
+      std::list<ServerIndexChange> pendingChanges_;
       uint64_t sizeOfFilesToRemove_;
+      bool insideTransaction_;
+
+      void Reset()
+      {
+        sizeOfFilesToRemove_ = 0;
+        hasRemainingLevel_ = false;
+        pendingFilesToRemove_.clear();
+        pendingChanges_.clear();
+      }
 
     public:
-      ServerIndexListener(ServerContext& context) : 
-        context_(context)
+      ServerIndexListener(ServerContext& context) : context_(context),
+                                                    insideTransaction_(false)      
       {
         Reset();
         assert(ResourceType_Patient < ResourceType_Study &&
@@ -76,11 +109,15 @@
                ResourceType_Series < ResourceType_Instance);
       }
 
-      void Reset()
+      void StartTransaction()
       {
-        sizeOfFilesToRemove_ = 0;
-        hasRemainingLevel_ = false;
-        pendingFilesToRemove_.clear();
+        Reset();
+        insideTransaction_ = true;
+      }
+
+      void EndTransaction()
+      {
+        insideTransaction_ = false;
       }
 
       uint64_t GetSizeOfFilesToRemove()
@@ -90,18 +127,28 @@
 
       void CommitFilesToRemove()
       {
-        for (std::list<std::string>::iterator 
+        for (std::list<FileToRemove>::const_iterator 
                it = pendingFilesToRemove_.begin();
              it != pendingFilesToRemove_.end(); ++it)
         {
-          context_.RemoveFile(*it);
+          context_.RemoveFile(it->GetUuid(), it->GetContentType());
+        }
+      }
+
+      void CommitChanges()
+      {
+        for (std::list<ServerIndexChange>::const_iterator 
+               it = pendingChanges_.begin(); 
+             it != pendingChanges_.end(); ++it)
+        {
+          context_.SignalChange(*it);
         }
       }
 
       virtual void SignalRemainingAncestor(ResourceType parentType,
                                            const std::string& publicId)
       {
-        LOG(INFO) << "Remaining ancestor \"" << publicId << "\" (" << parentType << ")";
+        VLOG(1) << "Remaining ancestor \"" << publicId << "\" (" << parentType << ")";
 
         if (hasRemainingLevel_)
         {
@@ -122,10 +169,26 @@
       virtual void SignalFileDeleted(const FileInfo& info)
       {
         assert(Toolbox::IsUuid(info.GetUuid()));
-        pendingFilesToRemove_.push_back(info.GetUuid());
+        pendingFilesToRemove_.push_back(FileToRemove(info));
         sizeOfFilesToRemove_ += info.GetCompressedSize();
       }
 
+      virtual void SignalChange(const ServerIndexChange& change)
+      {
+        VLOG(1) << "Change related to resource " << change.GetPublicId() << " of type " 
+                << EnumerationToString(change.GetResourceType()) << ": " 
+                << EnumerationToString(change.GetChangeType());
+
+        if (insideTransaction_)
+        {
+          pendingChanges_.push_back(change);
+        }
+        else
+        {
+          context_.SignalChange(change);
+        }
+      }
+
       bool HasRemainingLevel() const
       {
         return hasRemainingLevel_;
@@ -150,7 +213,7 @@
   {
   private:
     ServerIndex& index_;
-    std::auto_ptr<SQLite::Transaction> transaction_;
+    std::auto_ptr<SQLite::ITransaction> transaction_;
     bool isCommitted_;
 
   public:
@@ -158,11 +221,22 @@
       index_(index),
       isCommitted_(false)
     {
-      assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize());
+      transaction_.reset(index_.db_.StartTransaction());
+      transaction_->Begin();
+
+      assert(index_.currentStorageSize_ == index_.db_.GetTotalCompressedSize());
+
+      index_.listener_->StartTransaction();
+    }
 
-      index_.listener_->Reset();
-      transaction_.reset(index_.db_->StartTransaction());
-      transaction_->Begin();
+    ~Transaction()
+    {
+      index_.listener_->EndTransaction();
+
+      if (!isCommitted_)
+      {
+        transaction_->Rollback();
+      }
     }
 
     void Commit(uint64_t sizeOfAddedFiles)
@@ -181,7 +255,8 @@
         assert(index_.currentStorageSize_ >= index_.listener_->GetSizeOfFilesToRemove());
         index_.currentStorageSize_ -= index_.listener_->GetSizeOfFilesToRemove();
 
-        assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize());
+        // Send all the pending changes to the Orthanc plugins
+        index_.listener_->CommitChanges();
 
         isCommitted_ = true;
       }
@@ -189,16 +264,22 @@
   };
 
 
-  struct ServerIndex::UnstableResourcePayload
+  class ServerIndex::UnstableResourcePayload
   {
-    Orthanc::ResourceType type_;
+  private:
+    ResourceType type_;
+    std::string publicId_;
     boost::posix_time::ptime time_;
 
-    UnstableResourcePayload() : type_(Orthanc::ResourceType_Instance)
+  public:
+    UnstableResourcePayload() : type_(ResourceType_Instance)
     {
     }
 
-    UnstableResourcePayload(Orthanc::ResourceType type) : type_(type)
+    UnstableResourcePayload(Orthanc::ResourceType type,
+                            const std::string& publicId) : 
+      type_(type),
+      publicId_(publicId)
     {
       time_ = boost::posix_time::second_clock::local_time();
     }
@@ -207,6 +288,16 @@
     {
       return (boost::posix_time::second_clock::local_time() - time_).total_seconds();
     }
+
+    ResourceType GetResourceType() const
+    {
+      return type_;
+    }
+    
+    const std::string& GetPublicId() const
+    {
+      return publicId_;
+    }
   };
 
 
@@ -215,19 +306,18 @@
                                    ResourceType expectedType)
   {
     boost::mutex::scoped_lock lock(mutex_);
-    listener_->Reset();
 
     Transaction t(*this);
 
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(uuid, id, type) ||
+    if (!db_.LookupResource(id, type, uuid) ||
         expectedType != type)
     {
       return false;
     }
       
-    db_->DeleteResource(id);
+    db_.DeleteResource(id);
 
     if (listener_->HasRemainingLevel())
     {
@@ -252,18 +342,22 @@
 
   void ServerIndex::FlushThread(ServerIndex* that)
   {
-    unsigned int sleep;
+    // By default, wait for 10 seconds before flushing
+    unsigned int sleep = 10;
 
     try
     {
       boost::mutex::scoped_lock lock(that->mutex_);
-      std::string sleepString = that->db_->GetGlobalProperty(GlobalProperty_FlushSleep);
-      sleep = boost::lexical_cast<unsigned int>(sleepString);
+      std::string sleepString;
+
+      if (that->db_.LookupGlobalProperty(sleepString, GlobalProperty_FlushSleep) &&
+          Toolbox::IsInteger(sleepString))
+      {
+        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 << ")";
@@ -280,7 +374,7 @@
       }
 
       boost::mutex::scoped_lock lock(that->mutex_);
-      that->db_->FlushToDisk();
+      that->db_.FlushToDisk();
       count = 0;
     }
 
@@ -288,7 +382,7 @@
   }
 
 
-  static void ComputeExpectedNumberOfInstances(DatabaseWrapper& db,
+  static void ComputeExpectedNumberOfInstances(IDatabaseWrapper& db,
                                                int64_t series,
                                                const DicomMap& dicomSummary)
   {
@@ -328,40 +422,147 @@
   }
 
 
+
+
+  bool ServerIndex::GetMetadataAsInteger(int64_t& result,
+                                         int64_t id,
+                                         MetadataType type)
+  {
+    std::string s;
+    if (!db_.LookupMetadata(s, id, type))
+    {
+      return false;
+    }
+
+    try
+    {
+      result = boost::lexical_cast<int64_t>(s);
+      return true;
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      return false;
+    }
+  }
+
+
+  void ServerIndex::LogChange(int64_t internalId,
+                              ChangeType changeType,
+                              ResourceType resourceType,
+                              const std::string& publicId)
+  {
+    ServerIndexChange change(changeType, resourceType, publicId);
+
+    if (changeType <= ChangeType_INTERNAL_LastLogged)
+    {
+      db_.LogChange(internalId, change);
+    }
+
+    assert(listener_.get() != NULL);
+    listener_->SignalChange(change);
+  }
+
+
+  uint64_t ServerIndex::IncrementGlobalSequenceInternal(GlobalProperty property)
+  {
+    std::string oldValue;
+
+    if (db_.LookupGlobalProperty(oldValue, property))
+    {
+      uint64_t oldNumber;
+
+      try
+      {
+        oldNumber = boost::lexical_cast<uint64_t>(oldValue);
+        db_.SetGlobalProperty(property, boost::lexical_cast<std::string>(oldNumber + 1));
+        return oldNumber + 1;
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+    else
+    {
+      // Initialize the sequence at "1"
+      db_.SetGlobalProperty(property, "1");
+      return 1;
+    }
+  }
+
+
+
+  void ServerIndex::SetMainDicomTags(int64_t resource,
+                                     const DicomMap& tags)
+  {
+    DicomArray flattened(tags);
+    for (size_t i = 0; i < flattened.GetSize(); i++)
+    {
+      const DicomElement& element = flattened.GetElement(i);
+      db_.SetMainDicomTag(resource, element.GetTag(), element.GetValue().AsString());
+    }
+  }
+
+
+  int64_t ServerIndex::CreateResource(const std::string& publicId,
+                                      ResourceType type)
+  {
+    int64_t id = db_.CreateResource(publicId, type);
+
+    ChangeType changeType;
+    switch (type)
+    {
+    case ResourceType_Patient: 
+      changeType = ChangeType_NewPatient; 
+      break;
+
+    case ResourceType_Study: 
+      changeType = ChangeType_NewStudy; 
+      break;
+
+    case ResourceType_Series: 
+      changeType = ChangeType_NewSeries; 
+      break;
+
+    case ResourceType_Instance: 
+      changeType = ChangeType_NewInstance; 
+      break;
+
+    default:
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    ServerIndexChange change(changeType, type, publicId);
+    db_.LogChange(id, change);
+
+    assert(listener_.get() != NULL);
+    listener_->SignalChange(change);
+
+    return id;
+  }
+
+
   ServerIndex::ServerIndex(ServerContext& context,
-                           const std::string& dbPath) : 
+                           IDatabaseWrapper& db) : 
     done_(false),
+    db_(db),
     maximumStorageSize_(0),
     maximumPatients_(0)
   {
     listener_.reset(new Internals::ServerIndexListener(context));
-
-    if (dbPath == ":memory:")
-    {
-      db_.reset(new DatabaseWrapper(*listener_));
-    }
-    else
-    {
-      boost::filesystem::path p = dbPath;
+    db_.SetListener(*listener_);
 
-      try
-      {
-        boost::filesystem::create_directories(p);
-      }
-      catch (boost::filesystem::filesystem_error)
-      {
-      }
-
-      db_.reset(new DatabaseWrapper(p.string() + "/index", *listener_));
-    }
-
-    currentStorageSize_ = db_->GetTotalCompressedSize();
+    currentStorageSize_ = db_.GetTotalCompressedSize();
 
     // Initial recycling if the parameters have changed since the last
     // execution of Orthanc
     StandaloneRecycling();
 
-    flushThread_ = boost::thread(FlushThread, this);
+    if (db.HasFlushToDisk())
+    {
+      flushThread_ = boost::thread(FlushThread, this);
+    }
+
     unstableResourcesMonitorThread_ = boost::thread(UnstableResourcesMonitorThread, this);
   }
 
@@ -370,7 +571,8 @@
   {
     done_ = true;
 
-    if (flushThread_.joinable())
+    if (db_.HasFlushToDisk() &&
+        flushThread_.joinable())
     {
       flushThread_.join();
     }
@@ -382,12 +584,15 @@
   }
 
 
-  StoreStatus ServerIndex::Store(const DicomMap& dicomSummary,
+  StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata,
+                                 const DicomMap& dicomSummary,
                                  const Attachments& attachments,
-                                 const std::string& remoteAet)
+                                 const std::string& remoteAet,
+                                 const MetadataMap& metadata)
   {
     boost::mutex::scoped_lock lock(mutex_);
-    listener_->Reset();
+
+    instanceMetadata.clear();
 
     DicomInstanceHasher hasher(dicomSummary);
 
@@ -399,9 +604,10 @@
       {
         ResourceType type;
         int64_t tmp;
-        if (db_->LookupResource(hasher.HashInstance(), tmp, type))
+        if (db_.LookupResource(tmp, type, hasher.HashInstance()))
         {
           assert(type == ResourceType_Instance);
+          db_.GetAllMetadata(instanceMetadata, tmp);
           return StoreStatus_AlreadyStored;
         }
       }
@@ -417,11 +623,11 @@
       Recycle(instanceSize, hasher.HashPatient());
 
       // Create the instance
-      int64_t instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance);
+      int64_t instance = CreateResource(hasher.HashInstance(), ResourceType_Instance);
 
       DicomMap dicom;
       dicomSummary.ExtractInstanceInformation(dicom);
-      db_->SetMainDicomTags(instance, dicom);
+      SetMainDicomTags(instance, dicom);
 
       // Detect up to which level the patient/study/series/instance
       // hierarchy must be created
@@ -433,26 +639,26 @@
       {
         ResourceType dummy;
 
-        if (db_->LookupResource(hasher.HashSeries(), series, dummy))
+        if (db_.LookupResource(series, dummy, hasher.HashSeries()))
         {
           assert(dummy == ResourceType_Series);
           // The patient, the study and the series already exist
 
-          bool ok = (db_->LookupResource(hasher.HashPatient(), patient, dummy) &&
-                     db_->LookupResource(hasher.HashStudy(), study, dummy));
+          bool ok = (db_.LookupResource(patient, dummy, hasher.HashPatient()) &&
+                     db_.LookupResource(study, dummy, hasher.HashStudy()));
           assert(ok);
         }
-        else if (db_->LookupResource(hasher.HashStudy(), study, dummy))
+        else if (db_.LookupResource(study, dummy, hasher.HashStudy()))
         {
           assert(dummy == ResourceType_Study);
 
           // New series: The patient and the study already exist
           isNewSeries = true;
 
-          bool ok = db_->LookupResource(hasher.HashPatient(), patient, dummy);
+          bool ok = db_.LookupResource(patient, dummy, hasher.HashPatient());
           assert(ok);
         }
-        else if (db_->LookupResource(hasher.HashPatient(), patient, dummy))
+        else if (db_.LookupResource(patient, dummy, hasher.HashPatient()))
         {
           assert(dummy == ResourceType_Patient);
 
@@ -472,38 +678,38 @@
       // Create the series if needed
       if (isNewSeries)
       {
-        series = db_->CreateResource(hasher.HashSeries(), ResourceType_Series);
+        series = CreateResource(hasher.HashSeries(), ResourceType_Series);
         dicomSummary.ExtractSeriesInformation(dicom);
-        db_->SetMainDicomTags(series, dicom);
+        SetMainDicomTags(series, dicom);
       }
 
       // Create the study if needed
       if (isNewStudy)
       {
-        study = db_->CreateResource(hasher.HashStudy(), ResourceType_Study);
+        study = CreateResource(hasher.HashStudy(), ResourceType_Study);
         dicomSummary.ExtractStudyInformation(dicom);
-        db_->SetMainDicomTags(study, dicom);
+        SetMainDicomTags(study, dicom);
       }
 
       // Create the patient if needed
       if (isNewPatient)
       {
-        patient = db_->CreateResource(hasher.HashPatient(), ResourceType_Patient);
+        patient = CreateResource(hasher.HashPatient(), ResourceType_Patient);
         dicomSummary.ExtractPatientInformation(dicom);
-        db_->SetMainDicomTags(patient, dicom);
+        SetMainDicomTags(patient, dicom);
       }
 
       // Create the parent-to-child links
-      db_->AttachChild(series, instance);
+      db_.AttachChild(series, instance);
 
       if (isNewSeries)
       {
-        db_->AttachChild(study, series);
+        db_.AttachChild(study, series);
       }
 
       if (isNewStudy)
       {
-        db_->AttachChild(patient, study);
+        db_.AttachChild(patient, study);
       }
 
       // Sanity checks
@@ -516,40 +722,75 @@
       for (Attachments::const_iterator it = attachments.begin();
            it != attachments.end(); ++it)
       {
-        db_->AddAttachment(instance, *it);
+        db_.AddAttachment(instance, *it);
       }
 
-      // Attach the metadata
+      // Attach the user-specified metadata
+      for (MetadataMap::const_iterator 
+             it = metadata.begin(); it != metadata.end(); ++it)
+      {
+        switch (it->first.first)
+        {
+          case ResourceType_Patient:
+            db_.SetMetadata(patient, it->first.second, it->second);
+            break;
+
+          case ResourceType_Study:
+            db_.SetMetadata(study, it->first.second, it->second);
+            break;
+
+          case ResourceType_Series:
+            db_.SetMetadata(series, it->first.second, it->second);
+            break;
+
+          case ResourceType_Instance:
+            db_.SetMetadata(instance, it->first.second, it->second);
+            instanceMetadata[it->first.second] = it->second;
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+      }
+
+      // Attach the auto-computed metadata for the patient/study/series levels
       std::string now = Toolbox::GetNowIsoString();
-      db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, now);
-      db_->SetMetadata(series, MetadataType_LastUpdate, now);
-      db_->SetMetadata(study, MetadataType_LastUpdate, now);
-      db_->SetMetadata(patient, MetadataType_LastUpdate, now);
-      db_->SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet);
+      db_.SetMetadata(series, MetadataType_LastUpdate, now);
+      db_.SetMetadata(study, MetadataType_LastUpdate, now);
+      db_.SetMetadata(patient, MetadataType_LastUpdate, now);
+
+      // Attach the auto-computed metadata for the instance level,
+      // reflecting these additions into the input metadata map
+      db_.SetMetadata(instance, MetadataType_Instance_ReceptionDate, now);
+      instanceMetadata[MetadataType_Instance_ReceptionDate] = now;
+
+      db_.SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet);
+      instanceMetadata[MetadataType_Instance_RemoteAet] = remoteAet;
 
       const DicomValue* value;
       if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
           (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
       {
-        db_->SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString());
-      }
-
-      if (isNewSeries)
-      {
-        ComputeExpectedNumberOfInstances(*db_, series, dicomSummary);
+        db_.SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString());
+        instanceMetadata[MetadataType_Instance_IndexInSeries] = value->AsString();
       }
 
       // Check whether the series of this new instance is now completed
+      if (isNewSeries)
+      {
+        ComputeExpectedNumberOfInstances(db_, series, dicomSummary);
+      }
+
       SeriesStatus seriesStatus = GetSeriesStatus(series);
       if (seriesStatus == SeriesStatus_Complete)
       {
-        db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series);
+        LogChange(series, ChangeType_CompletedSeries, ResourceType_Series, hasher.HashSeries());
       }
 
       // Mark the parent resources of this instance as unstable
-      MarkAsUnstable(patient, ResourceType_Patient);
-      MarkAsUnstable(study, ResourceType_Study);
-      MarkAsUnstable(series, ResourceType_Series);
+      MarkAsUnstable(series, ResourceType_Series, hasher.HashSeries());
+      MarkAsUnstable(study, ResourceType_Study, hasher.HashStudy());
+      MarkAsUnstable(patient, ResourceType_Patient, hasher.HashPatient());
 
       t.Commit(instanceSize);
 
@@ -557,8 +798,7 @@
     }
     catch (OrthancException& e)
     {
-      LOG(ERROR) << "EXCEPTION [" << e.What() << "]" 
-                 << " (SQLite status: " << db_->GetErrorMessage() << ")";
+      LOG(ERROR) << "EXCEPTION [" << e.What() << "]";
     }
 
     return StoreStatus_Failure;
@@ -571,17 +811,17 @@
     target = Json::objectValue;
 
     uint64_t cs = currentStorageSize_;
-    assert(cs == db_->GetTotalCompressedSize());
-    uint64_t us = db_->GetTotalUncompressedSize();
+    assert(cs == db_.GetTotalCompressedSize());
+    uint64_t us = db_.GetTotalUncompressedSize();
     target["TotalDiskSize"] = boost::lexical_cast<std::string>(cs);
     target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us);
-    target["TotalDiskSizeMB"] = boost::lexical_cast<unsigned int>(cs / MEGA_BYTES);
-    target["TotalUncompressedSizeMB"] = boost::lexical_cast<unsigned int>(us / MEGA_BYTES);
+    target["TotalDiskSizeMB"] = static_cast<unsigned int>(cs / MEGA_BYTES);
+    target["TotalUncompressedSizeMB"] = static_cast<unsigned int>(us / MEGA_BYTES);
 
-    target["CountPatients"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Patient));
-    target["CountStudies"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Study));
-    target["CountSeries"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Series));
-    target["CountInstances"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Instance));
+    target["CountPatients"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Patient));
+    target["CountStudies"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Study));
+    target["CountSeries"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Series));
+    target["CountInstances"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Instance));
   }          
 
 
@@ -589,34 +829,23 @@
   SeriesStatus ServerIndex::GetSeriesStatus(int64_t id)
   {
     // Get the expected number of instances in this series (from the metadata)
-    std::string s = db_->GetMetadata(id, MetadataType_Series_ExpectedNumberOfInstances);
-
-    size_t expected;
-    try
-    {
-      expected = boost::lexical_cast<size_t>(s);
-    }
-    catch (boost::bad_lexical_cast&)
+    int64_t expected;
+    if (!GetMetadataAsInteger(expected, id, MetadataType_Series_ExpectedNumberOfInstances))
     {
       return SeriesStatus_Unknown;
     }
 
     // Loop over the instances of this series
     std::list<int64_t> children;
-    db_->GetChildrenInternalId(children, id);
+    db_.GetChildrenInternalId(children, id);
 
-    std::set<size_t> instances;
+    std::set<int64_t> instances;
     for (std::list<int64_t>::const_iterator 
            it = children.begin(); it != children.end(); ++it)
     {
       // Get the index of this instance in the series
-      s = db_->GetMetadata(*it, MetadataType_Instance_IndexInSeries);
-      size_t index;
-      try
-      {
-        index = boost::lexical_cast<size_t>(s);
-      }
-      catch (boost::bad_lexical_cast&)
+      int64_t index;
+      if (!GetMetadataAsInteger(index, *it, MetadataType_Instance_IndexInSeries))
       {
         return SeriesStatus_Unknown;
       }
@@ -636,7 +865,7 @@
       instances.insert(index);
     }
 
-    if (instances.size() == expected)
+    if (static_cast<int64_t>(instances.size()) == expected)
     {
       return SeriesStatus_Complete;
     }
@@ -652,7 +881,7 @@
                                         int64_t resourceId)
   {
     DicomMap tags;
-    db_->GetMainDicomTags(tags, resourceId);
+    db_.GetMainDicomTags(tags, resourceId);
     target["MainDicomTags"] = Json::objectValue;
     FromDcmtkBridge::ToJson(target["MainDicomTags"], tags);
   }
@@ -668,7 +897,7 @@
     // Lookup for the requested resource
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(publicId, id, type) ||
+    if (!db_.LookupResource(id, type, publicId) ||
         type != expectedType)
     {
       return false;
@@ -678,12 +907,12 @@
     if (type != ResourceType_Patient)
     {
       int64_t parentId;
-      if (!db_->LookupParent(parentId, id))
+      if (!db_.LookupParent(parentId, id))
       {
         throw OrthancException(ErrorCode_InternalError);
       }
 
-      std::string parent = db_->GetPublicId(parentId);
+      std::string parent = db_.GetPublicId(parentId);
 
       switch (type)
       {
@@ -706,7 +935,7 @@
 
     // List the children resources
     std::list<std::string> children;
-    db_->GetChildrenPublicId(children, id);
+    db_.GetChildrenPublicId(children, id);
 
     if (type != ResourceType_Instance)
     {
@@ -753,9 +982,9 @@
         result["Type"] = "Series";
         result["Status"] = EnumerationToString(GetSeriesStatus(id));
 
-        int i;
-        if (db_->GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances))
-          result["ExpectedNumberOfInstances"] = i;
+        int64_t i;
+        if (GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances))
+          result["ExpectedNumberOfInstances"] = static_cast<int>(i);
         else
           result["ExpectedNumberOfInstances"] = Json::nullValue;
 
@@ -767,7 +996,7 @@
         result["Type"] = "Instance";
 
         FileInfo attachment;
-        if (!db_->LookupAttachment(attachment, id, FileContentType_Dicom))
+        if (!db_.LookupAttachment(attachment, id, FileContentType_Dicom))
         {
           throw OrthancException(ErrorCode_InternalError);
         }
@@ -775,9 +1004,9 @@
         result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize());
         result["FileUuid"] = attachment.GetUuid();
 
-        int i;
-        if (db_->GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries))
-          result["IndexInSeries"] = i;
+        int64_t i;
+        if (GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries))
+          result["IndexInSeries"] = static_cast<int>(i);
         else
           result["IndexInSeries"] = Json::nullValue;
 
@@ -794,19 +1023,26 @@
 
     std::string tmp;
 
-    tmp = db_->GetMetadata(id, MetadataType_AnonymizedFrom);
-    if (tmp.size() != 0)
+    if (db_.LookupMetadata(tmp, id, MetadataType_AnonymizedFrom))
+    {
       result["AnonymizedFrom"] = tmp;
+    }
 
-    tmp = db_->GetMetadata(id, MetadataType_ModifiedFrom);
-    if (tmp.size() != 0)
+    if (db_.LookupMetadata(tmp, id, MetadataType_ModifiedFrom))
+    {
       result["ModifiedFrom"] = tmp;
+    }
 
     if (type == ResourceType_Patient ||
         type == ResourceType_Study ||
         type == ResourceType_Series)
     {
       result["IsStable"] = !unstableResources_.Contains(id);
+
+      if (db_.LookupMetadata(tmp, id, MetadataType_LastUpdate))
+      {
+        result["LastUpdate"] = tmp;
+      }
     }
 
     return true;
@@ -821,12 +1057,12 @@
 
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(instanceUuid, id, type))
+    if (!db_.LookupResource(id, type, instanceUuid))
     {
-      throw OrthancException(ErrorCode_InternalError);
+      throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    if (db_->LookupAttachment(attachment, id, contentType))
+    if (db_.LookupAttachment(attachment, id, contentType))
     {
       assert(attachment.GetContentType() == contentType);
       return true;
@@ -839,38 +1075,77 @@
 
 
 
-  void ServerIndex::GetAllUuids(Json::Value& target,
+  void ServerIndex::GetAllUuids(std::list<std::string>& target,
                                 ResourceType resourceType)
   {
     boost::mutex::scoped_lock lock(mutex_);
-    db_->GetAllPublicIds(target, resourceType);
+    db_.GetAllPublicIds(target, resourceType);
   }
 
 
-  bool ServerIndex::GetChanges(Json::Value& target,
+  template <typename T>
+  static void FormatLog(Json::Value& target,
+                        const std::list<T>& log,
+                        const std::string& name,
+                        bool done,
+                        int64_t since)
+  {
+    Json::Value items = Json::arrayValue;
+    for (typename std::list<T>::const_iterator
+           it = log.begin(); it != log.end(); ++it)
+    {
+      Json::Value item;
+      it->Format(item);
+      items.append(item);
+    }
+
+    target = Json::objectValue;
+    target[name] = items;
+    target["Done"] = done;
+
+    int64_t last = (log.empty() ? since : log.back().GetSeq());
+    target["Last"] = static_cast<int>(last);
+  }
+
+
+  void ServerIndex::GetChanges(Json::Value& target,
                                int64_t since,                               
                                unsigned int maxResults)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-    db_->GetChanges(target, since, maxResults);
-    return true;
+    std::list<ServerIndexChange> changes;
+    bool done;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      db_.GetChanges(changes, done, since, maxResults);
+    }
+
+    FormatLog(target, changes, "Changes", done, since);
   }
 
-  bool ServerIndex::GetLastChange(Json::Value& target)
+
+  void ServerIndex::GetLastChange(Json::Value& target)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-    db_->GetLastChange(target);
-    return true;
+    std::list<ServerIndexChange> changes;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      db_.GetLastChange(changes);
+    }
+
+    FormatLog(target, changes, "Changes", true, 0);
   }
 
+
   void ServerIndex::LogExportedResource(const std::string& publicId,
                                         const std::string& remoteModality)
   {
     boost::mutex::scoped_lock lock(mutex_);
+    Transaction transaction(*this);
 
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(publicId, id, type))
+    if (!db_.LookupResource(id, type, publicId))
     {
       throw OrthancException(ErrorCode_InternalError);
     }
@@ -888,7 +1163,7 @@
     while (!done)
     {
       DicomMap map;
-      db_->GetMainDicomTags(map, currentId);
+      db_.GetMainDicomTags(map, currentId);
 
       switch (currentType)
       {
@@ -920,36 +1195,52 @@
       // the current resource
       if (!done)
       {
-        bool ok = db_->LookupParent(currentId, currentId);
+        bool ok = db_.LookupParent(currentId, currentId);
         assert(ok);
       }
     }
 
-    // No need for a SQLite::Transaction here, as we only insert 1 record
-    db_->LogExportedResource(type,
-                             publicId,
-                             remoteModality,
-                             patientId,
-                             studyInstanceUid,
-                             seriesInstanceUid,
-                             sopInstanceUid);
+    ExportedResource resource(-1, 
+                              type,
+                              publicId,
+                              remoteModality,
+                              Toolbox::GetNowIsoString(),
+                              patientId,
+                              studyInstanceUid,
+                              seriesInstanceUid,
+                              sopInstanceUid);
+
+    db_.LogExportedResource(resource);
+    transaction.Commit(0);
   }
 
 
-  bool ServerIndex::GetExportedResources(Json::Value& target,
+  void ServerIndex::GetExportedResources(Json::Value& target,
                                          int64_t since,
                                          unsigned int maxResults)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-    db_->GetExportedResources(target, since, maxResults);
-    return true;
+    std::list<ExportedResource> exported;
+    bool done;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      db_.GetExportedResources(exported, done, since, maxResults);
+    }
+
+    FormatLog(target, exported, "Exports", done, since);
   }
 
-  bool ServerIndex::GetLastExportedResource(Json::Value& target)
+
+  void ServerIndex::GetLastExportedResource(Json::Value& target)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-    db_->GetLastExportedResource(target);
-    return true;
+    std::list<ExportedResource> exported;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      db_.GetLastExportedResource(exported);
+    }
+
+    FormatLog(target, exported, "Exports", true, 0);
   }
 
 
@@ -958,7 +1249,7 @@
     if (maximumStorageSize_ != 0)
     {
       uint64_t currentSize = currentStorageSize_ - listener_->GetSizeOfFilesToRemove();
-      assert(db_->GetTotalCompressedSize() == currentSize);
+      assert(db_.GetTotalCompressedSize() == currentSize);
 
       if (currentSize + instanceSize > maximumStorageSize_)
       {
@@ -968,7 +1259,7 @@
 
     if (maximumPatients_ != 0)
     {
-      uint64_t patientCount = db_->GetResourceCount(ResourceType_Patient);
+      uint64_t patientCount = db_.GetResourceCount(ResourceType_Patient);
       if (patientCount > maximumPatients_)
       {
         return true;
@@ -991,7 +1282,7 @@
     // already stored
     int64_t patientToAvoid;
     ResourceType type;
-    bool hasPatientToAvoid = db_->LookupResource(newPatientId, patientToAvoid, type);
+    bool hasPatientToAvoid = db_.LookupResource(patientToAvoid, type, newPatientId);
 
     if (hasPatientToAvoid && type != ResourceType_Patient)
     {
@@ -1006,16 +1297,16 @@
       // If other instances of this patient are already in the store,
       // we must avoid to recycle them
       bool ok = hasPatientToAvoid ?
-        db_->SelectPatientToRecycle(patientToRecycle, patientToAvoid) :
-        db_->SelectPatientToRecycle(patientToRecycle);
+        db_.SelectPatientToRecycle(patientToRecycle, patientToAvoid) :
+        db_.SelectPatientToRecycle(patientToRecycle);
         
       if (!ok)
       {
         throw OrthancException(ErrorCode_FullStorage);
       }
       
-      LOG(INFO) << "Recycling one patient";
-      db_->DeleteResource(patientToRecycle);
+      VLOG(1) << "Recycling one patient";
+      db_.DeleteResource(patientToRecycle);
 
       if (!IsRecyclingNeeded(instanceSize))
       {
@@ -1075,13 +1366,13 @@
     // Lookup for the requested resource
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(publicId, id, type) ||
+    if (!db_.LookupResource(id, type, publicId) ||
         type != ResourceType_Patient)
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    return db_->IsProtectedPatient(id);
+    return db_.IsProtectedPatient(id);
   }
      
 
@@ -1089,18 +1380,19 @@
                                         bool isProtected)
   {
     boost::mutex::scoped_lock lock(mutex_);
+    Transaction transaction(*this);
 
     // Lookup for the requested resource
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(publicId, id, type) ||
+    if (!db_.LookupResource(id, type, publicId) ||
         type != ResourceType_Patient)
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    // No need for a SQLite::Transaction here, as we only make 1 write to the DB
-    db_->SetProtectedPatient(id, isProtected);
+    db_.SetProtectedPatient(id, isProtected);
+    transaction.Commit(0);
 
     if (isProtected)
       LOG(INFO) << "Patient " << publicId << " has been protected";
@@ -1118,7 +1410,7 @@
 
     ResourceType type;
     int64_t resource;
-    if (!db_->LookupResource(publicId, resource, type))
+    if (!db_.LookupResource(resource, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
@@ -1130,12 +1422,12 @@
     }
 
     std::list<int64_t> tmp;
-    db_->GetChildrenInternalId(tmp, resource);
+    db_.GetChildrenInternalId(tmp, resource);
 
     for (std::list<int64_t>::const_iterator 
            it = tmp.begin(); it != tmp.end(); ++it)
     {
-      result.push_back(db_->GetPublicId(*it));
+      result.push_back(db_.GetPublicId(*it));
     }
   }
 
@@ -1149,7 +1441,7 @@
 
     ResourceType type;
     int64_t top;
-    if (!db_->LookupResource(publicId, top, type))
+    if (!db_.LookupResource(top, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
@@ -1172,14 +1464,14 @@
       int64_t resource = toExplore.top();
       toExplore.pop();
 
-      if (db_->GetResourceType(resource) == ResourceType_Instance)
+      if (db_.GetResourceType(resource) == ResourceType_Instance)
       {
-        result.push_back(db_->GetPublicId(resource));
+        result.push_back(db_.GetPublicId(resource));
       }
       else
       {
         // Tag all the children of this resource as to be explored
-        db_->GetChildrenInternalId(tmp, resource);
+        db_.GetChildrenInternalId(tmp, resource);
         for (std::list<int64_t>::const_iterator 
                it = tmp.begin(); it != tmp.end(); ++it)
         {
@@ -1198,12 +1490,12 @@
 
     ResourceType rtype;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, rtype))
+    if (!db_.LookupResource(id, rtype, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->SetMetadata(id, type, value);
+    db_.SetMetadata(id, type, value);
   }
 
 
@@ -1214,12 +1506,12 @@
 
     ResourceType rtype;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, rtype))
+    if (!db_.LookupResource(id, rtype, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->DeleteMetadata(id, type);
+    db_.DeleteMetadata(id, type);
   }
 
 
@@ -1231,12 +1523,12 @@
 
     ResourceType rtype;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, rtype))
+    if (!db_.LookupResource(id, rtype, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    return db_->LookupMetadata(target, id, type);
+    return db_.LookupMetadata(target, id, type);
   }
 
 
@@ -1247,12 +1539,12 @@
 
     ResourceType rtype;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, rtype))
+    if (!db_.LookupResource(id, rtype, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->ListAvailableMetadata(target, id);
+    db_.ListAvailableMetadata(target, id);
   }
 
 
@@ -1264,13 +1556,13 @@
 
     ResourceType type;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, type) ||
+    if (!db_.LookupResource(id, type, publicId) ||
         expectedType != type)
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->ListAvailableAttachments(target, id);
+    db_.ListAvailableAttachments(target, id);
   }
 
 
@@ -1281,15 +1573,15 @@
 
     ResourceType type;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, type))
+    if (!db_.LookupResource(id, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
     int64_t parentId;
-    if (db_->LookupParent(parentId, id))
+    if (db_.LookupParent(parentId, id))
     {
-      target = db_->GetPublicId(parentId);
+      target = db_.GetPublicId(parentId);
       return true;
     }
     else
@@ -1302,12 +1594,10 @@
   uint64_t ServerIndex::IncrementGlobalSequence(GlobalProperty sequence)
   {
     boost::mutex::scoped_lock lock(mutex_);
-
-    std::auto_ptr<SQLite::Transaction> transaction(db_->StartTransaction());
+    Transaction transaction(*this);
 
-    transaction->Begin();
-    uint64_t seq = db_->IncrementGlobalSequence(sequence);
-    transaction->Commit();
+    uint64_t seq = IncrementGlobalSequenceInternal(sequence);
+    transaction.Commit(0);
 
     return seq;
   }
@@ -1318,32 +1608,30 @@
                               const std::string& publicId)
   {
     boost::mutex::scoped_lock lock(mutex_);
-    std::auto_ptr<SQLite::Transaction> transaction(db_->StartTransaction());
-    transaction->Begin();
+    Transaction transaction(*this);
 
     int64_t id;
     ResourceType type;
-    if (!db_->LookupResource(publicId, id, type))
+    if (!db_.LookupResource(id, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->LogChange(changeType, id, type);
-
-    transaction->Commit();
+    LogChange(id, changeType, type, publicId);
+    transaction.Commit(0);
   }
 
 
   void ServerIndex::DeleteChanges()
   {
     boost::mutex::scoped_lock lock(mutex_);
-    db_->ClearTable("Changes");
+    db_.ClearChanges();
   }
 
   void ServerIndex::DeleteExportedResources()
   {
     boost::mutex::scoped_lock lock(mutex_);
-    db_->ClearTable("ExportedResources");
+    db_.ClearExportedResources();
   }
 
 
@@ -1370,16 +1658,16 @@
       int64_t resource = toExplore.top();
       toExplore.pop();
 
-      ResourceType thisType = db_->GetResourceType(resource);
+      ResourceType thisType = db_.GetResourceType(resource);
 
       std::list<FileContentType> f;
-      db_->ListAvailableAttachments(f, resource);
+      db_.ListAvailableAttachments(f, resource);
 
       for (std::list<FileContentType>::const_iterator
              it = f.begin(); it != f.end(); ++it)
       {
         FileInfo attachment;
-        if (db_->LookupAttachment(attachment, resource, *it))
+        if (db_.LookupAttachment(attachment, resource, *it))
         {
           compressedSize += attachment.GetCompressedSize();
           uncompressedSize += attachment.GetUncompressedSize();
@@ -1408,7 +1696,7 @@
 
         // Tag all the children of this resource as to be explored
         std::list<int64_t> tmp;
-        db_->GetChildrenInternalId(tmp, resource);
+        db_.GetChildrenInternalId(tmp, resource);
         for (std::list<int64_t>::const_iterator 
                it = tmp.begin(); it != tmp.end(); ++it)
         {
@@ -1437,7 +1725,7 @@
 
     ResourceType type;
     int64_t top;
-    if (!db_->LookupResource(publicId, top, type))
+    if (!db_.LookupResource(top, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
@@ -1452,9 +1740,9 @@
 
     target = Json::objectValue;
     target["DiskSize"] = boost::lexical_cast<std::string>(compressedSize);
-    target["DiskSizeMB"] = boost::lexical_cast<unsigned int>(compressedSize / MEGA_BYTES);
+    target["DiskSizeMB"] = static_cast<unsigned int>(compressedSize / MEGA_BYTES);
     target["UncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize);
-    target["UncompressedSizeMB"] = boost::lexical_cast<unsigned int>(uncompressedSize / MEGA_BYTES);
+    target["UncompressedSizeMB"] = static_cast<unsigned int>(uncompressedSize / MEGA_BYTES);
 
     switch (type)
     {
@@ -1486,7 +1774,7 @@
 
     ResourceType type;
     int64_t top;
-    if (!db_->LookupResource(publicId, top, type))
+    if (!db_.LookupResource(top, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
@@ -1523,20 +1811,20 @@
         int64_t id = that->unstableResources_.RemoveOldest(payload);
 
         // Ensure that the resource is still existing before logging the change
-        if (that->db_->IsExistingResource(id))
+        if (that->db_.IsExistingResource(id))
         {
-          switch (payload.type_)
+          switch (payload.GetResourceType())
           {
-            case Orthanc::ResourceType_Patient:
-              that->db_->LogChange(ChangeType_StablePatient, id, ResourceType_Patient);
+            case ResourceType_Patient:
+              that->LogChange(id, ChangeType_StablePatient, ResourceType_Patient, payload.GetPublicId());
               break;
 
-            case Orthanc::ResourceType_Study:
-              that->db_->LogChange(ChangeType_StableStudy, id, ResourceType_Study);
+            case ResourceType_Study:
+              that->LogChange(id, ChangeType_StableStudy, ResourceType_Study, payload.GetPublicId());
               break;
 
-            case Orthanc::ResourceType_Series:
-              that->db_->LogChange(ChangeType_StableSeries, id, ResourceType_Series);
+            case ResourceType_Series:
+              that->LogChange(id, ChangeType_StableSeries, ResourceType_Series, payload.GetPublicId());
               break;
 
             default:
@@ -1553,7 +1841,8 @@
   
 
   void ServerIndex::MarkAsUnstable(int64_t id,
-                                   Orthanc::ResourceType type)
+                                   Orthanc::ResourceType type,
+                                   const std::string& publicId)
   {
     // WARNING: Before calling this method, "mutex_" must be locked.
 
@@ -1561,68 +1850,72 @@
            type == Orthanc::ResourceType_Study ||
            type == Orthanc::ResourceType_Series);
 
-    unstableResources_.AddOrMakeMostRecent(id, type);
+    UnstableResourcePayload payload(type, publicId);
+    unstableResources_.AddOrMakeMostRecent(id, payload);
     //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id;
+
+    LogChange(id, ChangeType_NewChildInstance, type, publicId);
   }
 
 
 
-  void ServerIndex::LookupTagValue(std::list<std::string>& result,
-                                   DicomTag tag,
-                                   const std::string& value,
-                                   ResourceType type)
+  void ServerIndex::LookupIdentifier(std::list<std::string>& result,
+                                     const DicomTag& tag,
+                                     const std::string& value,
+                                     ResourceType type)
   {
     result.clear();
 
     boost::mutex::scoped_lock lock(mutex_);
 
     std::list<int64_t> id;
-    db_->LookupTagValue(id, tag, value);
+    db_.LookupIdentifier(id, tag, value);
 
     for (std::list<int64_t>::const_iterator 
            it = id.begin(); it != id.end(); ++it)
     {
-      if (db_->GetResourceType(*it) == type)
+      if (db_.GetResourceType(*it) == type)
       {
-        result.push_back(db_->GetPublicId(*it));
+        result.push_back(db_.GetPublicId(*it));
       }
     }
   }
 
 
-  void ServerIndex::LookupTagValue(std::list<std::string>& result,
-                                   DicomTag tag,
-                                   const std::string& value)
+  void ServerIndex::LookupIdentifier(std::list<std::string>& result,
+                                     const DicomTag& tag,
+                                     const std::string& value)
   {
     result.clear();
 
     boost::mutex::scoped_lock lock(mutex_);
 
     std::list<int64_t> id;
-    db_->LookupTagValue(id, tag, value);
+    db_.LookupIdentifier(id, tag, value);
 
     for (std::list<int64_t>::const_iterator 
            it = id.begin(); it != id.end(); ++it)
     {
-      result.push_back(db_->GetPublicId(*it));
+      result.push_back(db_.GetPublicId(*it));
     }
   }
 
 
-  void ServerIndex::LookupTagValue(std::list<std::string>& result,
-                                   const std::string& value)
+  void ServerIndex::LookupIdentifier(std::list< std::pair<ResourceType, std::string> >& result,
+                                     const std::string& value)
   {
     result.clear();
 
     boost::mutex::scoped_lock lock(mutex_);
 
     std::list<int64_t> id;
-    db_->LookupTagValue(id, value);
+    db_.LookupIdentifier(id, value);
 
     for (std::list<int64_t>::const_iterator 
            it = id.begin(); it != id.end(); ++it)
     {
-      result.push_back(db_->GetPublicId(*it));
+      result.push_back(std::make_pair(db_.GetResourceType(*it),
+                                      db_.GetPublicId(*it)));
     }
   }
 
@@ -1636,20 +1929,20 @@
 
     ResourceType resourceType;
     int64_t resourceId;
-    if (!db_->LookupResource(publicId, resourceId, resourceType))
+    if (!db_.LookupResource(resourceId, resourceType, publicId))
     {
       return StoreStatus_Failure;  // Inexistent resource
     }
 
     // Remove possible previous attachment
-    db_->DeleteAttachment(resourceId, attachment.GetContentType());
+    db_.DeleteAttachment(resourceId, attachment.GetContentType());
 
     // Locate the patient of the target resource
     int64_t patientId = resourceId;
     for (;;)
     {
       int64_t parent;
-      if (db_->LookupParent(parent, patientId))
+      if (db_.LookupParent(parent, patientId))
       {
         // We have not reached the patient level yet
         patientId = parent;
@@ -1662,10 +1955,10 @@
     }
 
     // Possibly apply the recycling mechanism while preserving this patient
-    assert(db_->GetResourceType(patientId) == ResourceType_Patient);
-    Recycle(attachment.GetCompressedSize(), db_->GetPublicId(patientId));
+    assert(db_.GetResourceType(patientId) == ResourceType_Patient);
+    Recycle(attachment.GetCompressedSize(), db_.GetPublicId(patientId));
 
-    db_->AddAttachment(resourceId, attachment);
+    db_.AddAttachment(resourceId, attachment);
 
     t.Commit(attachment.GetCompressedSize());
 
@@ -1677,21 +1970,101 @@
                                      FileContentType type)
   {
     boost::mutex::scoped_lock lock(mutex_);
-    listener_->Reset();
-
     Transaction t(*this);
 
     ResourceType rtype;
     int64_t id;
-    if (!db_->LookupResource(publicId, id, rtype))
+    if (!db_.LookupResource(id, rtype, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_->DeleteAttachment(id, type);
+    db_.DeleteAttachment(id, type);
 
     t.Commit(0);
   }
 
 
+  bool ServerIndex::GetMetadata(Json::Value& target,
+                                const std::string& publicId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    target = Json::objectValue;
+
+    ResourceType type;
+    int64_t id;
+    if (!db_.LookupResource(id, type, publicId))
+    {
+      return false;
+    }
+
+    std::list<MetadataType> metadata;
+    db_.ListAvailableMetadata(metadata, id);
+
+    for (std::list<MetadataType>::const_iterator
+           it = metadata.begin(); it != metadata.end(); ++it)
+    {
+      std::string key = EnumerationToString(*it);
+
+      std::string value;
+      if (!db_.LookupMetadata(value, id, *it))
+      {
+        value.clear();
+      }
+
+      target[key] = value;
+    }
+
+    return true;
+  }
+
+
+  void ServerIndex::SetGlobalProperty(GlobalProperty property,
+                                      const std::string& value)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    db_.SetGlobalProperty(property, value);
+  }
+
+
+  std::string ServerIndex::GetGlobalProperty(GlobalProperty property,
+                                             const std::string& defaultValue)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    std::string value;
+    if (db_.LookupGlobalProperty(value, property))
+    {
+      return value;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  bool ServerIndex::GetMainDicomTags(DicomMap& result,
+                                     const std::string& publicId,
+                                     ResourceType expectedType)
+  {
+    result.Clear();
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    // Lookup for the requested resource
+    int64_t id;
+    ResourceType type;
+    if (!db_.LookupResource(id, type, publicId) ||
+        type != expectedType)
+    {
+      return false;
+    }
+    else
+    {
+      db_.GetMainDicomTags(result, id);
+      return true;
+    }    
+  }
 }