Mercurial > hg > orthanc-tcia
changeset 42:0fff1fb5a0a9 default tip
sync
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Tue, 09 Dec 2025 15:25:32 +0100 |
| parents | ac3c6d4c89c8 |
| children | |
| files | Plugin/HttpCache.cpp Plugin/HttpCache.h Plugin/Plugin.cpp Resources/Orthanc/CMake/AutoGeneratedCode.cmake Resources/Orthanc/CMake/Compiler.cmake Resources/Orthanc/CMake/DownloadOrthancFramework.cmake Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Resources/SyncOrthancFolder.py |
| diffstat | 9 files changed, 858 insertions(+), 106 deletions(-) [+] |
line wrap: on
line diff
--- a/Plugin/HttpCache.cpp Tue Dec 09 15:23:01 2025 +0100 +++ b/Plugin/HttpCache.cpp Tue Dec 09 15:25:32 2025 +0100 @@ -38,11 +38,11 @@ std::string mime_; public: - Item(const char* bodyData, + Item(const void* bodyData, size_t bodySize, const std::string& mime) : time_(GetNow()), - body_(bodyData, bodySize), + body_(reinterpret_cast<const char*>(bodyData), bodySize), mime_(mime) { } @@ -137,7 +137,7 @@ void HttpCache::Write(const std::string& key, - const char* bodyData, + const void* bodyData, size_t bodySize, const std::string& mime) {
--- a/Plugin/HttpCache.h Tue Dec 09 15:23:01 2025 +0100 +++ b/Plugin/HttpCache.h Tue Dec 09 15:25:32 2025 +0100 @@ -58,7 +58,7 @@ const std::string& key); void Write(const std::string& key, - const char* bodyData, + const void* bodyData, size_t bodySize, const std::string& mime);
--- a/Plugin/Plugin.cpp Tue Dec 09 15:23:01 2025 +0100 +++ b/Plugin/Plugin.cpp Tue Dec 09 15:25:32 2025 +0100 @@ -126,7 +126,8 @@ OrthancPlugins::HttpCache::GetInstance().Write(tcia, body.GetData(), body.GetSize(), mime); - OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, body.GetData(), body.GetSize(), mime.c_str()); + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, + reinterpret_cast<const char*>(body.GetData()), body.GetSize(), mime.c_str()); } else {
--- a/Resources/Orthanc/CMake/AutoGeneratedCode.cmake Tue Dec 09 15:23:01 2025 +0100 +++ b/Resources/Orthanc/CMake/AutoGeneratedCode.cmake Tue Dec 09 15:25:32 2025 +0100 @@ -20,7 +20,7 @@ # <http://www.gnu.org/licenses/>. -set(EMBED_RESOURCES_PYTHON "${CMAKE_CURRENT_LIST_DIR}/../EmbedResources.py" +set(EMBED_RESOURCES_PYTHON "${CMAKE_CURRENT_LIST_DIR}/EmbedResources.py" CACHE INTERNAL "Path to the EmbedResources.py script from Orthanc") set(AUTOGENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/AUTOGENERATED") set(AUTOGENERATED_SOURCES)
--- a/Resources/Orthanc/CMake/Compiler.cmake Tue Dec 09 15:23:01 2025 +0100 +++ b/Resources/Orthanc/CMake/Compiler.cmake Tue Dec 09 15:25:32 2025 +0100 @@ -21,6 +21,18 @@ # This file sets all the compiler-related flags +message(STATUS "CMAKE_CXX_COMPILER_ID is ${CMAKE_CXX_COMPILER_ID}") +message(STATUS "CMAKE_SYSTEM_NAME is ${CMAKE_SYSTEM_NAME}") + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + # Since Orthanc 1.12.7 that allows CMake 4.0, builds for macOS + # require the C++ standard to be explicitly set to C++11. Do *not* + # do this on GNU/Linux, as third-party system libraries could have + # been compiled with higher versions of the C++ standard. + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) +endif() # Save the current compiler flags to the cache every time cmake configures the project @@ -236,10 +248,15 @@ # 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") + # it seems that some recent MacOS compilers don't set these flags correctly which prevents zlib from building correctly + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64") + add_definitions( -D_XOPEN_SOURCE=1 ) - link_libraries(iconv) + + # Linking with iconv breaks the Universal builds on modern compilers + # link_libraries(iconv) elseif (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") message("Building using Emscripten (for WebAssembly or asm.js targets)")
--- a/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake Tue Dec 09 15:23:01 2025 +0100 +++ b/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake Tue Dec 09 15:25:32 2025 +0100 @@ -167,6 +167,16 @@ set(ORTHANC_FRAMEWORK_MD5 "1e61779ea4a7cd705720bdcfed8a6a73") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.5") set(ORTHANC_FRAMEWORK_MD5 "5bb69f092981fdcfc11dec0a0f9a7db3") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.6") + set(ORTHANC_FRAMEWORK_MD5 "0e971f32f4f3e4951e0f3b5de49a3da6") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.7") + set(ORTHANC_FRAMEWORK_MD5 "f27c27d7a7a694dab1fd7f0a99d9715a") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.8") + set(ORTHANC_FRAMEWORK_MD5 "eb1c719234338e8277b80d3453563e9f") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.9") + set(ORTHANC_FRAMEWORK_MD5 "66b5a2ee60706c4a502896083b9e1a01") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.10") + set(ORTHANC_FRAMEWORK_MD5 "d5e1ba442104c89a24013cb859a9d6bf") # Below this point are development snapshots that were used to # release some plugin, before an official release of the Orthanc @@ -199,6 +209,28 @@ # DICOMweb 1.15 (framework pre-1.12.2) set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) set(ORTHANC_FRAMEWORK_MD5 "ebe8bdf388319f1c9536b2b680451848") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "36cd91a53403") + # Advanced storage 0.2.0 (framework pre-1.12.10) + set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) + set(ORTHANC_FRAMEWORK_MD5 "911105f18a154b5e106985d8fcfcb620") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "9eb77f159b9d") + # Advanced storage 0.2.2 (framework pre-1.12.10) + set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) + set(ORTHANC_FRAMEWORK_MD5 "bd5ba2cec329010b912209345acbdeaf") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "0ebe8cfd9bf7") + # Worklists plugin 0.9.0 (framework pre-1.12.10) + set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) + set(ORTHANC_FRAMEWORK_MD5 "17a5ca9254e881ab89c93d052d4655cb") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "e0979326ac53") + # DICOMweb 1.22 + PG 10.0 (framework post-1.12.10) + # for BlockingSharedMessageQueue + fix SetCurrentThreadName from plugins + set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) + set(ORTHANC_FRAMEWORK_MD5 "e66a7e996d56063b3abb790bb8f12e8d") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "94c7f3784456") + # PixelsMasker 0.1.0 (framework post-1.12.10) + # for BlockingSharedMessageQueue.WaitEmpty() + set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) + set(ORTHANC_FRAMEWORK_MD5 "c037cd2ddbe1b65b431692855483161b") endif() endif() endif() @@ -499,11 +531,18 @@ include(CheckIncludeFile) include(CheckIncludeFileCXX) - include(FindPythonInterp) + + if(CMAKE_VERSION VERSION_GREATER "3.11") + find_package(Python REQUIRED COMPONENTS Interpreter) + set(PYTHON_EXECUTABLE ${Python_EXECUTABLE}) + else() + include(FindPythonInterp) + find_package(PythonInterp REQUIRED) + endif() + include(${CMAKE_CURRENT_LIST_DIR}/Compiler.cmake) include(${CMAKE_CURRENT_LIST_DIR}/DownloadPackage.cmake) include(${CMAKE_CURRENT_LIST_DIR}/AutoGeneratedCode.cmake) - set(EMBED_RESOURCES_PYTHON ${CMAKE_CURRENT_LIST_DIR}/EmbedResources.py) if (ORTHANC_FRAMEWORK_USE_SHARED) list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Dec 09 15:23:01 2025 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Dec 09 15:25:32 2025 +0100 @@ -23,9 +23,11 @@ #include "OrthancPluginCppWrapper.h" +#include <cassert> #include <boost/algorithm/string/predicate.hpp> #include <boost/move/unique_ptr.hpp> #include <boost/thread.hpp> +#include <boost/algorithm/string/join.hpp> #include <json/reader.h> @@ -219,26 +221,17 @@ } -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - MemoryBuffer::MemoryBuffer(const void* buffer, - size_t size) - { - uint32_t s = static_cast<uint32_t>(size); - if (static_cast<size_t>(s) != size) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); - } - else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) != - OrthancPluginErrorCode_Success) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); - } - else - { - memcpy(buffer_.data, buffer, size); - } - } -#endif + MemoryBuffer::~MemoryBuffer() + { + try + { + Clear(); + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) + { + // Don't throw exceptions in destructors + } + } void MemoryBuffer::Clear() @@ -252,6 +245,51 @@ } +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void MemoryBuffer::Assign(const void* buffer, + size_t size) + { + uint32_t s = static_cast<uint32_t>(size); + if (static_cast<size_t>(s) != size) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + + Clear(); + + if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) != + OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + else + { + if (size > 0) + { + memcpy(buffer_.data, buffer, size); + } + } + } +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void MemoryBuffer::Assign(const std::string& s) + { + Assign(s.empty() ? NULL : s.c_str(), s.size()); + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void MemoryBuffer::AssignJson(const Json::Value& value) + { + std::string s; + WriteFastJson(s, value); + Assign(s); + } +#endif + + void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other) { Clear(); @@ -326,6 +364,38 @@ } } + +#if (HAS_ORTHANC_PLUGIN_PEERS == 1) || (HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1) || (HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1) + 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(); + } + } + } +#endif + + // helper class to convert std::map of headers to the plugin SDK C structure class PluginHttpHeaders { @@ -599,6 +669,19 @@ } + OrthancString::~OrthancString() + { + try + { + Clear(); + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) + { + // Don't throw exceptions in destructors + } + } + + void OrthancString::Assign(char* str) { Clear(); @@ -672,7 +755,7 @@ { OrthancString str; str.Assign(OrthancPluginDicomBufferToJson - (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength)); + (GetGlobalContext(), reinterpret_cast<const char*>(GetData()), GetSize(), format, flags, maxStringLength)); str.ToJson(target); } @@ -1248,6 +1331,20 @@ } } + + OrthancImage::~OrthancImage() + { + try + { + Clear(); + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) + { + // Don't throw exceptions in destructors + } + } + + void OrthancImage::UncompressPngImage(const void* data, size_t size) { @@ -1565,7 +1662,7 @@ { if (!answer.IsEmpty()) { - result.assign(answer.GetData(), answer.GetSize()); + result.assign(reinterpret_cast<const char*>(answer.GetData()), answer.GetSize()); } return true; } @@ -2051,6 +2148,48 @@ DoPost(target, index, uri, body, headers)); } + bool OrthancPeers::DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const + { + MemoryBuffer buffer; + HttpHeaders answerHeaders; + + if (DoPost(buffer, answerHeaders, index, uri, body, headers, timeout)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + bool OrthancPeers::DoPost(Json::Value& target, + HttpHeaders& answerHeaders, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const + { + MemoryBuffer buffer; + + if (DoPost(buffer, answerHeaders, index, uri, body, headers, timeout)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + bool OrthancPeers::DoPost(Json::Value& target, size_t index, @@ -2098,6 +2237,29 @@ const std::string& body, const HttpHeaders& headers) const { + HttpHeaders answerHeaders; + return DoPost(target, answerHeaders, index, uri, body, headers, timeout_); + } + + bool OrthancPeers::DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const + { + HttpHeaders answerHeaders; + return DoPost(target, answerHeaders, index, uri, body, headers, timeout); + } + + bool OrthancPeers::DoPost(MemoryBuffer& target, + HttpHeaders& answerHeaders, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const + { if (index >= index_.size()) { ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); @@ -2110,17 +2272,20 @@ } OrthancPlugins::MemoryBuffer answer; + OrthancPlugins::MemoryBuffer answerHeadersBuffer; uint16_t status; PluginHttpHeaders pluginHeaders(headers); OrthancPluginErrorCode code = OrthancPluginCallPeerApi - (GetGlobalContext(), *answer, NULL, &status, peers_, + (GetGlobalContext(), *answer, *answerHeadersBuffer, &status, peers_, static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(), - pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_); + pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout); if (code == OrthancPluginErrorCode_Success) { target.Swap(answer); + DecodeHttpHeaders(answerHeaders, answerHeadersBuffer); + return (status == 200); } else @@ -2276,6 +2441,8 @@ { assert(job != NULL); OrthancJob& that = *reinterpret_cast<OrthancJob*>(job); + + boost::mutex::scoped_lock lock(that.contentMutex_); return CopyStringToMemoryBuffer(target, that.content_); } #else @@ -2285,7 +2452,10 @@ try { - return reinterpret_cast<OrthancJob*>(job)->content_.c_str(); + OrthancJob& that = *reinterpret_cast<OrthancJob*>(job); + boost::mutex::scoped_lock lock(that.contentMutex_); + + return that.content_.c_str(); } catch (...) { @@ -2413,6 +2583,8 @@ void OrthancJob::UpdateContent(const Json::Value& content) { + boost::mutex::scoped_lock lock(contentMutex_); + if (content.type() != Json::objectValue) { ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); @@ -2555,32 +2727,42 @@ return; } - else if (state == "Running") + else if (state == "Running" || + state == "Pending" || + state == "Paused" || + state == "Retry") { continue; } - else if (!status.isMember("ErrorCode") || - status["ErrorCode"].type() != Json::intValue) + else if (state == "Failure") { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError); + if (!status.isMember("ErrorCode") || + status["ErrorCode"].type() != Json::intValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError); + } + else + { + if (!status.isMember("ErrorDescription") || + status["ErrorDescription"].type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); + } + else + { + #if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()), + status["ErrorDescription"].asString()); + #else + ORTHANC_PLUGINS_LOG_ERROR("Exception while executing the job: " + status["ErrorDescription"].asString()); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); + #endif + } + } } else { - if (!status.isMember("ErrorDescription") || - status["ErrorDescription"].type() != Json::stringValue) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); - } - else - { -#if HAS_ORTHANC_EXCEPTION == 1 - throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()), - status["ErrorDescription"].asString()); -#else - ORTHANC_PLUGINS_LOG_ERROR("Exception while executing the job: " + status["ErrorDescription"].asString()); - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); -#endif - } + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError); } } } @@ -3167,36 +3349,6 @@ } #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, @@ -4067,6 +4219,16 @@ } #endif + void GetGetArguments(GetArguments& result, const OrthancPluginHttpRequest* request) + { + result.clear(); + + for (uint32_t i = 0; i < request->getCount; ++i) + { + result[request->getKeys[i]] = request->getValues[i]; + } + } + void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request) { result.clear(); @@ -4077,6 +4239,26 @@ } } + void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request) + { + output.clear(); + std::vector<std::string> arguments; + for (uint32_t i = 0; i < request->getCount; ++i) + { + if (request->getValues[i] && strlen(request->getValues[i]) > 0) + { + arguments.push_back(std::string(request->getKeys[i]) + "=" + std::string(request->getValues[i])); + } + else + { + arguments.push_back(std::string(request->getKeys[i])); + } + } + + output = boost::algorithm::join(arguments, "&"); + } + + #if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4) static void SetPluginProperty(const std::string& pluginIdentifier, _OrthancPluginProperty property, @@ -4130,6 +4312,29 @@ httpStatus_(0) { } + + RestApiClient::RestApiClient(const char* url, + const OrthancPluginHttpRequest* request) : + method_(request->method), + path_(url), + afterPlugins_(false), + httpStatus_(0) + { + OrthancPlugins::GetHttpHeaders(requestHeaders_, request); + + std::string getArguments; + OrthancPlugins::SerializeGetArguments(getArguments, request); + + if (!getArguments.empty()) + { + path_ += "?" + getArguments; + } + + if (request->bodySize > 0 && request->body != NULL) + { + requestBody_.assign(reinterpret_cast<const char*>(request->body), request->bodySize); + } + } #endif @@ -4150,6 +4355,15 @@ #if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + void RestApiClient::SetRequestHeader(const std::string& key, + const std::string& value) + { + requestHeaders_[key] = value; + } +#endif + + +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 bool RestApiClient::Execute() { if (requestBody_.size() > 0xffffffffu) @@ -4195,6 +4409,40 @@ } } } + + void RestApiClient::ExecuteAndForwardAnswer(OrthancPluginContext* context, OrthancPluginRestOutput* output) + { + if (Execute()) + { + ForwardAnswer(context, output); + } + } + + void RestApiClient::ForwardAnswer(OrthancPluginContext* context, OrthancPluginRestOutput* output) + { + if (httpStatus_ == 200) + { + const char* mimeType = NULL; + for (HttpHeaders::const_iterator h = answerHeaders_.begin(); h != answerHeaders_.end(); ++h) + { + if (h->first == "content-type") + { + mimeType = h->second.c_str(); + } + } + + AnswerString(answerBody_, mimeType, output); + } + else + { + AnswerHttpError(httpStatus_, output); + } + } + + bool RestApiClient::GetAnswerJson(Json::Value& output) const + { + return ReadJson(output, answerBody_); + } #endif @@ -4251,4 +4499,262 @@ } } #endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + KeyValueStore::Iterator::Iterator(OrthancPluginKeysValuesIterator *iterator) : + iterator_(iterator) + { + if (iterator_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + KeyValueStore::Iterator::~Iterator() + { + OrthancPluginFreeKeysValuesIterator(OrthancPlugins::GetGlobalContext(), iterator_); + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + bool KeyValueStore::Iterator::Next() + { + uint8_t done; + OrthancPluginErrorCode code = OrthancPluginKeysValuesIteratorNext(OrthancPlugins::GetGlobalContext(), &done, iterator_); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + else + { + return (done != 0); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + std::string KeyValueStore::Iterator::GetKey() const + { + const char* s = OrthancPluginKeysValuesIteratorGetKey(OrthancPlugins::GetGlobalContext(), iterator_); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + else + { + return s; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + void KeyValueStore::Iterator::GetValue(std::string& value) const + { + OrthancPlugins::MemoryBuffer valueBuffer; + OrthancPluginErrorCode code = OrthancPluginKeysValuesIteratorGetValue(OrthancPlugins::GetGlobalContext(), *valueBuffer, iterator_); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + else + { + valueBuffer.ToString(value); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + void KeyValueStore::Store(const std::string& key, + const void* value, + size_t valueSize) + { + if (static_cast<size_t>(static_cast<uint32_t>(valueSize)) != valueSize) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + + OrthancPluginErrorCode code = OrthancPluginStoreKeyValue(OrthancPlugins::GetGlobalContext(), storeId_.c_str(), + key.c_str(), value, static_cast<uint32_t>(valueSize)); + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + bool KeyValueStore::GetValue(std::string& value, + const std::string& key) + { + uint8_t found = false; + OrthancPlugins::MemoryBuffer valueBuffer; + OrthancPluginErrorCode code = OrthancPluginGetKeyValue(OrthancPlugins::GetGlobalContext(), &found, + *valueBuffer, storeId_.c_str(), key.c_str()); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + else if (found) + { + valueBuffer.ToString(value); + return true; + } + else + { + return false; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + void KeyValueStore::DeleteKey(const std::string& key) + { + OrthancPluginErrorCode code = OrthancPluginDeleteKeyValue(OrthancPlugins::GetGlobalContext(), + storeId_.c_str(), key.c_str()); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + KeyValueStore::Iterator* KeyValueStore::CreateIterator() + { + return new Iterator(OrthancPluginCreateKeysValuesIterator(OrthancPlugins::GetGlobalContext(), storeId_.c_str())); + } +#endif + + +#if HAS_ORTHANC_PLUGIN_QUEUES == 1 + void Queue::Enqueue(const void* value, + size_t valueSize) + { + if (static_cast<size_t>(static_cast<uint32_t>(valueSize)) != valueSize) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + + OrthancPluginErrorCode code = OrthancPluginEnqueueValue(OrthancPlugins::GetGlobalContext(), + queueId_.c_str(), value, static_cast<uint32_t>(valueSize)); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_QUEUES == 1 + bool Queue::DequeueInternal(std::string& value, + OrthancPluginQueueOrigin origin) + { + uint8_t found = false; + OrthancPlugins::MemoryBuffer valueBuffer; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + OrthancPluginErrorCode code = OrthancPluginDequeueValue(OrthancPlugins::GetGlobalContext(), &found, + *valueBuffer, queueId_.c_str(), origin); +#pragma GCC diagnostic pop + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + else if (found) + { + valueBuffer.ToString(value); + return true; + } + else + { + return false; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_QUEUES == 1 + uint64_t Queue::GetSize() + { + uint64_t size = 0; + OrthancPluginErrorCode code = OrthancPluginGetQueueSize(OrthancPlugins::GetGlobalContext(), queueId_.c_str(), &size); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + else + { + return size; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1 + bool Queue::ReserveInternal(std::string& value, uint64_t& valueId, OrthancPluginQueueOrigin origin, uint32_t releaseTimeout) + { + uint8_t found = false; + OrthancPlugins::MemoryBuffer valueBuffer; + + OrthancPluginErrorCode code = OrthancPluginReserveQueueValue(OrthancPlugins::GetGlobalContext(), &found, + *valueBuffer, &valueId, queueId_.c_str(), origin, releaseTimeout); + + if (code != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + else if (found) + { + valueBuffer.ToString(value); + return true; + } + else + { + return false; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1 + bool Queue::ReserveBack(std::string& value, uint64_t& valueId, uint32_t releaseTimeout) + { + return ReserveInternal(value, valueId, OrthancPluginQueueOrigin_Back, releaseTimeout); + } +#endif + + +#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1 + bool Queue::ReserveFront(std::string& value, uint64_t& valueId, uint32_t releaseTimeout) + { + return ReserveInternal(value, valueId, OrthancPluginQueueOrigin_Front, releaseTimeout); + } +#endif + + +#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1 + void Queue::Acknowledge(uint64_t valueId) + { + OrthancPluginAcknowledgeQueueValue(OrthancPlugins::GetGlobalContext(), queueId_.c_str(), valueId); + } +#endif }
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Dec 09 15:23:01 2025 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Dec 09 15:25:32 2025 +0100 @@ -28,6 +28,7 @@ #include <orthanc/OrthancCPlugin.h> #include <boost/noncopyable.hpp> #include <boost/lexical_cast.hpp> +#include <boost/thread/mutex.hpp> #include <boost/date_time/posix_time/posix_time.hpp> #include <json/value.h> #include <vector> @@ -134,6 +135,20 @@ # define HAS_ORTHANC_PLUGIN_LOG_MESSAGE 0 #endif +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 8) +# define HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES 1 +# define HAS_ORTHANC_PLUGIN_QUEUES 1 +#else +# define HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES 0 +# define HAS_ORTHANC_PLUGIN_QUEUES 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 10) +# define HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE 1 +#else +# define HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE 0 +#endif + // Macro to tag a function as having been deprecated #if (__cplusplus >= 201402L) // C++14 @@ -172,6 +187,8 @@ { typedef std::map<std::string, std::string> HttpHeaders; + typedef std::map<std::string, std::string> GetArguments; + typedef void (*RestCallback) (OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request); @@ -203,23 +220,27 @@ public: MemoryBuffer(); -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - // This constructor makes a copy of the given buffer in the memory - // handled by the Orthanc core - MemoryBuffer(const void* buffer, - size_t size); -#endif - - ~MemoryBuffer() - { - Clear(); - } + ~MemoryBuffer(); OrthancPluginMemoryBuffer* operator*() { return &buffer_; } +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + // Copy of the given buffer into the memory managed by the Orthanc core + void Assign(const void* buffer, + size_t size); +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void Assign(const std::string& s); +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void AssignJson(const Json::Value& value); +#endif + // This transfers ownership from "other" to "this" void Assign(OrthancPluginMemoryBuffer& other); @@ -227,11 +248,11 @@ OrthancPluginMemoryBuffer Release(); - const char* GetData() const + const void* GetData() const { if (buffer_.size > 0) { - return reinterpret_cast<const char*>(buffer_.data); + return buffer_.data; } else { @@ -354,10 +375,7 @@ { } - ~OrthancString() - { - Clear(); - } + ~OrthancString(); // This transfers ownership, warning: The string must have been // allocated by the Orthanc core @@ -474,10 +492,7 @@ uint32_t pitch, void* buffer); - ~OrthancImage() - { - Clear(); - } + ~OrthancImage(); void UncompressPngImage(const void* data, size_t size); @@ -855,6 +870,21 @@ const HttpHeaders& headers) const; bool DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const; + + bool DoPost(MemoryBuffer& target, + HttpHeaders& answerHeaders, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const; + + bool DoPost(MemoryBuffer& target, const std::string& name, const std::string& uri, const std::string& body, @@ -867,6 +897,21 @@ const HttpHeaders& headers) const; bool DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const; + + bool DoPost(Json::Value& target, + HttpHeaders& answerHeaders, + size_t index, + const std::string& uri, + const std::string& body, + const HttpHeaders& headers, + unsigned int timeout) const; + + bool DoPost(Json::Value& target, const std::string& name, const std::string& uri, const std::string& body, @@ -899,6 +944,7 @@ { private: std::string jobType_; + boost::mutex contentMutex_; std::string content_; bool hasSerialized_; std::string serialized_; @@ -973,7 +1019,7 @@ #if HAS_ORTHANC_PLUGIN_METRICS == 1 - inline void SetMetricsValue(char* name, + inline void SetMetricsValue(const char* name, float value) { OrthancPluginSetMetricsValue(GetGlobalContext(), name, @@ -1399,6 +1445,12 @@ // helper method to convert Http headers from the plugin SDK to a std::map void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request); +// helper method to re-serialize the get arguments from the SDK into a string +void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request); + +// helper method to convert Get arguments from the plugin SDK to a std::map +void GetGetArguments(GetArguments& result, const OrthancPluginHttpRequest* request); + #if HAS_ORTHANC_PLUGIN_WEBDAV == 1 class IWebDavCollection : public boost::noncopyable { @@ -1528,6 +1580,10 @@ public: RestApiClient(); + + // used to forward a call from the plugin to the core + RestApiClient(const char* url, + const OrthancPluginHttpRequest* request); void SetMethod(OrthancPluginHttpMethod method) { @@ -1552,6 +1608,9 @@ void AddRequestHeader(const std::string& key, const std::string& value); + void SetRequestHeader(const std::string& key, + const std::string& value); + const HttpHeaders& GetRequestHeaders() const { return requestHeaders_; @@ -1582,14 +1641,144 @@ return requestBody_; } + // Execute only bool Execute(); + // Forward response as is + void ForwardAnswer(OrthancPluginContext* context, OrthancPluginRestOutput* output); + + // Execute and forward the response as is + void ExecuteAndForwardAnswer(OrthancPluginContext* context, OrthancPluginRestOutput* output); + uint16_t GetHttpStatus() const; bool LookupAnswerHeader(std::string& value, const std::string& key) const; const std::string& GetAnswerBody() const; + + bool GetAnswerJson(Json::Value& output) const; + }; +#endif + + +#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1 + class KeyValueStore : public boost::noncopyable + { + public: + class Iterator : public boost::noncopyable + { + private: + OrthancPluginKeysValuesIterator *iterator_; + + public: + explicit Iterator(OrthancPluginKeysValuesIterator *iterator); + + ~Iterator(); + + bool Next(); + + std::string GetKey() const; + + void GetValue(std::string& target) const; + }; + + private: + std::string storeId_; + + public: + explicit KeyValueStore(const std::string& storeId) : + storeId_(storeId) + { + } + + const std::string& GetStoreId() const + { + return storeId_; + } + + void Store(const std::string& key, + const void* value, + size_t valueSize); + + void Store(const std::string& key, + const std::string& value) + { + Store(key, value.empty() ? NULL : value.c_str(), value.size()); + } + + bool GetValue(std::string& value, + const std::string& key); + + void DeleteKey(const std::string& key); + + Iterator* CreateIterator(); + }; +#endif + + +#if HAS_ORTHANC_PLUGIN_QUEUES == 1 + class Queue : public boost::noncopyable + { + private: + std::string queueId_; + + bool DequeueInternal(std::string& value, OrthancPluginQueueOrigin origin); + +#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1 + bool ReserveInternal(std::string& value, uint64_t& valueId, OrthancPluginQueueOrigin origin, uint32_t releaseTimeout); +#endif + + public: + explicit Queue(const std::string& queueId) : + queueId_(queueId) + { + } + + const std::string& GetQueueId() const + { + return queueId_; + } + + void Enqueue(const void* value, + size_t valueSize); + + void Enqueue(const std::string& value) + { + Enqueue(value.empty() ? NULL : value.c_str(), value.size()); + } + +#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1 + // Use ReserveBack() instead + ORTHANC_PLUGIN_DEPRECATED +#endif + bool DequeueBack(std::string& value) + { + return DequeueInternal(value, OrthancPluginQueueOrigin_Back); + } + +#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1 + // Use ReserveFront() instead + ORTHANC_PLUGIN_DEPRECATED +#endif + bool DequeueFront(std::string& value) + { + return DequeueInternal(value, OrthancPluginQueueOrigin_Front); + } + + uint64_t GetSize(); + +#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1 + bool ReserveBack(std::string& value, uint64_t& valueId, uint32_t releaseTimeout); +#endif + +#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1 + bool ReserveFront(std::string& value, uint64_t& valueId, uint32_t releaseTimeout); +#endif + +#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1 + void Acknowledge(uint64_t valueId); +#endif }; #endif }
--- a/Resources/SyncOrthancFolder.py Tue Dec 09 15:23:01 2025 +0100 +++ b/Resources/SyncOrthancFolder.py Tue Dec 09 15:25:32 2025 +0100 @@ -19,7 +19,7 @@ ('OrthancFramework/Resources/CMake/Compiler.cmake', 'CMake'), ('OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake', 'CMake'), ('OrthancFramework/Resources/CMake/DownloadPackage.cmake', 'CMake'), - ('OrthancFramework/Resources/EmbedResources.py', 'CMake'), + ('OrthancFramework/Resources/CMake/EmbedResources.py', 'CMake'), ('OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake', 'Toolchains'), ('OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain32.cmake', 'Toolchains'), ('OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain64.cmake', 'Toolchains'),
