# HG changeset patch # User Sebastien Jodogne # Date 1734427180 -3600 # Node ID 239965a40220283310ff622a1806ed15bd7b9ae2 # Parent bdcea1ec1683c8c6aac8bd8ff8e0694ef7bbdc2a fix handling of revisions for cached data diff -r bdcea1ec1683 -r 239965a40220 NEWS --- a/NEWS Mon Dec 16 22:40:06 2024 +0100 +++ b/NEWS Tue Dec 17 10:19:40 2024 +0100 @@ -9,6 +9,7 @@ This greatly improves the download time of multi-frame images in OHIF. * Optimization when running with an Orthanc that supports the "ExtendedFind" primitive. * Added support for Orthanc running in "ReadOnly" mode. +* Fix handling of revisions for cached data. Version 1.17 (2024-06-05) diff -r bdcea1ec1683 -r 239965a40220 Plugin/Configuration.cpp --- a/Plugin/Configuration.cpp Mon Dec 16 22:40:06 2024 +0100 +++ b/Plugin/Configuration.cpp Tue Dec 17 10:19:40 2024 +0100 @@ -475,11 +475,10 @@ static bool LookupHttpHeader2(std::string& value, - const HttpClient::HttpHeaders& headers, + const HttpHeaders& headers, const std::string& name) { - for (HttpClient::HttpHeaders::const_iterator - it = headers.begin(); it != headers.end(); ++it) + for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) { if (boost::iequals(it->first, name)) { @@ -492,7 +491,7 @@ } - std::string GetBasePublicUrl(const HttpClient::HttpHeaders& headers) + std::string GetBasePublicUrl(const HttpHeaders& headers) { assert(dicomWebConfiguration_.get() != NULL); std::string host = dicomWebConfiguration_->GetStringValue("Host", ""); @@ -563,7 +562,7 @@ std::string GetBasePublicUrl(const OrthancPluginHttpRequest* request) { - HttpClient::HttpHeaders headers; + HttpHeaders headers; std::string value; if (LookupHttpHeader(value, request, "forwarded")) diff -r bdcea1ec1683 -r 239965a40220 Plugin/DicomWebClient.cpp --- a/Plugin/DicomWebClient.cpp Mon Dec 16 22:40:06 2024 +0100 +++ b/Plugin/DicomWebClient.cpp Tue Dec 17 10:19:40 2024 +0100 @@ -598,16 +598,16 @@ Action_Cancel }; - boost::mutex mutex_; - std::string serverName_; - std::vector instances_; - OrthancPlugins::HttpClient::HttpHeaders headers_; - std::string boundary_; - size_t position_; - Action action_; - size_t networkSize_; - bool debug_; - Json::Value resourcesForJobContent_; + boost::mutex mutex_; + std::string serverName_; + std::vector instances_; + OrthancPlugins::HttpHeaders headers_; + std::string boundary_; + size_t position_; + Action action_; + size_t networkSize_; + bool debug_; + Json::Value resourcesForJobContent_; bool ReadNextInstance(std::string& dicom, JobContext& context) @@ -740,7 +740,7 @@ client->AddHeaders(that_.headers_); } - OrthancPlugins::HttpClient::HttpHeaders answerHeaders; + OrthancPlugins::HttpHeaders answerHeaders; Json::Value answerBody; assert(client.get() != NULL); @@ -822,7 +822,7 @@ public: StowClientJob(const std::string& serverName, const std::list& instances, - const OrthancPlugins::HttpClient::HttpHeaders& headers, + const OrthancPlugins::HttpHeaders& headers, const Json::Value& resourcesForJobContent) : SingleFunctionJob("DicomWebStowClient"), serverName_(serverName), diff -r bdcea1ec1683 -r 239965a40220 Plugin/WadoRs.cpp --- a/Plugin/WadoRs.cpp Mon Dec 16 22:40:06 2024 +0100 +++ b/Plugin/WadoRs.cpp Tue Dec 17 10:19:40 2024 +0100 @@ -922,7 +922,15 @@ false /* JSON */, OrthancPluginDicomWebBinaryMode_Ignore, ""); } - buffer.RestApiPut("/instances/" + orthancId + "/attachments/4444", dicomweb, false); + try + { + buffer.RestApiPut("/instances/" + orthancId + "/attachments/4444", dicomweb, false); + } + catch (Orthanc::OrthancException& e) + { + // An exception might occur if another writer has concurrently created the attachment, ignore this case + } + writer.AddDicomWebInstanceSerializedJson(dicomweb.c_str(), dicomweb.size()); } } @@ -1479,7 +1487,24 @@ Json::Value putResult; std::string attachmentUrl = "/series/" + seriesOrthancId + "/attachments/" + SERIES_METADATA_ATTACHMENT_ID; - if (!OrthancPlugins::RestApiPut(putResult, attachmentUrl, cacheContent, false)) + + OrthancPlugins::RestApiClient client; + client.SetMethod(OrthancPluginHttpMethod_Get); + client.SetPath(attachmentUrl); + + std::string etag; + bool hasRevision = (client.Execute() && + client.LookupAnswerHeader(etag, "etag")); + + client.SetMethod(OrthancPluginHttpMethod_Put); + client.SwapRequestBody(cacheContent); + + if (hasRevision) + { + client.AddRequestHeader("If-Match", etag); + } + + if (!client.Execute()) { LOG(WARNING) << "DicomWEB: failed to write series metadata attachment"; } diff -r bdcea1ec1683 -r 239965a40220 Resources/Orthanc/CMake/Compiler.cmake --- a/Resources/Orthanc/CMake/Compiler.cmake Mon Dec 16 22:40:06 2024 +0100 +++ b/Resources/Orthanc/CMake/Compiler.cmake Tue Dec 17 10:19:40 2024 +0100 @@ -232,6 +232,10 @@ endif() elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + + # fix this error that appears with recent compilers on MacOS: boost/mpl/aux_/integral_wrapper.hpp:73:31: error: integer value -1 is outside the valid range of values [0, 3] for this enumeration type [-Wenum-constexpr-conversion] + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-enum-constexpr-conversion") + add_definitions( -D_XOPEN_SOURCE=1 ) diff -r bdcea1ec1683 -r 239965a40220 Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp --- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Mon Dec 16 22:40:06 2024 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Dec 17 10:19:40 2024 +0100 @@ -334,9 +334,9 @@ std::vector headersValues_; public: - explicit PluginHttpHeaders(const std::map& httpHeaders) - { - for (std::map::const_iterator + explicit PluginHttpHeaders(const HttpHeaders& httpHeaders) + { + for (HttpHeaders::const_iterator it = httpHeaders.begin(); it != httpHeaders.end(); ++it) { headersKeys_.push_back(it->first.c_str()); @@ -361,7 +361,7 @@ }; bool MemoryBuffer::RestApiGet(const std::string& uri, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins) { Clear(); @@ -400,7 +400,7 @@ bool MemoryBuffer::RestApiPost(const std::string& uri, const void* body, size_t bodySize, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins) { MemoryBuffer answerHeaders; @@ -422,7 +422,7 @@ bool MemoryBuffer::RestApiPost(const std::string& uri, const Json::Value& body, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins) { std::string s; @@ -1490,7 +1490,7 @@ bool RestApiGetString(std::string& result, const std::string& uri, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins) { MemoryBuffer answer; @@ -1508,7 +1508,7 @@ bool RestApiGet(Json::Value& result, const std::string& uri, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins) { MemoryBuffer answer; @@ -1598,7 +1598,7 @@ bool RestApiPost(Json::Value& result, const std::string& uri, const Json::Value& body, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins) { MemoryBuffer answer; @@ -1963,7 +1963,7 @@ bool OrthancPeers::DoGet(MemoryBuffer& target, size_t index, const std::string& uri, - const std::map& headers) const + const HttpHeaders& headers) const { if (index >= index_.size()) { @@ -1994,7 +1994,7 @@ bool OrthancPeers::DoGet(MemoryBuffer& target, const std::string& name, const std::string& uri, - const std::map& headers) const + const HttpHeaders& headers) const { size_t index; return (LookupName(index, name) && @@ -2005,7 +2005,7 @@ bool OrthancPeers::DoGet(Json::Value& target, size_t index, const std::string& uri, - const std::map& headers) const + const HttpHeaders& headers) const { MemoryBuffer buffer; @@ -2024,7 +2024,7 @@ bool OrthancPeers::DoGet(Json::Value& target, const std::string& name, const std::string& uri, - const std::map& headers) const + const HttpHeaders& headers) const { MemoryBuffer buffer; @@ -2044,7 +2044,7 @@ const std::string& name, const std::string& uri, const std::string& body, - const std::map& headers) const + const HttpHeaders& headers) const { size_t index; return (LookupName(index, name) && @@ -2056,7 +2056,7 @@ size_t index, const std::string& uri, const std::string& body, - const std::map& headers) const + const HttpHeaders& headers) const { MemoryBuffer buffer; @@ -2076,7 +2076,7 @@ const std::string& name, const std::string& uri, const std::string& body, - const std::map& headers) const + const HttpHeaders& headers) const { MemoryBuffer buffer; @@ -2096,7 +2096,7 @@ size_t index, const std::string& uri, const std::string& body, - const std::map& headers) const + const HttpHeaders& headers) const { if (index >= index_.size()) { @@ -2133,7 +2133,7 @@ bool OrthancPeers::DoPut(size_t index, const std::string& uri, const std::string& body, - const std::map& headers) const + const HttpHeaders& headers) const { if (index >= index_.size()) { @@ -2169,7 +2169,7 @@ bool OrthancPeers::DoPut(const std::string& name, const std::string& uri, const std::string& body, - const std::map& headers) const + const HttpHeaders& headers) const { size_t index; return (LookupName(index, name) && @@ -2179,7 +2179,7 @@ bool OrthancPeers::DoDelete(size_t index, const std::string& uri, - const std::map& headers) const + const HttpHeaders& headers) const { if (index >= index_.size()) { @@ -2208,7 +2208,7 @@ bool OrthancPeers::DoDelete(const std::string& name, const std::string& uri, - const std::map& headers) const + const HttpHeaders& headers) const { size_t index; return (LookupName(index, name) && @@ -2923,12 +2923,12 @@ std::vector headersValues_; public: - HeadersWrapper(const HttpClient::HttpHeaders& headers) + HeadersWrapper(const HttpHeaders& headers) { headersKeys_.reserve(headers.size()); headersValues_.reserve(headers.size()); - for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) + for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) { headersKeys_.push_back(it->first.c_str()); headersValues_.push_back(it->second.c_str()); @@ -3076,11 +3076,11 @@ class MemoryAnswer : public HttpClient::IAnswer { private: - HttpClient::HttpHeaders headers_; - ChunkedBuffer body_; + HttpHeaders headers_; + ChunkedBuffer body_; public: - const HttpClient::HttpHeaders& GetHeaders() const + const HttpHeaders& GetHeaders() const { return headers_; } @@ -3168,6 +3168,35 @@ #endif + static void DecodeHttpHeaders(HttpHeaders& target, + const MemoryBuffer& source) + { + Json::Value v; + source.ToJson(v); + + if (v.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + Json::Value::Members members = v.getMemberNames(); + target.clear(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& h = v[members[i]]; + if (h.type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + else + { + target[members[i]] = h.asString(); + } + } + } + + void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus, HttpHeaders& answerHeaders, std::string& answerBody, @@ -3208,30 +3237,7 @@ ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); } - Json::Value v; - answerHeadersBuffer.ToJson(v); - - if (v.type() != Json::objectValue) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - Json::Value::Members members = v.getMemberNames(); - answerHeaders.clear(); - - for (size_t i = 0; i < members.size(); i++) - { - const Json::Value& h = v[members[i]]; - if (h.type() != Json::stringValue) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - else - { - answerHeaders[members[i]] = h.asString(); - } - } - + DecodeHttpHeaders(answerHeaders, answerHeadersBuffer); answerBodyBuffer.ToString(answerBody); } @@ -4061,7 +4067,7 @@ } #endif - void GetHttpHeaders(std::map& result, const OrthancPluginHttpRequest* request) + void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request) { result.clear(); @@ -4114,4 +4120,135 @@ SetPluginProperty(pluginIdentifier, _OrthancPluginProperty_OrthancExplorer, javascript); #endif } + + +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + RestApiClient::RestApiClient() : + method_(OrthancPluginHttpMethod_Get), + path_("/"), + afterPlugins_(false), + httpStatus_(0) + { + } +#endif + + +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + void RestApiClient::AddRequestHeader(const std::string& key, + const std::string& value) + { + if (requestHeaders_.find(key) == requestHeaders_.end()) + { + requestHeaders_[key] = value; + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + bool RestApiClient::Execute() + { + if (requestBody_.size() > 0xffffffffu) + { + ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + PluginHttpHeaders converted(requestHeaders_); + + MemoryBuffer body; + MemoryBuffer headers; + + OrthancPluginErrorCode code = OrthancPluginCallRestApi(GetGlobalContext(), *body, *headers, &httpStatus_, method_, path_.c_str(), + requestHeaders_.size(), converted.GetKeys(), converted.GetValues(), + requestBody_.c_str(), requestBody_.size(), afterPlugins_ ? 1 : 0); + + answerHeaders_.clear(); + answerBody_.clear(); + + if (code == OrthancPluginErrorCode_Success) + { + if (httpStatus_ == 0) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + DecodeHttpHeaders(answerHeaders_, headers); + body.ToString(answerBody_); + return true; + } + else + { + if (code == OrthancPluginErrorCode_UnknownResource || + code == OrthancPluginErrorCode_InexistentItem) + { + httpStatus_ = 404; + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + uint16_t RestApiClient::GetHttpStatus() const + { + if (httpStatus_ == 0) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); + } + else + { + return httpStatus_; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + bool RestApiClient::LookupAnswerHeader(std::string& value, + const std::string& key) const + { + if (httpStatus_ == 0) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); + } + else + { + HttpHeaders::const_iterator found = answerHeaders_.find(key); + if (found == answerHeaders_.end()) + { + return false; + } + else + { + value = found->second; + return true; + } + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + const std::string& RestApiClient::GetAnswerBody() const + { + if (httpStatus_ == 0) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); + } + else + { + return answerBody_; + } + } +#endif } diff -r bdcea1ec1683 -r 239965a40220 Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h --- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Mon Dec 16 22:40:06 2024 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Dec 17 10:19:40 2024 +0100 @@ -170,6 +170,8 @@ namespace OrthancPlugins { + typedef std::map HttpHeaders; + typedef void (*RestCallback) (OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request); @@ -257,7 +259,7 @@ bool applyPlugins); bool RestApiGet(const std::string& uri, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins); bool RestApiPost(const std::string& uri, @@ -277,13 +279,13 @@ #if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 bool RestApiPost(const std::string& uri, const Json::Value& body, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins); bool RestApiPost(const std::string& uri, const void* body, size_t bodySize, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins); #endif @@ -581,7 +583,7 @@ bool RestApiGet(Json::Value& result, const std::string& uri, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins); bool RestApiGetString(std::string& result, @@ -590,7 +592,7 @@ bool RestApiGetString(std::string& result, const std::string& uri, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins); bool RestApiPost(std::string& result, @@ -609,7 +611,7 @@ bool RestApiPost(Json::Value& result, const std::string& uri, const Json::Value& body, - const std::map& httpHeaders, + const HttpHeaders& httpHeaders, bool applyPlugins); #endif @@ -829,64 +831,64 @@ bool DoGet(MemoryBuffer& target, size_t index, const std::string& uri, - const std::map& headers) const; + const HttpHeaders& headers) const; bool DoGet(MemoryBuffer& target, const std::string& name, const std::string& uri, - const std::map& headers) const; + const HttpHeaders& headers) const; bool DoGet(Json::Value& target, size_t index, const std::string& uri, - const std::map& headers) const; + const HttpHeaders& headers) const; bool DoGet(Json::Value& target, const std::string& name, const std::string& uri, - const std::map& headers) const; + const HttpHeaders& headers) const; bool DoPost(MemoryBuffer& target, size_t index, const std::string& uri, const std::string& body, - const std::map& headers) const; + const HttpHeaders& headers) const; bool DoPost(MemoryBuffer& target, const std::string& name, const std::string& uri, const std::string& body, - const std::map& headers) const; + const HttpHeaders& headers) const; bool DoPost(Json::Value& target, size_t index, const std::string& uri, const std::string& body, - const std::map& headers) const; + const HttpHeaders& headers) const; bool DoPost(Json::Value& target, const std::string& name, const std::string& uri, const std::string& body, - const std::map& headers) const; + const HttpHeaders& headers) const; bool DoPut(size_t index, const std::string& uri, const std::string& body, - const std::map& headers) const; + const HttpHeaders& headers) const; bool DoPut(const std::string& name, const std::string& uri, const std::string& body, - const std::map& headers) const; + const HttpHeaders& headers) const; bool DoDelete(size_t index, const std::string& uri, - const std::map& headers) const; + const HttpHeaders& headers) const; bool DoDelete(const std::string& name, const std::string& uri, - const std::map& headers) const; + const HttpHeaders& headers) const; }; #endif @@ -996,8 +998,6 @@ class HttpClient : public boost::noncopyable { public: - typedef std::map HttpHeaders; - class IRequestBody : public boost::noncopyable { public: @@ -1397,7 +1397,7 @@ }; // helper method to convert Http headers from the plugin SDK to a std::map -void GetHttpHeaders(std::map& result, const OrthancPluginHttpRequest* request); +void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request); #if HAS_ORTHANC_PLUGIN_WEBDAV == 1 class IWebDavCollection : public boost::noncopyable @@ -1508,4 +1508,88 @@ void ExtendOrthancExplorer(const std::string& pluginIdentifier, const std::string& javascript); + + +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + class RestApiClient : public boost::noncopyable + { + private: + // Request + OrthancPluginHttpMethod method_; + std::string path_; + HttpHeaders requestHeaders_; + std::string requestBody_; + bool afterPlugins_; + + // Answer + uint16_t httpStatus_; + HttpHeaders answerHeaders_; + std::string answerBody_; + + public: + RestApiClient(); + + void SetMethod(OrthancPluginHttpMethod method) + { + method_ = method; + } + + OrthancPluginHttpMethod GetMethod() const + { + return method_; + } + + void SetPath(const std::string& path) + { + path_ = path; + } + + const std::string& GetPath() const + { + return path_; + } + + void AddRequestHeader(const std::string& key, + const std::string& value); + + const HttpHeaders& GetRequestHeaders() const + { + return requestHeaders_; + } + + void SetRequestBody(const std::string& body) + { + requestBody_ = body; + } + + void SwapRequestBody(std::string& body) + { + requestBody_.swap(body); + } + + void SetAfterPlugins(bool afterPlugins) + { + afterPlugins_ = afterPlugins; + } + + bool IsAfterPlugins() const + { + return afterPlugins_; + } + + const std::string& GetRequestBody() const + { + return requestBody_; + } + + bool Execute(); + + uint16_t GetHttpStatus() const; + + bool LookupAnswerHeader(std::string& value, + const std::string& key) const; + + const std::string& GetAnswerBody() const; + }; +#endif }