/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
**/
#include "../Common/OrthancPluginCppWrapper.h"
#include
class Resource : public boost::noncopyable
{
private:
boost::posix_time::ptime dateTime_;
public:
Resource() :
dateTime_(boost::posix_time::second_clock::universal_time())
{
}
virtual ~Resource()
{
}
const boost::posix_time::ptime& GetDateTime() const
{
return dateTime_;
}
virtual bool IsFolder() const = 0;
virtual Resource* LookupPath(const std::vector& path) = 0;
};
class File : public Resource
{
private:
std::string content_;
public:
File(const void* data,
size_t size) :
content_(reinterpret_cast(data), size)
{
}
const std::string& GetContent() const
{
return content_;
}
virtual bool IsFolder() const
{
return false;
}
virtual Resource* LookupPath(const std::vector& path)
{
if (path.empty())
{
return this;
}
else
{
return NULL;
}
}
};
class Folder : public Resource
{
private:
typedef std::map Content;
Content content_;
public:
virtual ~Folder()
{
for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
{
assert(it->second != NULL);
delete it->second;
}
}
virtual bool IsFolder() const
{
return true;
}
virtual Resource* LookupPath(const std::vector& path)
{
if (path.empty())
{
return this;
}
else
{
Content::const_iterator found = content_.find(path[0]);
if (found == content_.end())
{
return NULL;
}
else
{
std::vector childPath(path.size() - 1);
for (size_t i = 0; i < childPath.size(); i++)
{
childPath[i] = path[i + 1];
}
return found->second->LookupPath(childPath);
}
}
}
void ListContent(std::list& files,
std::list& subfolders) const
{
for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
{
assert(it->second != NULL);
const std::string dateTime = boost::posix_time::to_iso_string(it->second->GetDateTime());
if (it->second->IsFolder())
{
subfolders.push_back(OrthancPlugins::IWebDavCollection::FolderInfo(it->first, dateTime));
}
else
{
const File& f = dynamic_cast(*it->second);
files.push_back(OrthancPlugins::IWebDavCollection::FileInfo(it->first, f.GetContent().size(), dateTime));
}
}
}
void StoreFile(const std::string& name,
File* f)
{
std::unique_ptr protection(f);
if (content_.find(name) != content_.end())
{
ORTHANC_PLUGINS_LOG_ERROR("Already existing: " + name);
ORTHANC_PLUGINS_THROW_EXCEPTION(BadRequest);
}
else
{
content_[name] = protection.release();
}
}
void CreateSubfolder(const std::string& name)
{
if (content_.find(name) != content_.end())
{
ORTHANC_PLUGINS_LOG_ERROR("Already existing: " + name);
ORTHANC_PLUGINS_THROW_EXCEPTION(BadRequest);
}
else
{
content_[name] = new Folder;
}
}
void DeleteItem(const std::string& name)
{
Content::iterator found = content_.find(name);
if (found == content_.end())
{
ORTHANC_PLUGINS_LOG_ERROR("Cannot delete inexistent path: " + name);
ORTHANC_PLUGINS_THROW_EXCEPTION(InexistentItem);
}
else
{
assert(found->second != NULL);
delete found->second;
content_.erase(found);
}
}
};
class WebDavFilesystem : public OrthancPlugins::IWebDavCollection
{
private:
boost::mutex mutex_;
std::unique_ptr root_;
static std::vector GetParentPath(const std::vector& path)
{
if (path.empty())
{
ORTHANC_PLUGINS_LOG_ERROR("Empty path");
ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
}
else
{
std::vector p(path.size() - 1);
for (size_t i = 0; i < p.size(); i++)
{
p[i] = path[i];
}
return p;
}
}
public:
WebDavFilesystem() :
root_(new Folder)
{
}
virtual bool IsExistingFolder(const std::vector& path)
{
boost::mutex::scoped_lock lock(mutex_);
Resource* resource = root_->LookupPath(path);
return (resource != NULL &&
resource->IsFolder());
}
virtual bool ListFolder(std::list& files,
std::list& subfolders,
const std::vector& path)
{
boost::mutex::scoped_lock lock(mutex_);
Resource* resource = root_->LookupPath(path);
if (resource != NULL &&
resource->IsFolder())
{
dynamic_cast(*resource).ListContent(files, subfolders);
return true;
}
else
{
return false;
}
}
virtual bool GetFile(std::string& content /* out */,
std::string& mime /* out */,
std::string& dateTime /* out */,
const std::vector& path)
{
boost::mutex::scoped_lock lock(mutex_);
Resource* resource = root_->LookupPath(path);
if (resource != NULL &&
!resource->IsFolder())
{
const File& file = dynamic_cast(*resource);
content = file.GetContent();
mime = ""; // Let the Orthanc core autodetect the MIME type
dateTime = boost::posix_time::to_iso_string(file.GetDateTime());
return true;
}
else
{
return false;
}
}
virtual bool StoreFile(const std::vector& path,
const void* data,
size_t size)
{
boost::mutex::scoped_lock lock(mutex_);
Resource* parent = root_->LookupPath(GetParentPath(path));
if (parent != NULL &&
parent->IsFolder())
{
dynamic_cast(*parent).StoreFile(path.back(), new File(data, size));
return true;
}
else
{
return false;
}
}
virtual bool CreateFolder(const std::vector& path)
{
boost::mutex::scoped_lock lock(mutex_);
Resource* parent = root_->LookupPath(GetParentPath(path));
if (parent != NULL &&
parent->IsFolder())
{
dynamic_cast(*parent).CreateSubfolder(path.back());
return true;
}
else
{
return false;
}
}
virtual bool DeleteItem(const std::vector& path)
{
boost::mutex::scoped_lock lock(mutex_);
Resource* parent = root_->LookupPath(GetParentPath(path));
if (parent != NULL &&
parent->IsFolder())
{
dynamic_cast(*parent).DeleteItem(path.back());
return true;
}
else
{
return false;
}
}
};
extern "C"
{
ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
{
OrthancPlugins::SetGlobalContext(c);
OrthancPluginLogWarning(c, "WebDAV plugin is initializing");
/* Check the version of the Orthanc core */
if (OrthancPluginCheckVersion(c) == 0)
{
char info[1024];
sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
c->orthancVersion,
ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
OrthancPluginLogError(c, info);
return -1;
}
static WebDavFilesystem filesystem;
OrthancPlugins::IWebDavCollection::Register("/webdav-plugin", filesystem);
return 0;
}
ORTHANC_PLUGINS_API void OrthancPluginFinalize()
{
OrthancPluginLogWarning(OrthancPlugins::GetGlobalContext(), "WebDAV plugin is finalizing");
}
ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
{
return "webdav-sample";
}
ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
{
return "0.0";
}
}