# HG changeset patch # User Sebastien Jodogne # Date 1602000866 -7200 # Node ID b313a00018935cd9ecdbc3f4e54acb56ba1127bf # Parent 013d6c6b2387391746771da6c7c6b8284d844c73 WebDavStorage diff -r 013d6c6b2387 -r b313a0001893 OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake --- 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() diff -r 013d6c6b2387 -r b313a0001893 OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp --- 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()); diff -r 013d6c6b2387 -r b313a0001893 OrthancFramework/Sources/HttpServer/WebDavStorage.cpp --- /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 + * . + **/ + + +#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 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 Files; + typedef std::map 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 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& 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 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 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& path) + { + if (path.empty()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + std::vector 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& path) + { + boost::recursive_mutex::scoped_lock lock(mutex_); + + return (root_->LookupFolder(path) != NULL); + } + + + bool WebDavStorage::ListCollection(Collection& collection, + const std::vector& 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& 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& 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& 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()); + } + } +} diff -r 013d6c6b2387 -r b313a0001893 OrthancFramework/Sources/HttpServer/WebDavStorage.h --- /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 + * . + **/ + + + +#pragma once + +#include "IWebDavBucket.h" + +#include + +namespace Orthanc +{ + class WebDavStorage : public IWebDavBucket + { + private: + class StorageFile; + class StorageFolder; + + StorageFolder* LookupParentFolder(const std::vector& path); + + boost::shared_ptr root_; // PImpl + boost::recursive_mutex mutex_; + bool isMemory_; + + public: + WebDavStorage(bool isMemory); + + virtual bool IsExistingFolder(const std::vector& path) ORTHANC_OVERRIDE; + + virtual bool ListCollection(Collection& collection, + const std::vector& path) ORTHANC_OVERRIDE; + + virtual bool GetFileContent(MimeType& mime, + std::string& content, + boost::posix_time::ptime& modificationTime, + const std::vector& path) ORTHANC_OVERRIDE; + + virtual bool StoreFile(const std::string& content, + const std::vector& path) ORTHANC_OVERRIDE; + + virtual bool CreateFolder(const std::vector& path) ORTHANC_OVERRIDE; + + virtual void Start() ORTHANC_OVERRIDE + { + } + + virtual void Stop() ORTHANC_OVERRIDE + { + } + }; +} diff -r 013d6c6b2387 -r b313a0001893 OrthancFramework/Sources/TemporaryFile.cpp --- 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_); + } } diff -r 013d6c6b2387 -r b313a0001893 OrthancFramework/Sources/TemporaryFile.h --- 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; }; } diff -r 013d6c6b2387 -r b313a0001893 OrthancServer/Sources/main.cpp --- 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& target, - const std::vector& 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 Files; - typedef std::map 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 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 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 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 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 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)