# HG changeset patch # User Sebastien Jodogne # Date 1647447662 -3600 # Node ID 47d734fa30f6e44eb2415c7e20eea4a5f5c8e20b # Parent bd7ad1cb40b6bf83cfc5da771e9ea57eb32dc161 adding function OrthancPluginRegisterWebDavCollection() to the plugin SDK diff -r bd7ad1cb40b6 -r 47d734fa30f6 NEWS --- a/NEWS Wed Mar 16 10:55:13 2022 +0100 +++ b/NEWS Wed Mar 16 17:21:02 2022 +0100 @@ -4,16 +4,21 @@ General ------- -* Improved DICOM authorization checks when multiple modalities are declared with - the same AET. - +* Improved DICOM authorization checks when multiple modalities are + declared with the same AET. + +Plugins +------- + +* New function in the SDK: "OrthancPluginRegisterWebDavCollection()" + to map a WebDAV virtual filesystem into the REST API of Orthanc. Documentation ------------- -* Removed the "LimitJobs" configuration that is not used anymore since the new - JobEngine has been introduced (in Orthanc 1.4.0). The pending list of jobs - is unlimited. +* Removed the "LimitJobs" configuration that is not used anymore since + the new JobEngine has been introduced (in Orthanc 1.4.0). The + pending list of jobs is unlimited. Version 1.10.0 (2022-02-23) diff -r bd7ad1cb40b6 -r 47d734fa30f6 OrthancFramework/Sources/HttpServer/IWebDavBucket.h --- a/OrthancFramework/Sources/HttpServer/IWebDavBucket.h Wed Mar 16 10:55:13 2022 +0100 +++ b/OrthancFramework/Sources/HttpServer/IWebDavBucket.h Wed Mar 16 17:21:02 2022 +0100 @@ -117,8 +117,6 @@ return mime_; } - void SetCreated(bool created); - virtual void Format(pugi::xml_node& node, const std::string& parentPath) const ORTHANC_OVERRIDE; }; diff -r bd7ad1cb40b6 -r 47d734fa30f6 OrthancFramework/Sources/SystemToolbox.cpp --- a/OrthancFramework/Sources/SystemToolbox.cpp Wed Mar 16 10:55:13 2022 +0100 +++ b/OrthancFramework/Sources/SystemToolbox.cpp Wed Mar 16 17:21:02 2022 +0100 @@ -817,6 +817,14 @@ { return MimeType_Ico; } + else if (extension == ".gz") + { + return MimeType_Gzip; + } + else if (extension == ".zip") + { + return MimeType_Zip; + } // Default type else diff -r bd7ad1cb40b6 -r 47d734fa30f6 OrthancServer/Plugins/Engine/OrthancPlugins.cpp --- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed Mar 16 10:55:13 2022 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed Mar 16 17:21:02 2022 +0100 @@ -39,6 +39,7 @@ #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" #include "../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h" #include "../../../OrthancFramework/Sources/DicomParsing/ToDcmtkBridge.h" +#include "../../../OrthancFramework/Sources/HttpServer/HttpServer.h" #include "../../../OrthancFramework/Sources/HttpServer/HttpToolbox.h" #include "../../../OrthancFramework/Sources/Images/Image.h" #include "../../../OrthancFramework/Sources/Images/ImageProcessing.h" @@ -75,6 +76,344 @@ namespace Orthanc { + class OrthancPlugins::WebDavCollection : public IWebDavBucket + { + private: + PluginsErrorDictionary& errorDictionary_; + std::string uri_; + OrthancPluginWebDavIsExistingFolderCallback isExistingFolder_; + OrthancPluginWebDavListFolderCallback listFolder_; + OrthancPluginWebDavRetrieveFileCallback retrieveFile_; + OrthancPluginWebDavStoreFileCallback storeFile_; + OrthancPluginWebDavCreateFolderCallback createFolder_; + OrthancPluginWebDavDeleteItemCallback deleteItem_; + void* payload_; + + class PathHelper : public boost::noncopyable + { + private: + std::vector items_; + + public: + PathHelper(const std::vector& path) + { + items_.resize(path.size()); + for (size_t i = 0; i < path.size(); i++) + { + items_[i] = path[i].c_str(); + } + } + + uint32_t GetSize() const + { + return static_cast(items_.size()); + } + + const char* const* GetItems() const + { + return (items_.empty() ? NULL : &items_[0]); + } + }; + + + static MimeType ParseMimeType(const char* mimeType) + { + MimeType mime; + if (LookupMimeType(mime, mimeType)) + { + return mime; + } + else + { + LOG(WARNING) << "Unknown MIME type in plugin: " << mimeType; + return MimeType_Binary; + } + } + + static OrthancPluginErrorCode AddFile( + OrthancPluginWebDavCollection* collection, + const char* displayName, + uint64_t contentSize, + const char* mimeType, + const char* creationTime) + { + try + { + std::unique_ptr f(new File(displayName)); + f->SetCreationTime(boost::posix_time::from_iso_string(creationTime)); + f->SetContentLength(contentSize); + + if (mimeType == NULL || + std::string(mimeType).empty()) + { + f->SetMimeType(SystemToolbox::AutodetectMimeType(displayName)); + } + else + { + f->SetMimeType(ParseMimeType(mimeType)); + } + + reinterpret_cast(collection)->AddResource(f.release()); + return OrthancPluginErrorCode_Success; + } + catch (OrthancException& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_InternalError; + } + } + + static OrthancPluginErrorCode AddFolder( + OrthancPluginWebDavCollection* collection, + const char* displayName, + const char* creationTime) + { + try + { + std::unique_ptr f(new Folder(displayName)); + f->SetCreationTime(boost::posix_time::from_iso_string(creationTime)); + reinterpret_cast(collection)->AddResource(f.release()); + return OrthancPluginErrorCode_Success; + } + catch (OrthancException& e) + { + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + LOG(ERROR) << "Presumably ill-formed date in the plugin"; + return OrthancPluginErrorCode_ParameterOutOfRange; + } + catch (...) + { + return OrthancPluginErrorCode_InternalError; + } + } + + + class ContentTarget : public boost::noncopyable + { + private: + bool isSent_; + MimeType& mime_; + std::string& content_; + boost::posix_time::ptime& modificationTime_; + + public: + ContentTarget(const std::string& displayName, + MimeType& mime, + std::string& content, + boost::posix_time::ptime& modificationTime) : + isSent_(false), + mime_(mime), + content_(content), + modificationTime_(modificationTime) + { + mime = SystemToolbox::AutodetectMimeType(displayName); + } + + bool IsSent() const + { + return isSent_; + } + + static OrthancPluginErrorCode RetrieveFile( + OrthancPluginWebDavCollection* collection, + const void* data, + uint64_t size, + const char* mimeType, + const char* creationTime) + { + ContentTarget& target = *reinterpret_cast(collection); + + if (target.isSent_) + { + return OrthancPluginErrorCode_BadSequenceOfCalls; + } + else + { + try + { + target.isSent_ = true; + + if (mimeType != NULL && + !std::string(mimeType).empty()) + { + target.mime_ = ParseMimeType(mimeType); + } + + target.content_.assign(reinterpret_cast(data), size); + target.modificationTime_ = boost::posix_time::from_iso_string(creationTime); + return OrthancPluginErrorCode_Success; + } + catch (Orthanc::OrthancException& e) + { + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + LOG(ERROR) << "Presumably ill-formed date in the plugin"; + return OrthancPluginErrorCode_ParameterOutOfRange; + } + catch (...) + { + return OrthancPluginErrorCode_InternalError; + } + } + } + }; + + + public: + WebDavCollection(PluginsErrorDictionary& errorDictionary, + const _OrthancPluginRegisterWebDavCollection& p) : + errorDictionary_(errorDictionary), + uri_(p.uri), + isExistingFolder_(p.isExistingFolder), + listFolder_(p.listFolder), + retrieveFile_(p.retrieveFile), + storeFile_(p.storeFile), + createFolder_(p.createFolder), + deleteItem_(p.deleteItem), + payload_(p.payload) + { + } + + const std::string& GetUri() const + { + return uri_; + } + + virtual bool IsExistingFolder(const std::vector& path) + { + PathHelper helper(path); + + uint8_t isExisting; + OrthancPluginErrorCode code = isExistingFolder_(&isExisting, helper.GetSize(), helper.GetItems(), payload_); + + if (code == OrthancPluginErrorCode_Success) + { + return (isExisting != 0); + } + else + { + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast(code)); + } + } + + virtual bool ListCollection(Collection& collection, + const std::vector& path) + { + PathHelper helper(path); + + uint8_t isExisting; + OrthancPluginErrorCode code = listFolder_(&isExisting, reinterpret_cast(&collection), + AddFile, AddFolder, helper.GetSize(), helper.GetItems(), payload_); + + if (code == OrthancPluginErrorCode_Success) + { + return (isExisting != 0); + } + else + { + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast(code)); + } + } + + virtual bool GetFileContent(MimeType& mime, + std::string& content, + boost::posix_time::ptime& modificationTime, + const std::vector& path) + { + PathHelper helper(path); + + ContentTarget target(path.back(), mime, content, modificationTime); + OrthancPluginErrorCode code = retrieveFile_( + reinterpret_cast(&target), + ContentTarget::RetrieveFile, helper.GetSize(), helper.GetItems(), payload_); + + if (code == OrthancPluginErrorCode_Success) + { + return target.IsSent(); + } + else + { + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast(code)); + } + } + + virtual bool StoreFile(const std::string& content, + const std::vector& path) + { + PathHelper helper(path); + + uint8_t isReadOnly; + OrthancPluginErrorCode code = storeFile_(&isReadOnly, helper.GetSize(), helper.GetItems(), + content.empty() ? NULL : content.c_str(), content.size(), payload_); + + if (code == OrthancPluginErrorCode_Success) + { + return (isReadOnly != 0); + } + else + { + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast(code)); + } + } + + virtual bool CreateFolder(const std::vector& path) + { + PathHelper helper(path); + + uint8_t isReadOnly; + OrthancPluginErrorCode code = createFolder_(&isReadOnly, helper.GetSize(), helper.GetItems(), payload_); + + if (code == OrthancPluginErrorCode_Success) + { + return (isReadOnly != 0); + } + else + { + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast(code)); + } + } + + virtual bool DeleteItem(const std::vector& path) + { + PathHelper helper(path); + + uint8_t isReadOnly; + OrthancPluginErrorCode code = deleteItem_(&isReadOnly, helper.GetSize(), helper.GetItems(), payload_); + + if (code == OrthancPluginErrorCode_Success) + { + return (isReadOnly != 0); + } + else + { + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast(code)); + } + } + + virtual void Start() + { + } + + virtual void Stop() + { + } + }; + + static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target, const void* data, size_t size) @@ -1164,6 +1503,7 @@ typedef std::list RefreshMetricsCallbacks; typedef std::list StorageCommitmentScpCallbacks; typedef std::map Properties; + typedef std::list WebDavCollections; PluginsManager manager_; @@ -1184,6 +1524,7 @@ OrthancPluginReceivedInstanceCallback receivedInstanceCallback_; // New in Orthanc 1.10.0 RefreshMetricsCallbacks refreshMetricsCallbacks_; StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_; + WebDavCollections webDavCollections_; // New in Orthanc 1.10.1 std::unique_ptr storageArea_; std::set authorizationTokens_; @@ -1768,7 +2109,13 @@ it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it) { delete *it; - } + } + + for (PImpl::WebDavCollections::iterator it = pimpl_->webDavCollections_.begin(); + it != pimpl_->webDavCollections_.end(); ++it) + { + delete *it; + } } @@ -5265,6 +5612,15 @@ return true; } + case _OrthancPluginService_RegisterWebDavCollection: + { + CLOG(INFO, PLUGINS) << "Plugin has registered a WebDAV collection"; + const _OrthancPluginRegisterWebDavCollection& p = + *reinterpret_cast(parameters); + pimpl_->webDavCollections_.push_back(new WebDavCollection(GetErrorDictionary(), p)); + return true; + } + default: { // This service is unknown to the Orthanc plugin engine @@ -5862,4 +6218,22 @@ boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); return pimpl_->maxDatabaseRetries_; } + + + void OrthancPlugins::RegisterWebDavCollections(HttpServer& target) + { + boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); + + while (!pimpl_->webDavCollections_.empty()) + { + WebDavCollection* collection = pimpl_->webDavCollections_.front(); + assert(collection != NULL); + + UriComponents components; + Toolbox::SplitUriComponents(components, collection->GetUri()); + target.Register(components, collection); + + pimpl_->webDavCollections_.pop_front(); + } + } } diff -r bd7ad1cb40b6 -r 47d734fa30f6 OrthancServer/Plugins/Engine/OrthancPlugins.h --- a/OrthancServer/Plugins/Engine/OrthancPlugins.h Wed Mar 16 10:55:13 2022 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h Wed Mar 16 17:21:02 2022 +0100 @@ -62,6 +62,7 @@ namespace Orthanc { + class HttpServer; class ServerContext; class OrthancPlugins : @@ -89,6 +90,7 @@ class DicomInstanceFromCallback; class DicomInstanceFromBuffer; class DicomInstanceFromTranscoded; + class WebDavCollection; void RegisterRestCallback(const void* parameters, bool lock); @@ -394,6 +396,8 @@ bool IsValidAuthorizationToken(const std::string& token) const; unsigned int GetMaxDatabaseRetries() const; + + void RegisterWebDavCollections(HttpServer& target); }; } diff -r bd7ad1cb40b6 -r 47d734fa30f6 OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h --- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Wed Mar 16 10:55:13 2022 +0100 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Wed Mar 16 17:21:02 2022 +0100 @@ -30,6 +30,7 @@ * - Possibly register a callback to keep/discard/modify incoming DICOM instances using OrthancPluginRegisterReceivedInstanceCallback(). * - Possibly register a custom transcoder for DICOM images using OrthancPluginRegisterTranscoderCallback(). * - Possibly register a callback to discard instances received through DICOM C-STORE using OrthancPluginRegisterIncomingCStoreInstanceFilter(). + * - Possibly register a callback to branch a WebDAV virtual filesystem using OrthancPluginRegisterWebDavCollection(). * -# void OrthancPluginFinalize(): * This function is invoked by Orthanc during its shutdown. The plugin * must free all its memory. @@ -119,7 +120,7 @@ #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 10 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 1 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) @@ -465,6 +466,7 @@ _OrthancPluginService_RegisterStorageArea2 = 1016, /* New in Orthanc 1.9.0 */ _OrthancPluginService_RegisterIncomingCStoreInstanceFilter = 1017, /* New in Orthanc 1.10.0 */ _OrthancPluginService_RegisterReceivedInstanceCallback = 1018, /* New in Orthanc 1.10.0 */ + _OrthancPluginService_RegisterWebDavCollection = 1019, /* New in Orthanc 1.10.1 */ /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -1064,7 +1066,7 @@ /** * @brief Opaque structure that represents the HTTP connection to the client application. - * @ingroup Callback + * @ingroup Callbacks **/ typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput; @@ -1371,7 +1373,7 @@ * @param headersKeys The keys of the HTTP headers (always converted to low-case). * @param headersValues The values of the HTTP headers. * @return 0 if forbidden access, 1 if allowed access, -1 if error. - * @ingroup Callback + * @ingroup Callbacks * @deprecated Please instead use OrthancPluginIncomingHttpRequestFilter2() **/ typedef int32_t (*OrthancPluginIncomingHttpRequestFilter) ( @@ -1407,7 +1409,7 @@ * @param getArgumentsKeys The keys of the GET arguments (only for the GET HTTP method). * @param getArgumentsValues The values of the GET arguments (only for the GET HTTP method). * @return 0 if forbidden access, 1 if allowed access, -1 if error. - * @ingroup Callback + * @ingroup Callbacks **/ typedef int32_t (*OrthancPluginIncomingHttpRequestFilter2) ( OrthancPluginHttpMethod method, @@ -7409,7 +7411,7 @@ /** * @brief Opaque structure that reads the content of a HTTP request body during a chunked HTTP transfer. - * @ingroup Callback + * @ingroup Callbacks **/ typedef struct _OrthancPluginServerChunkedRequestReader_t OrthancPluginServerChunkedRequestReader; @@ -7704,7 +7706,7 @@ * @param factory Factory function that creates the handler object * for incoming storage commitment requests. * @param destructor Destructor function to destroy the handler object. - * @param lookup Callback method to get the status of one DICOM instance. + * @param lookup Callback function to get the status of one DICOM instance. * @return 0 if success, other value if error. * @ingroup DicomCallbacks **/ @@ -7746,7 +7748,7 @@ * * @param instance The received DICOM instance. * @return 0 to discard the instance, 1 to store the instance, -1 if error. - * @ingroup Callback + * @ingroup Callbacks **/ typedef int32_t (*OrthancPluginIncomingDicomInstanceFilter) ( const OrthancPluginDicomInstance* instance); @@ -7808,7 +7810,7 @@ * DIMSE status to be sent by the C-STORE SCP of Orthanc * @param instance The received DICOM instance. * @return 0 to discard the instance, 1 to store the instance, -1 if error. - * @ingroup Callback + * @ingroup Callbacks **/ typedef int32_t (*OrthancPluginIncomingCStoreInstanceFilter) ( uint16_t* dimseStatus /* out */, @@ -7875,7 +7877,7 @@ * @return `OrthancPluginReceivedInstanceAction_KeepAsIs` to accept the instance as is,
* `OrthancPluginReceivedInstanceAction_Modify` to store the modified DICOM contained in `modifiedDicomBuffer`,
* `OrthancPluginReceivedInstanceAction_Discard` to tell Orthanc to discard the instance. - * @ingroup Callback + * @ingroup Callbacks **/ typedef OrthancPluginReceivedInstanceAction (*OrthancPluginReceivedInstanceCallback) ( OrthancPluginMemoryBuffer64* modifiedDicomBuffer, @@ -8473,7 +8475,7 @@ /** - * @brief Generate a token to grant full access to the REST API of Orthanc + * @brief Generate a token to grant full access to the REST API of Orthanc. * * This function generates a token that can be set in the HTTP * header "Authorization" so as to grant full access to the REST API @@ -8728,6 +8730,279 @@ } + + /** + * @brief Opaque structure that represents a WebDAV collection. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginWebDavCollection_t OrthancPluginWebDavCollection; + + + /** + * @brief Declare a file while returning the content of a folder. + * + * This function declares a file while returning the content of a + * WebDAV folder. + * + * @param collection Context of the collection. + * @param name Base name of the file. + * @param dateTime The date and time of creation of the file. + * Check out the documentation of OrthancPluginWebDavRetrieveFile() for more information. + * @param size Size of the file. + * @param mimeType The MIME type of the file. If empty or set to `NULL`, + * Orthanc will do a best guess depending on the file extension. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWebDavAddFile) ( + OrthancPluginWebDavCollection* collection, + const char* name, + uint64_t size, + const char* mimeType, + const char* dateTime); + + + /** + * @brief Declare a subfolder while returning the content of a folder. + * + * This function declares a subfolder while returning the content of a + * WebDAV folder. + * + * @param collection Context of the collection. + * @param name Base name of the subfolder. + * @param dateTime The date and time of creation of the subfolder. + * Check out the documentation of OrthancPluginWebDavRetrieveFile() for more information. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWebDavAddFolder) ( + OrthancPluginWebDavCollection* collection, + const char* name, + const char* dateTime); + + + /** + * @brief Retrieve the content of a file. + * + * This function is used to forward the content of a file from a + * WebDAV collection, to the core of Orthanc. + * + * @param collection Context of the collection. + * @param data Content of the file. + * @param size Size of the file. + * @param mimeType The MIME type of the file. If empty or set to `NULL`, + * Orthanc will do a best guess depending on the file extension. + * @param dateTime The date and time of creation of the file. + * It must be formatted as an ISO string of form + * `YYYYMMDDTHHMMSS,fffffffff` where T is the date-time + * separator. It must be expressed in UTC (it is the responsibility + * of the plugin to do the possible timezone + * conversions). Internally, this string will be parsed using + * `boost::posix_time::from_iso_string()`. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWebDavRetrieveFile) ( + OrthancPluginWebDavCollection* collection, + const void* data, + uint64_t size, + const char* mimeType, + const char* dateTime); + + + /** + * @brief Callback for testing the existence of a folder. + * + * Signature of a callback function that tests whether the given + * path in the WebDAV collection exists and corresponds to a folder. + * + * @param isExisting Pointer to a Boolean that must be set to `1` if the folder exists, or `0` otherwise. + * @param pathSize Number of levels in the path. + * @param pathItems Items making the path. + * @param payload The user payload. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWebDavIsExistingFolderCallback) ( + uint8_t* isExisting, /* out */ + uint32_t pathSize, + const char* const* pathItems, + void* payload); + + + /** + * @brief Callback for listing the content of a folder. + * + * Signature of a callback function that lists the content of a + * folder in the WebDAV collection. The callback must call the + * provided `addFile()` and `addFolder()` functions to emit the + * content of the folder. + * + * @param isExisting Pointer to a Boolean that must be set to `1` if the folder exists, or `0` otherwise. + * @param collection Context to be provided to `addFile()` and `addFolder()` functions. + * @param addFile Function to add a file to the list. + * @param addFolder Function to add a folder to the list. + * @param pathSize Number of levels in the path. + * @param pathItems Items making the path. + * @param payload The user payload. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWebDavListFolderCallback) ( + uint8_t* isExisting, /* out */ + OrthancPluginWebDavCollection* collection, + OrthancPluginWebDavAddFile addFile, + OrthancPluginWebDavAddFolder addFolder, + uint32_t pathSize, + const char* const* pathItems, + void* payload); + + + /** + * @brief Callback for retrieving the content of a file. + * + * Signature of a callback function that retrieves the content of a + * file in the WebDAV collection. The callback must call the + * provided `retrieveFile()` function to emit the actual content of + * the file. + * + * @param collection Context to be provided to `retrieveFile()` function. + * @param retrieveFile Function to return the content of the file. + * @param pathSize Number of levels in the path. + * @param pathItems Items making the path. + * @param payload The user payload. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWebDavRetrieveFileCallback) ( + OrthancPluginWebDavCollection* collection, + OrthancPluginWebDavRetrieveFile retrieveFile, + uint32_t pathSize, + const char* const* pathItems, + void* payload); + + + /** + * @brief Callback to store a file. + * + * Signature of a callback function that stores a file into the + * WebDAV collection. + * + * @param isReadOnly Pointer to a Boolean that must be set to `1` if the collection is read-only, or `0` otherwise. + * @param pathSize Number of levels in the path. + * @param pathItems Items making the path. + * @param data Content of the file to be stored. + * @param size Size of the file to be stored. + * @param payload The user payload. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWebDavStoreFileCallback) ( + uint8_t* isReadOnly, /* out */ + uint32_t pathSize, + const char* const* pathItems, + const void* data, + uint64_t size, + void* payload); + + + /** + * @brief Callback to create a folder. + * + * Signature of a callback function that creates a folder in the + * WebDAV collection. + * + * @param isReadOnly Pointer to a Boolean that must be set to `1` if the collection is read-only, or `0` otherwise. + * @param pathSize Number of levels in the path. + * @param pathItems Items making the path. + * @param payload The user payload. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWebDavCreateFolderCallback) ( + uint8_t* isReadOnly, /* out */ + uint32_t pathSize, + const char* const* pathItems, + void* payload); + + + /** + * @brief Callback to remove a file or a folder. + * + * Signature of a callback function that removes a file or a folder + * from the WebDAV collection. + * + * @param isReadOnly Pointer to a Boolean that must be set to `1` if the collection is read-only, or `0` otherwise. + * @param pathSize Number of levels in the path. + * @param pathItems Items making the path. + * @param payload The user payload. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWebDavDeleteItemCallback) ( + uint8_t* isReadOnly, /* out */ + uint32_t pathSize, + const char* const* pathItems, + void* payload); + + + typedef struct + { + const char* uri; + OrthancPluginWebDavIsExistingFolderCallback isExistingFolder; + OrthancPluginWebDavListFolderCallback listFolder; + OrthancPluginWebDavRetrieveFileCallback retrieveFile; + OrthancPluginWebDavStoreFileCallback storeFile; + OrthancPluginWebDavCreateFolderCallback createFolder; + OrthancPluginWebDavDeleteItemCallback deleteItem; + void* payload; + } _OrthancPluginRegisterWebDavCollection; + + /** + * @brief Register a WebDAV virtual filesystem. + * + * This function maps a WebDAV collection onto the given URI in the + * REST API of Orthanc. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri URI where to map the WebDAV collection (must start with a `/` character). + * @param isExistingFolder Callback method to test for the existence of a folder. + * @param listFolder Callback method to list the content of a folder. + * @param retrieveFile Callback method to retrieve the content of a file. + * @param storeFile Callback method to store a file into the WebDAV collection. + * @param createFolder Callback method to create a folder. + * @param deleteItem Callback method to delete a file or a folder. + * @param payload The user payload. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWebDavCollection( + OrthancPluginContext* context, + const char* uri, + OrthancPluginWebDavIsExistingFolderCallback isExistingFolder, + OrthancPluginWebDavListFolderCallback listFolder, + OrthancPluginWebDavRetrieveFileCallback retrieveFile, + OrthancPluginWebDavStoreFileCallback storeFile, + OrthancPluginWebDavCreateFolderCallback createFolder, + OrthancPluginWebDavDeleteItemCallback deleteItem, + void* payload) + { + _OrthancPluginRegisterWebDavCollection params; + params.uri = uri; + params.isExistingFolder = isExistingFolder; + params.listFolder = listFolder; + params.retrieveFile = retrieveFile; + params.storeFile = storeFile; + params.createFolder = createFolder; + params.deleteItem = deleteItem; + params.payload = payload; + + return context->InvokeService(context, _OrthancPluginService_RegisterWebDavCollection, ¶ms); + } + + #ifdef __cplusplus } #endif diff -r bd7ad1cb40b6 -r 47d734fa30f6 OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp --- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Wed Mar 16 10:55:13 2022 +0100 +++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Wed Mar 16 17:21:02 2022 +0100 @@ -2526,7 +2526,7 @@ } catch (...) { - return OrthancPluginErrorCode_InternalError; + return OrthancPluginErrorCode_Plugin; } } } @@ -3548,4 +3548,238 @@ } } #endif + + +#if HAS_ORTHANC_PLUGIN_WEBDAV == 1 + static std::vector WebDavConvertPath(uint32_t pathSize, + const char* const* pathItems) + { + std::vector result(pathSize); + + for (uint32_t i = 0; i < pathSize; i++) + { + result[i] = pathItems[i]; + } + + return result; + } +#endif + + +#if HAS_ORTHANC_PLUGIN_WEBDAV == 1 + static OrthancPluginErrorCode WebDavIsExistingFolder(uint8_t* isExisting, + uint32_t pathSize, + const char* const* pathItems, + void* payload) + { + IWebDavCollection& that = *reinterpret_cast(payload); + + try + { + *isExisting = (that.IsExistingFolder(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_WEBDAV == 1 + static OrthancPluginErrorCode WebDavListFolder(uint8_t* isExisting, + OrthancPluginWebDavCollection* collection, + OrthancPluginWebDavAddFile addFile, + OrthancPluginWebDavAddFolder addFolder, + uint32_t pathSize, + const char* const* pathItems, + void* payload) + { + IWebDavCollection& that = *reinterpret_cast(payload); + + try + { + std::list files; + std::list subfolders; + + if (!that.ListFolder(files, subfolders, WebDavConvertPath(pathSize, pathItems))) + { + *isExisting = 0; + } + else + { + *isExisting = 1; + + for (std::list::const_iterator + it = files.begin(); it != files.end(); ++it) + { + OrthancPluginErrorCode code = addFile( + collection, it->GetName().c_str(), it->GetContentSize(), + it->GetMimeType().c_str(), it->GetDateTime().c_str()); + + if (code != OrthancPluginErrorCode_Success) + { + return code; + } + } + + for (std::list::const_iterator it = + subfolders.begin(); it != subfolders.end(); ++it) + { + OrthancPluginErrorCode code = addFolder( + collection, it->GetName().c_str(), it->GetDateTime().c_str()); + + if (code != OrthancPluginErrorCode_Success) + { + return code; + } + } + } + + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_WEBDAV == 1 + static OrthancPluginErrorCode WebDavRetrieveFile(OrthancPluginWebDavCollection* collection, + OrthancPluginWebDavRetrieveFile retrieveFile, + uint32_t pathSize, + const char* const* pathItems, + void* payload) + { + IWebDavCollection& that = *reinterpret_cast(payload); + + try + { + std::string content, mime, dateTime; + + if (that.GetFile(content, mime, dateTime, WebDavConvertPath(pathSize, pathItems))) + { + return retrieveFile(collection, content.empty() ? NULL : content.c_str(), + content.size(), mime.c_str(), dateTime.c_str()); + } + else + { + // Inexisting file + return OrthancPluginErrorCode_Success; + } + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_InternalError; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_WEBDAV == 1 + static OrthancPluginErrorCode WebDavStoreFileCallback(uint8_t* isReadOnly, /* out */ + uint32_t pathSize, + const char* const* pathItems, + const void* data, + uint64_t size, + void* payload) + { + IWebDavCollection& that = *reinterpret_cast(payload); + + try + { + *isReadOnly = (that.StoreFile(WebDavConvertPath(pathSize, pathItems), data, size) ? 1 : 0); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_InternalError; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_WEBDAV == 1 + static OrthancPluginErrorCode WebDavCreateFolderCallback(uint8_t* isReadOnly, /* out */ + uint32_t pathSize, + const char* const* pathItems, + void* payload) + { + IWebDavCollection& that = *reinterpret_cast(payload); + + try + { + *isReadOnly = (that.CreateFolder(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_InternalError; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_WEBDAV == 1 + static OrthancPluginErrorCode WebDavDeleteItemCallback(uint8_t* isReadOnly, /* out */ + uint32_t pathSize, + const char* const* pathItems, + void* payload) + { + IWebDavCollection& that = *reinterpret_cast(payload); + + try + { + *isReadOnly = (that.DeleteItem(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_InternalError; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_WEBDAV == 1 + void IWebDavCollection::Register(const std::string& uri, + IWebDavCollection& collection) + { + OrthancPluginErrorCode code = OrthancPluginRegisterWebDavCollection( + GetGlobalContext(), uri.c_str(), WebDavIsExistingFolder, WebDavListFolder, WebDavRetrieveFile, + WebDavStoreFileCallback, WebDavCreateFolderCallback, WebDavDeleteItemCallback, &collection); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif } diff -r bd7ad1cb40b6 -r 47d734fa30f6 OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h --- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h Wed Mar 16 10:55:13 2022 +0100 +++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h Wed Mar 16 17:21:02 2022 +0100 @@ -115,6 +115,12 @@ # define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 0 #endif +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 10, 1) +# define HAS_ORTHANC_PLUGIN_WEBDAV 1 +#else +# define HAS_ORTHANC_PLUGIN_WEBDAV 0 +#endif + namespace OrthancPlugins @@ -1248,4 +1254,131 @@ const std::string& transferSyntax); #endif }; + + + +#if HAS_ORTHANC_PLUGIN_WEBDAV == 1 + class IWebDavCollection : public boost::noncopyable + { + public: + class FileInfo + { + private: + std::string name_; + uint64_t contentSize_; + std::string mime_; + std::string dateTime_; + + public: + FileInfo(const std::string& name, + uint64_t contentSize, + const std::string& dateTime) : + name_(name), + contentSize_(contentSize), + dateTime_(dateTime) + { + } + + const std::string& GetName() const + { + return name_; + } + + uint64_t GetContentSize() const + { + return contentSize_; + } + + void SetMimeType(const std::string& mime) + { + mime_ = mime; + } + + const std::string& GetMimeType() const + { + return mime_; + } + + const std::string& GetDateTime() const + { + return dateTime_; + } + }; + + class FolderInfo + { + private: + std::string name_; + std::string dateTime_; + + public: + FolderInfo(const std::string& name, + const std::string& dateTime) : + name_(name), + dateTime_(dateTime) + { + } + + const std::string& GetName() const + { + return name_; + } + + const std::string& GetDateTime() const + { + return dateTime_; + } + }; + + virtual ~IWebDavCollection() + { + } + + virtual bool IsExistingFolder(const std::vector& path) = 0; + + virtual bool ListFolder(std::list& files, + std::list& subfolders, + const std::vector& path) = 0; + + virtual bool GetFile(std::string& content /* out */, + std::string& mime /* out */, + std::string& dateTime /* out */, + const std::vector& path) = 0; + + virtual bool StoreFile(const std::vector& path, + const void* data, + size_t size) = 0; + + virtual bool CreateFolder(const std::vector& path) = 0; + + virtual bool DeleteItem(const std::vector& path) = 0; + + static void Register(const std::string& uri, + IWebDavCollection& collection); + }; +#endif + + +#if HAS_ORTHANC_PLUGIN_WEBDAV == 1 + class ReadOnlyWebDavCollection : public IWebDavCollection + { + public: + virtual bool StoreFile(const std::vector& path, + const void* data, + size_t size) + { + return false; + } + + virtual bool CreateFolder(const std::vector& path) + { + return false; + } + + virtual bool DeleteItem(const std::vector& path) + { + return false; + } + }; +#endif } diff -r bd7ad1cb40b6 -r 47d734fa30f6 OrthancServer/Plugins/Samples/WebDavFilesystem/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/WebDavFilesystem/CMakeLists.txt Wed Mar 16 17:21:02 2022 +0100 @@ -0,0 +1,42 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2022 Osimis S.A., Belgium +# Copyright (C) 2021-2022 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 . + + +cmake_minimum_required(VERSION 2.8) + +project(WebDavFilesystem) + +SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") + +SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") +SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost") + +include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake) +include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/JsonCppConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/BoostConfiguration.cmake) + +add_definitions(-DHAS_ORTHANC_EXCEPTION=0) + +add_library(WebDavFilesystem SHARED + Plugin.cpp + ${CMAKE_SOURCE_DIR}/../Common/OrthancPluginCppWrapper.cpp + ${JSONCPP_SOURCES} + ${BOOST_SOURCES} + ) diff -r bd7ad1cb40b6 -r 47d734fa30f6 OrthancServer/Plugins/Samples/WebDavFilesystem/Plugin.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/WebDavFilesystem/Plugin.cpp Wed Mar 16 17:21:02 2022 +0100 @@ -0,0 +1,390 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 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()) + { + OrthancPlugins::LogError("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()) + { + OrthancPlugins::LogError("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()) + { + OrthancPlugins::LogError("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()) + { + OrthancPlugins::LogError("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"; + } +} diff -r bd7ad1cb40b6 -r 47d734fa30f6 OrthancServer/Sources/main.cpp --- a/OrthancServer/Sources/main.cpp Wed Mar 16 10:55:13 2022 +0100 +++ b/OrthancServer/Sources/main.cpp Wed Mar 16 17:21:02 2022 +0100 @@ -1180,6 +1180,13 @@ } } +#if ORTHANC_ENABLE_PLUGINS == 1 + if (plugins != NULL) + { + plugins->RegisterWebDavCollections(httpServer); + } +#endif + MyHttpExceptionFormatter exceptionFormatter(httpDescribeErrors, plugins); httpServer.SetIncomingHttpRequestFilter(httpFilter);