diff OrthancServer/Sources/main.cpp @ 4240:799c0c527ced

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 09 Oct 2020 12:02:40 +0200
parents c8754c4c1862
children 5cfa6ba75dfc
line wrap: on
line diff
--- a/OrthancServer/Sources/main.cpp	Fri Oct 09 11:38:03 2020 +0200
+++ b/OrthancServer/Sources/main.cpp	Fri Oct 09 12:02:40 2020 +0200
@@ -34,8 +34,6 @@
 #include "PrecompiledHeadersServer.h"
 #include "OrthancRestApi/OrthancRestApi.h"
 
-#include <boost/algorithm/string/predicate.hpp>
-
 #include "../../OrthancFramework/Sources/Compatibility.h"
 #include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h"
 #include "../../OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.h"
@@ -52,14 +50,13 @@
 #include "OrthancGetRequestHandler.h"
 #include "OrthancInitialization.h"
 #include "OrthancMoveRequestHandler.h"
+#include "OrthancWebDav.h"
 #include "ServerContext.h"
 #include "ServerJobs/StorageCommitmentScpJob.h"
 #include "ServerToolbox.h"
 #include "StorageCommitmentReports.h"
 
-#include "../../OrthancFramework/Sources/HttpServer/WebDavStorage.h"  // TODO
-#include "Search/DatabaseLookup.h"  // TODO
-#include <boost/regex.hpp> // TODO
+#include <boost/algorithm/string/predicate.hpp>
 
 
 using namespace Orthanc;
@@ -615,1603 +612,6 @@
 };
 
 
-
-
-
-
-
-static const char* const UPLOAD_FOLDER = "upload";
-
-class DummyBucket : public IWebDavBucket  // TODO
-{
-private:
-  ServerContext&  context_;
-  WebDavStorage   storage_;
-
-  bool IsUploadedFolder(const UriComponents& path) const
-  {
-    return (path.size() >= 1 && path[0] == UPLOAD_FOLDER);
-  }
-
-public:
-  DummyBucket(ServerContext& context,
-              bool isMemory) :
-    context_(context),
-    storage_(isMemory)
-  {
-  }
-  
-  virtual bool IsExistingFolder(const UriComponents& path) ORTHANC_OVERRIDE
-  {
-    if (IsUploadedFolder(path))
-    {
-      return storage_.IsExistingFolder(UriComponents(path.begin() + 1, path.end()));
-    }
-    else
-    {
-      return (path.size() == 0 ||
-              (path.size() == 1 && path[0] == "Folder1") ||
-              (path.size() == 2 && path[0] == "Folder1" && path[1] == "Folder2"));
-    }
-  }
-
-  virtual bool ListCollection(Collection& collection,
-                              const UriComponents& path) ORTHANC_OVERRIDE
-  {
-    if (IsUploadedFolder(path))
-    {
-      return storage_.ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
-    }
-    else if (IsExistingFolder(path))
-    {
-      if (path.empty())
-      {
-        collection.AddResource(new Folder(UPLOAD_FOLDER));
-      }
-      
-      for (unsigned int i = 0; i < 5; i++)
-      {
-        std::unique_ptr<File> f(new File("IM" + boost::lexical_cast<std::string>(i) + ".dcm"));
-        f->SetContentLength(1024 * i);
-        f->SetMimeType(MimeType_PlainText);
-        collection.AddResource(f.release());
-      }
-        
-      for (unsigned int i = 0; i < 5; i++)
-      {
-        collection.AddResource(new Folder("Folder" + boost::lexical_cast<std::string>(i)));
-      }
-
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  virtual bool GetFileContent(MimeType& mime,
-                              std::string& content,
-                              boost::posix_time::ptime& time, 
-                              const UriComponents& path) ORTHANC_OVERRIDE
-  {
-    if (path.empty())
-    {
-      return false;
-    }
-    else if (IsUploadedFolder(path))
-    {
-      return storage_.GetFileContent(mime, content, time,
-                                     UriComponents(path.begin() + 1, path.end()));
-    }
-    else if (path.back() == "IM0.dcm" ||
-             path.back() == "IM1.dcm" ||
-             path.back() == "IM2.dcm" ||
-             path.back() == "IM3.dcm" ||
-             path.back() == "IM4.dcm")
-    {
-      time = boost::posix_time::second_clock::universal_time();
-
-      std::string s;
-      for (size_t i = 0; i < path.size(); i++)
-      {
-        s += "/" + path[i];
-      }
-      
-      content = "Hello world!\r\n" + s + "\r\n";
-      mime = MimeType_PlainText;
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  
-  virtual bool StoreFile(const std::string& content,
-                         const UriComponents& path) ORTHANC_OVERRIDE
-  {
-    if (IsUploadedFolder(path))
-    {
-      return storage_.StoreFile(content, UriComponents(path.begin() + 1, path.end()));
-    }
-    else
-    {
-      LOG(WARNING) << "Writing to a read-only location in WebDAV: " << Toolbox::FlattenUri(path);
-      return false;
-    }
-  }
-
-
-  virtual bool CreateFolder(const UriComponents& path) ORTHANC_OVERRIDE
-  {
-    if (IsUploadedFolder(path))
-    {
-      return storage_.CreateFolder(UriComponents(path.begin() + 1, path.end()));
-    }
-    else
-    {
-      LOG(WARNING) << "Writing to a read-only location in WebDAV: " << Toolbox::FlattenUri(path);
-      return false;
-    }
-  }
-
-  virtual bool DeleteItem(const std::vector<std::string>& path) ORTHANC_OVERRIDE
-  {
-    return false;  // read-only
-  }
-
-  virtual void Start() ORTHANC_OVERRIDE
-  {
-    LOG(WARNING) << "Starting WebDAV";
-  }
-
-  virtual void Stop() ORTHANC_OVERRIDE
-  {
-    LOG(WARNING) << "Stopping WebDAV";
-  }
-};
-
-
-
-
-
-static const char* const BY_PATIENTS = "by-patients";
-static const char* const BY_STUDIES = "by-studies";
-static const char* const BY_DATE = "by-dates";
-static const char* const BY_UIDS = "by-uids";
-static const char* const UPLOADS = "uploads";
-static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
-
-class DummyBucket2 : public IWebDavBucket  // TODO
-{
-private:
-  typedef std::map<ResourceType, std::string>  Templates;
-
-
-  static boost::posix_time::ptime GetNow()
-  {
-    return boost::posix_time::second_clock::universal_time();
-  }
-  
-
-  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 = GetNow();
-  }
-
-  
-  class DicomIdentifiersVisitor : public ServerContext::ILookupVisitor
-  {
-  private:
-    ServerContext&  context_;
-    bool            isComplete_;
-    Collection&     target_;
-    ResourceType    level_;
-
-  public:
-    DicomIdentifiersVisitor(ServerContext& context,
-                            Collection&  target,
-                            ResourceType level) :
-      context_(context),
-      isComplete_(false),
-      target_(target),
-      level_(level)
-    {
-    }
-      
-    virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
-    {
-      return false;   // (*)
-    }
-      
-    virtual void MarkAsComplete() ORTHANC_OVERRIDE
-    {
-      isComplete_ = true;  // TODO
-    }
-
-    virtual void Visit(const std::string& publicId,
-                       const std::string& instanceId   /* unused     */,
-                       const DicomMap& mainDicomTags,
-                       const Json::Value* dicomAsJson  /* unused (*) */)  ORTHANC_OVERRIDE
-    {
-      DicomTag tag(0, 0);
-      MetadataType timeMetadata;
-
-      switch (level_)
-      {
-        case ResourceType_Study:
-          tag = DICOM_TAG_STUDY_INSTANCE_UID;
-          timeMetadata = MetadataType_LastUpdate;
-          break;
-
-        case ResourceType_Series:
-          tag = DICOM_TAG_SERIES_INSTANCE_UID;
-          timeMetadata = MetadataType_LastUpdate;
-          break;
-        
-        case ResourceType_Instance:
-          tag = DICOM_TAG_SOP_INSTANCE_UID;
-          timeMetadata = MetadataType_Instance_ReceptionDate;
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-        
-      std::string s;
-      if (mainDicomTags.LookupStringValue(s, tag, false) &&
-          !s.empty())
-      {
-        std::unique_ptr<Resource> resource;
-
-        if (level_ == ResourceType_Instance)
-        {
-          FileInfo info;
-          if (context_.GetIndex().LookupAttachment(info, publicId, FileContentType_Dicom))
-          {
-            std::unique_ptr<File> f(new File(s + ".dcm"));
-            f->SetMimeType(MimeType_Dicom);
-            f->SetContentLength(info.GetUncompressedSize());
-            resource.reset(f.release());
-          }
-        }
-        else
-        {
-          resource.reset(new Folder(s));
-        }
-
-        if (resource.get() != NULL)
-        {
-          boost::posix_time::ptime t;
-          LookupTime(t, context_, publicId, timeMetadata);
-          resource->SetCreationTime(t);
-          target_.AddResource(resource.release());
-        }
-      }
-    }
-  };
-  
-  class DicomFileVisitor : public ServerContext::ILookupVisitor
-  {
-  private:
-    ServerContext&  context_;
-    bool            success_;
-    std::string&    target_;
-    boost::posix_time::ptime&  time_;
-
-  public:
-    DicomFileVisitor(ServerContext& context,
-                     std::string& target,
-                     boost::posix_time::ptime& time) :
-      context_(context),
-      success_(false),
-      target_(target),
-      time_(time)
-    {
-    }
-
-    bool IsSuccess() const
-    {
-      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(time_, 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;   // (*)
-    }
-      
-    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
-    {
-      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());
-    }
-  }
-
-
-
-
-  class ResourcesIndex : public boost::noncopyable
-  {
-  public:
-    typedef std::map<std::string, std::string>   Map;
-
-  private:
-    ServerContext&  context_;
-    ResourceType    level_;
-    std::string     template_;
-    Map             pathToResource_;
-    Map             resourceToPath_;
-
-    void CheckInvariants()
-    {
-#ifndef NDEBUG
-      assert(pathToResource_.size() == resourceToPath_.size());
-
-      for (Map::const_iterator it = pathToResource_.begin(); it != pathToResource_.end(); ++it)
-      {
-        assert(resourceToPath_[it->second] == it->first);
-      }
-
-      for (Map::const_iterator it = resourceToPath_.begin(); it != resourceToPath_.end(); ++it)
-      {
-        assert(pathToResource_[it->second] == it->first);
-      }
-#endif
-    }      
-
-    void AddTags(DicomMap& target,
-                 const std::string& resourceId,
-                 ResourceType tagsFromLevel)
-    {
-      DicomMap tags;
-      if (context_.GetIndex().GetMainDicomTags(tags, resourceId, level_, tagsFromLevel))
-      {
-        target.Merge(tags);
-      }
-    }
-
-    void Register(const std::string& resourceId)
-    {
-      // Don't register twice the same resource
-      if (resourceToPath_.find(resourceId) == resourceToPath_.end())
-      {
-        std::string name = template_;
-
-        DicomMap tags;
-
-        AddTags(tags, resourceId, level_);
-        
-        if (level_ == ResourceType_Study)
-        {
-          AddTags(tags, resourceId, ResourceType_Patient);
-        }
-        
-        DicomArray arr(tags);
-        for (size_t i = 0; i < arr.GetSize(); i++)
-        {
-          const DicomElement& element = arr.GetElement(i);
-          if (!element.GetValue().IsNull() &&
-              !element.GetValue().IsBinary())
-          {
-            const std::string tag = FromDcmtkBridge::GetTagName(element.GetTag(), "");
-            boost::replace_all(name, "{{" + tag + "}}", element.GetValue().GetContent());
-          } 
-        }
-
-        // Blank the tags that were not matched
-        static const boost::regex REGEX_BLANK_TAGS("{{.*?}}");  // non-greedy match
-        name = boost::regex_replace(name, REGEX_BLANK_TAGS, "");
-
-        // UTF-8 characters cannot be used on Windows XP
-        name = Toolbox::ConvertToAscii(name);
-        boost::replace_all(name, "/", "");
-        boost::replace_all(name, "\\", "");
-
-        // Trim sequences of spaces as one single space
-        static const boost::regex REGEX_TRIM_SPACES("{{.*?}}");
-        name = boost::regex_replace(name, REGEX_TRIM_SPACES, " ");
-        name = Toolbox::StripSpaces(name);
-
-        size_t count = 0;
-        for (;;)
-        {
-          std::string path = name;
-          if (count > 0)
-          {
-            path += " (" + boost::lexical_cast<std::string>(count) + ")";
-          }
-
-          if (pathToResource_.find(path) == pathToResource_.end())
-          {
-            pathToResource_[path] = resourceId;
-            resourceToPath_[resourceId] = path;
-            return;
-          }
-
-          count++;
-        }
-
-        throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-
-  public:
-    ResourcesIndex(ServerContext& context,
-                   ResourceType level,
-                   const std::string& templateString) :
-      context_(context),
-      level_(level),
-      template_(templateString)
-    {
-    }
-
-    ResourceType GetLevel() const
-    {
-      return level_;
-    }
-
-    void Refresh(std::set<std::string>& removedPaths /* out */,
-                 const std::set<std::string>& resources)
-    {
-      CheckInvariants();
-
-      // Detect the resources that have been removed since last refresh
-      removedPaths.clear();
-      std::set<std::string> removedResources;
-      
-      for (Map::iterator it = resourceToPath_.begin(); it != resourceToPath_.end(); ++it)
-      {
-        if (resources.find(it->first) == resources.end())
-        {
-          const std::string& path = it->second;
-          
-          assert(pathToResource_.find(path) != pathToResource_.end());
-          pathToResource_.erase(path);
-          removedPaths.insert(path);
-          
-          removedResources.insert(it->first);  // Delay the removal to avoid disturbing the iterator
-        }
-      }
-
-      // Remove the missing resources
-      for (std::set<std::string>::const_iterator it = removedResources.begin(); it != removedResources.end(); ++it)
-      {
-        assert(resourceToPath_.find(*it) != resourceToPath_.end());
-        resourceToPath_.erase(*it);
-      }
-
-      CheckInvariants();
-
-      for (std::set<std::string>::const_iterator it = resources.begin(); it != resources.end(); ++it)
-      {
-        Register(*it);
-      }
-
-      CheckInvariants();
-    }
-
-    const Map& GetPathToResource() const
-    {
-      return pathToResource_;
-    }
-  };
-
-
-  class INode : public boost::noncopyable
-  {
-  public:
-    virtual ~INode()
-    {
-    }
-
-    virtual bool ListCollection(IWebDavBucket::Collection& target,
-                                const UriComponents& path) = 0;
-
-    virtual bool GetFileContent(MimeType& mime,
-                                std::string& content,
-                                boost::posix_time::ptime& time, 
-                                const UriComponents& path) = 0;
-  };
-
-
-  
-  class InstancesOfSeries : public INode
-  {
-  private:
-    ServerContext&  context_;
-    std::string     parentSeries_;
-
-  public:
-    InstancesOfSeries(ServerContext& context,
-                      const std::string& parentSeries) :
-      context_(context),
-      parentSeries_(parentSeries)
-    {
-    }
-
-    virtual bool ListCollection(IWebDavBucket::Collection& target,
-                                const UriComponents& path) ORTHANC_OVERRIDE
-    {
-      if (path.empty())
-      {
-        std::list<std::string> resources;
-        try
-        {
-          context_.GetIndex().GetChildren(resources, parentSeries_);
-        }
-        catch (OrthancException&)
-        {
-          // Unknown (or deleted) parent series
-          return false;
-        }
-
-        for (std::list<std::string>::const_iterator
-               it = resources.begin(); it != resources.end(); ++it)
-        {
-          boost::posix_time::ptime time;
-          LookupTime(time, context_, *it, MetadataType_Instance_ReceptionDate);
-
-          FileInfo info;
-          if (context_.GetIndex().LookupAttachment(info, *it, FileContentType_Dicom))
-          {
-            std::unique_ptr<File> resource(new File(*it + ".dcm"));
-            resource->SetMimeType(MimeType_Dicom);
-            resource->SetContentLength(info.GetUncompressedSize());
-            resource->SetCreationTime(time);
-            target.AddResource(resource.release());
-          }          
-        }
-        
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-
-    virtual bool GetFileContent(MimeType& mime,
-                                std::string& content,
-                                boost::posix_time::ptime& time, 
-                                const UriComponents& path) ORTHANC_OVERRIDE
-    {
-      if (path.size() == 1 &&
-          boost::ends_with(path[0], ".dcm"))
-      {
-        std::string instanceId = path[0].substr(0, path[0].size() - 4);
-
-        try
-        {
-          mime = MimeType_Dicom;
-          context_.ReadDicom(content, instanceId);
-          LookupTime(time, context_, instanceId, MetadataType_Instance_ReceptionDate);
-          return true;
-        }
-        catch (OrthancException&)
-        {
-          // File was removed
-          return false;
-        }
-      }
-      else
-      {
-        return false;
-      }
-    }
-  };
-
-
-
-  /**
-   * The "InternalNode" class corresponds to a non-leaf node in the
-   * WebDAV tree, that only contains subfolders (no file).
-   * 
-   * TODO: Implement a LRU index to dynamically remove the oldest
-   * children on high RAM usage.
-   **/
-  class InternalNode : public INode
-  {
-  private:
-    typedef std::map<std::string, INode*>  Children;
-
-    Children  children_;
-
-    INode* GetChild(const std::string& path)  // Don't delete the result pointer!
-    {
-      Children::const_iterator child = children_.find(path);
-      if (child == children_.end())
-      {
-        INode* child = CreateChild(path);
-        
-        if (child == NULL)
-        {
-          return NULL;
-        }
-        else
-        {
-          children_[path] = child;
-          return child;
-        }
-      }
-      else
-      {
-        assert(child->second != NULL);
-        return child->second;
-      }
-    }
-
-  protected:
-    void RemoveSubfolder(const std::string& path)
-    {
-      Children::iterator child = children_.find(path);
-      if (child != children_.end())
-      {
-        assert(child->second != NULL);
-        delete child->second;
-        children_.erase(child);
-      }
-    }
-    
-    virtual void Refresh() = 0;
-    
-    virtual bool ListSubfolders(IWebDavBucket::Collection& target) = 0;
-
-    virtual INode* CreateChild(const std::string& path) = 0;
-
-  public:
-    virtual ~InternalNode()
-    {
-      for (Children::iterator it = children_.begin(); it != children_.end(); ++it)
-      {
-        assert(it->second != NULL);
-        delete it->second;
-      }
-    }
-
-    virtual bool ListCollection(IWebDavBucket::Collection& target,
-                                const UriComponents& path)
-      ORTHANC_OVERRIDE ORTHANC_FINAL
-    {
-      Refresh();
-      
-      if (path.empty())
-      {
-        return ListSubfolders(target);
-      }
-      else
-      {
-        // Recursivity
-        INode* child = GetChild(path[0]);
-        if (child == NULL)
-        {
-          return false;
-        }
-        else
-        {
-          UriComponents subpath(path.begin() + 1, path.end());
-          return child->ListCollection(target, subpath);
-        }
-      }
-    }
-
-    virtual bool GetFileContent(MimeType& mime,
-                                std::string& content,
-                                boost::posix_time::ptime& time, 
-                                const UriComponents& path)
-      ORTHANC_OVERRIDE ORTHANC_FINAL
-    {
-      if (path.empty())
-      {
-        return false;  // An internal node doesn't correspond to a file
-      }
-      else
-      {
-        // Recursivity
-        Refresh();
-      
-        INode* child = GetChild(path[0]);
-        if (child == NULL)
-        {
-          return false;
-        }
-        else
-        {
-          UriComponents subpath(path.begin() + 1, path.end());
-          return child->GetFileContent(mime, content, time, subpath);
-        }
-      }
-    }
-  };
-  
-
-  class ListOfResources : public InternalNode
-  {
-  private:
-    ServerContext&                   context_;
-    const Templates&                 templates_;
-    std::unique_ptr<ResourcesIndex>  index_;
-    MetadataType                     timeMetadata_;
-
-  protected:
-    virtual void Refresh() ORTHANC_OVERRIDE ORTHANC_FINAL
-    {
-      std::list<std::string> resources;
-      GetCurrentResources(resources);
-
-      std::set<std::string> removedPaths;
-      index_->Refresh(removedPaths, std::set<std::string>(resources.begin(), resources.end()));
-
-      // Remove the children whose associated resource doesn't exist anymore
-      for (std::set<std::string>::const_iterator
-             it = removedPaths.begin(); it != removedPaths.end(); ++it)
-      {
-        RemoveSubfolder(*it);
-      }
-    }
-
-    virtual bool ListSubfolders(IWebDavBucket::Collection& target) ORTHANC_OVERRIDE ORTHANC_FINAL
-    {
-      if (index_->GetLevel() == ResourceType_Instance)
-      {
-        // Not a collection, no subfolders
-        return false;
-      }
-      else
-      {
-        const ResourcesIndex::Map& paths = index_->GetPathToResource();
-        
-        for (ResourcesIndex::Map::const_iterator it = paths.begin(); it != paths.end(); ++it)
-        {
-          boost::posix_time::ptime time;
-          LookupTime(time, context_, it->second, timeMetadata_);
-
-          std::unique_ptr<IWebDavBucket::Resource> resource(new IWebDavBucket::Folder(it->first));
-          resource->SetCreationTime(time);
-          target.AddResource(resource.release());
-        }
-
-        return true;
-      }
-    }
-
-    virtual INode* CreateChild(const std::string& path) ORTHANC_OVERRIDE ORTHANC_FINAL
-    {
-      ResourcesIndex::Map::const_iterator resource = index_->GetPathToResource().find(path);
-      if (resource == index_->GetPathToResource().end())
-      {
-        return NULL;
-      }
-      else
-      {
-        return CreateResourceNode(resource->second);
-      }
-    }
-
-    ServerContext& GetContext() const
-    {
-      return context_;
-    }
-    
-    virtual void GetCurrentResources(std::list<std::string>& resources) = 0;
-
-    virtual INode* CreateResourceNode(const std::string& resource) = 0;
-    
-  public:
-    ListOfResources(ServerContext& context,
-                    ResourceType level,
-                    const Templates& templates) :
-      context_(context),
-      templates_(templates)
-    {
-      Templates::const_iterator t = templates.find(level);
-      if (t == templates.end())
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-      
-      index_.reset(new ResourcesIndex(context, level, t->second));
-      
-      if (level == ResourceType_Instance)
-      {
-        timeMetadata_ = MetadataType_Instance_ReceptionDate;
-      }
-      else
-      {
-        timeMetadata_ = MetadataType_LastUpdate;
-      }
-    }
-
-    ResourceType GetLevel() const
-    {
-      return index_->GetLevel();
-    }
-
-    const Templates& GetTemplates() const
-    {
-      return templates_;
-    }
-  };
-
-  
-
-  class SingleDicomResource : public ListOfResources
-  {
-  private:
-    std::string  parentId_;
-    
-  protected: 
-    virtual void GetCurrentResources(std::list<std::string>& resources) ORTHANC_OVERRIDE
-    {
-      try
-      {
-        GetContext().GetIndex().GetChildren(resources, parentId_);
-      }
-      catch (OrthancException&)
-      {
-        // Unknown parent resource
-        resources.clear();
-      }
-    }
-
-    virtual INode* CreateResourceNode(const std::string& resource) ORTHANC_OVERRIDE
-    {
-      if (GetLevel() == ResourceType_Instance)
-      {
-        return NULL;
-      }
-      else if (GetLevel() == ResourceType_Series)
-      {
-        return new InstancesOfSeries(GetContext(), resource);
-      }
-      else
-      {
-        ResourceType l = GetChildResourceType(GetLevel());
-        return new SingleDicomResource(GetContext(), l, resource, GetTemplates());
-      }
-    }
-
-  public:
-    SingleDicomResource(ServerContext& context,
-                  ResourceType level,
-                  const std::string& parentId,
-                  const Templates& templates) :
-      ListOfResources(context, level, templates),
-      parentId_(parentId)
-    {
-    }
-  };
-  
-  
-  class RootNode : public ListOfResources
-  {
-  protected:   
-    virtual void GetCurrentResources(std::list<std::string>& resources) ORTHANC_OVERRIDE
-    {
-      GetContext().GetIndex().GetAllUuids(resources, GetLevel());
-    }
-
-    virtual INode* CreateResourceNode(const std::string& resource) ORTHANC_OVERRIDE
-    {
-      if (GetLevel() == ResourceType_Series)
-      {
-        return new InstancesOfSeries(GetContext(), resource);
-      }
-      else
-      {
-        ResourceType l = GetChildResourceType(GetLevel());
-        return new SingleDicomResource(GetContext(), l, resource, GetTemplates());
-      }
-    }
-
-  public:
-    RootNode(ServerContext& context,
-             ResourceType level,
-             const Templates& templates) :
-      ListOfResources(context, level, templates)
-    {
-    }
-  };
-
-
-  class ListOfStudiesByDate : public ListOfResources
-  {
-  private:
-    std::string  year_;
-    std::string  month_;
-
-    class Visitor : public ServerContext::ILookupVisitor
-    {
-    private:
-      std::list<std::string>&  resources_;
-
-    public:
-      Visitor(std::list<std::string>& resources) :
-        resources_(resources)
-      {
-      }
-
-      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
-      {
-        resources_.push_back(publicId);
-      }
-    };
-    
-  protected:   
-    virtual void GetCurrentResources(std::list<std::string>& resources) ORTHANC_OVERRIDE
-    {
-      DatabaseLookup query;
-      query.AddRestConstraint(DICOM_TAG_STUDY_DATE, year_ + month_ + "01-" + year_ + month_ + "31",
-                              true /* case sensitive */, true /* mandatory tag */);
-
-      Visitor visitor(resources);
-      GetContext().Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */);
-    }
-
-    virtual INode* CreateResourceNode(const std::string& resource) ORTHANC_OVERRIDE
-    {
-      return new SingleDicomResource(GetContext(), ResourceType_Series, resource, GetTemplates());
-    }
-
-  public:
-    ListOfStudiesByDate(ServerContext& context,
-                        const std::string& year,
-                        const std::string& month,
-                        const Templates& templates) :
-      ListOfResources(context, ResourceType_Study, templates),
-      year_(year),
-      month_(month)
-    {
-      if (year.size() != 4 ||
-          month.size() != 2)
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-    }
-  };
-
-
-  class ListOfStudiesByMonth : public InternalNode
-  {
-  private:
-    ServerContext&    context_;
-    std::string       year_;
-    const Templates&  templates_;
-
-    class Visitor : public ServerContext::ILookupVisitor
-    {
-    private:
-      std::set<std::string> months_;
-      
-    public:
-      Visitor()
-      {
-      }
-
-      const std::set<std::string>& GetMonths() const
-      {
-        return months_;
-      }
-      
-      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
-      {
-        std::string s;
-        if (mainDicomTags.LookupStringValue(s, DICOM_TAG_STUDY_DATE, false) &&
-            s.size() == 8)
-        {
-          months_.insert(s.substr(4, 2)); // Get the month from "YYYYMMDD"
-        }
-      }
-    };
-
-  protected:
-    virtual void Refresh() ORTHANC_OVERRIDE
-    {
-    }
-
-    virtual bool ListSubfolders(IWebDavBucket::Collection& target) ORTHANC_OVERRIDE
-    {
-      DatabaseLookup query;
-      query.AddRestConstraint(DICOM_TAG_STUDY_DATE, year_ + "0101-" + year_ + "1231",
-                              true /* case sensitive */, true /* mandatory tag */);
-
-      Visitor visitor;
-      context_.Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */);
-
-      for (std::set<std::string>::const_iterator it = visitor.GetMonths().begin();
-           it != visitor.GetMonths().end(); ++it)
-      {
-        target.AddResource(new IWebDavBucket::Folder(year_ + "-" + *it));
-      }
-
-      return true;
-    }
-
-    virtual INode* CreateChild(const std::string& path) ORTHANC_OVERRIDE
-    {
-      if (path.size() != 7)  // Format: "YYYY-MM"
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-      else
-      {
-        const std::string year = path.substr(0, 4);
-        const std::string month = path.substr(5, 2);
-        return new ListOfStudiesByDate(context_, year, month, templates_);
-      }
-    }
-
-  public:
-    ListOfStudiesByMonth(ServerContext& context,
-                         const std::string& year,
-                         const Templates& templates) :
-      context_(context),
-      year_(year),
-      templates_(templates)
-    {
-      if (year_.size() != 4)
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-    }
-  };
-
-  
-  class ListOfStudiesByYear : public InternalNode
-  {
-  private:
-    ServerContext&    context_;
-    const Templates&  templates_;
-
-  protected:
-    virtual void Refresh() ORTHANC_OVERRIDE
-    {
-    }
-
-    virtual bool ListSubfolders(IWebDavBucket::Collection& target) ORTHANC_OVERRIDE
-    {
-      std::list<std::string> resources;
-      context_.GetIndex().GetAllUuids(resources, ResourceType_Study);
-
-      std::set<std::string> years;
-      
-      for (std::list<std::string>::const_iterator it = resources.begin(); it != resources.end(); ++it)
-      {
-        DicomMap tags;
-        std::string studyDate;
-        if (context_.GetIndex().GetMainDicomTags(tags, *it, ResourceType_Study, ResourceType_Study) &&
-            tags.LookupStringValue(studyDate, DICOM_TAG_STUDY_DATE, false) &&
-            studyDate.size() == 8)
-        {
-          years.insert(studyDate.substr(0, 4)); // Get the year from "YYYYMMDD"
-        }
-      }
-      
-      for (std::set<std::string>::const_iterator it = years.begin(); it != years.end(); ++it)
-      {
-        target.AddResource(new IWebDavBucket::Folder(*it));
-      }
-
-      return true;
-    }
-
-    virtual INode* CreateChild(const std::string& path) ORTHANC_OVERRIDE
-    {
-      return new ListOfStudiesByMonth(context_, path, templates_);
-    }
-
-  public:
-    ListOfStudiesByYear(ServerContext& context,
-                        const Templates& templates) :
-      context_(context),
-      templates_(templates)
-    {
-    }
-  };
-
-
-  static void UploadWorker(DummyBucket2* that)
-  {
-    assert(that != NULL);
-
-    boost::posix_time::ptime lastModification = GetNow();
-
-    while (that->running_)
-    {
-      std::unique_ptr<IDynamicObject> obj(that->uploadQueue_.Dequeue(100));
-      if (obj.get() != NULL)
-      {
-        that->Upload(reinterpret_cast<const SingleValueObject<std::string>&>(*obj).GetValue());
-        lastModification = GetNow();
-      }
-      else if (GetNow() - lastModification > boost::posix_time::seconds(10))
-      {
-        // After every 10 seconds of inactivity, remove the empty folders
-        LOG(INFO) << "Cleaning up the empty WebDAV upload folders";
-        that->uploads_.RemoveEmptyFolders();
-        lastModification = GetNow();
-      }
-    }
-  }
-
-  void Upload(const std::string& path)
-  {
-    UriComponents uri;
-    Toolbox::SplitUriComponents(uri, path);
-        
-    LOG(INFO) << "Upload from WebDAV: " << path;
-
-    MimeType mime;
-    std::string content;
-    boost::posix_time::ptime time;
-    if (uploads_.GetFileContent(mime, content, time, uri))
-    {
-      DicomInstanceToStore instance;
-      // instance.SetOrigin(DicomInstanceOrigin_WebDav);
-      instance.SetBuffer(content.c_str(), content.size());
-
-      std::string publicId;
-      StoreStatus status = context_.Store(publicId, instance, StoreInstanceMode_Default);
-      if (status == StoreStatus_Success ||
-          status == StoreStatus_AlreadyStored)
-      {
-        LOG(INFO) << "Successfully imported DICOM instance from WebDAV: " << path << " (Orthanc ID: " << publicId << ")";
-        uploads_.DeleteItem(uri);
-      }
-      else
-      {
-        LOG(WARNING) << "Cannot import DICOM instance from WebWAV: " << path;
-      }
-    }
-  }
-
-  
-  ServerContext&          context_;
-  std::unique_ptr<INode>  patients_;
-  std::unique_ptr<INode>  studies_;
-  std::unique_ptr<INode>  dates_;
-  Templates               patientsTemplates_;
-  Templates               studiesTemplates_;
-  WebDavStorage           uploads_;
-  SharedMessageQueue      uploadQueue_;
-  boost::thread           uploadThread_;
-  bool                    running_;
-  
-public:
-  DummyBucket2(ServerContext& context) :
-    context_(context),
-    uploads_(false /* store uploads as temporary files */),
-    running_(false)
-  {
-    patientsTemplates_[ResourceType_Patient] = "{{PatientID}} - {{PatientName}}";
-    patientsTemplates_[ResourceType_Study] = "{{StudyDate}} - {{StudyDescription}}";
-    patientsTemplates_[ResourceType_Series] = "{{Modality}} - {{SeriesDescription}}";
-
-    studiesTemplates_[ResourceType_Study] = "{{PatientID}} - {{PatientName}} - {{StudyDescription}}";
-    studiesTemplates_[ResourceType_Series] = patientsTemplates_[ResourceType_Series];
-
-    patients_.reset(new RootNode(context, ResourceType_Patient, patientsTemplates_));
-    studies_.reset(new RootNode(context, ResourceType_Study, studiesTemplates_));
-    dates_.reset(new ListOfStudiesByYear(context, studiesTemplates_));
-  }
-
-  virtual ~DummyBucket2()
-  {
-    Stop();
-  }
-
-  virtual bool IsExistingFolder(const UriComponents& path) ORTHANC_OVERRIDE
-  {
-    if (path.empty())
-    {
-      return true;
-    }
-    else if (path[0] == BY_UIDS)
-    {
-      return (path.size() <= 3 &&
-              (path.size() != 3 || path[2] != "study.json"));
-    }
-    else if (path[0] == BY_PATIENTS)
-    {
-      IWebDavBucket::Collection tmp;
-      return patients_->ListCollection(tmp, UriComponents(path.begin() + 1, path.end()));
-    }
-    else if (path[0] == BY_STUDIES)
-    {
-      IWebDavBucket::Collection tmp;
-      return studies_->ListCollection(tmp, UriComponents(path.begin() + 1, path.end()));
-    }
-    else if (path[0] == BY_DATE)
-    {
-      IWebDavBucket::Collection tmp;
-      return dates_->ListCollection(tmp, UriComponents(path.begin() + 1, path.end()));
-    }
-    else if (path[0] == UPLOADS)
-    {
-      return uploads_.IsExistingFolder(UriComponents(path.begin() + 1, path.end()));
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  virtual bool ListCollection(Collection& collection,
-                              const UriComponents& path) ORTHANC_OVERRIDE
-  {
-    if (path.empty())
-    {
-      collection.AddResource(new Folder(BY_DATE));
-      collection.AddResource(new Folder(BY_PATIENTS));
-      collection.AddResource(new Folder(BY_STUDIES));
-      collection.AddResource(new Folder(BY_UIDS));
-      collection.AddResource(new Folder(UPLOADS));
-      return true;
-    }   
-    else if (path[0] == 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 */);
-        query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2],
-                                true /* case sensitive */, true /* mandatory tag */);
-      }
-      else
-      {
-        return false;
-      }
-
-      DicomIdentifiersVisitor visitor(context_, collection, level);
-      context_.Apply(visitor, query, level, 0 /* since */, limit);
-      
-      return true;
-    }
-    else if (path[0] == BY_PATIENTS)
-    {
-      return patients_->ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
-    }
-    else if (path[0] == BY_STUDIES)
-    {
-      return studies_->ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
-    }
-    else if (path[0] == BY_DATE)
-    {
-      return dates_->ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
-    }
-    else if (path[0] == UPLOADS)
-    {
-      return uploads_.ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  virtual bool GetFileContent(MimeType& mime,
-                              std::string& content,
-                              boost::posix_time::ptime& modificationTime, 
-                              const UriComponents& path) ORTHANC_OVERRIDE
-  {
-    if (path.empty())
-    {
-      return false;
-    }
-    else if (path[0] == BY_UIDS)
-    {
-      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 */);
-      
-        OrthancJsonVisitor visitor(context_, content, ResourceType_Study);
-        context_.Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */);
-
-        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 */);
-      
-        OrthancJsonVisitor visitor(context_, content, ResourceType_Series);
-        context_.Apply(visitor, query, ResourceType_Series, 0 /* since */, 0 /* no limit */);
-
-        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;
-      }
-    }
-    else if (path[0] == BY_PATIENTS)
-    {
-      return patients_->GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end()));
-    }
-    else if (path[0] == BY_STUDIES)
-    {
-      return studies_->GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end()));
-    }
-    else if (path[0] == UPLOADS)
-    {
-      return uploads_.GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end()));
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  
-  virtual bool StoreFile(const std::string& content,
-                         const UriComponents& path) ORTHANC_OVERRIDE
-  {
-    if (path.size() >= 1 &&
-        path[0] == UPLOADS)
-    {
-      UriComponents subpath(UriComponents(path.begin() + 1, path.end()));
-
-      if (uploads_.StoreFile(content, subpath))
-      {
-        if (!content.empty())
-        {
-          uploadQueue_.Enqueue(new SingleValueObject<std::string>(Toolbox::FlattenUri(subpath)));
-        }
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  virtual bool CreateFolder(const UriComponents& path)
-  {
-    if (path.size() >= 1 &&
-        path[0] == UPLOADS)
-    {
-      return uploads_.CreateFolder(UriComponents(path.begin() + 1, path.end()));
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  virtual bool DeleteItem(const std::vector<std::string>& path) ORTHANC_OVERRIDE
-  {
-    if (path.size() >= 1 &&
-        path[0] == UPLOADS)
-    {
-      return uploads_.DeleteItem(UriComponents(path.begin() + 1, path.end()));
-    }
-    else
-    {
-      return false;  // read-only
-    }
-  }
-
-  virtual void Start() ORTHANC_OVERRIDE
-  {
-    if (running_)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      LOG(INFO) << "Starting the WebDAV upload thread";
-      running_ = true;
-      uploadThread_ = boost::thread(UploadWorker, this);
-    }
-  }
-
-  virtual void Stop() ORTHANC_OVERRIDE
-  {
-    if (running_)
-    {
-      LOG(INFO) << "Stopping the WebDAV upload thread";
-      running_ = false;
-      if (uploadThread_.joinable())
-      {
-        uploadThread_.join();
-      }
-    }
-  }
-};
-
-
-
 static void PrintHelp(const char* path)
 {
   std::cout 
@@ -2653,9 +1053,7 @@
       UriComponents root;  // TODO
       root.push_back("a");
       root.push_back("b");
-      //httpServer.Register(root, new WebDavStorage(true));
-      //httpServer.Register(root, new DummyBucket(context, true));
-      httpServer.Register(root, new DummyBucket2(context));
+      httpServer.Register(root, new OrthancWebDav(context));
     }
 
     if (httpServer.GetPortNumber() < 1024)