diff OrthancServer/Sources/main.cpp @ 4232:688435755466

added DELETE in WebDAV, first working virtual filesystem
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 07 Oct 2020 13:00:57 +0200
parents 290ffcb0a147
children ca2a55a62c81
line wrap: on
line diff
--- a/OrthancServer/Sources/main.cpp	Tue Oct 06 20:55:16 2020 +0200
+++ b/OrthancServer/Sources/main.cpp	Wed Oct 07 13:00:57 2020 +0200
@@ -743,7 +743,7 @@
   }
 
 
-  virtual bool CreateFolder(const UriComponents& path)
+  virtual bool CreateFolder(const UriComponents& path) ORTHANC_OVERRIDE
   {
     if (IsUploadedFolder(path))
     {
@@ -756,6 +756,11 @@
     }
   }
 
+  virtual bool DeleteItem(const std::vector<std::string>& path) ORTHANC_OVERRIDE
+  {
+    return false;  // read-only
+  }
+
   virtual void Start() ORTHANC_OVERRIDE
   {
     LOG(WARNING) << "Starting WebDAV";
@@ -771,13 +776,36 @@
 
 
 
-static const char* const DICOM_IDENTIFIERS = "DicomIdentifiers";
+static const char* const BY_UIDS = "by-uids";
 
 class DummyBucket2 : public IWebDavBucket  // TODO
 {
 private:
   ServerContext&  context_;
 
+
+  static void LookupTime(boost::posix_time::ptime& target,
+                         ServerContext& context,
+                         const std::string& publicId,
+                         MetadataType metadata)
+  {
+    std::string value;
+    if (context.GetIndex().LookupMetadata(value, publicId, metadata))
+    {
+      try
+      {
+        target = boost::posix_time::from_iso_string(value);
+        return;
+      }
+      catch (std::exception& e)
+      {
+      }
+    }
+
+    target = boost::posix_time::second_clock::universal_time();  // Now
+  }
+
+  
   class DicomIdentifiersVisitor : public ServerContext::ILookupVisitor
   {
   private:
@@ -813,18 +841,23 @@
                        const Json::Value* dicomAsJson  /* unused (*) */)  ORTHANC_OVERRIDE
     {
       DicomTag tag(0, 0);
+      MetadataType dateMetadata;
+
       switch (level_)
       {
         case ResourceType_Study:
           tag = DICOM_TAG_STUDY_INSTANCE_UID;
+          dateMetadata = MetadataType_LastUpdate;
           break;
 
         case ResourceType_Series:
           tag = DICOM_TAG_SERIES_INSTANCE_UID;
+          dateMetadata = MetadataType_LastUpdate;
           break;
         
         case ResourceType_Instance:
           tag = DICOM_TAG_SOP_INSTANCE_UID;
+          dateMetadata = MetadataType_Instance_ReceptionDate;
           break;
 
         default:
@@ -835,6 +868,8 @@
       if (mainDicomTags.LookupStringValue(s, tag, false) &&
           !s.empty())
       {
+        std::unique_ptr<Resource> resource;
+
         if (level_ == ResourceType_Instance)
         {
           FileInfo info;
@@ -843,13 +878,19 @@
             std::unique_ptr<File> f(new File(s + ".dcm"));
             f->SetMimeType(MimeType_Dicom);
             f->SetContentLength(info.GetUncompressedSize());
-            target_.AddResource(f.release());
+            resource.reset(f.release());
           }
         }
         else
         {
-          target_.AddResource(new Folder(s));
+          resource.reset(new Folder(s));
         }
+
+        boost::posix_time::ptime t;
+        LookupTime(t, context_, publicId, dateMetadata);
+        resource->SetCreationTime(t);
+        
+        target_.AddResource(resource.release());
       }
     }
   };
@@ -860,13 +901,16 @@
     ServerContext&  context_;
     bool            success_;
     std::string&    target_;
+    boost::posix_time::ptime&  modificationTime_;
 
   public:
     DicomFileVisitor(ServerContext& context,
-                     std::string& target) :
+                     std::string& target,
+                     boost::posix_time::ptime& modificationTime) :
       context_(context),
       success_(false),
-      target_(target)
+      target_(target),
+      modificationTime_(modificationTime)
     {
     }
 
@@ -874,7 +918,58 @@
     {
       return success_;
     }
+
+    virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
+    {
+      return false;   // (*)
+    }
       
+    virtual void MarkAsComplete() ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void Visit(const std::string& publicId,
+                       const std::string& instanceId   /* unused     */,
+                       const DicomMap& mainDicomTags,
+                       const Json::Value* dicomAsJson  /* unused (*) */)  ORTHANC_OVERRIDE
+    {
+      if (success_)
+      {
+        success_ = false;  // Two matches => Error
+      }
+      else
+      {
+        LookupTime(modificationTime_, context_, publicId, MetadataType_Instance_ReceptionDate);
+        context_.ReadDicom(target_, publicId);
+        success_ = true;
+      }
+    }
+  };
+  
+  class OrthancJsonVisitor : public ServerContext::ILookupVisitor
+  {
+  private:
+    ServerContext&  context_;
+    bool            success_;
+    std::string&    target_;
+    ResourceType    level_;
+
+  public:
+    OrthancJsonVisitor(ServerContext& context,
+                       std::string& target,
+                       ResourceType level) :
+      context_(context),
+      success_(false),
+      target_(target),
+      level_(level)
+    {
+    }
+
+    bool IsSuccess() const
+    {
+      return success_;
+    }
+
     virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
     {
       return false;   // (*)
@@ -889,10 +984,47 @@
                        const DicomMap& mainDicomTags,
                        const Json::Value* dicomAsJson  /* unused (*) */)  ORTHANC_OVERRIDE
     {
-      context_.ReadDicom(target_, publicId);
-      success_ = true;
+      Json::Value info;
+      if (context_.GetIndex().LookupResource(info, publicId, level_))
+      {
+        if (success_)
+        {
+          success_ = false;  // Two matches => Error
+        }
+        else
+        {
+          target_ = info.toStyledString();
+
+          // Replace UNIX newlines with DOS newlines 
+          boost::replace_all(target_, "\n", "\r\n");
+
+          success_ = true;
+        }
+      }
     }
   };
+
+
+  void AddVirtualFile(Collection& collection,
+                      const UriComponents& path,
+                      const std::string& filename)
+  {
+    MimeType mime;
+    std::string content;
+    boost::posix_time::ptime modification;
+
+    UriComponents p = path;
+    p.push_back(filename);
+
+    if (GetFileContent(mime, content, modification, p))
+    {
+      std::unique_ptr<File> f(new File(filename));
+      f->SetMimeType(mime);
+      f->SetContentLength(content.size());
+      f->SetCreationTime(modification);
+      collection.AddResource(f.release());
+    }
+  }    
   
 public:
   DummyBucket2(ServerContext& context) :
@@ -906,10 +1038,10 @@
     {
       return true;
     }
-    else if (path.front() == DICOM_IDENTIFIERS &&
-             path.size() <= 3)
+    else if (path.front() == BY_UIDS)
     {
-      return true;
+      return (path.size() <= 3 &&
+              (path.size() != 3 || path[2] != "study.json"));
     }
     else
     {
@@ -922,26 +1054,32 @@
   {
     if (path.empty())
     {
-      collection.AddResource(new Folder(DICOM_IDENTIFIERS));
+      collection.AddResource(new Folder(BY_UIDS));
       return true;
     }
-    else if (path.front() == DICOM_IDENTIFIERS)
+    else if (path.front() == BY_UIDS)
     {
       DatabaseLookup query;
       ResourceType level;
+      size_t limit = 0;  // By default, no limits
 
       if (path.size() == 1)
       {
         level = ResourceType_Study;
+        limit = 100;  // TODO
       }
       else if (path.size() == 2)
       {
+        AddVirtualFile(collection, path, "study.json");
+
         level = ResourceType_Series;
         query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1],
                                 true /* case sensitive */, true /* mandatory tag */);
       }      
       else if (path.size() == 3)
       {
+        AddVirtualFile(collection, path, "series.json");
+
         level = ResourceType_Instance;
         query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1],
                                 true /* case sensitive */, true /* mandatory tag */);
@@ -954,7 +1092,7 @@
       }
 
       DicomIdentifiersVisitor visitor(context_, collection, level);
-      context_.Apply(visitor, query, level, 0 /* since */, 100 /* limit */);
+      context_.Apply(visitor, query, level, 0 /* since */, limit);
       
       return true;
     }
@@ -969,32 +1107,60 @@
                               boost::posix_time::ptime& modificationTime, 
                               const UriComponents& path) ORTHANC_OVERRIDE
   {
-    if (path.size() == 4 &&
-        path[0] == DICOM_IDENTIFIERS &&
-        boost::ends_with(path[3], ".dcm"))
+    if (!path.empty() &&
+        path[0] == BY_UIDS)
     {
-      std::string sopInstanceUid = path[3];
-      sopInstanceUid.resize(sopInstanceUid.size() - 4);
+      if (path.size() == 3 &&
+          path[2] == "study.json")
+      {
+        DatabaseLookup query;
+        query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1],
+                                true /* case sensitive */, true /* mandatory tag */);
       
-      mime = MimeType_Dicom;
+        OrthancJsonVisitor visitor(context_, content, ResourceType_Study);
+        context_.Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */);
 
-      DatabaseLookup query;
-      query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1],
+        mime = MimeType_Json;
+        return visitor.IsSuccess();
+      }
+      else if (path.size() == 4 &&
+               path[3] == "series.json")
+      {
+        DatabaseLookup query;
+        query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1],
+                                true /* case sensitive */, true /* mandatory tag */);
+        query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2],
                                 true /* case sensitive */, true /* mandatory tag */);
-      query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2],
-                              true /* case sensitive */, true /* mandatory tag */);
-      query.AddRestConstraint(DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUid,
-                              true /* case sensitive */, true /* mandatory tag */);
       
-      DicomFileVisitor visitor(context_, content);
-      context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 100 /* limit */);
+        OrthancJsonVisitor visitor(context_, content, ResourceType_Series);
+        context_.Apply(visitor, query, ResourceType_Series, 0 /* since */, 0 /* no limit */);
 
-      return visitor.IsSuccess();
+        mime = MimeType_Json;
+        return visitor.IsSuccess();
+      }
+      else if (path.size() == 4 &&
+               boost::ends_with(path[3], ".dcm"))
+      {
+        std::string sopInstanceUid = path[3];
+        sopInstanceUid.resize(sopInstanceUid.size() - 4);
+        
+        DatabaseLookup query;
+        query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1],
+                                true /* case sensitive */, true /* mandatory tag */);
+        query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2],
+                                true /* case sensitive */, true /* mandatory tag */);
+        query.AddRestConstraint(DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUid,
+                                true /* case sensitive */, true /* mandatory tag */);
+      
+        DicomFileVisitor visitor(context_, content, modificationTime);
+        context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 0 /* no limit */);
+        
+        mime = MimeType_Dicom;
+        return visitor.IsSuccess();
+      }
     }
-    else
-    {
-      return false;
-    }
+      
+    return false;
   }
 
   
@@ -1010,6 +1176,12 @@
     return false;
   }
 
+  virtual bool DeleteItem(const std::vector<std::string>& path) ORTHANC_OVERRIDE
+  {
+    LOG(WARNING) << "DELETE: " << Toolbox::FlattenUri(path);
+    return false;  // read-only
+  }
+
   virtual void Start() ORTHANC_OVERRIDE
   {
   }