changeset 231:8098448bd827

export log
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 30 Nov 2012 12:18:44 +0100
parents ae2367145b49
children 5368bbe813cf
files Core/RestApi/RestApi.h NEWS OrthancServer/DatabaseWrapper.cpp OrthancServer/DatabaseWrapper.h OrthancServer/OrthancRestApi.cpp OrthancServer/PrepareDatabase.sql OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h
diffstat 8 files changed, 231 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/Core/RestApi/RestApi.h	Fri Nov 30 11:01:29 2012 +0100
+++ b/Core/RestApi/RestApi.h	Fri Nov 30 12:18:44 2012 +0100
@@ -72,13 +72,13 @@
       }
 
       std::string GetUriComponent(const std::string& name,
-                                  const std::string& defaultValue)
+                                  const std::string& defaultValue) const
       {
         return HttpHandler::GetArgument(*uriComponents_, name, defaultValue);
       }
 
       std::string GetHttpHeader(const std::string& name,
-                                const std::string& defaultValue)
+                                const std::string& defaultValue) const
       {
         return HttpHandler::GetArgument(*httpHeaders_, name, defaultValue);
       }
@@ -95,7 +95,7 @@
 
     public:
       std::string GetArgument(const std::string& name,
-                              const std::string& defaultValue)
+                              const std::string& defaultValue) const
       {
         return HttpHandler::GetArgument(*getArguments_, name, defaultValue);
       }
--- a/NEWS	Fri Nov 30 11:01:29 2012 +0100
+++ b/NEWS	Fri Nov 30 12:18:44 2012 +0100
@@ -9,13 +9,15 @@
 * The patient/study/series/instances are now indexed by SHA-1 digests
   of their DICOM Instance IDs (and not by UUIDs anymore): The same
   DICOM objects are thus always identified by the same Orthanc IDs
+* Log of exported instances through DICOM C-Store SCU ("/exported" URI)
 
 Minor changes
 -------------
 
 * Generate a sample configuration file from command line
 * "CompletedSeries" event in the changes API
-* Thread to continuously flush DB to disk (robustness against reboots)
+* Thread to continuously flush DB to disk (SQLite checkpoints for
+  robustness against reboots)
 
 
 Version 0.2.3 (2012/10/26)
--- a/OrthancServer/DatabaseWrapper.cpp	Fri Nov 30 11:01:29 2012 +0100
+++ b/OrthancServer/DatabaseWrapper.cpp	Fri Nov 30 12:18:44 2012 +0100
@@ -554,20 +554,89 @@
   }
 
 
-  void DatabaseWrapper::LogExportedInstance(const std::string& remoteModality,
-                                            DicomInstanceHasher& hasher,
+  void DatabaseWrapper::LogExportedResource(ResourceType resourceType,
+                                            const std::string& publicId,
+                                            const std::string& remoteModality,
+                                            const std::string& patientId,
+                                            const std::string& studyInstanceUid,
+                                            const std::string& seriesInstanceUid,
+                                            const std::string& sopInstanceUid,
                                             const boost::posix_time::ptime& date)
   {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO ExportedInstances VALUES(NULL, ?, ?, ?, ?, ?, ?)");
-    s.BindString(0, remoteModality);
-    s.BindString(1, hasher.HashInstance());
-    s.BindString(2, hasher.GetPatientId());
-    s.BindString(3, hasher.GetStudyUid());
-    s.BindString(4, hasher.GetSeriesUid());
-    s.BindString(5, hasher.GetInstanceUid());
-    s.BindString(6, boost::posix_time::to_iso_string(date));
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
+
+    s.BindInt(0, resourceType);
+    s.BindString(1, publicId);
+    s.BindString(2, remoteModality);
+    s.BindString(3, patientId);
+    s.BindString(4, studyInstanceUid);
+    s.BindString(5, seriesInstanceUid);
+    s.BindString(6, sopInstanceUid);
+    s.BindString(7, boost::posix_time::to_iso_string(date));
+
     s.Run();      
   }
+
+
+  void DatabaseWrapper::GetExportedResources(Json::Value& target,
+                                             int64_t since,
+                                             unsigned int maxResults)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?");
+    s.BindInt(0, since);
+    s.BindInt(1, maxResults + 1);
+
+    Json::Value changes = Json::arrayValue;
+    int64_t last = 0;
+
+    while (changes.size() < maxResults && s.Step())
+    {
+      int64_t seq = s.ColumnInt(0);
+      ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1));
+      std::string publicId = s.ColumnString(2);
+
+      Json::Value item = Json::objectValue;
+      item["Seq"] = static_cast<int>(seq);
+      item["ResourceType"] = ToString(resourceType);
+      item["ID"] = publicId;
+      item["Path"] = GetBasePath(resourceType, publicId);
+      item["RemoteModality"] = s.ColumnString(3);
+      item["Date"] = s.ColumnString(8);
+
+      // WARNING: Do not add "break" below and do not reorder the case items!
+      switch (resourceType)
+      {
+      case ResourceType_Instance:
+        item["SopInstanceUid"] = s.ColumnString(7);
+
+      case ResourceType_Series:
+        item["SeriesInstanceUid"] = s.ColumnString(6);
+
+      case ResourceType_Study:
+        item["StudyInstanceUid"] = s.ColumnString(5);
+
+      case ResourceType_Patient:
+        item["PatientId"] = s.ColumnString(4);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      last = seq;
+
+      changes.append(item);
+    }
+
+    target = Json::objectValue;
+    target["Changes"] = changes;
+    target["PendingChanges"] = (changes.size() == maxResults && s.Step());
+    target["LastSeq"] = static_cast<int>(last);
+  }
+
+
     
 
   int64_t DatabaseWrapper::GetTableRecordCount(const std::string& table)
--- a/OrthancServer/DatabaseWrapper.h	Fri Nov 30 11:01:29 2012 +0100
+++ b/OrthancServer/DatabaseWrapper.h	Fri Nov 30 12:18:44 2012 +0100
@@ -163,10 +163,20 @@
                     int64_t since,
                     unsigned int maxResults);
 
-    void LogExportedInstance(const std::string& remoteModality,
-                             DicomInstanceHasher& hasher,
-                             const boost::posix_time::ptime& date = boost::posix_time::second_clock::local_time());
+    void LogExportedResource(ResourceType resourceType,
+                             const std::string& publicId,
+                             const std::string& remoteModality,
+                             const std::string& patientId,
+                             const std::string& studyInstanceUid,
+                             const std::string& seriesInstanceUid,
+                             const std::string& sopInstanceUid,
+                             const boost::posix_time::ptime& date = 
+                             boost::posix_time::second_clock::local_time());
     
+    void GetExportedResources(Json::Value& target,
+                              int64_t since,
+                              unsigned int maxResults);
+
     int64_t GetTableRecordCount(const std::string& table);
     
     uint64_t GetTotalCompressedSize();
--- a/OrthancServer/OrthancRestApi.cpp	Fri Nov 30 11:01:29 2012 +0100
+++ b/OrthancServer/OrthancRestApi.cpp	Fri Nov 30 12:18:44 2012 +0100
@@ -242,13 +242,18 @@
   {
     RETRIEVE_CONTEXT(call);
 
+    std::string remote = call.GetUriComponent("id", "");
     DicomUserConnection connection;
-    ConnectToModality(connection, call.GetUriComponent("id", ""));
+    ConnectToModality(connection, remote);
+
+    const std::string& resourceId = call.GetPostBody();
 
     Json::Value found;
-    if (context.GetIndex().LookupResource(found, call.GetPostBody(), ResourceType_Series))
+    if (context.GetIndex().LookupResource(found, resourceId, ResourceType_Series))
     {
       // The UUID corresponds to a series
+      context.GetIndex().LogExportedResource(resourceId, remote);
+
       for (Json::Value::ArrayIndex i = 0; i < found["Instances"].size(); i++)
       {
         std::string instanceId = found["Instances"][i].asString();
@@ -259,12 +264,13 @@
 
       call.GetOutput().AnswerBuffer("{}", "application/json");
     }
-    else if (context.GetIndex().LookupResource(found, call.GetPostBody(), ResourceType_Instance))
+    else if (context.GetIndex().LookupResource(found, resourceId, ResourceType_Instance))
     {
       // The UUID corresponds to an instance
-      std::string instanceId = call.GetPostBody();
+      context.GetIndex().LogExportedResource(resourceId, remote);
+
       std::string dicom;
-      context.ReadFile(dicom, instanceId, AttachedFileType_Dicom);
+      context.ReadFile(dicom, resourceId, AttachedFileType_Dicom);
       connection.Store(dicom);
 
       call.GetOutput().AnswerBuffer("{}", "application/json");
@@ -273,7 +279,7 @@
     {
       // The POST body is not a known resource, assume that it
       // contains a raw DICOM instance
-      connection.Store(call.GetPostBody());
+      connection.Store(resourceId);
       call.GetOutput().AnswerBuffer("{}", "application/json");
     }
   }
@@ -342,16 +348,12 @@
 
   // Changes API --------------------------------------------------------------
  
-  static void GetChanges(RestApi::GetCall& call)
+  static void GetSinceAndLimit(int64_t& since,
+                               unsigned int& limit,
+                               const RestApi::GetCall& call)
   {
-    RETRIEVE_CONTEXT(call);
-
     static const unsigned int MAX_RESULTS = 100;
-    ServerIndex& index = context.GetIndex();
         
-    //std::string filter = GetArgument(getArguments, "filter", "");
-    int64_t since;
-    unsigned int limit;
     try
     {
       since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0"));
@@ -366,15 +368,40 @@
     {
       limit = MAX_RESULTS;
     }
+  }
+
+  static void GetChanges(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+
+    //std::string filter = GetArgument(getArguments, "filter", "");
+    int64_t since;
+    unsigned int limit;
+    GetSinceAndLimit(since, limit, call);
 
     Json::Value result;
-    if (index.GetChanges(result, since, limit))
+    if (context.GetIndex().GetChanges(result, since, limit))
     {
       call.GetOutput().AnswerJson(result);
     }
   }
 
 
+  static void GetExports(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+
+    int64_t since;
+    unsigned int limit;
+    GetSinceAndLimit(since, limit, call);
+
+    Json::Value result;
+    if (context.GetIndex().GetExportedResources(result, since, limit))
+    {
+      call.GetOutput().AnswerJson(result);
+    }
+  }
+
   
   // Get information about a single instance ----------------------------------
  
@@ -592,6 +619,7 @@
     Register("/", ServeRoot);
     Register("/system", GetSystemInformation);
     Register("/changes", GetChanges);
+    Register("/exported", GetExports);
 
     Register("/instances", UploadDicomFile);
     Register("/instances", ListResources<ResourceType_Instance>);
--- a/OrthancServer/PrepareDatabase.sql	Fri Nov 30 11:01:29 2012 +0100
+++ b/OrthancServer/PrepareDatabase.sql	Fri Nov 30 12:18:44 2012 +0100
@@ -43,10 +43,11 @@
        date TEXT
        );
 
-CREATE TABLE ExportedInstances(
+CREATE TABLE ExportedResources(
        seq INTEGER PRIMARY KEY AUTOINCREMENT,
+       resourceType INTEGER,
+       publicId TEXT,
        remoteModality TEXT,
-       publicId TEXT,
        patientId TEXT,
        studyInstanceUid TEXT,
        seriesInstanceUid TEXT,
--- a/OrthancServer/ServerIndex.cpp	Fri Nov 30 11:01:29 2012 +0100
+++ b/OrthancServer/ServerIndex.cpp	Fri Nov 30 12:18:44 2012 +0100
@@ -617,4 +617,85 @@
 
     return true;
   }
+
+  void ServerIndex::LogExportedResource(const std::string& publicId,
+                                        const std::string& remoteModality)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    int64_t id;
+    ResourceType type;
+    if (!db_->LookupResource(publicId, id, type))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    std::string patientId;
+    std::string studyInstanceUid;
+    std::string seriesInstanceUid;
+    std::string sopInstanceUid;
+
+    int64_t currentId = id;
+    ResourceType currentType = type;
+
+    // Iteratively go up inside the patient/study/series/instance hierarchy
+    bool done = false;
+    while (!done)
+    {
+      DicomMap map;
+      db_->GetMainDicomTags(map, currentId);
+
+      switch (currentType)
+      {
+      case ResourceType_Patient:
+        patientId = map.GetValue(DICOM_TAG_PATIENT_ID).AsString();
+        done = true;
+        break;
+
+      case ResourceType_Study:
+        studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
+        currentType = ResourceType_Patient;
+        break;
+
+      case ResourceType_Series:
+        seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
+        currentType = ResourceType_Study;
+        break;
+
+      case ResourceType_Instance:
+        sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
+        currentType = ResourceType_Series;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      // If we have not reached the Patient level, find the parent of
+      // the current resource
+      if (!done)
+      {
+        assert(db_->LookupParent(currentId, currentId));
+      }
+    }
+
+    // No need for a SQLite::Transaction here, as we only insert 1 record
+    db_->LogExportedResource(type,
+                             publicId,
+                             remoteModality,
+                             patientId,
+                             studyInstanceUid,
+                             seriesInstanceUid,
+                             sopInstanceUid);
+  }
+
+
+  bool ServerIndex::GetExportedResources(Json::Value& target,
+                                         int64_t since,
+                                         unsigned int maxResults)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    db_->GetExportedResources(target, since, maxResults);
+    return true;
+  }
 }
--- a/OrthancServer/ServerIndex.h	Fri Nov 30 11:01:29 2012 +0100
+++ b/OrthancServer/ServerIndex.h	Fri Nov 30 12:18:44 2012 +0100
@@ -101,5 +101,12 @@
     bool GetChanges(Json::Value& target,
                     int64_t since,
                     unsigned int maxResults);
+
+    void LogExportedResource(const std::string& publicId,
+                             const std::string& remoteModality);
+
+    bool GetExportedResources(Json::Value& target,
+                              int64_t since,
+                              unsigned int maxResults);
   };
 }