changeset 4554:efd90f778cd2 db-changes

simplification
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 03 Mar 2021 16:31:57 +0100
parents beb8ba8a0b12
children 456ed3fcff81
files OrthancServer/Sources/LuaScripting.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/OrthancWebDav.cpp OrthancServer/Sources/ServerIndex.cpp OrthancServer/Sources/ServerIndex.h
diffstat 5 files changed, 395 insertions(+), 356 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/Sources/LuaScripting.cpp	Wed Mar 03 13:44:01 2021 +0100
+++ b/OrthancServer/Sources/LuaScripting.cpp	Wed Mar 03 16:31:57 2021 +0100
@@ -38,6 +38,7 @@
 #include "OrthancRestApi/OrthancRestApi.h"
 #include "ServerContext.h"
 
+#include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
 #include "../../OrthancFramework/Sources/HttpServer/StringHttpOutput.h"
 #include "../../OrthancFramework/Sources/Logging.h"
 #include "../../OrthancFramework/Sources/Lua/LuaFunctionCall.h"
@@ -131,7 +132,7 @@
     private:
       const ServerIndexChange&            change_;
       bool                                ok_;
-      Json::Value                         tags_;
+      DicomMap                            tags_;
       std::map<MetadataType, std::string> metadata_;      
 
     public:
@@ -143,11 +144,15 @@
       
       virtual void Apply(ServerIndex::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE
       {
-        if (transaction.LookupResource(tags_, change_.GetPublicId(), change_.GetResourceType()))
+        int64_t internalId;
+        ResourceType level;
+        if (transaction.LookupResource(internalId, level, change_.GetPublicId()) &&
+            level == change_.GetResourceType())
         {
-          transaction.GetAllMetadata(metadata_, change_.GetPublicId(), change_.GetResourceType());
+          transaction.GetMainDicomTags(tags_, internalId);
+          transaction.GetAllMetadata(metadata_, internalId);
           ok_ = true;
-        }               
+        }
       }
 
       void CallLua(LuaScripting& that,
@@ -171,9 +176,22 @@
             {
               that.InitializeJob();
 
+              Json::Value json = Json::objectValue;
+
+              if (change_.GetResourceType() == ResourceType_Study)
+              {
+                DicomMap t;
+                tags_.ExtractStudyInformation(t);  // Discard patient-related tags
+                FromDcmtkBridge::ToJson(json, t, true);
+              }
+              else
+              {
+                FromDcmtkBridge::ToJson(json, tags_, true);
+              }
+
               LuaFunctionCall call(lock.GetLua(), name);
               call.PushString(change_.GetPublicId());
-              call.PushJson(tags_["MainDicomTags"]);
+              call.PushJson(json);
               call.PushJson(formattedMetadata);
               call.Execute();
 
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed Mar 03 13:44:01 2021 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed Mar 03 16:31:57 2021 +0100
@@ -168,11 +168,10 @@
     {
       if (expand)
       {
-        ServerIndex::ExpandResourceOperation operation(*resource, level);
-        index.Apply(operation);
-        if (operation.IsFound())
+        Json::Value expanded;
+        if (index.ExpandResource(expanded, *resource, level))
         {
-          answer.append(operation.GetResource());
+          answer.append(expanded);
         }
       }
       else
@@ -257,12 +256,10 @@
       return;
     }
 
-    ServerIndex::ExpandResourceOperation operation(call.GetUriComponent("id", ""), resourceType);
-    OrthancRestApi::GetIndex(call).Apply(operation);
-
-    if (operation.IsFound())
+    Json::Value json;
+    if (OrthancRestApi::GetIndex(call).ExpandResource(json, call.GetUriComponent("id", ""), resourceType))
     {
-      call.GetOutput().AnswerJson(operation.GetResource());
+      call.GetOutput().AnswerJson(json);
     }
   }
 
@@ -1418,35 +1415,13 @@
     }
 
     assert(!call.GetFullUri().empty());
+    const std::string publicId = call.GetUriComponent("id", "");
+    ResourceType level = StringToResourceType(call.GetFullUri() [0].c_str());
 
     typedef std::map<MetadataType, std::string>  Metadata;
 
     Metadata metadata;
-    
-    class Operation : public ServerIndex::IReadOnlyOperations
-    {
-    private:
-      Metadata&     metadata_;
-      std::string   publicId_;
-      ResourceType  level_;
-
-    public:
-      Operation(Metadata& metadata,
-                const RestApiGetCall& call) :
-        metadata_(metadata),
-        publicId_(call.GetUriComponent("id", "")),
-        level_(StringToResourceType(call.GetFullUri() [0].c_str()))        
-      {
-      }
-      
-      virtual void Apply(ServerIndex::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE
-      {
-        transaction.GetAllMetadata(metadata_, publicId_, level_);
-      }
-    };
-
-    Operation operation(metadata, call);
-    OrthancRestApi::GetIndex(call).Apply(operation);
+    OrthancRestApi::GetIndex(call).GetAllMetadata(metadata, publicId, level);
 
     Json::Value result;
 
@@ -2568,12 +2543,10 @@
     for (std::list<std::string>::const_iterator
            it = a.begin(); it != a.end(); ++it)
     {
-      ServerIndex::ExpandResourceOperation operation(*it, end);
-      OrthancRestApi::GetIndex(call).Apply(operation);
-
-      if (operation.IsFound())
+      Json::Value resource;
+      if (OrthancRestApi::GetIndex(call).ExpandResource(resource, *it, end))
       {
-        result.append(operation.GetResource());
+        result.append(resource);
       }
     }
 
@@ -2679,12 +2652,10 @@
 
     assert(currentType == end);
 
-    ServerIndex::ExpandResourceOperation operation(current, end);
-    OrthancRestApi::GetIndex(call).Apply(operation);
-
-    if (operation.IsFound())
+    Json::Value resource;
+    if (OrthancRestApi::GetIndex(call).ExpandResource(resource, current, end))
     {
-      call.GetOutput().AnswerJson(operation.GetResource());
+      call.GetOutput().AnswerJson(resource);
     }
   }
 
--- a/OrthancServer/Sources/OrthancWebDav.cpp	Wed Mar 03 13:44:01 2021 +0100
+++ b/OrthancServer/Sources/OrthancWebDav.cpp	Wed Mar 03 16:31:57 2021 +0100
@@ -268,10 +268,8 @@
                        const DicomMap& mainDicomTags,
                        const Json::Value* dicomAsJson  /* unused (*) */)  ORTHANC_OVERRIDE
     {
-      ServerIndex::ExpandResourceOperation operation(publicId, level_);
-      context_.GetIndex().Apply(operation);
-
-      if (operation.IsFound())
+      Json::Value resource;
+      if (context_.GetIndex().ExpandResource(resource, publicId, level_))
       {
         if (success_)
         {
@@ -279,7 +277,7 @@
         }
         else
         {
-          target_ = operation.GetResource().toStyledString();
+          target_ = resource.toStyledString();
 
           // Replace UNIX newlines with DOS newlines 
           boost::replace_all(target_, "\n", "\r\n");
--- a/OrthancServer/Sources/ServerIndex.cpp	Wed Mar 03 13:44:01 2021 +0100
+++ b/OrthancServer/Sources/ServerIndex.cpp	Wed Mar 03 16:31:57 2021 +0100
@@ -671,6 +671,11 @@
   }
 
 
+  bool ServerIndex::IsUnstableResource(int64_t id)
+  {
+    return unstableResources_.Contains(id);
+  }
+
 
   ServerIndex::ServerIndex(ServerContext& context,
                            IDatabaseWrapper& db,
@@ -680,7 +685,7 @@
     maximumStorageSize_(0),
     maximumPatients_(0),
     mainDicomTagsRegistry_(new MainDicomTagsRegistry),
-    maxRetries_(0)
+    maxRetries_(10)
   {
     listener_.reset(new Listener(context));
     db_.SetListener(*listener_);
@@ -1014,7 +1019,7 @@
       int64_t expectedNumberOfInstances;
       if (ComputeExpectedNumberOfInstances(expectedNumberOfInstances, dicomSummary))
       {
-        SeriesStatus seriesStatus = GetSeriesStatus(status.seriesId_, expectedNumberOfInstances);
+        SeriesStatus seriesStatus = GetSeriesStatus(db_, status.seriesId_, expectedNumberOfInstances);
         if (seriesStatus == SeriesStatus_Complete)
         {
           LogChange(status.seriesId_, ChangeType_CompletedSeries, ResourceType_Series, hashSeries);
@@ -1057,11 +1062,12 @@
   }
 
   
-  SeriesStatus ServerIndex::GetSeriesStatus(int64_t id,
+  SeriesStatus ServerIndex::GetSeriesStatus(IDatabaseWrapper& db,
+                                            int64_t id,
                                             int64_t expectedNumberOfInstances)
   {
     std::list<std::string> values;
-    db_.GetChildrenMetadata(values, id, MetadataType_Instance_IndexInSeries);
+    db.GetChildrenMetadata(values, id, MetadataType_Instance_IndexInSeries);
 
     std::set<int64_t> instances;
 
@@ -1106,11 +1112,12 @@
 
 
   void ServerIndex::MainDicomTagsToJson(Json::Value& target,
+                                        IDatabaseWrapper& db,
                                         int64_t resourceId,
                                         ResourceType resourceType)
   {
     DicomMap tags;
-    db_.GetMainDicomTags(tags, resourceId);
+    db.GetMainDicomTags(tags, resourceId);
 
     if (resourceType == ResourceType_Study)
     {
@@ -1132,187 +1139,6 @@
   }
 
   
-  bool ServerIndex::LookupResource(Json::Value& result,
-                                   const std::string& publicId,
-                                   ResourceType expectedType)
-  {
-    result = Json::objectValue;
-
-    boost::mutex::scoped_lock lock(mutex_);
-
-    // Lookup for the requested resource
-    int64_t id;
-    ResourceType type;
-    std::string parent;
-    if (!db_.LookupResourceAndParent(id, type, parent, publicId) ||
-        type != expectedType)
-    {
-      return false;
-    }
-
-    // Set information about the parent resource (if it exists)
-    if (type == ResourceType_Patient)
-    {
-      if (!parent.empty())
-      {
-        throw OrthancException(ErrorCode_DatabasePlugin);
-      }
-    }
-    else
-    {
-      if (parent.empty())
-      {
-        throw OrthancException(ErrorCode_DatabasePlugin);
-      }
-
-      switch (type)
-      {
-        case ResourceType_Study:
-          result["ParentPatient"] = parent;
-          break;
-
-        case ResourceType_Series:
-          result["ParentStudy"] = parent;
-          break;
-
-        case ResourceType_Instance:
-          result["ParentSeries"] = parent;
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-
-    // List the children resources
-    std::list<std::string> children;
-    db_.GetChildrenPublicId(children, id);
-
-    if (type != ResourceType_Instance)
-    {
-      Json::Value c = Json::arrayValue;
-
-      for (std::list<std::string>::const_iterator
-             it = children.begin(); it != children.end(); ++it)
-      {
-        c.append(*it);
-      }
-
-      switch (type)
-      {
-        case ResourceType_Patient:
-          result["Studies"] = c;
-          break;
-
-        case ResourceType_Study:
-          result["Series"] = c;
-          break;
-
-        case ResourceType_Series:
-          result["Instances"] = c;
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-
-    // Extract the metadata
-    std::map<MetadataType, std::string> metadata;
-    db_.GetAllMetadata(metadata, id);
-
-    // Set the resource type
-    switch (type)
-    {
-      case ResourceType_Patient:
-        result["Type"] = "Patient";
-        break;
-
-      case ResourceType_Study:
-        result["Type"] = "Study";
-        break;
-
-      case ResourceType_Series:
-      {
-        result["Type"] = "Series";
-
-        int64_t i;
-        if (LookupIntegerMetadata(i, metadata, MetadataType_Series_ExpectedNumberOfInstances))
-        {
-          result["ExpectedNumberOfInstances"] = static_cast<int>(i);
-          result["Status"] = EnumerationToString(GetSeriesStatus(id, i));
-        }
-        else
-        {
-          result["ExpectedNumberOfInstances"] = Json::nullValue;
-          result["Status"] = EnumerationToString(SeriesStatus_Unknown);
-        }
-
-        break;
-      }
-
-      case ResourceType_Instance:
-      {
-        result["Type"] = "Instance";
-
-        FileInfo attachment;
-        if (!db_.LookupAttachment(attachment, id, FileContentType_Dicom))
-        {
-          throw OrthancException(ErrorCode_InternalError);
-        }
-
-        result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize());
-        result["FileUuid"] = attachment.GetUuid();
-
-        int64_t i;
-        if (LookupIntegerMetadata(i, metadata, MetadataType_Instance_IndexInSeries))
-        {
-          result["IndexInSeries"] = static_cast<int>(i);
-        }
-        else
-        {
-          result["IndexInSeries"] = Json::nullValue;
-        }
-
-        break;
-      }
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    // Record the remaining information
-    result["ID"] = publicId;
-    MainDicomTagsToJson(result, id, type);
-
-    std::string tmp;
-
-    if (LookupStringMetadata(tmp, metadata, MetadataType_AnonymizedFrom))
-    {
-      result["AnonymizedFrom"] = tmp;
-    }
-
-    if (LookupStringMetadata(tmp, metadata, MetadataType_ModifiedFrom))
-    {
-      result["ModifiedFrom"] = tmp;
-    }
-
-    if (type == ResourceType_Patient ||
-        type == ResourceType_Study ||
-        type == ResourceType_Series)
-    {
-      result["IsStable"] = !unstableResources_.Contains(id);
-
-      if (LookupStringMetadata(tmp, metadata, MetadataType_LastUpdate))
-      {
-        result["LastUpdate"] = tmp;
-      }
-    }
-
-    return true;
-  }
-
-
   bool ServerIndex::LookupAttachment(FileInfo& attachment,
                                      const std::string& instanceUuid,
                                      FileContentType contentType)
@@ -1885,24 +1711,6 @@
   }
 
 
-  void ServerIndex::GetAllMetadata(std::map<MetadataType, std::string>& target,
-                                   const std::string& publicId,
-                                   ResourceType expectedType)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    ResourceType type;
-    int64_t id;
-    if (!db_.LookupResource(id, type, publicId) ||
-        expectedType != type)
-    {
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
-
-    return db_.GetAllMetadata(target, id);
-  }
-
-
   void ServerIndex::ListAvailableAttachments(std::set<FileContentType>& target,
                                              const std::string& publicId,
                                              ResourceType expectedType)
@@ -2632,34 +2440,6 @@
    ** PROTOTYPING FOR DB REFACTORING BELOW
    ***/
     
-  ServerIndex::ExpandResourceOperation::ExpandResourceOperation(const std::string& resource,
-                                                                ResourceType level) :
-    found_(false),
-    resource_(resource),
-    level_(level)
-  {
-  }
-
-  
-  void ServerIndex::ExpandResourceOperation::Apply(ServerIndex::ReadOnlyTransaction& transaction)
-  {
-    found_ = transaction.LookupResource(item_, resource_, level_);
-  }
-
-  
-  const Json::Value& ServerIndex::ExpandResourceOperation::GetResource() const
-  {
-    if (found_)
-    {
-      return item_;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-
   class ServerIndex::ReadOnlyWrapper : public IReadOnlyOperations
   {
   private:
@@ -2713,15 +2493,17 @@
     {
       try
       {
+        boost::mutex::scoped_lock lock(mutex_);  // TODO - REMOVE
+
         if (readOperations != NULL)
         {
-          ReadOnlyTransaction transaction(*this);
+          ReadOnlyTransaction transaction(db_);
           readOperations->Apply(transaction);
         }
         else
         {
           assert(writeOperations != NULL);
-          ReadWriteTransaction transaction(*this);
+          ReadWriteTransaction transaction(db_);
           writeOperations->Apply(transaction);          
         }
         
@@ -2782,4 +2564,262 @@
     ReadWriteWrapper wrapper(func);
     Apply(wrapper);
   }
+
+
+  bool ServerIndex::ExpandResource(Json::Value& target,
+                                   const std::string& publicId,
+                                   ResourceType level)
+  {    
+    class Operations : public ServerIndex::IReadOnlyOperations
+    {
+    private:
+      Json::Value&        target_;
+      bool                found_;
+      ServerIndex&        index_;
+      const std::string&  publicId_;
+      ResourceType        level_;
+
+    public:
+      Operations(Json::Value& target,
+                 ServerIndex& index,
+                 const std::string& publicId,
+                 ResourceType level) :
+        target_(target),
+        found_(false),
+        index_(index),
+        publicId_(publicId),
+        level_(level)
+      {
+      }
+      
+      virtual void Apply(ServerIndex::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE
+      {
+        // Lookup for the requested resource
+        int64_t internalId;  // unused
+        ResourceType type;
+        std::string parent;
+        if (!transaction.LookupResourceAndParent(internalId, type, parent, publicId_) ||
+            type != level_)
+        {
+          found_ = false;
+        }
+        else
+        {
+          target_ = Json::objectValue;
+        
+          // Set information about the parent resource (if it exists)
+          if (type == ResourceType_Patient)
+          {
+            if (!parent.empty())
+            {
+              throw OrthancException(ErrorCode_DatabasePlugin);
+            }
+          }
+          else
+          {
+            if (parent.empty())
+            {
+              throw OrthancException(ErrorCode_DatabasePlugin);
+            }
+
+            switch (type)
+            {
+              case ResourceType_Study:
+                target_["ParentPatient"] = parent;
+                break;
+
+              case ResourceType_Series:
+                target_["ParentStudy"] = parent;
+                break;
+
+              case ResourceType_Instance:
+                target_["ParentSeries"] = parent;
+                break;
+
+              default:
+                throw OrthancException(ErrorCode_InternalError);
+            }
+          }
+
+          // List the children resources
+          std::list<std::string> children;
+          transaction.GetChildrenPublicId(children, internalId);
+
+          if (type != ResourceType_Instance)
+          {
+            Json::Value c = Json::arrayValue;
+
+            for (std::list<std::string>::const_iterator
+                   it = children.begin(); it != children.end(); ++it)
+            {
+              c.append(*it);
+            }
+
+            switch (type)
+            {
+              case ResourceType_Patient:
+                target_["Studies"] = c;
+                break;
+
+              case ResourceType_Study:
+                target_["Series"] = c;
+                break;
+
+              case ResourceType_Series:
+                target_["Instances"] = c;
+                break;
+
+              default:
+                throw OrthancException(ErrorCode_InternalError);
+            }
+          }
+
+          // Extract the metadata
+          std::map<MetadataType, std::string> metadata;
+          transaction.GetAllMetadata(metadata, internalId);
+
+          // Set the resource type
+          switch (type)
+          {
+            case ResourceType_Patient:
+              target_["Type"] = "Patient";
+              break;
+
+            case ResourceType_Study:
+              target_["Type"] = "Study";
+              break;
+
+            case ResourceType_Series:
+            {
+              target_["Type"] = "Series";
+
+              int64_t i;
+              if (LookupIntegerMetadata(i, metadata, MetadataType_Series_ExpectedNumberOfInstances))
+              {
+                target_["ExpectedNumberOfInstances"] = static_cast<int>(i);
+                target_["Status"] = EnumerationToString(transaction.GetSeriesStatus(internalId, i));
+              }
+              else
+              {
+                target_["ExpectedNumberOfInstances"] = Json::nullValue;
+                target_["Status"] = EnumerationToString(SeriesStatus_Unknown);
+              }
+
+              break;
+            }
+
+            case ResourceType_Instance:
+            {
+              target_["Type"] = "Instance";
+
+              FileInfo attachment;
+              if (!transaction.LookupAttachment(attachment, internalId, FileContentType_Dicom))
+              {
+                throw OrthancException(ErrorCode_InternalError);
+              }
+
+              target_["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize());
+              target_["FileUuid"] = attachment.GetUuid();
+
+              int64_t i;
+              if (LookupIntegerMetadata(i, metadata, MetadataType_Instance_IndexInSeries))
+              {
+                target_["IndexInSeries"] = static_cast<int>(i);
+              }
+              else
+              {
+                target_["IndexInSeries"] = Json::nullValue;
+              }
+
+              break;
+            }
+
+            default:
+              throw OrthancException(ErrorCode_InternalError);
+          }
+
+          // Record the remaining information
+          target_["ID"] = publicId_;
+          transaction.MainDicomTagsToJson(target_, internalId, type);
+
+          std::string tmp;
+
+          if (LookupStringMetadata(tmp, metadata, MetadataType_AnonymizedFrom))
+          {
+            target_["AnonymizedFrom"] = tmp;
+          }
+
+          if (LookupStringMetadata(tmp, metadata, MetadataType_ModifiedFrom))
+          {
+            target_["ModifiedFrom"] = tmp;
+          }
+
+          if (type == ResourceType_Patient ||
+              type == ResourceType_Study ||
+              type == ResourceType_Series)
+          {
+            target_["IsStable"] = !index_.IsUnstableResource(internalId);
+
+            if (LookupStringMetadata(tmp, metadata, MetadataType_LastUpdate))
+            {
+              target_["LastUpdate"] = tmp;
+            }
+          }
+
+          found_ = true;
+        }
+      }
+
+      bool HasFound() const
+      {
+        return found_;
+      }
+    };
+
+    Operations operations(target, *this, publicId, level);
+    Apply(operations);
+    return operations.HasFound();
+  }
+
+
+  void ServerIndex::GetAllMetadata(std::map<MetadataType, std::string>& target,
+                                   const std::string& publicId,
+                                   ResourceType level)
+  {
+    class Operations : public ServerIndex::IReadOnlyOperations
+    {
+    private:
+      std::map<MetadataType, std::string>&  metadata_;
+      std::string   publicId_;
+      ResourceType  level_;
+
+    public:
+      Operations(std::map<MetadataType, std::string>& metadata,
+                 const std::string& publicId,
+                 ResourceType level) :
+        metadata_(metadata),
+        publicId_(publicId),
+        level_(level)
+      {
+      }
+      
+      virtual void Apply(ServerIndex::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE
+      {
+        ResourceType type;
+        int64_t id;
+        if (!transaction.LookupResource(id, type, publicId_) ||
+            level_ != type)
+        {
+          throw OrthancException(ErrorCode_UnknownResource);
+        }
+        else
+        {
+          transaction.GetAllMetadata(metadata_, id);
+        }
+      }
+    };
+
+    Operations operations(target, publicId, level);
+    Apply(operations);
+  }
 }
--- a/OrthancServer/Sources/ServerIndex.h	Wed Mar 03 13:44:01 2021 +0100
+++ b/OrthancServer/Sources/ServerIndex.h	Wed Mar 03 16:31:57 2021 +0100
@@ -79,9 +79,11 @@
     static void UnstableResourcesMonitorThread(ServerIndex* that,
                                                unsigned int threadSleep);
 
-    void MainDicomTagsToJson(Json::Value& result,
-                             int64_t resourceId,
-                             ResourceType resourceType);
+    // A transaction must be running
+    static void MainDicomTagsToJson(Json::Value& result,
+                                    IDatabaseWrapper& db,
+                                    int64_t resourceId,
+                                    ResourceType resourceType);
 
     bool IsRecyclingNeeded(uint64_t instanceSize);
 
@@ -110,8 +112,12 @@
                          const DatabaseLookup& source,
                          ResourceType level) const;
 
-    SeriesStatus GetSeriesStatus(int64_t id,
-                                 int64_t expectedNumberOfInstances);
+    // A transaction must be running
+    static SeriesStatus GetSeriesStatus(IDatabaseWrapper& db,
+                                        int64_t id,
+                                        int64_t expectedNumberOfInstances);
+
+    bool IsUnstableResource(int64_t id);
 
   public:
     ServerIndex(ServerContext& context,
@@ -156,12 +162,6 @@
                              /* out */ uint64_t& countSeries, 
                              /* out */ uint64_t& countInstances);
 
-  private:
-    bool LookupResource(Json::Value& result,
-                        const std::string& publicId,
-                        ResourceType expectedType);
-
-  public:
     bool LookupAttachment(FileInfo& attachment,
                           const std::string& instanceUuid,
                           FileContentType contentType);
@@ -211,12 +211,6 @@
     void DeleteMetadata(const std::string& publicId,
                         MetadataType type);
 
-  private:
-    void GetAllMetadata(std::map<MetadataType, std::string>& target,
-                        const std::string& publicId,
-                        ResourceType expectedType);
-
-  public:
     bool LookupMetadata(std::string& target,
                         const std::string& publicId,
                         ResourceType expectedType,
@@ -304,26 +298,73 @@
     class ReadOnlyTransaction : public boost::noncopyable
     {
     protected:
-      ServerIndex&  index_;
+      IDatabaseWrapper&  db_;
       
     public:
-      ReadOnlyTransaction(ServerIndex& index) :
-        index_(index)
+      ReadOnlyTransaction(IDatabaseWrapper& db) :
+        db_(db)
+      {
+      }
+
+      /**
+       * Higher-level constructions
+       **/
+
+      SeriesStatus GetSeriesStatus(int64_t id,
+                                   int64_t expectedNumberOfInstances)
+      {
+        return ServerIndex::GetSeriesStatus(db_, id, expectedNumberOfInstances);
+      }
+
+      void MainDicomTagsToJson(Json::Value& result,
+                               int64_t resourceId,
+                               ResourceType resourceType)
       {
+        ServerIndex::MainDicomTagsToJson(result, db_, resourceId, resourceType);
+      }
+
+      /**
+       * Read-only methods from "IDatabaseWrapper"
+       **/
+
+      void GetAllMetadata(std::map<MetadataType, std::string>& target,
+                          int64_t id)
+      {
+        db_.GetAllMetadata(target, id);
+      }        
+
+      void GetChildrenPublicId(std::list<std::string>& target,
+                               int64_t id)
+      {
+        db_.GetChildrenPublicId(target, id);
+      }
+
+      void GetMainDicomTags(DicomMap& map,
+                            int64_t id)
+      {
+        db_.GetMainDicomTags(map, id);
       }
       
-      bool LookupResource(Json::Value& result,
-                          const std::string& publicId,
-                          ResourceType expectedType)
+      bool LookupAttachment(FileInfo& attachment,
+                            int64_t id,
+                            FileContentType contentType)
       {
-        return index_.LookupResource(result, publicId, expectedType);
+        return db_.LookupAttachment(attachment, id, contentType);
       }
-
-      void GetAllMetadata(std::map<MetadataType, std::string>& target,
-                          const std::string& publicId,
-                          ResourceType expectedType)
+      
+      bool LookupResource(int64_t& id,
+                          ResourceType& type,
+                          const std::string& publicId)
       {
-        index_.GetAllMetadata(target, publicId, expectedType);
+        return db_.LookupResource(id, type, publicId);
+      }
+      
+      bool LookupResourceAndParent(int64_t& id,
+                                   ResourceType& type,
+                                   std::string& parentPublicId,
+                                   const std::string& publicId)
+      {
+        return db_.LookupResourceAndParent(id, type, parentPublicId, publicId);
       }
     };
 
@@ -331,25 +372,11 @@
     class ReadWriteTransaction : public ReadOnlyTransaction
     {
     public:
-      ReadWriteTransaction(ServerIndex& index) :
-        ReadOnlyTransaction(index)
+      ReadWriteTransaction(IDatabaseWrapper& db) :
+        ReadOnlyTransaction(db)
       {
       }
 
-      StoreStatus Store(std::map<MetadataType, std::string>& instanceMetadata,
-                        const DicomMap& dicomSummary,
-                        const Attachments& attachments,
-                        const MetadataMap& metadata,
-                        const DicomInstanceOrigin& origin,
-                        bool overwrite,
-                        bool hasTransferSyntax,
-                        DicomTransferSyntax transferSyntax,
-                        bool hasPixelDataOffset,
-                        uint64_t pixelDataOffset)
-      {
-        return index_.Store(instanceMetadata, dicomSummary, attachments, metadata, origin,
-                            overwrite, hasTransferSyntax, transferSyntax, hasPixelDataOffset, pixelDataOffset);
-      }
     };
 
 
@@ -375,29 +402,6 @@
     };
 
 
-    class ExpandResourceOperation : public ServerIndex::IReadOnlyOperations
-    {
-    private:
-      Json::Value   item_;
-      bool          found_;
-      std::string   resource_;
-      ResourceType  level_;
-
-    public:
-      ExpandResourceOperation(const std::string& resource,
-                              ResourceType level);
-      
-      virtual void Apply(ServerIndex::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE;
-
-      bool IsFound() const
-      {
-        return found_;
-      }
-
-      const Json::Value& GetResource() const;
-    };
-  
-  
     typedef void (*ReadOnlyFunction) (ReadOnlyTransaction& transaction);
     typedef void (*ReadWriteFunction) (ReadWriteTransaction& transaction);
 
@@ -419,5 +423,13 @@
     void Apply(ReadOnlyFunction func);
 
     void Apply(ReadWriteFunction func);
+
+    bool ExpandResource(Json::Value& target,
+                        const std::string& publicId,
+                        ResourceType level);
+
+    void GetAllMetadata(std::map<MetadataType, std::string>& target,
+                        const std::string& publicId,
+                        ResourceType level);
   };
 }