changeset 4230:b313a0001893

WebDavStorage
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 06 Oct 2020 18:14:26 +0200
parents 013d6c6b2387
children 290ffcb0a147
files OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp OrthancFramework/Sources/HttpServer/WebDavStorage.cpp OrthancFramework/Sources/HttpServer/WebDavStorage.h OrthancFramework/Sources/TemporaryFile.cpp OrthancFramework/Sources/TemporaryFile.h OrthancServer/Sources/main.cpp
diffstat 7 files changed, 468 insertions(+), 303 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Tue Oct 06 13:13:17 2020 +0200
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Tue Oct 06 18:14:26 2020 +0200
@@ -313,6 +313,7 @@
   if (ENABLE_PUGIXML)
     list(APPEND ORTHANC_CORE_SOURCES_INTERNAL
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/HttpServer/IWebDavBucket.cpp
+      ${CMAKE_CURRENT_LIST_DIR}/../../Sources/HttpServer/WebDavStorage.cpp
       )
   endif()
 endif()
--- a/OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp	Tue Oct 06 13:13:17 2020 +0200
+++ b/OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp	Tue Oct 06 18:14:26 2020 +0200
@@ -107,7 +107,7 @@
 
     pugi::xml_node prop = propstat.append_child("D:prop");
 
-    // IMPORTANT: The "Z" suffix is mandatory on Windows >= 7
+    // IMPORTANT: The "Z" suffix is mandatory on Windows >= 7 (it indicates UTC)
     s = boost::posix_time::to_iso_extended_string(GetCreationTime()) + "Z";
     prop.append_child("D:creationdate").append_child(pugi::node_pcdata).set_value(s.c_str());
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/HttpServer/WebDavStorage.cpp	Tue Oct 06 18:14:26 2020 +0200
@@ -0,0 +1,367 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "WebDavStorage.h"
+
+#include "../OrthancException.h"
+#include "../SystemToolbox.h"
+#include "../TemporaryFile.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  class WebDavStorage::StorageFile : public boost::noncopyable
+  {
+  private:
+    std::unique_ptr<TemporaryFile>  file_;
+    std::string                     content_;
+    MimeType                        mime_;
+    boost::posix_time::ptime        time_;
+
+    void Touch()
+    {
+      time_ = boost::posix_time::second_clock::universal_time();
+    }
+    
+  public:
+    StorageFile() :
+      mime_(MimeType_Binary)
+    {
+      Touch();
+    }
+    
+    void SetContent(const std::string& content,
+                    MimeType mime,
+                    bool isMemory)
+    {
+      if (isMemory)
+      {
+        content_ = content;
+        file_.reset();
+      }
+      else
+      {
+        content_.clear();
+        file_.reset(new TemporaryFile);
+        file_->Write(content);
+      }
+      
+      mime_ = mime;
+      Touch();
+    }
+
+    MimeType GetMimeType() const
+    {
+      return mime_;
+    }
+
+    void GetContent(std::string& target) const
+    {
+      if (file_.get() == NULL)
+      {
+        target = content_;
+      }
+      else
+      {
+        file_->Read(target);
+      }
+    }
+
+    const boost::posix_time::ptime& GetTime() const
+    {
+      return time_;
+    }
+
+    uint64_t GetContentLength() const
+    {
+      if (file_.get() == NULL)
+      {
+        return content_.size();
+      }
+      else
+      {
+        return file_->GetFileSize();
+      }
+    }
+  };
+
+
+  class WebDavStorage::StorageFolder : public boost::noncopyable
+  {
+  private:
+    typedef std::map<std::string, StorageFile*>    Files;
+    typedef std::map<std::string, StorageFolder*>  Subfolders;
+
+    Files       files_;
+    Subfolders  subfolders_;
+
+    void CheckName(const std::string& name)
+    {
+      if (name.empty() ||
+          name.find('/') != std::string::npos ||
+          name.find('\\') != std::string::npos ||
+          name.find('\0') != std::string::npos)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange,
+                               "Bad resource name for WebDAV: " + name);
+      }
+    }
+
+    bool IsExisting(const std::string& name) const
+    {
+      return (files_.find(name) != files_.end() ||
+              subfolders_.find(name) != subfolders_.end());
+    }
+
+  public:
+    ~StorageFolder()
+    {
+      for (Files::iterator it = files_.begin(); it != files_.end(); ++it)
+      {
+        assert(it->second != NULL);
+        delete it->second;
+      }        
+
+      for (Subfolders::iterator it = subfolders_.begin(); it != subfolders_.end(); ++it)
+      {
+        assert(it->second != NULL);
+        delete it->second;
+      }        
+    }
+
+    const StorageFile* LookupFile(const std::string& name) const
+    {
+      Files::const_iterator found = files_.find(name);
+      if (found == files_.end())
+      {
+        return NULL;
+      }
+      else
+      {
+        assert(found->second != NULL);
+        return found->second;
+      }
+    }
+
+    bool CreateSubfolder(const std::string& name)
+    {
+      CheckName(name);
+
+      if (IsExisting(name))
+      {
+        LOG(ERROR) << "WebDAV folder already existing: " << name;
+        return false;
+      }
+      else
+      {
+        subfolders_[name] = new StorageFolder;
+        return true;
+      }
+    }
+
+    bool StoreFile(const std::string& name,
+                   const std::string& content,
+                   MimeType mime,
+                   bool isMemory)
+    {
+      CheckName(name);
+
+      if (subfolders_.find(name) != subfolders_.end())
+      {
+        LOG(ERROR) << "WebDAV folder already existing: " << name;
+        return false;
+      }
+
+      Files::iterator found = files_.find(name);
+      if (found == files_.end())
+      {
+        std::unique_ptr<StorageFile> f(new StorageFile);
+        f->SetContent(content, mime, isMemory);
+        files_[name] = f.release();
+      }
+      else
+      {
+        assert(found->second != NULL);
+        found->second->SetContent(content, mime, isMemory);
+      }
+      
+      return true;
+    }
+
+    StorageFolder* LookupFolder(const std::vector<std::string>& path)
+    {
+      if (path.empty())
+      {
+        return this;
+      }
+      else
+      {
+        Subfolders::const_iterator found = subfolders_.find(path[0]);
+        if (found == subfolders_.end())
+        {
+          return NULL;
+        }
+        else
+        {
+          assert(found->second != NULL);
+
+          std::vector<std::string> p(path.begin() + 1, path.end());
+          return found->second->LookupFolder(p);
+        }
+      }
+    }
+
+    void ListCollection(Collection& collection) const
+    {
+      for (Files::const_iterator it = files_.begin(); it != files_.end(); ++it)
+      {
+        assert(it->second != NULL);
+        
+        std::unique_ptr<File> f(new File(it->first));
+        f->SetContentLength(it->second->GetContentLength());
+        f->SetCreationTime(it->second->GetTime());
+        collection.AddResource(f.release());
+      }
+
+      for (Subfolders::const_iterator it = subfolders_.begin(); it != subfolders_.end(); ++it)
+      {
+        collection.AddResource(new Folder(it->first));
+      }
+    }
+  };
+
+
+  WebDavStorage::StorageFolder* WebDavStorage::LookupParentFolder(const std::vector<std::string>& path)
+  {
+    if (path.empty())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    std::vector<std::string> p(path.begin(), path.end() - 1);      
+    return root_->LookupFolder(p);
+  }
+    
+
+  WebDavStorage::WebDavStorage(bool isMemory) :
+    root_(new StorageFolder),
+    isMemory_(isMemory)
+  {
+  }
+  
+
+  bool WebDavStorage::IsExistingFolder(const std::vector<std::string>& path)
+  {
+    boost::recursive_mutex::scoped_lock lock(mutex_);
+    
+    return (root_->LookupFolder(path) != NULL);
+  }
+
+  
+  bool WebDavStorage::ListCollection(Collection& collection,
+                                     const std::vector<std::string>& path)
+  {
+    boost::recursive_mutex::scoped_lock lock(mutex_);
+    
+    const StorageFolder* folder = root_->LookupFolder(path);
+    if (folder == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      folder->ListCollection(collection);
+      return true;
+    }
+  }
+
+  
+  bool WebDavStorage::GetFileContent(MimeType& mime,
+                                     std::string& content,
+                                     boost::posix_time::ptime& modificationTime, 
+                                     const std::vector<std::string>& path)
+  {
+    boost::recursive_mutex::scoped_lock lock(mutex_);
+    
+    const StorageFolder* folder = LookupParentFolder(path);    
+    if (folder == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      const StorageFile* file = folder->LookupFile(path.back());
+      if (file == NULL)
+      {
+        return false;
+      }
+      else
+      {
+        mime = file->GetMimeType();
+        file->GetContent(content);
+        modificationTime = file->GetTime();
+        return true;
+      }
+    }
+  }
+
+  
+  bool WebDavStorage::StoreFile(const std::string& content,
+                                const std::vector<std::string>& path)
+  {
+    boost::recursive_mutex::scoped_lock lock(mutex_);
+    
+    StorageFolder* folder = LookupParentFolder(path);
+    if (folder == NULL)
+    {
+      LOG(WARNING) << "Inexisting folder in WebDAV: " << Toolbox::FlattenUri(path);
+      return false;
+    }
+    else
+    {
+      LOG(INFO) << "Storing " << content.size()
+                << " bytes in WebDAV bucket: " << Toolbox::FlattenUri(path);;
+
+      MimeType mime = SystemToolbox::AutodetectMimeType(path.back());
+      return folder->StoreFile(path.back(), content, mime, isMemory_);
+    }
+  }
+
+  
+  bool WebDavStorage::CreateFolder(const std::vector<std::string>& path)
+  {
+    boost::recursive_mutex::scoped_lock lock(mutex_);
+
+    StorageFolder* folder = LookupParentFolder(path);      
+    if (folder == NULL)
+    {
+      LOG(WARNING) << "Inexisting folder in WebDAV: " << Toolbox::FlattenUri(path);
+      return false;
+    }
+    else
+    {
+      LOG(INFO) << "Creating folder in WebDAV bucket: " << Toolbox::FlattenUri(path);
+      return folder->CreateSubfolder(path.back());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/HttpServer/WebDavStorage.h	Tue Oct 06 18:14:26 2020 +0200
@@ -0,0 +1,70 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+#include "IWebDavBucket.h"
+
+#include <boost/thread/recursive_mutex.hpp>
+
+namespace Orthanc
+{
+  class WebDavStorage : public IWebDavBucket
+  {
+  private:
+    class StorageFile;
+    class StorageFolder;
+    
+    StorageFolder* LookupParentFolder(const std::vector<std::string>& path);
+
+    boost::shared_ptr<StorageFolder>  root_;  // PImpl
+    boost::recursive_mutex            mutex_;
+    bool                              isMemory_;
+
+  public:
+    WebDavStorage(bool isMemory);
+  
+    virtual bool IsExistingFolder(const std::vector<std::string>& path) ORTHANC_OVERRIDE;
+
+    virtual bool ListCollection(Collection& collection,
+                                const std::vector<std::string>& path) ORTHANC_OVERRIDE;
+
+    virtual bool GetFileContent(MimeType& mime,
+                                std::string& content,
+                                boost::posix_time::ptime& modificationTime, 
+                                const std::vector<std::string>& path) ORTHANC_OVERRIDE;
+  
+    virtual bool StoreFile(const std::string& content,
+                           const std::vector<std::string>& path) ORTHANC_OVERRIDE;
+
+    virtual bool CreateFolder(const std::vector<std::string>& path) ORTHANC_OVERRIDE;
+
+    virtual void Start() ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void Stop() ORTHANC_OVERRIDE
+    {
+    }
+  };
+}
--- a/OrthancFramework/Sources/TemporaryFile.cpp	Tue Oct 06 13:13:17 2020 +0200
+++ b/OrthancFramework/Sources/TemporaryFile.cpp	Tue Oct 06 18:14:26 2020 +0200
@@ -126,4 +126,10 @@
     std::string empty;
     Write(empty);
   }
+
+
+  uint64_t TemporaryFile::GetFileSize() const
+  {
+    return SystemToolbox::GetFileSize(path_);
+  }
 }
--- a/OrthancFramework/Sources/TemporaryFile.h	Tue Oct 06 13:13:17 2020 +0200
+++ b/OrthancFramework/Sources/TemporaryFile.h	Tue Oct 06 18:14:26 2020 +0200
@@ -60,5 +60,7 @@
     void Read(std::string& content) const;
 
     void Touch();
+
+    uint64_t GetFileSize() const;
   };
 }
--- a/OrthancServer/Sources/main.cpp	Tue Oct 06 13:13:17 2020 +0200
+++ b/OrthancServer/Sources/main.cpp	Tue Oct 06 18:14:26 2020 +0200
@@ -43,7 +43,6 @@
 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
 #include "../../OrthancFramework/Sources/HttpServer/FilesystemHttpHandler.h"
 #include "../../OrthancFramework/Sources/HttpServer/HttpServer.h"
-#include "../../OrthancFramework/Sources/HttpServer/IWebDavBucket.h"  // TODO
 #include "../../OrthancFramework/Sources/Logging.h"
 #include "../../OrthancFramework/Sources/Lua/LuaFunctionCall.h"
 #include "../Plugins/Engine/OrthancPlugins.h"
@@ -58,6 +57,9 @@
 #include "ServerToolbox.h"
 #include "StorageCommitmentReports.h"
 
+#include "../../OrthancFramework/Sources/HttpServer/WebDavStorage.h"  // TODO
+
+
 using namespace Orthanc;
 
 
@@ -613,256 +615,35 @@
 
 
 
+
+
+
 static const char* const UPLOAD_FOLDER = "upload";
 
 class DummyBucket : public IWebDavBucket  // TODO
 {
 private:
-  ServerContext& context_;
-
-  static void RemoveFirstElement(std::vector<std::string>& target,
-                                 const std::vector<std::string>& source)
-  {
-    if (source.empty())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      target.resize(source.size() - 1);
-      for (size_t i = 1; i < source.size(); i++)
-      {
-        target[i - 1] = source[i];
-      }
-    }
-  }
-  
-  class UploadedFile : public boost::noncopyable
-  {
-  private:
-    std::string               content_;
-    MimeType                  mime_;
-    boost::posix_time::ptime  time_;
-
-    void Touch()
-    {
-      time_ = boost::posix_time::second_clock::universal_time();
-    }
-    
-  public:
-    UploadedFile() :
-      mime_(MimeType_Binary)
-    {
-      Touch();
-    }
-    
-    void SetContent(const std::string& content,
-                    MimeType mime)
-    {
-      content_ = content;
-      mime_ = mime;
-      Touch();
-    }
-
-    MimeType GetMimeType() const
-    {
-      return mime_;
-    }
-
-    const std::string& GetContent() const
-    {
-      return content_;
-    }
-
-    const boost::posix_time::ptime& GetTime() const
-    {
-      return time_;
-    }
-  };
-
-
-  class UploadedFolder : public boost::noncopyable
-  {
-  private:
-    typedef std::map<std::string, UploadedFile*>    Files;
-    typedef std::map<std::string, UploadedFolder*>  Subfolders;
-
-    Files       files_;
-    Subfolders  subfolders_;
-
-    void CheckName(const std::string& name)
-    {
-      if (name.empty() ||
-          name.find('/') != std::string::npos ||
-          name.find('\\') != std::string::npos ||
-          name.find('\0') != std::string::npos)
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange,
-                               "Bad resource name for WebDAV: " + name);
-      }
-    }
-
-    bool IsExisting(const std::string& name) const
-    {
-      return (files_.find(name) != files_.end() ||
-              subfolders_.find(name) != subfolders_.end());
-    }
-
-  public:
-    ~UploadedFolder()
-    {
-      for (Files::iterator it = files_.begin(); it != files_.end(); ++it)
-      {
-        assert(it->second != NULL);
-        delete it->second;
-      }        
-
-      for (Subfolders::iterator it = subfolders_.begin(); it != subfolders_.end(); ++it)
-      {
-        assert(it->second != NULL);
-        delete it->second;
-      }        
-    }
-
-    const UploadedFile* LookupFile(const std::string& name) const
-    {
-      Files::const_iterator found = files_.find(name);
-      if (found == files_.end())
-      {
-        return NULL;
-      }
-      else
-      {
-        assert(found->second != NULL);
-        return found->second;
-      }
-    }
-
-    bool CreateSubfolder(const std::string& name)
-    {
-      CheckName(name);
-
-      if (IsExisting(name))
-      {
-        LOG(ERROR) << "WebDAV folder already existing: " << name;
-        return false;
-      }
-      else
-      {
-        subfolders_[name] = new UploadedFolder;
-        return true;
-      }
-    }
-
-    bool StoreFile(const std::string& name,
-                   const std::string& content,
-                   MimeType mime)
-    {
-      CheckName(name);
-
-      if (subfolders_.find(name) != subfolders_.end())
-      {
-        LOG(ERROR) << "WebDAV folder already existing: " << name;
-        return false;
-      }
-
-      Files::iterator found = files_.find(name);
-      if (found == files_.end())
-      {
-        std::unique_ptr<UploadedFile> f(new UploadedFile);
-        f->SetContent(content, mime);
-        files_[name] = f.release();
-      }
-      else
-      {
-        assert(found->second != NULL);
-        found->second->SetContent(content, mime);
-      }
-      
-      return true;
-    }
-
-    UploadedFolder* LookupSubfolder(const UriComponents& path)
-    {
-      if (path.empty())
-      {
-        return this;
-      }
-      else
-      {
-        Subfolders::const_iterator found = subfolders_.find(path[0]);
-        if (found == subfolders_.end())
-        {
-          return NULL;
-        }
-        else
-        {
-          assert(found->second != NULL);
-
-          UriComponents p;
-          RemoveFirstElement(p, path);
-          
-          return found->second->LookupSubfolder(p);
-        }
-      }
-    }
-
-    void ListCollection(Collection& collection) const
-    {
-      for (Files::const_iterator it = files_.begin(); it != files_.end(); ++it)
-      {
-        assert(it->second != NULL);
-        
-        std::unique_ptr<File> f(new File(it->first));
-        f->SetContentLength(it->second->GetContent().size());
-        f->SetCreationTime(it->second->GetTime());
-        collection.AddResource(f.release());
-      }
-
-      for (Subfolders::const_iterator it = subfolders_.begin(); it != subfolders_.end(); ++it)
-      {
-        collection.AddResource(new Folder(it->first));
-      }
-    }
-  };
+  ServerContext&  context_;
+  WebDavStorage   storage_;
 
   bool IsUploadedFolder(const UriComponents& path) const
   {
-    return (path.size() >= 1 &&
-            path[0] == UPLOAD_FOLDER);
+    return (path.size() >= 1 && path[0] == UPLOAD_FOLDER);
   }
 
-  UploadedFolder  uploads_;
-  boost::recursive_mutex    mutex_;
-
-  UploadedFolder* LookupUploadedFolder(const UriComponents& path)
-  {
-    if (IsUploadedFolder(path))
-    {
-      UriComponents p;
-      RemoveFirstElement(p, path);
-      
-      return uploads_.LookupSubfolder(p);
-    }
-    else
-    {
-      return NULL;
-    }
-  }
-  
 public:
-  DummyBucket(ServerContext& context) :
-    context_(context)
+  DummyBucket(ServerContext& context,
+              bool isMemory) :
+    context_(context),
+    storage_(isMemory)
   {
   }
   
   virtual bool IsExistingFolder(const UriComponents& path) ORTHANC_OVERRIDE
   {
-    boost::recursive_mutex::scoped_lock lock(mutex_);
-    
     if (IsUploadedFolder(path))
     {
-      return LookupUploadedFolder(path) != NULL;
+      return storage_.IsExistingFolder(UriComponents(path.begin() + 1, path.end()));
     }
     else
     {
@@ -875,20 +656,9 @@
   virtual bool ListCollection(Collection& collection,
                               const UriComponents& path) ORTHANC_OVERRIDE
   {
-    boost::recursive_mutex::scoped_lock lock(mutex_);
-    
     if (IsUploadedFolder(path))
     {
-      const UploadedFolder* folder = LookupUploadedFolder(path);
-      if (folder == NULL)
-      {
-        return false;
-      }
-      else
-      {
-        folder->ListCollection(collection);
-        return true;
-      }
+      return storage_.ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
     }
     else if (IsExistingFolder(path))
     {
@@ -923,34 +693,14 @@
                               boost::posix_time::ptime& modificationTime, 
                               const UriComponents& path) ORTHANC_OVERRIDE
   {
-    boost::recursive_mutex::scoped_lock lock(mutex_);
-    
     if (path.empty())
     {
       return false;
     }
     else if (IsUploadedFolder(path))
     {
-      std::vector<std::string> p(path.begin(), path.end() - 1);
-      
-      const UploadedFolder* folder = LookupUploadedFolder(p);
-      if (folder == NULL)
-      {
-        return false;
-      }
-
-      const UploadedFile* file = folder->LookupFile(path.back());
-      if (file == NULL)
-      {
-        return false;
-      }
-      else
-      {
-        mime = file->GetMimeType();
-        content = file->GetContent();
-        modificationTime = file->GetTime();
-        return true;
-      }
+      return storage_.GetFileContent(mime, content, modificationTime,
+                                     UriComponents(path.begin() + 1, path.end()));
     }
     else if (path.back() == "IM0.dcm" ||
              path.back() == "IM1.dcm" ||
@@ -980,22 +730,9 @@
   virtual bool StoreFile(const std::string& content,
                          const UriComponents& path) ORTHANC_OVERRIDE
   {
-    boost::recursive_mutex::scoped_lock lock(mutex_);
-    
     if (IsUploadedFolder(path))
     {
-      std::vector<std::string> p(path.begin(), path.end() - 1);
-      
-      UploadedFolder* folder = LookupUploadedFolder(p);
-      if (folder == NULL)
-      {
-        return false;
-      }
-      else
-      {
-        printf("STORING %d bytes at %s\n", content.size(), path.back().c_str());
-        return folder->StoreFile(path.back(), content, SystemToolbox::AutodetectMimeType(path.back()));
-      }
+      return storage_.StoreFile(content, UriComponents(path.begin() + 1, path.end()));
     }
     else
     {
@@ -1007,22 +744,9 @@
 
   virtual bool CreateFolder(const UriComponents& path)
   {
-    boost::recursive_mutex::scoped_lock lock(mutex_);
-    
     if (IsUploadedFolder(path))
     {
-      std::vector<std::string> p(path.begin(), path.end() - 1);
-      
-      UploadedFolder* folder = LookupUploadedFolder(p);
-      if (folder == NULL)
-      {
-        return false;
-      }
-      else
-      {
-        printf("CREATING FOLDER %s\n", path.back().c_str());
-        return folder->CreateSubfolder(path.back());
-      }
+      return storage_.CreateFolder(UriComponents(path.begin() + 1, path.end()));
     }
     else
     {
@@ -1031,19 +755,13 @@
     }
   }
 
-
   virtual void Start() ORTHANC_OVERRIDE
   {
-    boost::recursive_mutex::scoped_lock lock(mutex_);
-    
     LOG(WARNING) << "Starting WebDAV";
   }
 
-
   virtual void Stop() ORTHANC_OVERRIDE
   {
-    boost::recursive_mutex::scoped_lock lock(mutex_);
-    
     LOG(WARNING) << "Stopping WebDAV";
   }
 };
@@ -1491,7 +1209,8 @@
       UriComponents root;  // TODO
       root.push_back("a");
       root.push_back("b");
-      httpServer.Register(root, new DummyBucket(context));
+      //httpServer.Register(root, new WebDavStorage(true));
+      httpServer.Register(root, new DummyBucket(context, true));
     }
 
     if (httpServer.GetPortNumber() < 1024)