# HG changeset patch # User Alain Mazy # Date 1727183061 -7200 # Node ID f18e46d7dbf8598c6cf9758973760b0d11c516fe # Parent 82f73188b58d291b066ebb0266f6c3be2de098f6# Parent 77c8544bbd7df2374ea0bf28eff374ade0351d28 merged find-refactoring -> attach-custom-data diff -r 82f73188b58d -r f18e46d7dbf8 .hgignore --- a/.hgignore Wed Feb 01 16:24:37 2023 +0100 +++ b/.hgignore Tue Sep 24 15:04:21 2024 +0200 @@ -1,3 +1,4 @@ +syntax: glob build* PostgreSQL/CMakeLists.txt.user PostgreSQL/ThirdPartyDownloads/ @@ -5,4 +6,4 @@ MySQL/ThirdPartyDownloads/ SQLite/ThirdPartyDownloads/ .vscode/ - +*~ diff -r 82f73188b58d -r f18e46d7dbf8 AUTHORS --- a/AUTHORS Wed Feb 01 16:24:37 2023 +0100 +++ b/AUTHORS Tue Sep 24 15:04:21 2024 +0200 @@ -14,7 +14,29 @@ 4000 Liege Belgium -* Osimis S.A. - Rue des Chasseurs Ardennais 3 - 4031 Liege +* Osimis S.A. + Quai Banning 6 + 4000 Liege + Belgium + +* Orthanc Team SRL + Rue Joseph Marchal 14 + 4910 Theux Belgium + https://orthanc.team/ + +* ICTEAM, UCLouvain + Place de l'Universite 1 + 1348 Ottignies-Louvain-la-Neuve + Belgium + https://uclouvain.be/icteam + + +Contributors +------------ + +* The Orthanc community + https://groups.google.com/forum/#!forum/orthanc-users + +* Cédric Simon (https://discourse.orthanc-server.org/u/csimon3/summary) + Improved MySQL delete procedure \ No newline at end of file diff -r 82f73188b58d -r f18e46d7dbf8 CITATION.cff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CITATION.cff Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,14 @@ +cff-version: "1.1.0" +message: "If you use this software, please cite it using these metadata." +title: Orthanc +abstract: "Orthanc is a lightweight open-source DICOM server for medical imaging supporting representational state transfer (REST)." +authors: + - + affiliation: UCLouvain + family-names: Jodogne + given-names: "Sébastien" +doi: "10.1007/s10278-018-0082-y" +license: "GPL-3.0-or-later" +repository-code: "https://orthanc.uclouvain.be/hg/orthanc/" +version: 1.12.3 +date-released: 2024-01-31 diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/BinaryStringValue.cpp --- a/Framework/Common/BinaryStringValue.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/BinaryStringValue.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/BinaryStringValue.h --- a/Framework/Common/BinaryStringValue.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/BinaryStringValue.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/DatabaseManager.cpp --- a/Framework/Common/DatabaseManager.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/DatabaseManager.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -77,9 +79,9 @@ } - IPrecompiledStatement* DatabaseManager::LookupCachedStatement(const StatementLocation& location) const + IPrecompiledStatement* DatabaseManager::LookupCachedStatement(const StatementId& statementId) const { - CachedStatements::const_iterator found = cachedStatements_.find(location); + CachedStatements::const_iterator found = cachedStatements_.find(statementId); if (found == cachedStatements_.end()) { @@ -93,10 +95,10 @@ } - IPrecompiledStatement& DatabaseManager::CacheStatement(const StatementLocation& location, + IPrecompiledStatement& DatabaseManager::CacheStatement(const StatementId& statementId, const Query& query) { - LOG(TRACE) << "Caching statement from " << location.GetFile() << ":" << location.GetLine(); + LOG(TRACE) << "Caching statement from " << statementId.GetFile() << ":" << statementId.GetLine() << "" << statementId.GetDynamicStatement(); std::unique_ptr statement(GetDatabase().Compile(query)); @@ -106,8 +108,8 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - assert(cachedStatements_.find(location) == cachedStatements_.end()); - cachedStatements_[location] = statement.release(); + assert(cachedStatements_.find(statementId) == cachedStatements_.end()); + cachedStatements_[statementId] = statement.release(); return *tmp; } @@ -491,8 +493,8 @@ return dynamic_cast(value).GetValue(); default: - //LOG(ERROR) << value.Format(); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + // LOG(ERROR) << value.GetType(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "The returned field is not of the correct type (Integer64)"); } } } @@ -510,8 +512,7 @@ if (value != static_cast(static_cast(value))) { - LOG(ERROR) << "Integer overflow"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Integer overflow"); } else { @@ -520,7 +521,18 @@ } } - + bool DatabaseManager::StatementBase::IsNull(size_t field) const + { + if (IsDone()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); + } + else + { + return GetResultField(field).GetType() == ValueType_Null; + } + } + std::string DatabaseManager::StatementBase::ReadString(size_t field) const { const IValue& value = GetResultField(field); @@ -534,19 +546,29 @@ return dynamic_cast(value).GetContent(); default: - //LOG(ERROR) << value.Format(); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "The returned field is not of the correct type (String)"); } } + std::string DatabaseManager::StatementBase::ReadStringOrNull(size_t field) const + { + if (IsNull(field)) + { + return std::string(); + } + else + { + return ReadString(field); + } + } - DatabaseManager::CachedStatement::CachedStatement(const StatementLocation& location, + DatabaseManager::CachedStatement::CachedStatement(const StatementId& statementId, DatabaseManager& manager, const std::string& sql) : StatementBase(manager), - location_(location) + statementId_(statementId) { - statement_ = GetManager().LookupCachedStatement(location_); + statement_ = GetManager().LookupCachedStatement(statementId_); if (statement_ == NULL) { @@ -555,12 +577,11 @@ else { LOG(TRACE) << "Reusing cached statement from " - << location_.GetFile() << ":" << location_.GetLine(); + << statementId_.GetFile() << ":" << statementId_.GetLine() << " " << statementId_.GetDynamicStatement(); } } - - void DatabaseManager::CachedStatement::Execute(const Dictionary& parameters) + void DatabaseManager::CachedStatement::ExecuteInternal(const Dictionary& parameters, bool withResults) { try { @@ -570,7 +591,7 @@ { // Register the newly-created statement assert(statement_ == NULL); - statement_ = &GetManager().CacheStatement(location_, *query); + statement_ = &GetManager().CacheStatement(statementId_, *query); } assert(statement_ != NULL); @@ -586,7 +607,14 @@ #endif */ - SetResult(GetTransaction().Execute(*statement_, parameters)); + if (withResults) + { + SetResult(GetTransaction().Execute(*statement_, parameters)); + } + else + { + GetTransaction().ExecuteWithoutResult(*statement_, parameters); + } } catch (Orthanc::OrthancException& e) { @@ -594,7 +622,18 @@ throw; } } + + + void DatabaseManager::CachedStatement::Execute(const Dictionary& parameters) + { + ExecuteInternal(parameters, true); + } + void DatabaseManager::CachedStatement::ExecuteWithoutResult(const Dictionary& parameters) + { + ExecuteInternal(parameters, false); + } + DatabaseManager::StandaloneStatement::StandaloneStatement(DatabaseManager& manager, const std::string& sql) : @@ -614,6 +653,16 @@ void DatabaseManager::StandaloneStatement::Execute(const Dictionary& parameters) { + ExecuteInternal(parameters, true); + } + + void DatabaseManager::StandaloneStatement::ExecuteWithoutResult(const Dictionary& parameters) + { + ExecuteInternal(parameters, false); + } + + void DatabaseManager::StandaloneStatement::ExecuteInternal(const Dictionary& parameters, bool withResults) + { try { std::unique_ptr query(ReleaseQuery()); @@ -625,7 +674,12 @@ statement_.reset(GetManager().GetDatabase().Compile(*query)); assert(statement_.get() != NULL); - SetResult(GetTransaction().Execute(*statement_, parameters)); + std::unique_ptr result(GetTransaction().Execute(*statement_, parameters)); + + if (withResults) + { + SetResult(result.release()); + } } catch (Orthanc::OrthancException& e) { diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/DatabaseManager.h --- a/Framework/Common/DatabaseManager.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/DatabaseManager.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -22,7 +24,7 @@ #pragma once #include "IDatabaseFactory.h" -#include "StatementLocation.h" +#include "StatementId.h" #include // For std::unique_ptr<> #include @@ -47,7 +49,7 @@ class DatabaseManager : public boost::noncopyable { private: - typedef std::map CachedStatements; + typedef std::map CachedStatements; std::unique_ptr factory_; std::unique_ptr database_; @@ -57,9 +59,9 @@ void CloseIfUnavailable(Orthanc::ErrorCode e); - IPrecompiledStatement* LookupCachedStatement(const StatementLocation& location) const; + IPrecompiledStatement* LookupCachedStatement(const StatementId& statementId) const; - IPrecompiledStatement& CacheStatement(const StatementLocation& location, + IPrecompiledStatement& CacheStatement(const StatementId& statementId, const Query& query); ITransaction& GetTransaction(); @@ -186,6 +188,10 @@ std::string ReadString(size_t field) const; + std::string ReadStringOrNull(size_t field) const; + + bool IsNull(size_t field) const; + void PrintResult(std::ostream& stream) { IResult::Print(stream, GetResult()); @@ -203,11 +209,11 @@ class CachedStatement : public StatementBase { private: - StatementLocation location_; + StatementId statementId_; IPrecompiledStatement* statement_; public: - CachedStatement(const StatementLocation& location, + CachedStatement(const StatementId& statementId, DatabaseManager& manager, const std::string& sql); @@ -218,6 +224,17 @@ } void Execute(const Dictionary& parameters); + + void ExecuteWithoutResult() + { + Dictionary parameters; + ExecuteWithoutResult(parameters); + } + + void ExecuteWithoutResult(const Dictionary& parameters); + + private: + void ExecuteInternal(const Dictionary& parameters, bool withResults); }; @@ -239,6 +256,17 @@ } void Execute(const Dictionary& parameters); + + void ExecuteWithoutResult() + { + Dictionary parameters; + ExecuteWithoutResult(parameters); + } + + void ExecuteWithoutResult(const Dictionary& parameters); + + private: + void ExecuteInternal(const Dictionary& parameters, bool withResults); }; }; } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/DatabasesEnumerations.cpp --- a/Framework/Common/DatabasesEnumerations.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/DatabasesEnumerations.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/DatabasesEnumerations.h --- a/Framework/Common/DatabasesEnumerations.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/DatabasesEnumerations.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -29,6 +31,7 @@ ValueType_BinaryString, ValueType_InputFile, ValueType_Integer64, + ValueType_Integer32, ValueType_Null, ValueType_ResultFile, ValueType_Utf8String diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/Dictionary.cpp --- a/Framework/Common/Dictionary.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/Dictionary.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -23,6 +25,7 @@ #include "BinaryStringValue.h" #include "InputFileValue.h" +#include "Integer32Value.h" #include "Integer64Value.h" #include "NullValue.h" #include "Utf8StringValue.h" @@ -124,7 +127,13 @@ SetValue(key, new Integer64Value(value)); } - + + void Dictionary::SetInteger32Value(const std::string& key, + int32_t value) + { + SetValue(key, new Integer32Value(value)); + } + void Dictionary::SetNullValue(const std::string& key) { SetValue(key, new NullValue); diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/Dictionary.h --- a/Framework/Common/Dictionary.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/Dictionary.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -66,6 +68,9 @@ void SetIntegerValue(const std::string& key, int64_t value); + void SetInteger32Value(const std::string& key, + int32_t value); + void SetNullValue(const std::string& key); const IValue& GetValue(const std::string& key) const; diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/GenericFormatter.cpp --- a/Framework/Common/GenericFormatter.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/GenericFormatter.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/GenericFormatter.h --- a/Framework/Common/GenericFormatter.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/GenericFormatter.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/IDatabase.h --- a/Framework/Common/IDatabase.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/IDatabase.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/IDatabaseFactory.h --- a/Framework/Common/IDatabaseFactory.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/IDatabaseFactory.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/IPrecompiledStatement.h --- a/Framework/Common/IPrecompiledStatement.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/IPrecompiledStatement.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/IResult.cpp --- a/Framework/Common/IResult.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/IResult.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/IResult.h --- a/Framework/Common/IResult.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/IResult.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/ITransaction.h --- a/Framework/Common/ITransaction.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/ITransaction.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/IValue.h --- a/Framework/Common/IValue.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/IValue.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/ImplicitTransaction.cpp --- a/Framework/Common/ImplicitTransaction.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/ImplicitTransaction.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/ImplicitTransaction.h --- a/Framework/Common/ImplicitTransaction.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/ImplicitTransaction.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/InputFileValue.cpp --- a/Framework/Common/InputFileValue.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/InputFileValue.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/InputFileValue.h --- a/Framework/Common/InputFileValue.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/InputFileValue.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/Integer32Value.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Common/Integer32Value.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,55 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "Integer32Value.h" + +#include "BinaryStringValue.h" +#include "NullValue.h" +#include "Utf8StringValue.h" + +#include + +#include + +namespace OrthancDatabases +{ + IValue* Integer32Value::Convert(ValueType target) const + { + std::string s = boost::lexical_cast(value_); + + switch (target) + { + case ValueType_Null: + return new NullValue; + + case ValueType_BinaryString: + return new BinaryStringValue(s); + + case ValueType_Utf8String: + return new Utf8StringValue(s); + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/Integer32Value.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Common/Integer32Value.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "IValue.h" + +#include + +#include + +namespace OrthancDatabases +{ + class Integer32Value : public IValue + { + private: + int32_t value_; + + public: + explicit Integer32Value(int32_t value) : + value_(value) + { + } + + int32_t GetValue() const + { + return value_; + } + + virtual ValueType GetType() const ORTHANC_OVERRIDE + { + return ValueType_Integer32; + } + + virtual IValue* Convert(ValueType target) const ORTHANC_OVERRIDE; + }; +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/Integer64Value.cpp --- a/Framework/Common/Integer64Value.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/Integer64Value.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/Integer64Value.h --- a/Framework/Common/Integer64Value.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/Integer64Value.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/NullValue.cpp --- a/Framework/Common/NullValue.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/NullValue.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/NullValue.h --- a/Framework/Common/NullValue.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/NullValue.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/Query.cpp --- a/Framework/Common/Query.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/Query.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/Query.h --- a/Framework/Common/Query.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/Query.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/ResultBase.cpp --- a/Framework/Common/ResultBase.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/ResultBase.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -55,11 +57,6 @@ for (size_t i = 0; i < fields_.size(); i++) { - if (fields_[i] == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - ValueType sourceType = fields_[i]->GetType(); ValueType targetType = expectedType_[i]; @@ -94,11 +91,11 @@ for (size_t i = 0; i < fields_.size(); i++) { fields_[i] = FetchField(i); + } - if (fields_[i] == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } + if (fields_.size() == 1 && fields_[0] == NULL) // this is a "void" result + { + return; } ConvertFields(); diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/ResultBase.h --- a/Framework/Common/ResultBase.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/ResultBase.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/ResultFileValue.cpp --- a/Framework/Common/ResultFileValue.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/ResultFileValue.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/ResultFileValue.h --- a/Framework/Common/ResultFileValue.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/ResultFileValue.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/RetryDatabaseFactory.cpp --- a/Framework/Common/RetryDatabaseFactory.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/RetryDatabaseFactory.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/RetryDatabaseFactory.h --- a/Framework/Common/RetryDatabaseFactory.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/RetryDatabaseFactory.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/StatementId.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Common/StatementId.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,44 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "StatementId.h" + +#include + +namespace OrthancDatabases +{ + bool StatementId::operator< (const StatementId& other) const + { + if (line_ != other.line_) + { + return line_ < other.line_; + } + + if (strcmp(file_, other.file_) < 0) + { + return true; + } + + return statement_ < other.statement_; + } +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/StatementId.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Common/StatementId.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,77 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include + +#define STATEMENT_FROM_HERE ::OrthancDatabases::StatementId(__FILE__, __LINE__) +#define STATEMENT_FROM_HERE_DYNAMIC(sql) ::OrthancDatabases::StatementId(__FILE__, __LINE__, sql) + + +namespace OrthancDatabases +{ + class StatementId + { + private: + const char* file_; + int line_; + std::string statement_; + + StatementId(); // Forbidden + + public: + StatementId(const char* file, + int line) : + file_(file), + line_(line) + { + } + + StatementId(const char* file, + int line, + const std::string& statement) : + file_(file), + line_(line), + statement_(statement) + { + } + + const char* GetFile() const + { + return file_; + } + + int GetLine() const + { + return line_; + } + + const std::string& GetDynamicStatement() const + { + return statement_; + } + + bool operator< (const StatementId& other) const; + }; +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/StatementLocation.cpp --- a/Framework/Common/StatementLocation.cpp Wed Feb 01 16:24:37 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero 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 - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#include "StatementLocation.h" - -#include - -namespace OrthancDatabases -{ - bool StatementLocation::operator< (const StatementLocation& other) const - { - if (line_ != other.line_) - { - return line_ < other.line_; - } - else - { - return strcmp(file_, other.file_) < 0; - } - } -} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/StatementLocation.h --- a/Framework/Common/StatementLocation.h Wed Feb 01 16:24:37 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero 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 - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#define STATEMENT_FROM_HERE ::OrthancDatabases::StatementLocation(__FILE__, __LINE__) - - -namespace OrthancDatabases -{ - class StatementLocation - { - private: - const char* file_; - int line_; - - StatementLocation(); // Forbidden - - public: - StatementLocation(const char* file, - int line) : - file_(file), - line_(line) - { - } - - const char* GetFile() const - { - return file_; - } - - int GetLine() const - { - return line_; - } - - bool operator< (const StatementLocation& other) const; - }; -} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/Utf8StringValue.cpp --- a/Framework/Common/Utf8StringValue.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/Utf8StringValue.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Common/Utf8StringValue.h --- a/Framework/Common/Utf8StringValue.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Common/Utf8StringValue.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/MySQL/MySQLDatabase.cpp --- a/Framework/MySQL/MySQLDatabase.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/MySQL/MySQLDatabase.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/MySQL/MySQLDatabase.h --- a/Framework/MySQL/MySQLDatabase.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/MySQL/MySQLDatabase.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/MySQL/MySQLParameters.cpp --- a/Framework/MySQL/MySQLParameters.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/MySQL/MySQLParameters.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/MySQL/MySQLParameters.h --- a/Framework/MySQL/MySQLParameters.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/MySQL/MySQLParameters.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/MySQL/MySQLResult.cpp --- a/Framework/MySQL/MySQLResult.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/MySQL/MySQLResult.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/MySQL/MySQLResult.h --- a/Framework/MySQL/MySQLResult.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/MySQL/MySQLResult.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/MySQL/MySQLStatement.cpp --- a/Framework/MySQL/MySQLStatement.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/MySQL/MySQLStatement.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/MySQL/MySQLStatement.h --- a/Framework/MySQL/MySQLStatement.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/MySQL/MySQLStatement.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/MySQL/MySQLTransaction.cpp --- a/Framework/MySQL/MySQLTransaction.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/MySQL/MySQLTransaction.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/MySQL/MySQLTransaction.h --- a/Framework/MySQL/MySQLTransaction.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/MySQL/MySQLTransaction.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Odbc/OdbcDatabase.cpp --- a/Framework/Odbc/OdbcDatabase.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Odbc/OdbcDatabase.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -617,7 +619,7 @@ * "odbc-index" cannot start because it doesn't have * exclusive access. **/ - db->ExecuteMultiLines("ALTER DATABASE CURRENT COLLATE LATIN1_GENERAL_100_CI_AS_SC_UTF8"); + db->ExecuteMultiLines("IF 'Latin1_General_100_CI_AS_SC_UTF8' != (SELECT CONVERT (varchar(256), DATABASEPROPERTYEX(DB_NAME(),'collation'))) ALTER DATABASE CURRENT COLLATE LATIN1_GENERAL_100_CI_AS_SC_UTF8"); } return db.release(); diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Odbc/OdbcDatabase.h --- a/Framework/Odbc/OdbcDatabase.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Odbc/OdbcDatabase.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Odbc/OdbcEnvironment.cpp --- a/Framework/Odbc/OdbcEnvironment.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Odbc/OdbcEnvironment.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Odbc/OdbcEnvironment.h --- a/Framework/Odbc/OdbcEnvironment.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Odbc/OdbcEnvironment.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Odbc/OdbcPreparedStatement.cpp --- a/Framework/Odbc/OdbcPreparedStatement.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Odbc/OdbcPreparedStatement.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Odbc/OdbcPreparedStatement.h --- a/Framework/Odbc/OdbcPreparedStatement.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Odbc/OdbcPreparedStatement.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Odbc/OdbcResult.cpp --- a/Framework/Odbc/OdbcResult.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Odbc/OdbcResult.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Odbc/OdbcResult.h --- a/Framework/Odbc/OdbcResult.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Odbc/OdbcResult.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Odbc/OdbcStatement.cpp --- a/Framework/Odbc/OdbcStatement.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Odbc/OdbcStatement.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -81,6 +83,11 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Collision between multiple writers"); #endif } + else if (state == "08S01") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseUnavailable); + } + } else { diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Odbc/OdbcStatement.h --- a/Framework/Odbc/OdbcStatement.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Odbc/OdbcStatement.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/DatabaseBackendAdapterV2.cpp --- a/Framework/Plugins/DatabaseBackendAdapterV2.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/DatabaseBackendAdapterV2.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -89,7 +91,8 @@ if (manager_.get() == NULL) { - manager_.reset(IndexBackend::CreateSingleDatabaseManager(*backend_)); + std::list identifierTags; + manager_.reset(IndexBackend::CreateSingleDatabaseManager(*backend_, false, identifierTags)); } else { @@ -947,7 +950,9 @@ try { DatabaseBackendAdapterV2::Adapter::DatabaseAccessor accessor(*adapter); - adapter->GetBackend().LogExportedResource(accessor.GetManager(), *exported); + adapter->GetBackend().LogExportedResource(accessor.GetManager(), exported->resourceType, exported->publicId, + exported->modality, exported->date, exported->patientId, + exported->studyInstanceUid, exported->seriesInstanceUid, exported->sopInstanceUid); return OrthancPluginErrorCode_Success; } ORTHANC_PLUGINS_DATABASE_CATCH; @@ -1410,15 +1415,17 @@ { DatabaseBackendAdapterV2::Adapter::DatabaseAccessor accessor(*adapter); - std::vector lookup; - lookup.reserve(constraintsCount); + DatabaseConstraints lookup; for (uint32_t i = 0; i < constraintsCount; i++) { - lookup.push_back(Orthanc::DatabaseConstraint(constraints[i])); + lookup.AddConstraint(new DatabaseConstraint(constraints[i])); } - adapter->GetBackend().LookupResources(*output, accessor.GetManager(), lookup, queryLevel, limit, (requestSomeInstance != 0)); + std::set noLabel; + adapter->GetBackend().LookupResources(*output, accessor.GetManager(), lookup, queryLevel, noLabel, + LabelsConstraint_All, limit, (requestSomeInstance != 0)); + return OrthancPluginErrorCode_Success; } ORTHANC_PLUGINS_DATABASE_CATCH; @@ -1633,17 +1640,21 @@ void DatabaseBackendAdapterV2::Register(IDatabaseBackend* backend) { - if (backend == NULL) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } + std::unique_ptr protection(backend); + + if (backend == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } - if (adapter_.get() != NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + if (adapter_.get() != NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + adapter_.reset(new Adapter(protection.release())); } - - adapter_.reset(new Adapter(backend)); OrthancPluginDatabaseBackend params; memset(¶ms, 0, sizeof(params)); diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/DatabaseBackendAdapterV2.h --- a/Framework/Plugins/DatabaseBackendAdapterV2.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/DatabaseBackendAdapterV2.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/DatabaseBackendAdapterV3.cpp --- a/Framework/Plugins/DatabaseBackendAdapterV3.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/DatabaseBackendAdapterV3.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -24,8 +26,9 @@ #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 # if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) +#include "IndexConnectionsPool.h" + #include -#include #include #include @@ -81,172 +84,6 @@ } - class DatabaseBackendAdapterV3::Adapter : public boost::noncopyable - { - private: - class ManagerReference : public Orthanc::IDynamicObject - { - private: - DatabaseManager* manager_; - - public: - ManagerReference(DatabaseManager& manager) : - manager_(&manager) - { - } - - DatabaseManager& GetManager() - { - assert(manager_ != NULL); - return *manager_; - } - }; - - std::unique_ptr backend_; - OrthancPluginContext* context_; - boost::shared_mutex connectionsMutex_; - size_t countConnections_; - std::list connections_; - Orthanc::SharedMessageQueue availableConnections_; - - public: - Adapter(IndexBackend* backend, - size_t countConnections) : - backend_(backend), - countConnections_(countConnections) - { - if (countConnections == 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, - "There must be a non-zero number of connections to the database"); - } - else if (backend == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - context_ = backend_->GetContext(); - } - } - - ~Adapter() - { - for (std::list::iterator - it = connections_.begin(); it != connections_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - } - - OrthancPluginContext* GetContext() const - { - return context_; - } - - void OpenConnections() - { - boost::unique_lock lock(connectionsMutex_); - - if (connections_.size() == 0) - { - assert(backend_.get() != NULL); - - { - std::unique_ptr manager(new DatabaseManager(backend_->CreateDatabaseFactory())); - manager->GetDatabase(); // Make sure to open the database connection - - backend_->ConfigureDatabase(*manager); - connections_.push_back(manager.release()); - } - - for (size_t i = 1; i < countConnections_; i++) - { - connections_.push_back(new DatabaseManager(backend_->CreateDatabaseFactory())); - connections_.back()->GetDatabase(); // Make sure to open the database connection - } - - for (std::list::iterator - it = connections_.begin(); it != connections_.end(); ++it) - { - assert(*it != NULL); - availableConnections_.Enqueue(new ManagerReference(**it)); - } - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - void CloseConnections() - { - boost::unique_lock lock(connectionsMutex_); - - if (connections_.size() != countConnections_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else if (availableConnections_.GetSize() != countConnections_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Some connections are still in use, bug in the Orthanc core"); - } - else - { - for (std::list::iterator - it = connections_.begin(); it != connections_.end(); ++it) - { - assert(*it != NULL); - (*it)->Close(); - } - } - } - - class DatabaseAccessor : public boost::noncopyable - { - private: - boost::shared_lock lock_; - Adapter& adapter_; - DatabaseManager* manager_; - - public: - DatabaseAccessor(Adapter& adapter) : - lock_(adapter.connectionsMutex_), - adapter_(adapter), - manager_(NULL) - { - for (;;) - { - std::unique_ptr manager(adapter.availableConnections_.Dequeue(100)); - if (manager.get() != NULL) - { - manager_ = &dynamic_cast(*manager).GetManager(); - return; - } - } - } - - ~DatabaseAccessor() - { - assert(manager_ != NULL); - adapter_.availableConnections_.Enqueue(new ManagerReference(*manager_)); - } - - IndexBackend& GetBackend() const - { - return *adapter_.backend_; - } - - DatabaseManager& GetManager() const - { - assert(manager_ != NULL); - return *manager_; - } - }; - }; - - class DatabaseBackendAdapterV3::Output : public IDatabaseBackendOutput { private: @@ -803,14 +640,14 @@ class DatabaseBackendAdapterV3::Transaction : public boost::noncopyable { private: - Adapter& adapter_; - std::unique_ptr accessor_; - std::unique_ptr output_; + IndexConnectionsPool& pool_; + std::unique_ptr accessor_; + std::unique_ptr output_; public: - Transaction(Adapter& adapter) : - adapter_(adapter), - accessor_(new Adapter::DatabaseAccessor(adapter)), + Transaction(IndexConnectionsPool& pool) : + pool_(pool), + accessor_(new IndexConnectionsPool::Accessor(pool)), output_(new Output) { } @@ -962,35 +799,36 @@ static OrthancPluginErrorCode Open(void* database) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - adapter->OpenConnections(); + std::list identifierTags; + pool->OpenConnections(false, identifierTags); return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } static OrthancPluginErrorCode Close(void* database) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - adapter->CloseConnections(); + pool->CloseConnections(); return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } static OrthancPluginErrorCode DestructDatabase(void* database) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); - if (adapter == NULL) + if (pool == NULL) { return OrthancPluginErrorCode_InternalError; } @@ -1002,10 +840,10 @@ } else { - OrthancPluginLogError(adapter->GetContext(), "More than one index backend was registered, internal error"); + OrthancPluginLogError(pool->GetContext(), "More than one index backend was registered, internal error"); } - delete adapter; + delete pool; return OrthancPluginErrorCode_Success; } @@ -1015,15 +853,15 @@ static OrthancPluginErrorCode GetDatabaseVersion(void* database, uint32_t* version) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - DatabaseBackendAdapterV3::Adapter::DatabaseAccessor accessor(*adapter); + IndexConnectionsPool::Accessor accessor(*pool); *version = accessor.GetBackend().GetDatabaseVersion(accessor.GetManager()); return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } @@ -1031,30 +869,30 @@ OrthancPluginStorageArea* storageArea, uint32_t targetVersion) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - DatabaseBackendAdapterV3::Adapter::DatabaseAccessor accessor(*adapter); + IndexConnectionsPool::Accessor accessor(*pool); accessor.GetBackend().UpgradeDatabase(accessor.GetManager(), targetVersion, storageArea); return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } static OrthancPluginErrorCode HasRevisionsSupport(void* database, uint8_t* target) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - DatabaseBackendAdapterV3::Adapter::DatabaseAccessor accessor(*adapter); + IndexConnectionsPool::Accessor accessor(*pool); *target = (accessor.GetBackend().HasRevisionsSupport() ? 1 : 0); return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } @@ -1062,11 +900,11 @@ OrthancPluginDatabaseTransaction** target /* out */, OrthancPluginDatabaseTransactionType type) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - std::unique_ptr transaction(new DatabaseBackendAdapterV3::Transaction(*adapter)); + std::unique_ptr transaction(new DatabaseBackendAdapterV3::Transaction(*pool)); switch (type) { @@ -1086,7 +924,7 @@ return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } @@ -1667,19 +1505,9 @@ try { - OrthancPluginExportedResource exported; - exported.seq = 0; - exported.resourceType = resourceType; - exported.publicId = publicId; - exported.modality = modality; - exported.date = date; - exported.patientId = patientId; - exported.studyInstanceUid = studyInstanceUid; - exported.seriesInstanceUid = seriesInstanceUid; - exported.sopInstanceUid = sopInstanceUid; - t->GetOutput().Clear(); - t->GetBackend().LogExportedResource(t->GetManager(), exported); + t->GetBackend().LogExportedResource(t->GetManager(), resourceType, publicId, modality, date, + patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid); return OrthancPluginErrorCode_Success; } ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); @@ -1814,15 +1642,16 @@ { t->GetOutput().Clear(); - std::vector lookup; - lookup.reserve(constraintsCount); + DatabaseConstraints lookup; for (uint32_t i = 0; i < constraintsCount; i++) { - lookup.push_back(Orthanc::DatabaseConstraint(constraints[i])); + lookup.AddConstraint(new DatabaseConstraint(constraints[i])); } - t->GetBackend().LookupResources(t->GetOutput(), t->GetManager(), lookup, queryLevel, limit, (requestSomeInstanceId != 0)); + std::set noLabel; + t->GetBackend().LookupResources(t->GetOutput(), t->GetManager(), lookup, queryLevel, noLabel, + LabelsConstraint_All, limit, (requestSomeInstanceId != 0)); return OrthancPluginErrorCode_Success; } ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); @@ -1989,6 +1818,8 @@ size_t countConnections, unsigned int maxDatabaseRetries) { + std::unique_ptr protection(backend); + if (isBackendInUse_) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); @@ -2072,12 +1903,13 @@ params.setProtectedPatient = SetProtectedPatient; params.setResourcesContent = SetResourcesContent; - OrthancPluginContext* context = backend->GetContext(); + OrthancPluginContext* context = protection->GetContext(); if (OrthancPluginRegisterDatabaseBackendV3( context, ¶ms, sizeof(params), maxDatabaseRetries, - new Adapter(backend, countConnections)) != OrthancPluginErrorCode_Success) + new IndexConnectionsPool(protection.release(), countConnections)) != OrthancPluginErrorCode_Success) { + delete backend; throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Unable to register the database backend"); } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/DatabaseBackendAdapterV3.h --- a/Framework/Plugins/DatabaseBackendAdapterV3.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/DatabaseBackendAdapterV3.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -48,7 +50,6 @@ } public: - class Adapter; class Transaction; class Factory : public IDatabaseBackendOutput::IFactory diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/DatabaseBackendAdapterV4.cpp --- a/Framework/Plugins/DatabaseBackendAdapterV4.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/DatabaseBackendAdapterV4.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -24,8 +26,12 @@ #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 # if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) +#include "IndexConnectionsPool.h" +#include "MessagesToolbox.h" + +#include // Include protobuf messages + #include -#include #include #include @@ -34,550 +40,155 @@ #include -#define ORTHANC_PLUGINS_DATABASE_CATCH(context) \ - catch (::Orthanc::OrthancException& e) \ - { \ - return static_cast(e.GetErrorCode()); \ - } \ - catch (::std::runtime_error& e) \ - { \ - const std::string message = "Exception in database back-end: " + std::string(e.what()); \ - OrthancPluginLogError(context, message.c_str()); \ - return OrthancPluginErrorCode_DatabasePlugin; \ - } \ - catch (...) \ - { \ - OrthancPluginLogError(context, "Native exception"); \ - return OrthancPluginErrorCode_DatabasePlugin; \ - } +#define ORTHANC_PLUGINS_DATABASE_CATCH(context) \ namespace OrthancDatabases { static bool isBackendInUse_ = false; // Only for sanity checks - - template - static void CopyListToVector(std::vector& target, - const std::list& source) + + static Orthanc::DatabasePluginMessages::ResourceType Convert(OrthancPluginResourceType resourceType) { - /** - * This has the the same effect as: - * - * target.reserve(source.size()); - * std::copy(std::begin(source), std::end(source), std::back_inserter(target)); - * - * However, this implementation is compatible with C++03 (Linux - * Standard Base), whereas "std::back_inserter" requires C++11. - **/ + switch (resourceType) + { + case OrthancPluginResourceType_Patient: + return Orthanc::DatabasePluginMessages::RESOURCE_PATIENT; + + case OrthancPluginResourceType_Study: + return Orthanc::DatabasePluginMessages::RESOURCE_STUDY; - target.clear(); - target.reserve(source.size()); + case OrthancPluginResourceType_Series: + return Orthanc::DatabasePluginMessages::RESOURCE_SERIES; - for (typename std::list::const_iterator it = source.begin(); it != source.end(); ++it) - { - target.push_back(*it); + case OrthancPluginResourceType_Instance: + return Orthanc::DatabasePluginMessages::RESOURCE_INSTANCE; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } } - - - class DatabaseBackendAdapterV4::Adapter : public boost::noncopyable + + + static OrthancPluginResourceType Convert(Orthanc::DatabasePluginMessages::ResourceType resourceType) + { + switch (resourceType) + { + case Orthanc::DatabasePluginMessages::RESOURCE_PATIENT: + return OrthancPluginResourceType_Patient; + + case Orthanc::DatabasePluginMessages::RESOURCE_STUDY: + return OrthancPluginResourceType_Study; + + case Orthanc::DatabasePluginMessages::RESOURCE_SERIES: + return OrthancPluginResourceType_Series; + + case Orthanc::DatabasePluginMessages::RESOURCE_INSTANCE: + return OrthancPluginResourceType_Instance; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + class Output : public IDatabaseBackendOutput { private: - class ManagerReference : public Orthanc::IDynamicObject - { - private: - DatabaseManager* manager_; - - public: - ManagerReference(DatabaseManager& manager) : - manager_(&manager) - { - } - - DatabaseManager& GetManager() - { - assert(manager_ != NULL); - return *manager_; - } - }; - - std::unique_ptr backend_; - OrthancPluginContext* context_; - boost::shared_mutex connectionsMutex_; - size_t countConnections_; - std::list connections_; - Orthanc::SharedMessageQueue availableConnections_; - - public: - Adapter(IndexBackend* backend, - size_t countConnections) : - backend_(backend), - countConnections_(countConnections) - { - if (countConnections == 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, - "There must be a non-zero number of connections to the database"); - } - else if (backend == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - context_ = backend_->GetContext(); - } - } - - ~Adapter() - { - for (std::list::iterator - it = connections_.begin(); it != connections_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - } - - OrthancPluginContext* GetContext() const - { - return context_; - } - - void OpenConnections() - { - boost::unique_lock lock(connectionsMutex_); - - if (connections_.size() == 0) - { - assert(backend_.get() != NULL); - - { - std::unique_ptr manager(new DatabaseManager(backend_->CreateDatabaseFactory())); - manager->GetDatabase(); // Make sure to open the database connection - - backend_->ConfigureDatabase(*manager); - connections_.push_back(manager.release()); - } - - for (size_t i = 1; i < countConnections_; i++) - { - connections_.push_back(new DatabaseManager(backend_->CreateDatabaseFactory())); - connections_.back()->GetDatabase(); // Make sure to open the database connection - } - - for (std::list::iterator - it = connections_.begin(); it != connections_.end(); ++it) - { - assert(*it != NULL); - availableConnections_.Enqueue(new ManagerReference(**it)); - } - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - void CloseConnections() - { - boost::unique_lock lock(connectionsMutex_); + Orthanc::DatabasePluginMessages::DeleteAttachment::Response* deleteAttachment_; + Orthanc::DatabasePluginMessages::DeleteResource::Response* deleteResource_; + Orthanc::DatabasePluginMessages::GetChanges::Response* getChanges_; + Orthanc::DatabasePluginMessages::GetExportedResources::Response* getExportedResources_; + Orthanc::DatabasePluginMessages::GetLastChange::Response* getLastChange_; + Orthanc::DatabasePluginMessages::GetLastExportedResource::Response* getLastExportedResource_; + Orthanc::DatabasePluginMessages::GetMainDicomTags::Response* getMainDicomTags_; + Orthanc::DatabasePluginMessages::LookupAttachment::Response* lookupAttachment_; + Orthanc::DatabasePluginMessages::LookupResources::Response* lookupResources_; - if (connections_.size() != countConnections_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else if (availableConnections_.GetSize() != countConnections_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Some connections are still in use, bug in the Orthanc core"); - } - else - { - for (std::list::iterator - it = connections_.begin(); it != connections_.end(); ++it) - { - assert(*it != NULL); - (*it)->Close(); - } - } - } - - class DatabaseAccessor : public boost::noncopyable - { - private: - boost::shared_lock lock_; - Adapter& adapter_; - DatabaseManager* manager_; - - public: - DatabaseAccessor(Adapter& adapter) : - lock_(adapter.connectionsMutex_), - adapter_(adapter), - manager_(NULL) - { - for (;;) - { - std::unique_ptr manager(adapter.availableConnections_.Dequeue(100)); - if (manager.get() != NULL) - { - manager_ = &dynamic_cast(*manager).GetManager(); - return; - } - } - } - - ~DatabaseAccessor() - { - assert(manager_ != NULL); - adapter_.availableConnections_.Enqueue(new ManagerReference(*manager_)); - } - - IndexBackend& GetBackend() const - { - return *adapter_.backend_; - } - - DatabaseManager& GetManager() const - { - assert(manager_ != NULL); - return *manager_; - } - }; - }; - - - class DatabaseBackendAdapterV4::Output : public IDatabaseBackendOutput - { - private: - struct Metadata - { - int32_t metadata; - const char* value; - }; - - _OrthancPluginDatabaseAnswerType answerType_; - std::list stringsStore_; - - std::vector attachments_; - std::vector changes_; - std::vector tags_; - std::vector exported_; - std::vector events_; - std::vector integers32_; - std::vector integers64_; - std::vector matches_; - std::vector metadata_; - std::vector stringAnswers_; - - const char* StoreString(const std::string& s) - { - stringsStore_.push_back(s); - return stringsStore_.back().c_str(); - } - - void SetupAnswerType(_OrthancPluginDatabaseAnswerType type) - { - if (answerType_ == _OrthancPluginDatabaseAnswerType_None) - { - answerType_ = type; - } - else if (answerType_ != type) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - public: - Output() : - answerType_(_OrthancPluginDatabaseAnswerType_None) - { - } +#if ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED == 1 + Orthanc::DatabasePluginMessages::GetChangesExtended::Response* getChangesExtended_; +#endif void Clear() { - // We don't systematically clear all the vectors, in order to - // avoid spending unnecessary time - - switch (answerType_) - { - case _OrthancPluginDatabaseAnswerType_None: - break; - - case _OrthancPluginDatabaseAnswerType_Attachment: - attachments_.clear(); - break; - - case _OrthancPluginDatabaseAnswerType_Change: - changes_.clear(); - break; - - case _OrthancPluginDatabaseAnswerType_DicomTag: - tags_.clear(); - break; - - case _OrthancPluginDatabaseAnswerType_ExportedResource: - exported_.clear(); - break; - - case _OrthancPluginDatabaseAnswerType_Int32: - integers32_.clear(); - break; - - case _OrthancPluginDatabaseAnswerType_Int64: - integers64_.clear(); - break; - - case _OrthancPluginDatabaseAnswerType_MatchingResource: - matches_.clear(); - break; - - case _OrthancPluginDatabaseAnswerType_Metadata: - metadata_.clear(); - break; - - case _OrthancPluginDatabaseAnswerType_String: - stringAnswers_.clear(); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - answerType_ = _OrthancPluginDatabaseAnswerType_None; - stringsStore_.clear(); - events_.clear(); - - assert(attachments_.empty()); - assert(changes_.empty()); - assert(tags_.empty()); - assert(exported_.empty()); - assert(events_.empty()); - assert(integers32_.empty()); - assert(integers64_.empty()); - assert(matches_.empty()); - assert(metadata_.empty()); - assert(stringAnswers_.empty()); - } - + deleteAttachment_ = NULL; + deleteResource_ = NULL; + getChanges_ = NULL; + getExportedResources_ = NULL; + getLastChange_ = NULL; + getLastExportedResource_ = NULL; + getMainDicomTags_ = NULL; + lookupAttachment_ = NULL; + lookupResources_ = NULL; - OrthancPluginErrorCode ReadAnswersCount(uint32_t& target) const +#if ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED == 1 + getChangesExtended_ = NULL; +#endif + } + + public: + Output(Orthanc::DatabasePluginMessages::DeleteAttachment::Response& deleteAttachment) { - switch (answerType_) - { - case _OrthancPluginDatabaseAnswerType_None: - target = static_cast(0); - break; - - case _OrthancPluginDatabaseAnswerType_Attachment: - target = static_cast(attachments_.size()); - break; - - case _OrthancPluginDatabaseAnswerType_Change: - target = static_cast(changes_.size()); - break; - - case _OrthancPluginDatabaseAnswerType_DicomTag: - target = static_cast(tags_.size()); - break; - - case _OrthancPluginDatabaseAnswerType_ExportedResource: - target = static_cast(exported_.size()); - break; - - case _OrthancPluginDatabaseAnswerType_Int32: - target = static_cast(integers32_.size()); - break; - - case _OrthancPluginDatabaseAnswerType_Int64: - target = static_cast(integers64_.size()); - break; - - case _OrthancPluginDatabaseAnswerType_MatchingResource: - target = static_cast(matches_.size()); - break; - - case _OrthancPluginDatabaseAnswerType_Metadata: - target = static_cast(metadata_.size()); - break; - - case _OrthancPluginDatabaseAnswerType_String: - target = static_cast(stringAnswers_.size()); - break; - - default: - return OrthancPluginErrorCode_InternalError; - } - - return OrthancPluginErrorCode_Success; + Clear(); + deleteAttachment_ = &deleteAttachment; } - - - OrthancPluginErrorCode ReadAnswerAttachment2(OrthancPluginAttachment2& target /* out */, - uint32_t index) const + + Output(Orthanc::DatabasePluginMessages::DeleteResource::Response& deleteResource) { - if (index < attachments_.size()) - { - target = attachments_[index]; - return OrthancPluginErrorCode_Success; - } - else - { - return OrthancPluginErrorCode_ParameterOutOfRange; - } + Clear(); + deleteResource_ = &deleteResource; + } + + Output(Orthanc::DatabasePluginMessages::GetChanges::Response& getChanges) + { + Clear(); + getChanges_ = &getChanges; } - - OrthancPluginErrorCode ReadAnswerChange(OrthancPluginChange& target /* out */, - uint32_t index) const +#if ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED == 1 + Output(Orthanc::DatabasePluginMessages::GetChangesExtended::Response& getChangesExtended) { - if (index < changes_.size()) - { - target = changes_[index]; - return OrthancPluginErrorCode_Success; - } - else - { - return OrthancPluginErrorCode_ParameterOutOfRange; - } - } - - - OrthancPluginErrorCode ReadAnswerDicomTag(uint16_t& group, - uint16_t& element, - const char*& value, - uint32_t index) const - { - if (index < tags_.size()) - { - const OrthancPluginDicomTag& tag = tags_[index]; - group = tag.group; - element = tag.element; - value = tag.value; - return OrthancPluginErrorCode_Success; - } - else - { - return OrthancPluginErrorCode_ParameterOutOfRange; - } + Clear(); + getChangesExtended_ = &getChangesExtended; } - - - OrthancPluginErrorCode ReadAnswerExportedResource(OrthancPluginExportedResource& target /* out */, - uint32_t index) const - { - if (index < exported_.size()) - { - target = exported_[index]; - return OrthancPluginErrorCode_Success; - } - else - { - return OrthancPluginErrorCode_ParameterOutOfRange; - } - } - +#endif - OrthancPluginErrorCode ReadAnswerInt32(int32_t& target, - uint32_t index) const + Output(Orthanc::DatabasePluginMessages::GetExportedResources::Response& getExportedResources) { - if (index < integers32_.size()) - { - target = integers32_[index]; - return OrthancPluginErrorCode_Success; - } - else - { - return OrthancPluginErrorCode_ParameterOutOfRange; - } + Clear(); + getExportedResources_ = &getExportedResources; } - - - OrthancPluginErrorCode ReadAnswerInt64(int64_t& target, - uint32_t index) const + + Output(Orthanc::DatabasePluginMessages::GetLastChange::Response& getLastChange) + { + Clear(); + getLastChange_ = &getLastChange; + } + + Output(Orthanc::DatabasePluginMessages::GetLastExportedResource::Response& getLastExportedResource) { - if (index < integers64_.size()) - { - target = integers64_[index]; - return OrthancPluginErrorCode_Success; - } - else - { - return OrthancPluginErrorCode_ParameterOutOfRange; - } + Clear(); + getLastExportedResource_ = &getLastExportedResource; } - - - OrthancPluginErrorCode ReadAnswerMatchingResource(OrthancPluginMatchingResource& target, - uint32_t index) const - { - if (index < matches_.size()) - { - target = matches_[index]; - return OrthancPluginErrorCode_Success; - } - else - { - return OrthancPluginErrorCode_ParameterOutOfRange; - } - } - - - OrthancPluginErrorCode ReadAnswerMetadata(int32_t& metadata, - const char*& value, - uint32_t index) const + + Output(Orthanc::DatabasePluginMessages::GetMainDicomTags::Response& getMainDicomTags) { - if (index < metadata_.size()) - { - const Metadata& tmp = metadata_[index]; - metadata = tmp.metadata; - value = tmp.value; - return OrthancPluginErrorCode_Success; - } - else - { - return OrthancPluginErrorCode_ParameterOutOfRange; - } + Clear(); + getMainDicomTags_ = &getMainDicomTags; } - - - OrthancPluginErrorCode ReadAnswerString(const char*& target, - uint32_t index) const + + Output(Orthanc::DatabasePluginMessages::LookupAttachment::Response& lookupAttachment) { - if (index < stringAnswers_.size()) - { - target = stringAnswers_[index].c_str(); - return OrthancPluginErrorCode_Success; - } - else - { - return OrthancPluginErrorCode_ParameterOutOfRange; - } - } - - - OrthancPluginErrorCode ReadEventsCount(uint32_t& target /* out */) const - { - target = static_cast(events_.size()); - return OrthancPluginErrorCode_Success; + Clear(); + lookupAttachment_ = &lookupAttachment; } - - OrthancPluginErrorCode ReadEvent2(OrthancPluginDatabaseEvent2& event /* out */, - uint32_t index) const + Output(Orthanc::DatabasePluginMessages::LookupResources::Response& lookupResources) { - if (index < events_.size()) - { - event = events_[index]; - return OrthancPluginErrorCode_Success; - } - else - { - return OrthancPluginErrorCode_ParameterOutOfRange; - } + Clear(); + lookupResources_ = &lookupResources; } - - + virtual void SignalDeletedAttachment(const std::string& uuid, int32_t contentType, uint64_t uncompressedSize, @@ -587,45 +198,72 @@ const std::string& compressedHash, const std::string& customData) ORTHANC_OVERRIDE { - OrthancPluginDatabaseEvent2 event; - event.type = OrthancPluginDatabaseEventType_DeletedAttachment; - event.content.attachment.uuid = StoreString(uuid); - event.content.attachment.contentType = contentType; - event.content.attachment.uncompressedSize = uncompressedSize; - event.content.attachment.uncompressedHash = StoreString(uncompressedHash); - event.content.attachment.compressionType = compressionType; - event.content.attachment.compressedSize = compressedSize; - event.content.attachment.compressedHash = StoreString(compressedHash); - event.content.attachment.customData = StoreString(customData); + Orthanc::DatabasePluginMessages::FileInfo* attachment; + + if (deleteAttachment_ != NULL) + { + if (deleteAttachment_->has_deleted_attachment()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } - events_.push_back(event); + attachment = deleteAttachment_->mutable_deleted_attachment(); + } + else if (deleteResource_ != NULL) + { + attachment = deleteResource_->add_deleted_attachments(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + attachment->set_uuid(uuid); + attachment->set_content_type(contentType); + attachment->set_uncompressed_size(uncompressedSize); + attachment->set_uncompressed_hash(uncompressedHash); + attachment->set_compression_type(compressionType); + attachment->set_compressed_size(compressedSize); + attachment->set_compressed_hash(compressedHash); } - - + virtual void SignalDeletedResource(const std::string& publicId, OrthancPluginResourceType resourceType) ORTHANC_OVERRIDE { - OrthancPluginDatabaseEvent2 event; - event.type = OrthancPluginDatabaseEventType_DeletedResource; - event.content.resource.level = resourceType; - event.content.resource.publicId = StoreString(publicId); - - events_.push_back(event); + if (deleteResource_ != NULL) + { + Orthanc::DatabasePluginMessages::DeleteResource_Response_Resource* resource = deleteResource_->add_deleted_resources(); + resource->set_level(Convert(resourceType)); + resource->set_public_id(publicId); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } } - virtual void SignalRemainingAncestor(const std::string& ancestorId, OrthancPluginResourceType ancestorType) ORTHANC_OVERRIDE { - OrthancPluginDatabaseEvent2 event; - event.type = OrthancPluginDatabaseEventType_RemainingAncestor; - event.content.resource.level = ancestorType; - event.content.resource.publicId = StoreString(ancestorId); - - events_.push_back(event); + if (deleteResource_ != NULL) + { + if (deleteResource_->is_remaining_ancestor()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + deleteResource_->set_is_remaining_ancestor(true); + deleteResource_->mutable_remaining_ancestor()->set_level(Convert(ancestorType)); + deleteResource_->mutable_remaining_ancestor()->set_public_id(ancestorId); + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } } - virtual void AnswerAttachment(const std::string& uuid, int32_t contentType, uint64_t uncompressedSize, @@ -635,21 +273,27 @@ const std::string& compressedHash, const std::string& customData) ORTHANC_OVERRIDE { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_Attachment); + if (lookupAttachment_ != NULL) + { + if (lookupAttachment_->found()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } - OrthancPluginAttachment2 attachment; - attachment.uuid = StoreString(uuid); - attachment.contentType = contentType; - attachment.uncompressedSize = uncompressedSize; - attachment.uncompressedHash = StoreString(uncompressedHash); - attachment.compressionType = compressionType; - attachment.compressedSize = compressedSize; - attachment.compressedHash = StoreString(compressedHash); - attachment.customData = StoreString(customData); - - attachments_.push_back(attachment); + lookupAttachment_->set_found(true); + lookupAttachment_->mutable_attachment()->set_uuid(uuid); + lookupAttachment_->mutable_attachment()->set_content_type(contentType); + lookupAttachment_->mutable_attachment()->set_uncompressed_size(uncompressedSize); + lookupAttachment_->mutable_attachment()->set_uncompressed_hash(uncompressedHash); + lookupAttachment_->mutable_attachment()->set_compression_type(compressionType); + lookupAttachment_->mutable_attachment()->set_compressed_size(compressedSize); + lookupAttachment_->mutable_attachment()->set_compressed_hash(compressedHash); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } } - virtual void AnswerChange(int64_t seq, int32_t changeType, @@ -657,33 +301,56 @@ const std::string& publicId, const std::string& date) ORTHANC_OVERRIDE { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_Change); + Orthanc::DatabasePluginMessages::ServerIndexChange* change; + + if (getChanges_ != NULL) + { + change = getChanges_->add_changes(); + } +#if ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED == 1 + else if (getChangesExtended_ != NULL) + { + change = getChangesExtended_->add_changes(); + } +#endif + else if (getLastChange_ != NULL) + { + if (getLastChange_->found()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } - OrthancPluginChange change; - change.seq = seq; - change.changeType = changeType; - change.resourceType = resourceType; - change.publicId = StoreString(publicId); - change.date = StoreString(date); + getLastChange_->set_found(true); + change = getLastChange_->mutable_change(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } - changes_.push_back(change); + change->set_seq(seq); + change->set_change_type(changeType); + change->set_resource_type(Convert(resourceType)); + change->set_public_id(publicId); + change->set_date(date); } - virtual void AnswerDicomTag(uint16_t group, uint16_t element, const std::string& value) ORTHANC_OVERRIDE { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_DicomTag); - - OrthancPluginDicomTag tag; - tag.group = group; - tag.element = element; - tag.value = StoreString(value); - - tags_.push_back(tag); + if (getMainDicomTags_ != NULL) + { + Orthanc::DatabasePluginMessages::GetMainDicomTags_Response_Tag* tag = getMainDicomTags_->add_tags(); + tag->set_group(group); + tag->set_element(element); + tag->set_value(value); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } } - virtual void AnswerExportedResource(int64_t seq, OrthancPluginResourceType resourceType, @@ -695,98 +362,57 @@ const std::string& seriesInstanceUid, const std::string& sopInstanceUid) ORTHANC_OVERRIDE { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_ExportedResource); + Orthanc::DatabasePluginMessages::ExportedResource* resource; + + if (getExportedResources_ != NULL) + { + resource = getExportedResources_->add_resources(); + } + else if (getLastExportedResource_ != NULL) + { + if (getLastExportedResource_->found()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } - OrthancPluginExportedResource exported; - exported.seq = seq; - exported.resourceType = resourceType; - exported.publicId = StoreString(publicId); - exported.modality = StoreString(modality); - exported.date = StoreString(date); - exported.patientId = StoreString(patientId); - exported.studyInstanceUid = StoreString(studyInstanceUid); - exported.seriesInstanceUid = StoreString(seriesInstanceUid); - exported.sopInstanceUid = StoreString(sopInstanceUid); - - exported_.push_back(exported); + getLastExportedResource_->set_found(true); + resource = getLastExportedResource_->mutable_resource(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + resource->set_seq(seq); + resource->set_resource_type(Convert(resourceType)); + resource->set_public_id(publicId); + resource->set_modality(modality); + resource->set_date(date); + resource->set_patient_id(patientId); + resource->set_study_instance_uid(studyInstanceUid); + resource->set_series_instance_uid(seriesInstanceUid); + resource->set_sop_instance_uid(sopInstanceUid); } - virtual void AnswerMatchingResource(const std::string& resourceId) ORTHANC_OVERRIDE { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_MatchingResource); - - OrthancPluginMatchingResource match; - match.resourceId = StoreString(resourceId); - match.someInstanceId = NULL; - - matches_.push_back(match); + if (lookupResources_ != NULL) + { + lookupResources_->add_resources_ids(resourceId); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } } - virtual void AnswerMatchingResource(const std::string& resourceId, const std::string& someInstanceId) ORTHANC_OVERRIDE { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_MatchingResource); - - OrthancPluginMatchingResource match; - match.resourceId = StoreString(resourceId); - match.someInstanceId = StoreString(someInstanceId); - - matches_.push_back(match); - } - - - void AnswerIntegers32(const std::list& values) - { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_Int32); - CopyListToVector(integers32_, values); - } - - - void AnswerIntegers64(const std::list& values) - { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_Int64); - CopyListToVector(integers64_, values); - } - - - void AnswerInteger64(int64_t value) - { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_Int64); - - integers64_.resize(1); - integers64_[0] = value; - } - - - void AnswerMetadata(int32_t metadata, - const std::string& value) - { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_Metadata); - - Metadata tmp; - tmp.metadata = metadata; - tmp.value = StoreString(value); - - metadata_.push_back(tmp); - } - - - void AnswerStrings(const std::list& values) - { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_String); - CopyListToVector(stringAnswers_, values); - } - - - void AnswerString(const std::string& value) - { - SetupAnswerType(_OrthancPluginDatabaseAnswerType_String); - - if (stringAnswers_.empty()) + if (lookupResources_ != NULL) { - stringAnswers_.push_back(value); + lookupResources_->add_resources_ids(resourceId); + lookupResources_->add_instances_ids(someInstanceId); } else { @@ -794,1312 +420,1054 @@ } } }; - - - IDatabaseBackendOutput* DatabaseBackendAdapterV4::Factory::CreateOutput() - { - return new DatabaseBackendAdapterV4::Output; - } - - - class DatabaseBackendAdapterV4::Transaction : public boost::noncopyable - { - private: - Adapter& adapter_; - std::unique_ptr accessor_; - std::unique_ptr output_; - - public: - Transaction(Adapter& adapter) : - adapter_(adapter), - accessor_(new Adapter::DatabaseAccessor(adapter)), - output_(new Output) - { - } + - ~Transaction() - { - } - - IndexBackend& GetBackend() const - { - return accessor_->GetBackend(); - } - - Output& GetOutput() const - { - return *output_; - } - - DatabaseManager& GetManager() const - { - return accessor_->GetManager(); - } - }; - - - static OrthancPluginErrorCode ReadAnswersCount(OrthancPluginDatabaseTransaction* transaction, - uint32_t* target /* out */) + static void ProcessDatabaseOperation(Orthanc::DatabasePluginMessages::DatabaseResponse& response, + const Orthanc::DatabasePluginMessages::DatabaseRequest& request, + IndexConnectionsPool& pool) { - assert(target != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadAnswersCount(*target); - } + switch (request.operation()) + { + case Orthanc::DatabasePluginMessages::OPERATION_GET_SYSTEM_INFORMATION: + { + IndexConnectionsPool::Accessor accessor(pool); + response.mutable_get_system_information()->set_database_version(accessor.GetBackend().GetDatabaseVersion(accessor.GetManager())); + response.mutable_get_system_information()->set_supports_flush_to_disk(false); + response.mutable_get_system_information()->set_supports_revisions(accessor.GetBackend().HasRevisionsSupport()); + response.mutable_get_system_information()->set_supports_labels(accessor.GetBackend().HasLabelsSupport()); +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 3) + response.mutable_get_system_information()->set_supports_increment_global_property(accessor.GetBackend().HasAtomicIncrementGlobalProperty()); + response.mutable_get_system_information()->set_has_update_and_get_statistics(accessor.GetBackend().HasUpdateAndGetStatistics()); + response.mutable_get_system_information()->set_has_measure_latency(accessor.GetBackend().HasMeasureLatency()); +#endif + +#if ORTHANC_PLUGINS_HAS_INTEGRATED_FIND == 1 + response.mutable_get_system_information()->set_supports_find(accessor.GetBackend().HasFindSupport()); + response.mutable_get_system_information()->set_has_extended_changes(accessor.GetBackend().HasExtendedChanges()); +#endif + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_OPEN: + { + std::list identifierTags; - static OrthancPluginErrorCode ReadAnswerAttachment2(OrthancPluginDatabaseTransaction* transaction, - OrthancPluginAttachment2* target /* out */, - uint32_t index) - { - assert(target != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadAnswerAttachment2(*target, index); - } + if (request.open().identifier_tags().empty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "No identifier tag was provided by the Orthanc core"); + } + for (int i = 0; i < request.open().identifier_tags().size(); i++) + { + const Orthanc::DatabasePluginMessages::Open_Request_IdentifierTag& tag = request.open().identifier_tags(i); + identifierTags.push_back(IdentifierTag(MessagesToolbox::Convert(tag.level()), + Orthanc::DicomTag(tag.group(), tag.element()), + tag.name())); + } + + pool.OpenConnections(true, identifierTags); + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_CLOSE: + { + pool.CloseConnections(); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_FLUSH_TO_DISK: + { + // Raise an exception since "set_supports_flush_to_disk(false)" + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } - static OrthancPluginErrorCode ReadAnswerChange(OrthancPluginDatabaseTransaction* transaction, - OrthancPluginChange* target /* out */, - uint32_t index) - { - assert(target != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadAnswerChange(*target, index); - } + case Orthanc::DatabasePluginMessages::OPERATION_START_TRANSACTION: + { + std::unique_ptr transaction(new IndexConnectionsPool::Accessor(pool)); + switch (request.start_transaction().type()) + { + case Orthanc::DatabasePluginMessages::TRANSACTION_READ_ONLY: + transaction->GetManager().StartTransaction(TransactionType_ReadOnly); + break; + + case Orthanc::DatabasePluginMessages::TRANSACTION_READ_WRITE: + transaction->GetManager().StartTransaction(TransactionType_ReadWrite); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } - static OrthancPluginErrorCode ReadAnswerDicomTag(OrthancPluginDatabaseTransaction* transaction, - uint16_t* group, - uint16_t* element, - const char** value, - uint32_t index) - { - assert(group != NULL); - assert(element != NULL); - assert(value != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadAnswerDicomTag(*group, *element, *value, index); - } + response.mutable_start_transaction()->set_transaction(reinterpret_cast(transaction.release())); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_UPGRADE: + { + IndexConnectionsPool::Accessor accessor(pool); + OrthancPluginStorageArea* storageArea = reinterpret_cast(request.upgrade().storage_area()); + accessor.GetBackend().UpgradeDatabase(accessor.GetManager(), request.upgrade().target_version(), storageArea); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_FINALIZE_TRANSACTION: + { + IndexConnectionsPool::Accessor* transaction = reinterpret_cast(request.finalize_transaction().transaction()); + + if (transaction == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + delete transaction; + } + + break; + } +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 3) + case Orthanc::DatabasePluginMessages::OPERATION_MEASURE_LATENCY: + { + IndexConnectionsPool::Accessor accessor(pool); + response.mutable_measure_latency()->set_latency_us(accessor.GetBackend().MeasureLatency(accessor.GetManager())); + break; + } +#endif - static OrthancPluginErrorCode ReadAnswerExportedResource(OrthancPluginDatabaseTransaction* transaction, - OrthancPluginExportedResource* target /* out */, - uint32_t index) - { - assert(target != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadAnswerExportedResource(*target, index); + default: + LOG(ERROR) << "Not implemented database operation from protobuf: " << request.operation(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } } - static OrthancPluginErrorCode ReadAnswerInt32(OrthancPluginDatabaseTransaction* transaction, - int32_t* target, - uint32_t index) - { - assert(target != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadAnswerInt32(*target, index); - } - - - static OrthancPluginErrorCode ReadAnswerInt64(OrthancPluginDatabaseTransaction* transaction, - int64_t* target, - uint32_t index) + static void ApplyLookupResources(Orthanc::DatabasePluginMessages::LookupResources_Response& response, + const Orthanc::DatabasePluginMessages::LookupResources_Request& request, + IndexBackend& backend, + DatabaseManager& manager) { - assert(target != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadAnswerInt64(*target, index); - } + size_t countValues = 0; + + for (int i = 0; i < request.lookup().size(); i++) + { + const Orthanc::DatabasePluginMessages::DatabaseConstraint& constraint = request.lookup(i); + countValues += constraint.values().size(); + } + + std::vector values; + values.reserve(countValues); + + DatabaseConstraints lookup; + for (int i = 0; i < request.lookup().size(); i++) + { + const Orthanc::DatabasePluginMessages::DatabaseConstraint& constraint = request.lookup(i); + + if (constraint.tag_group() > 0xffffu || + constraint.tag_element() > 0xffffu) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + OrthancPluginDatabaseConstraint c; + c.level = Convert(constraint.level()); + c.tagGroup = constraint.tag_group(); + c.tagElement = constraint.tag_element(); + c.isIdentifierTag = (constraint.is_identifier_tag() ? 1 : 0); + c.isCaseSensitive = (constraint.is_case_sensitive() ? 1 : 0); + c.isMandatory = (constraint.is_mandatory() ? 1 : 0); - static OrthancPluginErrorCode ReadAnswerMatchingResource(OrthancPluginDatabaseTransaction* transaction, - OrthancPluginMatchingResource* target, - uint32_t index) - { - assert(target != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadAnswerMatchingResource(*target, index); + switch (constraint.type()) + { + case Orthanc::DatabasePluginMessages::CONSTRAINT_EQUAL: + c.type = OrthancPluginConstraintType_Equal; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_SMALLER_OR_EQUAL: + c.type = OrthancPluginConstraintType_SmallerOrEqual; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_GREATER_OR_EQUAL: + c.type = OrthancPluginConstraintType_GreaterOrEqual; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_WILDCARD: + c.type = OrthancPluginConstraintType_Wildcard; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_LIST: + c.type = OrthancPluginConstraintType_List; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + c.valuesCount = constraint.values().size(); + + if (c.valuesCount == 0) + { + c.values = NULL; + } + else + { + c.values = &values[values.size()]; + + for (int j = 0; j < constraint.values().size(); j++) + { + assert(values.size() < countValues); + values.push_back(constraint.values(j).c_str()); + } + } + + lookup.AddConstraint(new DatabaseConstraint(c)); + } + + assert(values.size() == countValues); + + std::set labels; + + for (int i = 0; i < request.labels().size(); i++) + { + labels.insert(request.labels(i)); + } + + LabelsConstraint labelsConstraint; + switch (request.labels_constraint()) + { + case Orthanc::DatabasePluginMessages::LABELS_CONSTRAINT_ALL: + labelsConstraint = LabelsConstraint_All; + break; + + case Orthanc::DatabasePluginMessages::LABELS_CONSTRAINT_ANY: + labelsConstraint = LabelsConstraint_Any; + break; + + case Orthanc::DatabasePluginMessages::LABELS_CONSTRAINT_NONE: + labelsConstraint = LabelsConstraint_None; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + Output output(response); + backend.LookupResources(output, manager, lookup, Convert(request.query_level()), + labels, labelsConstraint, request.limit(), request.retrieve_instances_ids()); } - - static OrthancPluginErrorCode ReadAnswerMetadata(OrthancPluginDatabaseTransaction* transaction, - int32_t* metadata, - const char** value, - uint32_t index) + + static void ProcessTransactionOperation(Orthanc::DatabasePluginMessages::TransactionResponse& response, + const Orthanc::DatabasePluginMessages::TransactionRequest& request, + IndexBackend& backend, + DatabaseManager& manager) { - assert(metadata != NULL); - assert(value != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadAnswerMetadata(*metadata, *value, index); - } + switch (request.operation()) + { + case Orthanc::DatabasePluginMessages::OPERATION_ROLLBACK: + { + manager.RollbackTransaction(); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_COMMIT: + { + manager.CommitTransaction(); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_ADD_ATTACHMENT: + { + OrthancPluginAttachment attachment; + attachment.uuid = request.add_attachment().attachment().uuid().c_str(); + attachment.contentType = request.add_attachment().attachment().content_type(); + attachment.uncompressedSize = request.add_attachment().attachment().uncompressed_size(); + attachment.uncompressedHash = request.add_attachment().attachment().uncompressed_hash().c_str(); + attachment.compressionType = request.add_attachment().attachment().compression_type(); + attachment.compressedSize = request.add_attachment().attachment().compressed_size(); + attachment.compressedHash = request.add_attachment().attachment().compressed_hash().c_str(); + + backend.AddAttachment(manager, request.add_attachment().id(), attachment, request.add_attachment().revision()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_CHANGES: + { + backend.ClearChanges(manager); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_EXPORTED_RESOURCES: + { + backend.ClearExportedResources(manager); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_DELETE_ATTACHMENT: + { + Output output(*response.mutable_delete_attachment()); + backend.DeleteAttachment(output, manager, request.delete_attachment().id(), request.delete_attachment().type()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_DELETE_METADATA: + { + backend.DeleteMetadata(manager, request.delete_metadata().id(), request.delete_metadata().type()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_DELETE_RESOURCE: + { + response.mutable_delete_resource()->set_is_remaining_ancestor(false); + + Output output(*response.mutable_delete_resource()); + backend.DeleteResource(output, manager, request.delete_resource().id()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_METADATA: + { + typedef std::map Values; + + Values values; + backend.GetAllMetadata(values, manager, request.get_all_metadata().id()); + + response.mutable_get_all_metadata()->mutable_metadata()->Reserve(values.size()); + for (Values::const_iterator it = values.begin(); it != values.end(); ++it) + { + Orthanc::DatabasePluginMessages::GetAllMetadata_Response_Metadata* metadata = + response.mutable_get_all_metadata()->add_metadata(); + metadata->set_type(it->first); + metadata->set_value(it->second); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_PUBLIC_IDS: + { + std::list values; + backend.GetAllPublicIds(values, manager, Convert(request.get_all_public_ids().resource_type())); + + response.mutable_get_all_public_ids()->mutable_ids()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_get_all_public_ids()->add_ids(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_PUBLIC_IDS_WITH_LIMITS: + { + std::list values; + backend.GetAllPublicIds(values, manager, Convert(request.get_all_public_ids_with_limits().resource_type()), + request.get_all_public_ids_with_limits().since(), + request.get_all_public_ids_with_limits().limit()); + + response.mutable_get_all_public_ids_with_limits()->mutable_ids()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_get_all_public_ids_with_limits()->add_ids(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_CHANGES: + { + Output output(*response.mutable_get_changes()); + + bool done; + backend.GetChanges(output, done, manager, request.get_changes().since(), request.get_changes().limit()); + + response.mutable_get_changes()->set_done(done); + break; + } +#if ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED == 1 + case Orthanc::DatabasePluginMessages::OPERATION_GET_CHANGES_EXTENDED: + { + Output output(*response.mutable_get_changes_extended()); + + bool done; + std::set changeTypes; + for (int i = 0; i < request.get_changes_extended().change_type_size(); ++i) + { + changeTypes.insert(request.get_changes_extended().change_type(i)); + } + + backend.GetChangesExtended(output, done, manager, request.get_changes_extended().since(), request.get_changes_extended().to(), changeTypes, request.get_changes_extended().limit()); + + response.mutable_get_changes_extended()->set_done(done); + break; + } +#endif + + case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_INTERNAL_ID: + { + std::list values; + backend.GetChildrenInternalId(values, manager, request.get_children_internal_id().id()); + + response.mutable_get_children_internal_id()->mutable_ids()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_get_children_internal_id()->add_ids(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_PUBLIC_ID: + { + std::list values; + backend.GetChildrenPublicId(values, manager, request.get_children_public_id().id()); + response.mutable_get_children_public_id()->mutable_ids()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_get_children_public_id()->add_ids(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_EXPORTED_RESOURCES: + { + Output output(*response.mutable_get_exported_resources()); + + bool done; + backend.GetExportedResources(output, done, manager, request.get_exported_resources().since(), + request.get_exported_resources().limit()); + + response.mutable_get_exported_resources()->set_done(done); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_CHANGE: + { + response.mutable_get_last_change()->set_found(false); + + Output output(*response.mutable_get_last_change()); + backend.GetLastChange(output, manager); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_EXPORTED_RESOURCE: + { + response.mutable_get_last_exported_resource()->set_found(false); + + Output output(*response.mutable_get_last_exported_resource()); + backend.GetLastExportedResource(output, manager); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_MAIN_DICOM_TAGS: + { + Output output(*response.mutable_get_main_dicom_tags()); + backend.GetMainDicomTags(output, manager, request.get_main_dicom_tags().id()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_PUBLIC_ID: + { + const std::string id = backend.GetPublicId(manager, request.get_public_id().id()); + response.mutable_get_public_id()->set_id(id); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_RESOURCES_COUNT: + { + OrthancPluginResourceType type = Convert(request.get_resources_count().type()); + uint64_t count = backend.GetResourcesCount(manager, type); + response.mutable_get_resources_count()->set_count(count); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_RESOURCE_TYPE: + { + OrthancPluginResourceType type = backend.GetResourceType(manager, request.get_resource_type().id()); + response.mutable_get_resource_type()->set_type(Convert(type)); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_TOTAL_COMPRESSED_SIZE: + { + response.mutable_get_total_compressed_size()->set_size(backend.GetTotalCompressedSize(manager)); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_TOTAL_UNCOMPRESSED_SIZE: + { + response.mutable_get_total_uncompressed_size()->set_size(backend.GetTotalUncompressedSize(manager)); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_IS_PROTECTED_PATIENT: + { + bool isProtected = backend.IsProtectedPatient(manager, request.is_protected_patient().patient_id()); + response.mutable_is_protected_patient()->set_protected_patient(isProtected); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LIST_AVAILABLE_ATTACHMENTS: + { + std::list values; + backend.ListAvailableAttachments(values, manager, request.list_available_attachments().id()); + + response.mutable_list_available_attachments()->mutable_attachments()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_list_available_attachments()->add_attachments(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOG_CHANGE: + { + backend.LogChange(manager, request.log_change().change_type(), + request.log_change().resource_id(), + Convert(request.log_change().resource_type()), + request.log_change().date().c_str()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOG_EXPORTED_RESOURCE: + { + backend.LogExportedResource(manager, + Convert(request.log_exported_resource().resource_type()), + request.log_exported_resource().public_id().c_str(), + request.log_exported_resource().modality().c_str(), + request.log_exported_resource().date().c_str(), + request.log_exported_resource().patient_id().c_str(), + request.log_exported_resource().study_instance_uid().c_str(), + request.log_exported_resource().series_instance_uid().c_str(), + request.log_exported_resource().sop_instance_uid().c_str()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_ATTACHMENT: + { + Output output(*response.mutable_lookup_attachment()); + + int64_t revision = -1; + backend.LookupAttachment(output, revision, manager, request.lookup_attachment().id(), request.lookup_attachment().content_type()); + + if (response.lookup_attachment().found()) + { + response.mutable_lookup_attachment()->set_revision(revision); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_GLOBAL_PROPERTY: + { + std::string value; + if (backend.LookupGlobalProperty(value, manager, request.lookup_global_property().server_id().c_str(), + request.lookup_global_property().property())) + { + response.mutable_lookup_global_property()->set_found(true); + response.mutable_lookup_global_property()->set_value(value); + } + else + { + response.mutable_lookup_global_property()->set_found(false); + } + + break; + } + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 3) + case Orthanc::DatabasePluginMessages::OPERATION_UPDATE_AND_GET_STATISTICS: + { + int64_t patientsCount, studiesCount, seriesCount, instancesCount, compressedSize, uncompressedSize; + backend.UpdateAndGetStatistics(manager, patientsCount, studiesCount, seriesCount, instancesCount, compressedSize, uncompressedSize); + + response.mutable_update_and_get_statistics()->set_patients_count(patientsCount); + response.mutable_update_and_get_statistics()->set_studies_count(studiesCount); + response.mutable_update_and_get_statistics()->set_series_count(seriesCount); + response.mutable_update_and_get_statistics()->set_instances_count(instancesCount); + response.mutable_update_and_get_statistics()->set_total_compressed_size(compressedSize); + response.mutable_update_and_get_statistics()->set_total_uncompressed_size(uncompressedSize); + break; + } +#endif - static OrthancPluginErrorCode ReadAnswerString(OrthancPluginDatabaseTransaction* transaction, - const char** target, - uint32_t index) - { - assert(target != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadAnswerString(*target, index); +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 3) + case Orthanc::DatabasePluginMessages::OPERATION_INCREMENT_GLOBAL_PROPERTY: + { + int64_t value = backend.IncrementGlobalProperty(manager, request.increment_global_property().server_id().c_str(), + request.increment_global_property().property(), + request.increment_global_property().increment()); + response.mutable_increment_global_property()->set_new_value(value); + break; + } +#endif + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_METADATA: + { + std::string value; + int64_t revision = -1; + if (backend.LookupMetadata(value, revision, manager, request.lookup_metadata().id(), request.lookup_metadata().metadata_type())) + { + response.mutable_lookup_metadata()->set_found(true); + response.mutable_lookup_metadata()->set_value(value); + response.mutable_lookup_metadata()->set_revision(revision); + } + else + { + response.mutable_lookup_metadata()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_PARENT: + { + int64_t parent = -1; + if (backend.LookupParent(parent, manager, request.lookup_parent().id())) + { + response.mutable_lookup_parent()->set_found(true); + response.mutable_lookup_parent()->set_parent(parent); + } + else + { + response.mutable_lookup_parent()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCE: + { + int64_t internalId = -1; + OrthancPluginResourceType type; + if (backend.LookupResource(internalId, type, manager, request.lookup_resource().public_id().c_str())) + { + response.mutable_lookup_resource()->set_found(true); + response.mutable_lookup_resource()->set_internal_id(internalId); + response.mutable_lookup_resource()->set_type(Convert(type)); + } + else + { + response.mutable_lookup_resource()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SELECT_PATIENT_TO_RECYCLE: + { + int64_t patientId = -1; + if (backend.SelectPatientToRecycle(patientId, manager)) + { + response.mutable_select_patient_to_recycle()->set_found(true); + response.mutable_select_patient_to_recycle()->set_patient_id(patientId); + } + else + { + response.mutable_select_patient_to_recycle()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SELECT_PATIENT_TO_RECYCLE_WITH_AVOID: + { + int64_t patientId = -1; + if (backend.SelectPatientToRecycle(patientId, manager, request.select_patient_to_recycle_with_avoid().patient_id_to_avoid())) + { + response.mutable_select_patient_to_recycle_with_avoid()->set_found(true); + response.mutable_select_patient_to_recycle_with_avoid()->set_patient_id(patientId); + } + else + { + response.mutable_select_patient_to_recycle_with_avoid()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SET_GLOBAL_PROPERTY: + { + backend.SetGlobalProperty(manager, request.set_global_property().server_id().c_str(), + request.set_global_property().property(), + request.set_global_property().value().c_str()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_MAIN_DICOM_TAGS: + { + backend.ClearMainDicomTags(manager, request.clear_main_dicom_tags().id()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SET_METADATA: + { + backend.SetMetadata(manager, request.set_metadata().id(), + request.set_metadata().metadata_type(), + request.set_metadata().value().c_str(), + request.set_metadata().revision()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SET_PROTECTED_PATIENT: + { + backend.SetProtectedPatient(manager, request.set_protected_patient().patient_id(), + request.set_protected_patient().protected_patient()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_IS_DISK_SIZE_ABOVE: + { + bool above = (backend.GetTotalCompressedSize(manager) >= request.is_disk_size_above().threshold()); + response.mutable_is_disk_size_above()->set_result(above); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCES: + { + ApplyLookupResources(*response.mutable_lookup_resources(), request.lookup_resources(), backend, manager); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_CREATE_INSTANCE: + { + const char* hashPatient = request.create_instance().patient().c_str(); + const char* hashStudy = request.create_instance().study().c_str(); + const char* hashSeries = request.create_instance().series().c_str(); + const char* hashInstance = request.create_instance().instance().c_str(); + + OrthancPluginCreateInstanceResult result; + + if (backend.HasCreateInstance()) + { + backend.CreateInstance(result, manager, hashPatient, hashStudy, hashSeries, hashInstance); + } + else + { + backend.CreateInstanceGeneric(result, manager, hashPatient, hashStudy, hashSeries, hashInstance); + } + + response.mutable_create_instance()->set_is_new_instance(result.isNewInstance); + response.mutable_create_instance()->set_instance_id(result.instanceId); + + if (result.isNewInstance) + { + response.mutable_create_instance()->set_is_new_patient(result.isNewPatient); + response.mutable_create_instance()->set_is_new_study(result.isNewStudy); + response.mutable_create_instance()->set_is_new_series(result.isNewSeries); + response.mutable_create_instance()->set_patient_id(result.patientId); + response.mutable_create_instance()->set_study_id(result.studyId); + response.mutable_create_instance()->set_series_id(result.seriesId); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SET_RESOURCES_CONTENT: + { + std::vector identifierTags; + std::vector mainDicomTags; + + identifierTags.reserve(request.set_resources_content().tags().size()); + mainDicomTags.reserve(request.set_resources_content().tags().size()); + + for (int i = 0; i < request.set_resources_content().tags().size(); i++) + { + if (request.set_resources_content().tags(i).group() > 0xffffu || + request.set_resources_content().tags(i).element() > 0xffffu) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + OrthancPluginResourcesContentTags tag; + tag.resource = request.set_resources_content().tags(i).resource_id(); + tag.group = request.set_resources_content().tags(i).group(); + tag.element = request.set_resources_content().tags(i).element(); + tag.value = request.set_resources_content().tags(i).value().c_str(); + + if (request.set_resources_content().tags(i).is_identifier()) + { + identifierTags.push_back(tag); + } + else + { + mainDicomTags.push_back(tag); + } + } + + std::vector metadata; + metadata.reserve(request.set_resources_content().metadata().size()); + + for (int i = 0; i < request.set_resources_content().metadata().size(); i++) + { + OrthancPluginResourcesContentMetadata item; + item.resource = request.set_resources_content().metadata(i).resource_id(); + item.metadata = request.set_resources_content().metadata(i).metadata(); + item.value = request.set_resources_content().metadata(i).value().c_str(); + metadata.push_back(item); + } + + backend.SetResourcesContent(manager, + identifierTags.size(), (identifierTags.empty() ? NULL : &identifierTags[0]), + mainDicomTags.size(), (mainDicomTags.empty() ? NULL : &mainDicomTags[0]), + metadata.size(), (metadata.empty() ? NULL : &metadata[0])); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_METADATA: + { + std::list values; + backend.GetChildrenMetadata(values, manager, request.get_children_metadata().id(), request.get_children_metadata().metadata()); + + response.mutable_get_children_metadata()->mutable_values()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_get_children_metadata()->add_values(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_CHANGE_INDEX: + { + response.mutable_get_last_change_index()->set_result(backend.GetLastChangeIndex(manager)); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCE_AND_PARENT: + { + int64_t id; + OrthancPluginResourceType type; + std::string parent; + + if (backend.LookupResourceAndParent(id, type, parent, manager, request.lookup_resource_and_parent().public_id().c_str())) + { + response.mutable_lookup_resource_and_parent()->set_found(true); + response.mutable_lookup_resource_and_parent()->set_id(id); + response.mutable_lookup_resource_and_parent()->set_type(Convert(type)); + + switch (type) + { + case OrthancPluginResourceType_Study: + case OrthancPluginResourceType_Series: + case OrthancPluginResourceType_Instance: + if (parent.empty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + response.mutable_lookup_resource_and_parent()->set_parent_public_id(parent); + } + break; + + case OrthancPluginResourceType_Patient: + if (!parent.empty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + else + { + response.mutable_lookup_resource_and_parent()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_ADD_LABEL: + { + backend.AddLabel(manager, request.add_label().id(), request.add_label().label()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_REMOVE_LABEL: + { + backend.RemoveLabel(manager, request.remove_label().id(), request.remove_label().label()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LIST_LABELS: + { + std::list labels; + + if (request.list_labels().single_resource()) + { + backend.ListLabels(labels, manager, request.list_labels().id()); + } + else + { + backend.ListAllLabels(labels, manager); + } + + response.mutable_list_available_attachments()->mutable_attachments()->Reserve(labels.size()); + for (std::list::const_iterator it = labels.begin(); it != labels.end(); ++it) + { + response.mutable_list_labels()->add_labels(*it); + } + + break; + } + +#if ORTHANC_PLUGINS_HAS_INTEGRATED_FIND == 1 + case Orthanc::DatabasePluginMessages::OPERATION_FIND: + { + backend.ExecuteFind(response, manager, request.find()); + break; + } +#endif + + default: + LOG(ERROR) << "Not implemented transaction operation from protobuf: " << request.operation(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } } - - static OrthancPluginErrorCode ReadEventsCount(OrthancPluginDatabaseTransaction* transaction, - uint32_t* target /* out */) + + static OrthancPluginErrorCode CallBackend(OrthancPluginMemoryBuffer64* serializedResponse, + void* rawPool, + const void* requestData, + uint64_t requestSize) { - assert(target != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadEventsCount(*target); - } + Orthanc::DatabasePluginMessages::Request request; + if (!request.ParseFromArray(requestData, requestSize)) + { + LOG(ERROR) << "Cannot parse message from the Orthanc core using protobuf"; + return OrthancPluginErrorCode_InternalError; + } - - static OrthancPluginErrorCode ReadEvent2(OrthancPluginDatabaseTransaction* transaction, - OrthancPluginDatabaseEvent2* event /* out */, - uint32_t index) - { - assert(event != NULL); - const DatabaseBackendAdapterV4::Transaction& that = *reinterpret_cast(transaction); - return that.GetOutput().ReadEvent2(*event, index); - } + if (rawPool == NULL) + { + LOG(ERROR) << "Received a NULL pointer from the database"; + return OrthancPluginErrorCode_InternalError; + } - - static OrthancPluginErrorCode Open(void* database) - { - DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool& pool = *reinterpret_cast(rawPool); try { - adapter->OpenConnections(); + Orthanc::DatabasePluginMessages::Response response; + + switch (request.type()) + { + case Orthanc::DatabasePluginMessages::REQUEST_DATABASE: + ProcessDatabaseOperation(*response.mutable_database_response(), request.database_request(), pool); + break; + + case Orthanc::DatabasePluginMessages::REQUEST_TRANSACTION: + { + IndexConnectionsPool::Accessor& transaction = *reinterpret_cast(request.transaction_request().transaction()); + ProcessTransactionOperation(*response.mutable_transaction_response(), request.transaction_request(), + transaction.GetBackend(), transaction.GetManager()); + break; + } + + default: + LOG(ERROR) << "Not implemented request type from protobuf: " << request.type(); + break; + } + + std::string s; + if (!response.SerializeToString(&s)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Cannot serialize to protobuf"); + } + + if (OrthancPluginCreateMemoryBuffer64(pool.GetContext(), serializedResponse, s.size()) != OrthancPluginErrorCode_Success) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory, "Cannot allocate a memory buffer"); + } + + if (!s.empty()) + { + assert(serializedResponse->size == s.size()); + memcpy(serializedResponse->data, s.c_str(), s.size()); + } + return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + catch (::Orthanc::OrthancException& e) + { + if (e.GetErrorCode() == ::Orthanc::ErrorCode_DatabaseCannotSerialize) + { + LOG(WARNING) << "An SQL transaction failed and will likely be retried: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "Exception in database back-end: " << e.What(); + } + return static_cast(e.GetErrorCode()); + } + catch (::std::runtime_error& e) + { + LOG(ERROR) << "Exception in database back-end: " << e.what(); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (...) + { + LOG(ERROR) << "Native exception"; + return OrthancPluginErrorCode_DatabasePlugin; + } } - - static OrthancPluginErrorCode Close(void* database) + static void FinalizeBackend(void* rawPool) { - DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); - - try + if (rawPool != NULL) { - adapter->CloseConnections(); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); - } - - - static OrthancPluginErrorCode DestructDatabase(void* database) - { - DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); - - if (adapter == NULL) - { - return OrthancPluginErrorCode_InternalError; - } - else - { + IndexConnectionsPool* pool = reinterpret_cast(rawPool); + if (isBackendInUse_) { isBackendInUse_ = false; } else { - OrthancPluginLogError(adapter->GetContext(), "More than one index backend was registered, internal error"); + LOG(ERROR) << "More than one index backend was registered, internal error"; } - - delete adapter; - - return OrthancPluginErrorCode_Success; - } - } - - - static OrthancPluginErrorCode GetDatabaseVersion(void* database, - uint32_t* version) - { - DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); - - try - { - DatabaseBackendAdapterV4::Adapter::DatabaseAccessor accessor(*adapter); - *version = accessor.GetBackend().GetDatabaseVersion(accessor.GetManager()); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); - } - - - static OrthancPluginErrorCode UpgradeDatabase(void* database, - OrthancPluginStorageArea* storageArea, - uint32_t targetVersion) - { - DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); - - try - { - DatabaseBackendAdapterV4::Adapter::DatabaseAccessor accessor(*adapter); - accessor.GetBackend().UpgradeDatabase(accessor.GetManager(), targetVersion, storageArea); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); - } - - static OrthancPluginErrorCode HasRevisionsSupport(void* database, - uint8_t* target) - { - DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); - - try - { - DatabaseBackendAdapterV4::Adapter::DatabaseAccessor accessor(*adapter); - *target = (accessor.GetBackend().HasRevisionsSupport() ? 1 : 0); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); - } - - - static OrthancPluginErrorCode HasAttachmentCustomDataSupport(void* database, - uint8_t* target) - { - DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); - - try - { - DatabaseBackendAdapterV4::Adapter::DatabaseAccessor accessor(*adapter); - *target = (accessor.GetBackend().HasAttachmentCustomDataSupport() ? 1 : 0); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); - } - - static OrthancPluginErrorCode StartTransaction(void* database, - OrthancPluginDatabaseTransaction** target /* out */, - OrthancPluginDatabaseTransactionType type) - { - DatabaseBackendAdapterV4::Adapter* adapter = reinterpret_cast(database); - - try - { - std::unique_ptr transaction(new DatabaseBackendAdapterV4::Transaction(*adapter)); - - switch (type) - { - case OrthancPluginDatabaseTransactionType_ReadOnly: - transaction->GetManager().StartTransaction(TransactionType_ReadOnly); - break; - - case OrthancPluginDatabaseTransactionType_ReadWrite: - transaction->GetManager().StartTransaction(TransactionType_ReadWrite); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - *target = reinterpret_cast(transaction.release()); - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); - } - - - static OrthancPluginErrorCode DestructTransaction(OrthancPluginDatabaseTransaction* transaction) - { - if (transaction == NULL) - { - return OrthancPluginErrorCode_NullPointer; + delete pool; } else { - delete reinterpret_cast(transaction); - return OrthancPluginErrorCode_Success; + LOG(ERROR) << "Received a null pointer from the Orthanc core, internal error"; } } - static OrthancPluginErrorCode Rollback(OrthancPluginDatabaseTransaction* transaction) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetManager().RollbackTransaction(); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode Commit(OrthancPluginDatabaseTransaction* transaction, - int64_t fileSizeDelta /* TODO - not used? */) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetManager().CommitTransaction(); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode AddAttachment2(OrthancPluginDatabaseTransaction* transaction, - int64_t id, - const OrthancPluginAttachment2* attachment, - int64_t revision) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().AddAttachment2(t->GetManager(), id, *attachment, revision); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode ClearChanges(OrthancPluginDatabaseTransaction* transaction) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().ClearChanges(t->GetManager()); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode ClearExportedResources(OrthancPluginDatabaseTransaction* transaction) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().ClearExportedResources(t->GetManager()); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode ClearMainDicomTags(OrthancPluginDatabaseTransaction* transaction, - int64_t resourceId) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().ClearMainDicomTags(t->GetManager(), resourceId); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode CreateInstance(OrthancPluginDatabaseTransaction* transaction, - OrthancPluginCreateInstanceResult* target /* out */, - const char* hashPatient, - const char* hashStudy, - const char* hashSeries, - const char* hashInstance) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - if (t->GetBackend().HasCreateInstance()) - { - t->GetBackend().CreateInstance(*target, t->GetManager(), hashPatient, hashStudy, hashSeries, hashInstance); - } - else - { - t->GetBackend().CreateInstanceGeneric(*target, t->GetManager(), hashPatient, hashStudy, hashSeries, hashInstance); - } - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode DeleteAttachment(OrthancPluginDatabaseTransaction* transaction, - int64_t id, - int32_t contentType) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().DeleteAttachment(t->GetOutput(), t->GetManager(), id, contentType); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode DeleteMetadata(OrthancPluginDatabaseTransaction* transaction, - int64_t id, - int32_t metadataType) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().DeleteMetadata(t->GetManager(), id, metadataType); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode DeleteResource(OrthancPluginDatabaseTransaction* transaction, - int64_t id) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().DeleteResource(t->GetOutput(), t->GetManager(), id); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetAllMetadata(OrthancPluginDatabaseTransaction* transaction, - int64_t id) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - std::map values; - t->GetBackend().GetAllMetadata(values, t->GetManager(), id); - - for (std::map::const_iterator it = values.begin(); it != values.end(); ++it) - { - t->GetOutput().AnswerMetadata(it->first, it->second); - } - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetAllPublicIds(OrthancPluginDatabaseTransaction* transaction, - OrthancPluginResourceType resourceType) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - std::list values; - t->GetBackend().GetAllPublicIds(values, t->GetManager(), resourceType); - t->GetOutput().AnswerStrings(values); - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetAllPublicIdsWithLimit(OrthancPluginDatabaseTransaction* transaction, - OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - std::list values; - t->GetBackend().GetAllPublicIds(values, t->GetManager(), resourceType, since, limit); - t->GetOutput().AnswerStrings(values); - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetChanges(OrthancPluginDatabaseTransaction* transaction, - uint8_t* targetDone /* out */, - int64_t since, - uint32_t maxResults) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - bool done; - t->GetBackend().GetChanges(t->GetOutput(), done, t->GetManager(), since, maxResults); - *targetDone = (done ? 1 : 0); - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetChildrenInternalId(OrthancPluginDatabaseTransaction* transaction, - int64_t id) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - std::list values; - t->GetBackend().GetChildrenInternalId(values, t->GetManager(), id); - t->GetOutput().AnswerIntegers64(values); - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetChildrenMetadata(OrthancPluginDatabaseTransaction* transaction, - int64_t resourceId, - int32_t metadata) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - std::list values; - t->GetBackend().GetChildrenMetadata(values, t->GetManager(), resourceId, metadata); - t->GetOutput().AnswerStrings(values); - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetChildrenPublicId(OrthancPluginDatabaseTransaction* transaction, - int64_t id) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - std::list values; - t->GetBackend().GetChildrenPublicId(values, t->GetManager(), id); - t->GetOutput().AnswerStrings(values); - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetExportedResources(OrthancPluginDatabaseTransaction* transaction, - uint8_t* targetDone /* out */, - int64_t since, - uint32_t maxResults) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - bool done; - t->GetBackend().GetExportedResources(t->GetOutput(), done, t->GetManager(), since, maxResults); - *targetDone = (done ? 1 : 0); - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetLastChange(OrthancPluginDatabaseTransaction* transaction) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().GetLastChange(t->GetOutput(), t->GetManager()); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetLastChangeIndex(OrthancPluginDatabaseTransaction* transaction, - int64_t* target) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - *target = t->GetBackend().GetLastChangeIndex(t->GetManager()); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetLastExportedResource(OrthancPluginDatabaseTransaction* transaction) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().GetLastExportedResource(t->GetOutput(), t->GetManager()); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetMainDicomTags(OrthancPluginDatabaseTransaction* transaction, - int64_t id) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().GetMainDicomTags(t->GetOutput(), t->GetManager(), id); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetPublicId(OrthancPluginDatabaseTransaction* transaction, - int64_t id) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetOutput().AnswerString(t->GetBackend().GetPublicId(t->GetManager(), id)); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetResourcesCount(OrthancPluginDatabaseTransaction* transaction, - uint64_t* target /* out */, - OrthancPluginResourceType resourceType) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - *target = t->GetBackend().GetResourcesCount(t->GetManager(), resourceType); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetResourceType(OrthancPluginDatabaseTransaction* transaction, - OrthancPluginResourceType* target /* out */, - uint64_t resourceId) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - *target = t->GetBackend().GetResourceType(t->GetManager(), resourceId); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetTotalCompressedSize(OrthancPluginDatabaseTransaction* transaction, - uint64_t* target /* out */) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - *target = t->GetBackend().GetTotalCompressedSize(t->GetManager()); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode GetTotalUncompressedSize(OrthancPluginDatabaseTransaction* transaction, - uint64_t* target /* out */) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - *target = t->GetBackend().GetTotalUncompressedSize(t->GetManager()); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode IsDiskSizeAbove(OrthancPluginDatabaseTransaction* transaction, - uint8_t* target, - uint64_t threshold) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - bool above = (t->GetBackend().GetTotalCompressedSize(t->GetManager()) >= threshold); - *target = (above ? 1 : 0); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode IsExistingResource(OrthancPluginDatabaseTransaction* transaction, - uint8_t* target, - int64_t resourceId) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - bool exists = t->GetBackend().IsExistingResource(t->GetManager(), resourceId); - *target = (exists ? 1 : 0); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode IsProtectedPatient(OrthancPluginDatabaseTransaction* transaction, - uint8_t* target, - int64_t resourceId) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - bool isProtected = t->GetBackend().IsProtectedPatient(t->GetManager(), resourceId); - *target = (isProtected ? 1 : 0); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode ListAvailableAttachments(OrthancPluginDatabaseTransaction* transaction, - int64_t resourceId) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - std::list values; - t->GetBackend().ListAvailableAttachments(values, t->GetManager(), resourceId); - t->GetOutput().AnswerIntegers32(values); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode LogChange(OrthancPluginDatabaseTransaction* transaction, - int32_t changeType, - int64_t resourceId, - OrthancPluginResourceType resourceType, - const char* date) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().LogChange(t->GetManager(), changeType, resourceId, resourceType, date); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode LogExportedResource(OrthancPluginDatabaseTransaction* transaction, - OrthancPluginResourceType resourceType, - const char* publicId, - const char* modality, - const char* date, - const char* patientId, - const char* studyInstanceUid, - const char* seriesInstanceUid, - const char* sopInstanceUid) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - OrthancPluginExportedResource exported; - exported.seq = 0; - exported.resourceType = resourceType; - exported.publicId = publicId; - exported.modality = modality; - exported.date = date; - exported.patientId = patientId; - exported.studyInstanceUid = studyInstanceUid; - exported.seriesInstanceUid = seriesInstanceUid; - exported.sopInstanceUid = sopInstanceUid; - - t->GetOutput().Clear(); - t->GetBackend().LogExportedResource(t->GetManager(), exported); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode LookupAttachment(OrthancPluginDatabaseTransaction* transaction, - int64_t* revision /* out */, - int64_t resourceId, - int32_t contentType) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().LookupAttachment(t->GetOutput(), *revision, t->GetManager(), resourceId, contentType); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode LookupGlobalProperty(OrthancPluginDatabaseTransaction* transaction, - const char* serverIdentifier, - int32_t property) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - std::string s; - if (t->GetBackend().LookupGlobalProperty(s, t->GetManager(), serverIdentifier, property)) - { - t->GetOutput().AnswerString(s); - } - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode LookupMetadata(OrthancPluginDatabaseTransaction* transaction, - int64_t* revision /* out */, - int64_t id, - int32_t metadata) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - std::string s; - if (t->GetBackend().LookupMetadata(s, *revision, t->GetManager(), id, metadata)) - { - t->GetOutput().AnswerString(s); - } - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode LookupParent(OrthancPluginDatabaseTransaction* transaction, - uint8_t* existing /* out */, - int64_t* parentId /* out */, - int64_t id) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - if (t->GetBackend().LookupParent(*parentId, t->GetManager(), id)) - { - *existing = 1; - } - else - { - *existing = 0; - } - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode LookupResource(OrthancPluginDatabaseTransaction* transaction, - uint8_t* isExisting /* out */, - int64_t* id /* out */, - OrthancPluginResourceType* type /* out */, - const char* publicId) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - if (t->GetBackend().LookupResource(*id, *type, t->GetManager(), publicId)) - { - *isExisting = 1; - } - else - { - *isExisting = 0; - } - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode LookupResources(OrthancPluginDatabaseTransaction* transaction, - uint32_t constraintsCount, - const OrthancPluginDatabaseConstraint* constraints, - OrthancPluginResourceType queryLevel, - uint32_t limit, - uint8_t requestSomeInstanceId) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - std::vector lookup; - lookup.reserve(constraintsCount); - - for (uint32_t i = 0; i < constraintsCount; i++) - { - lookup.push_back(Orthanc::DatabaseConstraint(constraints[i])); - } - - t->GetBackend().LookupResources(t->GetOutput(), t->GetManager(), lookup, queryLevel, limit, (requestSomeInstanceId != 0)); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode LookupResourceAndParent(OrthancPluginDatabaseTransaction* transaction, - uint8_t* isExisting /* out */, - int64_t* id /* out */, - OrthancPluginResourceType* type /* out */, - const char* publicId) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - std::string parent; - if (t->GetBackend().LookupResourceAndParent(*id, *type, parent, t->GetManager(), publicId)) - { - *isExisting = 1; - - if (!parent.empty()) - { - t->GetOutput().AnswerString(parent); - } - } - else - { - *isExisting = 0; - } - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode SelectPatientToRecycle(OrthancPluginDatabaseTransaction* transaction, - uint8_t* patientAvailable, - int64_t* patientId) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - if (t->GetBackend().SelectPatientToRecycle(*patientId, t->GetManager())) - { - *patientAvailable = 1; - } - else - { - *patientAvailable = 0; - } - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode SelectPatientToRecycle2(OrthancPluginDatabaseTransaction* transaction, - uint8_t* patientAvailable, - int64_t* patientId, - int64_t patientIdToAvoid) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - - if (t->GetBackend().SelectPatientToRecycle(*patientId, t->GetManager(), patientIdToAvoid)) - { - *patientAvailable = 1; - } - else - { - *patientAvailable = 0; - } - - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode SetGlobalProperty(OrthancPluginDatabaseTransaction* transaction, - const char* serverIdentifier, - int32_t property, - const char* value) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().SetGlobalProperty(t->GetManager(), serverIdentifier, property, value); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode SetMetadata(OrthancPluginDatabaseTransaction* transaction, - int64_t id, - int32_t metadata, - const char* value, - int64_t revision) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().SetMetadata(t->GetManager(), id, metadata, value, revision); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode SetProtectedPatient(OrthancPluginDatabaseTransaction* transaction, - int64_t id, - uint8_t isProtected) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().SetProtectedPatient(t->GetManager(), id, (isProtected != 0)); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - - static OrthancPluginErrorCode SetResourcesContent(OrthancPluginDatabaseTransaction* transaction, - uint32_t countIdentifierTags, - const OrthancPluginResourcesContentTags* identifierTags, - uint32_t countMainDicomTags, - const OrthancPluginResourcesContentTags* mainDicomTags, - uint32_t countMetadata, - const OrthancPluginResourcesContentMetadata* metadata) - { - DatabaseBackendAdapterV4::Transaction* t = reinterpret_cast(transaction); - - try - { - t->GetOutput().Clear(); - t->GetBackend().SetResourcesContent(t->GetManager(), countIdentifierTags, identifierTags, - countMainDicomTags, mainDicomTags, countMetadata, metadata); - return OrthancPluginErrorCode_Success; - } - ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); - } - - void DatabaseBackendAdapterV4::Register(IndexBackend* backend, size_t countConnections, unsigned int maxDatabaseRetries) { + std::unique_ptr pool(new IndexConnectionsPool(backend, countConnections)); + if (isBackendInUse_) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - - if (backend == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - OrthancPluginDatabaseBackendV4 params; - memset(¶ms, 0, sizeof(params)); - - params.readAnswersCount = ReadAnswersCount; - params.readAnswerAttachment2 = ReadAnswerAttachment2; - params.readAnswerChange = ReadAnswerChange; - params.readAnswerDicomTag = ReadAnswerDicomTag; - params.readAnswerExportedResource = ReadAnswerExportedResource; - params.readAnswerInt32 = ReadAnswerInt32; - params.readAnswerInt64 = ReadAnswerInt64; - params.readAnswerMatchingResource = ReadAnswerMatchingResource; - params.readAnswerMetadata = ReadAnswerMetadata; - params.readAnswerString = ReadAnswerString; - - params.readEventsCount = ReadEventsCount; - params.readEvent2 = ReadEvent2; - - params.open = Open; - params.close = Close; - params.destructDatabase = DestructDatabase; - params.getDatabaseVersion = GetDatabaseVersion; - params.upgradeDatabase = UpgradeDatabase; - params.hasRevisionsSupport = HasRevisionsSupport; - params.hasAttachmentCustomDataSupport = HasAttachmentCustomDataSupport; - params.startTransaction = StartTransaction; - params.destructTransaction = DestructTransaction; - params.rollback = Rollback; - params.commit = Commit; - - params.addAttachment2 = AddAttachment2; - params.clearChanges = ClearChanges; - params.clearExportedResources = ClearExportedResources; - params.clearMainDicomTags = ClearMainDicomTags; - params.createInstance = CreateInstance; - params.deleteAttachment = DeleteAttachment; - params.deleteMetadata = DeleteMetadata; - params.deleteResource = DeleteResource; - params.getAllMetadata = GetAllMetadata; - params.getAllPublicIds = GetAllPublicIds; - params.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit; - params.getChanges = GetChanges; - params.getChildrenInternalId = GetChildrenInternalId; - params.getChildrenMetadata = GetChildrenMetadata; - params.getChildrenPublicId = GetChildrenPublicId; - params.getExportedResources = GetExportedResources; - params.getLastChange = GetLastChange; - params.getLastChangeIndex = GetLastChangeIndex; - params.getLastExportedResource = GetLastExportedResource; - params.getMainDicomTags = GetMainDicomTags; - params.getPublicId = GetPublicId; - params.getResourceType = GetResourceType; - params.getResourcesCount = GetResourcesCount; - params.getTotalCompressedSize = GetTotalCompressedSize; - params.getTotalUncompressedSize = GetTotalUncompressedSize; - params.isDiskSizeAbove = IsDiskSizeAbove; - params.isExistingResource = IsExistingResource; - params.isProtectedPatient = IsProtectedPatient; - params.listAvailableAttachments = ListAvailableAttachments; - params.logChange = LogChange; - params.logExportedResource = LogExportedResource; - params.lookupAttachment = LookupAttachment; - params.lookupGlobalProperty = LookupGlobalProperty; - params.lookupMetadata = LookupMetadata; - params.lookupParent = LookupParent; - params.lookupResource = LookupResource; - params.lookupResourceAndParent = LookupResourceAndParent; - params.lookupResources = LookupResources; - params.selectPatientToRecycle = SelectPatientToRecycle; - params.selectPatientToRecycle2 = SelectPatientToRecycle2; - params.setGlobalProperty = SetGlobalProperty; - params.setMetadata = SetMetadata; - params.setProtectedPatient = SetProtectedPatient; - params.setResourcesContent = SetResourcesContent; OrthancPluginContext* context = backend->GetContext(); - if (OrthancPluginRegisterDatabaseBackendV4( - context, ¶ms, sizeof(params), maxDatabaseRetries, - new Adapter(backend, countConnections)) != OrthancPluginErrorCode_Success) + if (OrthancPluginRegisterDatabaseBackendV4(context, pool.release(), maxDatabaseRetries, + CallBackend, FinalizeBackend) != OrthancPluginErrorCode_Success) { + delete backend; throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Unable to register the database backend"); } - backend->SetOutputFactory(new Factory); - isBackendInUse_ = true; } @@ -2108,7 +1476,7 @@ { if (isBackendInUse_) { - fprintf(stderr, "The Orthanc core has not destructed the index backend, internal error\n"); + LOG(ERROR) << "The Orthanc core has not destructed the index backend, internal error"; } } } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/DatabaseBackendAdapterV4.h --- a/Framework/Plugins/DatabaseBackendAdapterV4.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/DatabaseBackendAdapterV4.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -35,28 +37,17 @@ * * Class creating the bridge between the C low-level primitives for * custom database engines, and the high-level IDatabaseBackend C++ - * interface, for Orthanc >= 1.12.0. + * interface, through ProtocolBuffers for Orthanc >= 1.12.0. **/ class DatabaseBackendAdapterV4 { private: - class Output; - // This class cannot be instantiated DatabaseBackendAdapterV4() { } public: - class Adapter; - class Transaction; - - class Factory : public IDatabaseBackendOutput::IFactory - { - public: - virtual IDatabaseBackendOutput* CreateOutput() ORTHANC_OVERRIDE; - }; - static void Register(IndexBackend* backend, size_t countConnections, unsigned int maxDatabaseRetries); diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/DatabaseConstraint.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/DatabaseConstraint.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,289 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +/** + * NB: Until 2024-09-09, this file was synchronized with the following + * folder from the Orthanc main project: + * https://orthanc.uclouvain.be/hg/orthanc/file/default/OrthancServer/Sources/Search/ + **/ + + +#include "DatabaseConstraint.h" + +#include + +#include +#include + + +namespace OrthancDatabases +{ + DatabaseConstraint::DatabaseConstraint(Orthanc::ResourceType level, + const Orthanc::DicomTag& tag, + bool isIdentifier, + ConstraintType type, + const std::vector& values, + bool caseSensitive, + bool mandatory) : + level_(level), + tag_(tag), + isIdentifier_(isIdentifier), + constraintType_(type), + values_(values), + caseSensitive_(caseSensitive), + mandatory_(mandatory) + { + if (type != ConstraintType_List && + values_.size() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + DatabaseConstraint::DatabaseConstraint(const OrthancPluginDatabaseConstraint& constraint) : + level_(MessagesToolbox::Convert(constraint.level)), + tag_(constraint.tagGroup, constraint.tagElement), + isIdentifier_(constraint.isIdentifierTag), + constraintType_(MessagesToolbox::Convert(constraint.type)), + caseSensitive_(constraint.isCaseSensitive), + mandatory_(constraint.isMandatory) + { + if (constraintType_ != ConstraintType_List && + constraint.valuesCount != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + values_.resize(constraint.valuesCount); + + for (uint32_t i = 0; i < constraint.valuesCount; i++) + { + assert(constraint.values[i] != NULL); + values_[i].assign(constraint.values[i]); + } + } +#endif + + +#if ORTHANC_PLUGINS_HAS_INTEGRATED_FIND == 1 + DatabaseConstraint::DatabaseConstraint(const Orthanc::DatabasePluginMessages::DatabaseConstraint& constraint) : + level_(MessagesToolbox::Convert(constraint.level())), + tag_(constraint.tag_group(), constraint.tag_element()), + isIdentifier_(constraint.is_identifier_tag()), + caseSensitive_(constraint.is_case_sensitive()), + mandatory_(constraint.is_mandatory()) + { + switch (constraint.type()) + { + case Orthanc::DatabasePluginMessages::CONSTRAINT_EQUAL: + constraintType_ = ConstraintType_Equal; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_SMALLER_OR_EQUAL: + constraintType_ = ConstraintType_SmallerOrEqual; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_GREATER_OR_EQUAL: + constraintType_ = ConstraintType_GreaterOrEqual; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_WILDCARD: + constraintType_ = ConstraintType_Wildcard; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_LIST: + constraintType_ = ConstraintType_List; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (constraintType_ != ConstraintType_List && + constraint.values().size() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + values_.resize(constraint.values().size()); + + for (int i = 0; i < constraint.values().size(); i++) + { + values_[i] = constraint.values(i); + } + } +#endif + + + const std::string& DatabaseConstraint::GetValue(size_t index) const + { + if (index >= values_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + return values_[index]; + } + } + + + const std::string& DatabaseConstraint::GetSingleValue() const + { + if (values_.size() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return values_[0]; + } + } + + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + void DatabaseConstraint::EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint, + std::vector& tmpValues) const + { + memset(&constraint, 0, sizeof(constraint)); + + tmpValues.resize(values_.size()); + + for (size_t i = 0; i < values_.size(); i++) + { + tmpValues[i] = values_[i].c_str(); + } + + constraint.level = MessagesToolbox::ConvertToPlainC(level_); + constraint.tagGroup = tag_.GetGroup(); + constraint.tagElement = tag_.GetElement(); + constraint.isIdentifierTag = isIdentifier_; + constraint.isCaseSensitive = caseSensitive_; + constraint.isMandatory = mandatory_; + constraint.type = MessagesToolbox::ConvertToPlainC(constraintType_); + constraint.valuesCount = values_.size(); + constraint.values = (tmpValues.empty() ? NULL : &tmpValues[0]); + } +#endif + + + void DatabaseConstraints::Clear() + { + for (size_t i = 0; i < constraints_.size(); i++) + { + assert(constraints_[i] != NULL); + delete constraints_[i]; + } + + constraints_.clear(); + } + + + void DatabaseConstraints::AddConstraint(DatabaseConstraint* constraint) + { + if (constraint == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + constraints_.push_back(constraint); + } + } + + + const DatabaseConstraint& DatabaseConstraints::GetConstraint(size_t index) const + { + if (index >= constraints_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + assert(constraints_[index] != NULL); + return *constraints_[index]; + } + } + + + std::string DatabaseConstraints::Format() const + { + std::string s; + + for (size_t i = 0; i < constraints_.size(); i++) + { + assert(constraints_[i] != NULL); + const DatabaseConstraint& constraint = *constraints_[i]; + s += "Constraint " + boost::lexical_cast(i) + " at " + EnumerationToString(constraint.GetLevel()) + + ": " + constraint.GetTag().Format(); + + switch (constraint.GetConstraintType()) + { + case ConstraintType_Equal: + s += " == " + constraint.GetSingleValue(); + break; + + case ConstraintType_SmallerOrEqual: + s += " <= " + constraint.GetSingleValue(); + break; + + case ConstraintType_GreaterOrEqual: + s += " >= " + constraint.GetSingleValue(); + break; + + case ConstraintType_Wildcard: + s += " ~~ " + constraint.GetSingleValue(); + break; + + case ConstraintType_List: + { + s += " in [ "; + bool first = true; + for (size_t j = 0; j < constraint.GetValuesCount(); j++) + { + if (first) + { + first = false; + } + else + { + s += ", "; + } + s += constraint.GetValue(j); + } + s += "]"; + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + s += "\n"; + } + + return s; + } +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/DatabaseConstraint.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/DatabaseConstraint.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,146 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +/** + * NB: Until 2024-09-09, this file was synchronized with the following + * folder from the Orthanc main project: + * https://orthanc.uclouvain.be/hg/orthanc/file/default/OrthancServer/Sources/Search/ + **/ + + +#pragma once + +#include "MessagesToolbox.h" + +#include + +#include + +namespace OrthancDatabases +{ + class DatabaseConstraint : public boost::noncopyable + { + private: + Orthanc::ResourceType level_; + Orthanc::DicomTag tag_; + bool isIdentifier_; + ConstraintType constraintType_; + std::vector values_; + bool caseSensitive_; + bool mandatory_; + + public: + DatabaseConstraint(Orthanc::ResourceType level, + const Orthanc::DicomTag& tag, + bool isIdentifier, + ConstraintType type, + const std::vector& values, + bool caseSensitive, + bool mandatory); + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + explicit DatabaseConstraint(const OrthancPluginDatabaseConstraint& constraint); +#endif + +#if ORTHANC_PLUGINS_HAS_INTEGRATED_FIND == 1 + explicit DatabaseConstraint(const Orthanc::DatabasePluginMessages::DatabaseConstraint& constraint); +#endif + + Orthanc::ResourceType GetLevel() const + { + return level_; + } + + const Orthanc::DicomTag& GetTag() const + { + return tag_; + } + + bool IsIdentifier() const + { + return isIdentifier_; + } + + ConstraintType GetConstraintType() const + { + return constraintType_; + } + + size_t GetValuesCount() const + { + return values_.size(); + } + + const std::string& GetValue(size_t index) const; + + const std::string& GetSingleValue() const; + + bool IsCaseSensitive() const + { + return caseSensitive_; + } + + bool IsMandatory() const + { + return mandatory_; + } + + bool IsMatch(const Orthanc::DicomMap& dicom) const; + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + void EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint, + std::vector& tmpValues) const; +#endif + }; + + + class DatabaseConstraints : public boost::noncopyable + { + private: + std::deque constraints_; + + public: + ~DatabaseConstraints() + { + Clear(); + } + + void Clear(); + + void AddConstraint(DatabaseConstraint* constraint); // Takes ownership + + bool IsEmpty() const + { + return constraints_.empty(); + } + + size_t GetSize() const + { + return constraints_.size(); + } + + const DatabaseConstraint& GetConstraint(size_t index) const; + + std::string Format() const; + }; +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/GlobalProperties.h --- a/Framework/Plugins/GlobalProperties.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/GlobalProperties.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -30,7 +32,7 @@ * The enum "GlobalProperty" is a subset of the "GlobalProperty_XXX" * values from the Orthanc server that have a special meaning to the * database plugins: - * https://hg.orthanc-server.com/orthanc/file/default/OrthancServer/Sources/ServerEnumerations.h + * https://orthanc.uclouvain.be/hg/orthanc/file/default/OrthancServer/Sources/ServerEnumerations.h * * WARNING: The values must be the same between the Orthanc core and * this enum! diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/IDatabaseBackend.h --- a/Framework/Plugins/IDatabaseBackend.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/IDatabaseBackend.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -22,9 +24,11 @@ #pragma once -#include "IDatabaseBackendOutput.h" +#include "../Common/DatabaseManager.h" #include "../Common/DatabasesEnumerations.h" -#include "../Common/DatabaseManager.h" +#include "IDatabaseBackendOutput.h" +#include "ISqlLookupFormatter.h" +#include "IdentifierTag.h" #include @@ -41,8 +45,13 @@ virtual IDatabaseFactory* CreateDatabaseFactory() = 0; - // This function is invoked once, even if multiple connections are open - virtual void ConfigureDatabase(DatabaseManager& database) = 0; + /** + * This function is invoked once, even if multiple connections are + * open. It is notably used to update the schema of the database. + **/ + virtual void ConfigureDatabase(DatabaseManager& database, + bool hasIdentifierTags, + const std::list& identifierTags) = 0; virtual void SetOutputFactory(IDatabaseBackendOutput::IFactory* factory) = 0; @@ -101,15 +110,23 @@ virtual void GetAllPublicIds(std::list& target, DatabaseManager& manager, OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) = 0; + int64_t since, + uint32_t limit) = 0; /* Use GetOutput().AnswerChange() */ virtual void GetChanges(IDatabaseBackendOutput& output, bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) = 0; + uint32_t limit) = 0; + + virtual void GetChangesExtended(IDatabaseBackendOutput& output, + bool& done /*out*/, + DatabaseManager& manager, + int64_t since, + int64_t to, + const std::set& changeTypes, + uint32_t limit) = 0; virtual void GetChildrenInternalId(std::list& target /*out*/, DatabaseManager& manager, @@ -124,7 +141,7 @@ bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) = 0; + uint32_t limit) = 0; /* Use GetOutput().AnswerChange() */ virtual void GetLastChange(IDatabaseBackendOutput& output, @@ -173,7 +190,14 @@ const char* date) = 0; virtual void LogExportedResource(DatabaseManager& manager, - const OrthancPluginExportedResource& resource) = 0; + OrthancPluginResourceType resourceType, + const char* publicId, + const char* modality, + const char* date, + const char* patientId, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid) = 0; /* Use GetOutput().AnswerAttachment() */ virtual bool LookupAttachment(IDatabaseBackendOutput& output, @@ -271,8 +295,10 @@ #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 virtual void LookupResources(IDatabaseBackendOutput& output, DatabaseManager& manager, - const std::vector& lookup, + const DatabaseConstraints& lookup, OrthancPluginResourceType queryLevel, + const std::set& labels, // New in Orthanc 1.12.0 + LabelsConstraint labelsConstraint, // New in Orthanc 1.12.0 uint32_t limit, bool requestSomeInstance) = 0; #endif @@ -309,23 +335,76 @@ virtual void TagMostRecentPatient(DatabaseManager& manager, int64_t patientId) = 0; -#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in 1.3.1 -# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) // NB: "parentPublicId" must be cleared if the resource has no parent virtual bool LookupResourceAndParent(int64_t& id, OrthancPluginResourceType& type, std::string& parentPublicId, DatabaseManager& manager, const char* publicId) = 0; -# endif -#endif -#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in 1.3.1 -# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) virtual void GetAllMetadata(std::map& result, DatabaseManager& manager, int64_t id) = 0; -# endif + + // New in Orthanc 1.12.0 + virtual bool HasLabelsSupport() const = 0; + + // New in Orthanc 1.12.0 + virtual void AddLabel(DatabaseManager& manager, + int64_t resource, + const std::string& label) = 0; + + // New in Orthanc 1.12.0 + virtual void RemoveLabel(DatabaseManager& manager, + int64_t resource, + const std::string& label) = 0; + + // New in Orthanc 1.12.0 + virtual void ListLabels(std::list& target, + DatabaseManager& manager, + int64_t resource) = 0; + + // New in Orthanc 1.12.0 + virtual void ListAllLabels(std::list& target, + DatabaseManager& manager) = 0; + + // New in Orthanc 1.12.3 + virtual bool HasAtomicIncrementGlobalProperty() = 0; + + // New in Orthanc 1.12.3 + virtual int64_t IncrementGlobalProperty(DatabaseManager& manager, + const char* serverIdentifier, + int32_t property, + int64_t increment) = 0; + + // New in Orthanc 1.12.3 + virtual bool HasUpdateAndGetStatistics() = 0; + + // New in Orthanc 1.12.3 + virtual void UpdateAndGetStatistics(DatabaseManager& manager, + int64_t& patientsCount, + int64_t& studiesCount, + int64_t& seriesCount, + int64_t& instancesCount, + int64_t& compressedSize, + int64_t& uncompressedSize) = 0; + + // New in Orthanc 1.12.3 + virtual bool HasMeasureLatency() = 0; + + // New in Orthanc 1.12.3 + virtual uint64_t MeasureLatency(DatabaseManager& manager) = 0; + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + virtual bool HasFindSupport() const = 0; + virtual bool HasExtendedChanges() const = 0; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + // New in Orthanc 1.12.5 + virtual void ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, + DatabaseManager& manager, + const Orthanc::DatabasePluginMessages::Find_Request& request) = 0; #endif }; } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/IDatabaseBackendOutput.h --- a/Framework/Plugins/IDatabaseBackendOutput.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/IDatabaseBackendOutput.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -21,7 +23,7 @@ #pragma once -#include "../../Resources/Orthanc/Databases/DatabaseConstraint.h" +#include "DatabaseConstraint.h" namespace OrthancDatabases { diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/ISqlLookupFormatter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/ISqlLookupFormatter.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,944 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +/** + * NB: Until 2024-09-09, this file was synchronized with the following + * folder from the Orthanc main project: + * https://orthanc.uclouvain.be/hg/orthanc/file/default/OrthancServer/Sources/Search/ + **/ + + +#include "ISqlLookupFormatter.h" + +#include "DatabaseConstraint.h" + +#include +#include + +#include +#include +#include + + +namespace OrthancDatabases +{ + static std::string FormatLevel(Orthanc::ResourceType level) + { + switch (level) + { + case Orthanc::ResourceType_Patient: + return "patients"; + + case Orthanc::ResourceType_Study: + return "studies"; + + case Orthanc::ResourceType_Series: + return "series"; + + case Orthanc::ResourceType_Instance: + return "instances"; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + static bool FormatComparison(std::string& target, + ISqlLookupFormatter& formatter, + const DatabaseConstraint& constraint, + size_t index, + bool escapeBrackets) + { + std::string tag = "t" + boost::lexical_cast(index); + + std::string comparison; + + switch (constraint.GetConstraintType()) + { + case ConstraintType_Equal: + case ConstraintType_SmallerOrEqual: + case ConstraintType_GreaterOrEqual: + { + std::string op; + switch (constraint.GetConstraintType()) + { + case ConstraintType_Equal: + op = "="; + break; + + case ConstraintType_SmallerOrEqual: + op = "<="; + break; + + case ConstraintType_GreaterOrEqual: + op = ">="; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::string parameter = formatter.GenerateParameter(constraint.GetSingleValue()); + + if (constraint.IsCaseSensitive()) + { + comparison = tag + ".value " + op + " " + parameter; + } + else + { + comparison = "lower(" + tag + ".value) " + op + " lower(" + parameter + ")"; + } + + break; + } + + case ConstraintType_List: + { + for (size_t i = 0; i < constraint.GetValuesCount(); i++) + { + if (!comparison.empty()) + { + comparison += ", "; + } + + std::string parameter = formatter.GenerateParameter(constraint.GetValue(i)); + + if (constraint.IsCaseSensitive()) + { + comparison += parameter; + } + else + { + comparison += "lower(" + parameter + ")"; + } + } + + if (constraint.IsCaseSensitive()) + { + comparison = tag + ".value IN (" + comparison + ")"; + } + else + { + comparison = "lower(" + tag + ".value) IN (" + comparison + ")"; + } + + break; + } + + case ConstraintType_Wildcard: + { + const std::string value = constraint.GetSingleValue(); + + if (value == "*") + { + if (!constraint.IsMandatory()) + { + // Universal constraint on an optional tag, ignore it + return false; + } + } + else + { + std::string escaped; + escaped.reserve(value.size()); + + for (size_t i = 0; i < value.size(); i++) + { + if (value[i] == '*') + { + escaped += "%"; + } + else if (value[i] == '?') + { + escaped += "_"; + } + else if (value[i] == '%') + { + escaped += "\\%"; + } + else if (value[i] == '_') + { + escaped += "\\_"; + } + else if (value[i] == '\\') + { + escaped += "\\\\"; + } + else if (escapeBrackets && value[i] == '[') + { + escaped += "\\["; + } + else if (escapeBrackets && value[i] == ']') + { + escaped += "\\]"; + } + else + { + escaped += value[i]; + } + } + + std::string parameter = formatter.GenerateParameter(escaped); + + if (constraint.IsCaseSensitive()) + { + comparison = (tag + ".value LIKE " + parameter + " " + + formatter.FormatWildcardEscape()); + } + else + { + comparison = ("lower(" + tag + ".value) LIKE lower(" + + parameter + ") " + formatter.FormatWildcardEscape()); + } + } + + break; + } + + default: + return false; + } + + if (constraint.IsMandatory()) + { + target = comparison; + } + else if (comparison.empty()) + { + target = tag + ".value IS NULL"; + } + else + { + target = tag + ".value IS NULL OR " + comparison; + } + + return true; + } + + + static void FormatJoin(std::string& target, + const DatabaseConstraint& constraint, + size_t index) + { + std::string tag = "t" + boost::lexical_cast(index); + + if (constraint.IsMandatory()) + { + target = " INNER JOIN "; + } + else + { + target = " LEFT JOIN "; + } + + if (constraint.IsIdentifier()) + { + target += "DicomIdentifiers "; + } + else + { + target += "MainDicomTags "; + } + + target += (tag + " ON " + tag + ".id = " + FormatLevel(constraint.GetLevel()) + + ".internalId AND " + tag + ".tagGroup = " + + boost::lexical_cast(constraint.GetTag().GetGroup()) + + " AND " + tag + ".tagElement = " + + boost::lexical_cast(constraint.GetTag().GetElement())); + } + + + static std::string Join(const std::list& values, + const std::string& prefix, + const std::string& separator) + { + if (values.empty()) + { + return ""; + } + else + { + std::string s = prefix; + + bool first = true; + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + if (first) + { + first = false; + } + else + { + s += separator; + } + + s += *it; + } + + return s; + } + } + + static bool FormatComparison2(std::string& target, + ISqlLookupFormatter& formatter, + const DatabaseConstraint& constraint, + bool escapeBrackets) + { + std::string comparison; + std::string tagFilter = ("tagGroup = " + boost::lexical_cast(constraint.GetTag().GetGroup()) + + " AND tagElement = " + boost::lexical_cast(constraint.GetTag().GetElement())); + + switch (constraint.GetConstraintType()) + { + case ConstraintType_Equal: + case ConstraintType_SmallerOrEqual: + case ConstraintType_GreaterOrEqual: + { + std::string op; + switch (constraint.GetConstraintType()) + { + case ConstraintType_Equal: + op = "="; + break; + + case ConstraintType_SmallerOrEqual: + op = "<="; + break; + + case ConstraintType_GreaterOrEqual: + op = ">="; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::string parameter = formatter.GenerateParameter(constraint.GetSingleValue()); + + if (constraint.IsCaseSensitive()) + { + comparison = " AND value " + op + " " + parameter; + } + else + { + comparison = " AND lower(value) " + op + " lower(" + parameter + ")"; + } + + break; + } + + case ConstraintType_List: + { + std::vector comparisonValues; + for (size_t i = 0; i < constraint.GetValuesCount(); i++) + { + std::string parameter = formatter.GenerateParameter(constraint.GetValue(i)); + + if (constraint.IsCaseSensitive()) + { + comparisonValues.push_back(parameter); + } + else + { + comparisonValues.push_back("lower(" + parameter + ")"); + } + } + + std::string values; + Orthanc::Toolbox::JoinStrings(values, comparisonValues, ", "); + + if (constraint.IsCaseSensitive()) + { + comparison = " AND value IN (" + values + ")"; + } + else + { + comparison = " AND lower(value) IN (" + values + ")"; + } + + break; + } + + case ConstraintType_Wildcard: + { + const std::string value = constraint.GetSingleValue(); + + if (value == "*") + { + if (!constraint.IsMandatory()) + { + // Universal constraint on an optional tag, ignore it + return false; + } + } + else + { + std::string escaped; + escaped.reserve(value.size()); + + for (size_t i = 0; i < value.size(); i++) + { + if (value[i] == '*') + { + escaped += "%"; + } + else if (value[i] == '?') + { + escaped += "_"; + } + else if (value[i] == '%') + { + escaped += "\\%"; + } + else if (value[i] == '_') + { + escaped += "\\_"; + } + else if (value[i] == '\\') + { + escaped += "\\\\"; + } + else if (escapeBrackets && value[i] == '[') + { + escaped += "\\["; + } + else if (escapeBrackets && value[i] == ']') + { + escaped += "\\]"; + } + else + { + escaped += value[i]; + } + } + + std::string parameter = formatter.GenerateParameter(escaped); + + if (constraint.IsCaseSensitive()) + { + comparison = " AND value LIKE " + parameter + " " + formatter.FormatWildcardEscape(); + } + else + { + comparison = " AND lower(value) LIKE lower(" + parameter + ") " + formatter.FormatWildcardEscape(); + } + } + + break; + } + + default: + return false; + } + + if (constraint.IsMandatory()) + { + target = tagFilter + comparison; + } + else if (comparison.empty()) + { + target = tagFilter + " AND value IS NULL"; + } + else + { + target = tagFilter + " AND value IS NULL OR " + comparison; + } + + return true; + } + + + void ISqlLookupFormatter::GetLookupLevels(Orthanc::ResourceType& lowerLevel, + Orthanc::ResourceType& upperLevel, + const Orthanc::ResourceType& queryLevel, + const DatabaseConstraints& lookup) + { + assert(Orthanc::ResourceType_Patient < Orthanc::ResourceType_Study && + Orthanc::ResourceType_Study < Orthanc::ResourceType_Series && + Orthanc::ResourceType_Series < Orthanc::ResourceType_Instance); + + lowerLevel = queryLevel; + upperLevel = queryLevel; + + for (size_t i = 0; i < lookup.GetSize(); i++) + { + Orthanc::ResourceType level = lookup.GetConstraint(i).GetLevel(); + + if (level < upperLevel) + { + upperLevel = level; + } + + if (level > lowerLevel) + { + lowerLevel = level; + } + } + } + + + void ISqlLookupFormatter::Apply(std::string& sql, + ISqlLookupFormatter& formatter, + const DatabaseConstraints& lookup, + Orthanc::ResourceType queryLevel, + const std::set& labels, + LabelsConstraint labelsConstraint, + size_t limit) + { + Orthanc::ResourceType lowerLevel, upperLevel; + GetLookupLevels(lowerLevel, upperLevel, queryLevel, lookup); + + assert(upperLevel <= queryLevel && + queryLevel <= lowerLevel); + + const bool escapeBrackets = formatter.IsEscapeBrackets(); + + std::string joins, comparisons; + + size_t count = 0; + + for (size_t i = 0; i < lookup.GetSize(); i++) + { + const DatabaseConstraint& constraint = lookup.GetConstraint(i); + + std::string comparison; + + if (FormatComparison(comparison, formatter, constraint, count, escapeBrackets)) + { + std::string join; + FormatJoin(join, constraint, count); + joins += join; + + if (!comparison.empty()) + { + comparisons += " AND " + comparison; + } + + count ++; + } + } + + sql = ("SELECT " + + FormatLevel(queryLevel) + ".publicId, " + + FormatLevel(queryLevel) + ".internalId" + + " FROM Resources AS " + FormatLevel(queryLevel)); + + for (int level = queryLevel - 1; level >= upperLevel; level--) + { + sql += (" INNER JOIN Resources " + + FormatLevel(static_cast(level)) + " ON " + + FormatLevel(static_cast(level)) + ".internalId=" + + FormatLevel(static_cast(level + 1)) + ".parentId"); + } + + for (int level = queryLevel + 1; level <= lowerLevel; level++) + { + sql += (" INNER JOIN Resources " + + FormatLevel(static_cast(level)) + " ON " + + FormatLevel(static_cast(level - 1)) + ".internalId=" + + FormatLevel(static_cast(level)) + ".parentId"); + } + + std::list where; + where.push_back(FormatLevel(queryLevel) + ".resourceType = " + + formatter.FormatResourceType(queryLevel) + comparisons); + + if (!labels.empty()) + { + /** + * "In SQL Server, NOT EXISTS and NOT IN predicates are the best + * way to search for missing values, as long as both columns in + * question are NOT NULL." + * https://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-is-null-sql-server/ + **/ + + std::list formattedLabels; + for (std::set::const_iterator it = labels.begin(); it != labels.end(); ++it) + { + formattedLabels.push_back(formatter.GenerateParameter(*it)); + } + + std::string condition; + switch (labelsConstraint) + { + case LabelsConstraint_Any: + condition = "> 0"; + break; + + case LabelsConstraint_All: + condition = "= " + boost::lexical_cast(labels.size()); + break; + + case LabelsConstraint_None: + condition = "= 0"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + where.push_back("(SELECT COUNT(1) FROM Labels AS selectedLabels WHERE selectedLabels.id = " + FormatLevel(queryLevel) + + ".internalId AND selectedLabels.label IN (" + Join(formattedLabels, "", ", ") + ")) " + condition); + } + + sql += joins + Join(where, " WHERE ", " AND "); + + if (limit != 0) + { + sql += " LIMIT " + boost::lexical_cast(limit); + } + } + + +#if ORTHANC_PLUGINS_HAS_INTEGRATED_FIND == 1 + static Orthanc::ResourceType DetectLevel(const Orthanc::DatabasePluginMessages::Find_Request& request) + { + // This corresponds to "Orthanc::OrthancIdentifiers()::DetectLevel()" in the Orthanc core + if (!request.orthanc_id_patient().empty() && + request.orthanc_id_study().empty() && + request.orthanc_id_series().empty() && + request.orthanc_id_instance().empty()) + { + return Orthanc::ResourceType_Patient; + } + else if (!request.orthanc_id_study().empty() && + request.orthanc_id_series().empty() && + request.orthanc_id_instance().empty()) + { + return Orthanc::ResourceType_Study; + } + else if (!request.orthanc_id_series().empty() && + request.orthanc_id_instance().empty()) + { + return Orthanc::ResourceType_Series; + } + else if (!request.orthanc_id_instance().empty()) + { + return Orthanc::ResourceType_Instance; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + void ISqlLookupFormatter::Apply(std::string& sql, + ISqlLookupFormatter& formatter, + const Orthanc::DatabasePluginMessages::Find_Request& request) + { + const bool escapeBrackets = formatter.IsEscapeBrackets(); + Orthanc::ResourceType queryLevel = MessagesToolbox::Convert(request.level()); + const std::string& strQueryLevel = FormatLevel(queryLevel); + + DatabaseConstraints constraints; + + for (int i = 0; i < request.dicom_tag_constraints().size(); i++) + { + constraints.AddConstraint(new DatabaseConstraint(request.dicom_tag_constraints(i))); + } + + Orthanc::ResourceType lowerLevel, upperLevel; + GetLookupLevels(lowerLevel, upperLevel, queryLevel, constraints); + + assert(upperLevel <= queryLevel && + queryLevel <= lowerLevel); + + std::string ordering = "row_number() over (order by " + strQueryLevel + ".publicId) as rowNumber"; // we need a default ordering in order to make default queries repeatable when using since&limit + + sql = ("SELECT " + + strQueryLevel + ".publicId, " + + strQueryLevel + ".internalId, " + + ordering + + " FROM Resources AS " + strQueryLevel); + + + std::string joins, comparisons; + + const bool isOrthancIdentifiersDefined = (!request.orthanc_id_patient().empty() || + !request.orthanc_id_study().empty() || + !request.orthanc_id_series().empty() || + !request.orthanc_id_instance().empty()); + + if (isOrthancIdentifiersDefined && + Orthanc::IsResourceLevelAboveOrEqual(DetectLevel(request), queryLevel)) + { + // single child resource matching, there should not be other constraints (at least for now) + if (request.dicom_tag_constraints().size() != 0 || + request.labels().size() != 0 || + request.has_limits()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + Orthanc::ResourceType topParentLevel = DetectLevel(request); + const std::string& strTopParentLevel = FormatLevel(topParentLevel); + + std::string publicId; + switch (topParentLevel) + { + case Orthanc::ResourceType_Patient: + publicId = request.orthanc_id_patient(); + break; + + case Orthanc::ResourceType_Study: + publicId = request.orthanc_id_study(); + break; + + case Orthanc::ResourceType_Series: + publicId = request.orthanc_id_series(); + break; + + case Orthanc::ResourceType_Instance: + publicId = request.orthanc_id_instance(); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (publicId.empty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + comparisons = " AND " + strTopParentLevel + ".publicId = " + formatter.GenerateParameter(publicId); + + for (int level = queryLevel; level > topParentLevel; level--) + { + sql += (" INNER JOIN Resources " + + FormatLevel(static_cast(level - 1)) + " ON " + + FormatLevel(static_cast(level - 1)) + ".internalId=" + + FormatLevel(static_cast(level)) + ".parentId"); + } + } + else + { + size_t count = 0; + + for (size_t i = 0; i < constraints.GetSize(); i++) + { + const DatabaseConstraint& constraint = constraints.GetConstraint(i); + + std::string comparison; + + if (FormatComparison(comparison, formatter, constraint, count, escapeBrackets)) + { + std::string join; + FormatJoin(join, constraint, count); + joins += join; + + if (!comparison.empty()) + { + comparisons += " AND " + comparison; + } + + count ++; + } + } + } + + for (int level = queryLevel - 1; level >= upperLevel; level--) + { + sql += (" INNER JOIN Resources " + + FormatLevel(static_cast(level)) + " ON " + + FormatLevel(static_cast(level)) + ".internalId=" + + FormatLevel(static_cast(level + 1)) + ".parentId"); + } + + for (int level = queryLevel + 1; level <= lowerLevel; level++) + { + sql += (" INNER JOIN Resources " + + FormatLevel(static_cast(level)) + " ON " + + FormatLevel(static_cast(level - 1)) + ".internalId=" + + FormatLevel(static_cast(level)) + ".parentId"); + } + + std::list where; + where.push_back(strQueryLevel + ".resourceType = " + + formatter.FormatResourceType(queryLevel) + comparisons); + + + if (!request.labels().empty()) + { + /** + * "In SQL Server, NOT EXISTS and NOT IN predicates are the best + * way to search for missing values, as long as both columns in + * question are NOT NULL." + * https://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-is-null-sql-server/ + **/ + + std::list formattedLabels; + for (int i = 0; i < request.labels().size(); i++) + { + formattedLabels.push_back(formatter.GenerateParameter(request.labels(i))); + } + + std::string condition; + switch (request.labels_constraint()) + { + case Orthanc::DatabasePluginMessages::LABELS_CONSTRAINT_ANY: + condition = "> 0"; + break; + + case Orthanc::DatabasePluginMessages::LABELS_CONSTRAINT_ALL: + condition = "= " + boost::lexical_cast(request.labels().size()); + break; + + case Orthanc::DatabasePluginMessages::LABELS_CONSTRAINT_NONE: + condition = "= 0"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + where.push_back("(SELECT COUNT(1) FROM Labels AS selectedLabels WHERE selectedLabels.id = " + strQueryLevel + + ".internalId AND selectedLabels.label IN (" + Join(formattedLabels, "", ", ") + ")) " + condition); + } + + sql += joins + Join(where, " WHERE ", " AND "); + + if (request.has_limits()) + { + sql += formatter.FormatLimits(request.limits().since(), request.limits().count()); + } + + } +#endif + + + void ISqlLookupFormatter::ApplySingleLevel(std::string& sql, + ISqlLookupFormatter& formatter, + const DatabaseConstraints& lookup, + Orthanc::ResourceType queryLevel, + const std::set& labels, + LabelsConstraint labelsConstraint, + size_t limit + ) + { + Orthanc::ResourceType lowerLevel, upperLevel; + GetLookupLevels(lowerLevel, upperLevel, queryLevel, lookup); + + assert(upperLevel == queryLevel && + queryLevel == lowerLevel); + + const bool escapeBrackets = formatter.IsEscapeBrackets(); + + std::vector mainDicomTagsComparisons, dicomIdentifiersComparisons; + + for (size_t i = 0; i < lookup.GetSize(); i++) + { + const DatabaseConstraint& constraint = lookup.GetConstraint(i); + + std::string comparison; + + if (FormatComparison2(comparison, formatter, constraint, escapeBrackets)) + { + if (!comparison.empty()) + { + if (constraint.IsIdentifier()) + { + dicomIdentifiersComparisons.push_back(comparison); + } + else + { + mainDicomTagsComparisons.push_back(comparison); + } + } + } + } + + sql = ("SELECT publicId, internalId " + "FROM Resources " + "WHERE resourceType = " + formatter.FormatResourceType(queryLevel) + + " "); + + if (dicomIdentifiersComparisons.size() > 0) + { + for (std::vector::const_iterator it = dicomIdentifiersComparisons.begin(); it < dicomIdentifiersComparisons.end(); ++it) + { + sql += (" AND internalId IN (SELECT id FROM DicomIdentifiers WHERE " + *it + ") "); + } + } + + if (mainDicomTagsComparisons.size() > 0) + { + for (std::vector::const_iterator it = mainDicomTagsComparisons.begin(); it < mainDicomTagsComparisons.end(); ++it) + { + sql += (" AND internalId IN (SELECT id FROM MainDicomTags WHERE " + *it + ") "); + } + } + + if (!labels.empty()) + { + /** + * "In SQL Server, NOT EXISTS and NOT IN predicates are the best + * way to search for missing values, as long as both columns in + * question are NOT NULL." + * https://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-is-null-sql-server/ + **/ + + std::list formattedLabels; + for (std::set::const_iterator it = labels.begin(); it != labels.end(); ++it) + { + formattedLabels.push_back(formatter.GenerateParameter(*it)); + } + + std::string condition; + std::string inOrNotIn; + switch (labelsConstraint) + { + case LabelsConstraint_Any: + condition = "> 0"; + inOrNotIn = "IN"; + break; + + case LabelsConstraint_All: + condition = "= " + boost::lexical_cast(labels.size()); + inOrNotIn = "IN"; + break; + + case LabelsConstraint_None: + condition = "> 0"; + inOrNotIn = "NOT IN"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + sql += (" AND internalId " + inOrNotIn + " (SELECT id" + " FROM (SELECT id, COUNT(1) AS labelsCount " + "FROM Labels " + "WHERE label IN (" + Join(formattedLabels, "", ", ") + ") GROUP BY id" + ") AS temp " + " WHERE labelsCount " + condition + ")"); + } + + if (limit != 0) + { + sql += " LIMIT " + boost::lexical_cast(limit); + } + } +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/ISqlLookupFormatter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/ISqlLookupFormatter.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,99 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +/** + * NB: Until 2024-09-09, this file was synchronized with the following + * folder from the Orthanc main project: + * https://orthanc.uclouvain.be/hg/orthanc/file/default/OrthancServer/Sources/Search/ + **/ + + +#pragma once + +#include "MessagesToolbox.h" + +#include +#include + +namespace OrthancDatabases +{ + class DatabaseConstraints; + class FindRequest; + + enum LabelsConstraint + { + LabelsConstraint_All, + LabelsConstraint_Any, + LabelsConstraint_None + }; + + class ISqlLookupFormatter : public boost::noncopyable + { + public: + virtual ~ISqlLookupFormatter() + { + } + + virtual std::string GenerateParameter(const std::string& value) = 0; + + virtual std::string FormatResourceType(Orthanc::ResourceType level) = 0; + + virtual std::string FormatWildcardEscape() = 0; + + virtual std::string FormatLimits(uint64_t since, uint64_t count) = 0; + + /** + * Whether to escape '[' and ']', which is only needed for + * MSSQL. New in Orthanc 1.10.0, from the following changeset: + * https://orthanc.uclouvain.be/hg/orthanc-databases/rev/389c037387ea + **/ + virtual bool IsEscapeBrackets() const = 0; + + static void GetLookupLevels(Orthanc::ResourceType& lowerLevel, + Orthanc::ResourceType& upperLevel, + const Orthanc::ResourceType& queryLevel, + const DatabaseConstraints& lookup); + + static void Apply(std::string& sql, + ISqlLookupFormatter& formatter, + const DatabaseConstraints& lookup, + Orthanc::ResourceType queryLevel, + const std::set& labels, // New in Orthanc 1.12.0 + LabelsConstraint labelsConstraint, // New in Orthanc 1.12.0 + size_t limit); + + static void ApplySingleLevel(std::string& sql, + ISqlLookupFormatter& formatter, + const DatabaseConstraints& lookup, + Orthanc::ResourceType queryLevel, + const std::set& labels, // New in Orthanc 1.12.0 + LabelsConstraint labelsConstraint, // New in Orthanc 1.12.0 + size_t limit); + +#if ORTHANC_PLUGINS_HAS_INTEGRATED_FIND == 1 + static void Apply(std::string& sql, + ISqlLookupFormatter& formatter, + const Orthanc::DatabasePluginMessages::Find_Request& request); +#endif + }; +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/IdentifierTag.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/IdentifierTag.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,63 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + + +#pragma once + +#include + +namespace OrthancDatabases +{ + class IdentifierTag + { + private: + Orthanc::ResourceType level_; + Orthanc::DicomTag tag_; + std::string name_; + + public: + IdentifierTag(Orthanc::ResourceType level, + const Orthanc::DicomTag& tag, + const std::string& name) : + level_(level), + tag_(tag), + name_(name) + { + } + + Orthanc::ResourceType GetLevel() const + { + return level_; + } + + const Orthanc::DicomTag& GetTag() const + { + return tag_; + } + + const std::string& GetName() const + { + return name_; + } + }; +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/IndexBackend.cpp --- a/Framework/Plugins/IndexBackend.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/IndexBackend.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -21,7 +23,6 @@ #include "IndexBackend.h" -#include "../../Resources/Orthanc/Databases/ISqlLookupFormatter.h" #include "../Common/BinaryStringValue.h" #include "../Common/Integer64Value.h" #include "../Common/Utf8StringValue.h" @@ -33,6 +34,7 @@ #include // For std::unique_ptr<> #include #include +#include namespace OrthancDatabases @@ -58,6 +60,19 @@ return s; } + static std::string JoinChanges(const std::set& changeTypes) + { + std::set changeTypesString; + for (std::set::const_iterator it = changeTypes.begin(); it != changeTypes.end(); ++it) + { + changeTypesString.insert(boost::lexical_cast(*it)); + } + + std::string joinedChangesTypes; + Orthanc::Toolbox::JoinStrings(joinedChangesTypes, changeTypesString, ", "); + + return joinedChangesTypes; + } template static void ReadListOfIntegers(std::list& target, @@ -110,33 +125,69 @@ } + namespace // Anonymous namespace to avoid clashes between compilation modules + { + struct Change + { + int64_t seq_; + int32_t changeType_; + OrthancPluginResourceType resourceType_; + std::string publicId_; + std::string changeDate_; + + Change(int64_t seq, int32_t changeType, OrthancPluginResourceType resourceType, const std::string& publicId, const std::string& changeDate) + : seq_(seq), changeType_(changeType), resourceType_(resourceType), publicId_(publicId), changeDate_(changeDate) + { + } + }; + } + + void IndexBackend::ReadChangesInternal(IDatabaseBackendOutput& output, bool& done, DatabaseManager& manager, DatabaseManager::CachedStatement& statement, const Dictionary& args, - uint32_t maxResults) + uint32_t limit, + bool returnFirstResults) { statement.Execute(args); - uint32_t count = 0; - - while (count < maxResults && - !statement.IsDone()) + std::list changes; + while (!statement.IsDone()) { - output.AnswerChange( + changes.push_back(Change( statement.ReadInteger64(0), statement.ReadInteger32(1), static_cast(statement.ReadInteger32(2)), statement.ReadString(3), - statement.ReadString(4)); + statement.ReadString(4) + )); statement.Next(); - count++; } - - done = (count < maxResults || - statement.IsDone()); + + done = changes.size() <= limit; // 'done' means we have returned all requested changes + + // if we have retrieved more changes than requested -> cleanup + if (changes.size() > limit) + { + assert(changes.size() == limit+1); // the statement should only request 1 element more + + if (returnFirstResults) + { + changes.pop_back(); + } + else + { + changes.pop_front(); + } + } + + for (std::list::const_iterator it = changes.begin(); it != changes.end(); ++it) + { + output.AnswerChange(it->seq_, it->changeType_, it->resourceType_, it->publicId_, it->changeDate_); + } } @@ -144,13 +195,13 @@ bool& done, DatabaseManager::CachedStatement& statement, const Dictionary& args, - uint32_t maxResults) + uint32_t limit) { statement.Execute(args); uint32_t count = 0; - while (count < maxResults && + while (count < limit && !statement.IsDone()) { int64_t seq = statement.ReadInteger64(0); @@ -172,10 +223,20 @@ count++; } - done = (count < maxResults || + done = (count < limit || statement.IsDone()); } + void IndexBackend::ClearRemainingAncestor(DatabaseManager& manager) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "DELETE FROM RemainingAncestor"); + + statement.Execute(); + } + + void IndexBackend::ClearDeletedFiles(DatabaseManager& manager) { @@ -217,7 +278,7 @@ statement.ReadInteger32(4), statement.ReadInteger64(5), statement.ReadString(6), - statement.ReadString(8)); + statement.ReadStringOrNull(8)); statement.Next(); } @@ -441,14 +502,7 @@ { ClearDeletedFiles(manager); ClearDeletedResources(manager); - - { - DatabaseManager::CachedStatement statement( - STATEMENT_FROM_HERE, manager, - "DELETE FROM RemainingAncestor"); - - statement.Execute(); - } + ClearRemainingAncestor(manager); { DatabaseManager::CachedStatement statement( @@ -468,7 +522,6 @@ DatabaseManager::CachedStatement statement( STATEMENT_FROM_HERE, manager, "SELECT * FROM RemainingAncestor"); - statement.Execute(); if (!statement.IsDone()) @@ -481,9 +534,10 @@ assert((statement.Next(), statement.IsDone())); } } - + SignalDeletedFiles(output, manager); SignalDeletedResources(output, manager); + } @@ -526,70 +580,140 @@ void IndexBackend::GetAllPublicIds(std::list& target, DatabaseManager& manager, OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) + int64_t since, + uint32_t limit) { std::string suffix; if (manager.GetDialect() == Dialect_MSSQL) { suffix = "OFFSET ${since} ROWS FETCH FIRST ${limit} ROWS ONLY"; } - else + else if (limit > 0) { suffix = "LIMIT ${limit} OFFSET ${since}"; } - DatabaseManager::CachedStatement statement( - STATEMENT_FROM_HERE, manager, - "SELECT publicId FROM (SELECT publicId FROM Resources " - "WHERE resourceType=${type}) AS tmp ORDER BY tmp.publicId " + suffix); + std::string sql = "SELECT publicId FROM (SELECT publicId FROM Resources " + "WHERE resourceType=${type}) AS tmp ORDER BY tmp.publicId " + suffix; + + DatabaseManager::CachedStatement statement(STATEMENT_FROM_HERE_DYNAMIC(sql), manager, sql); statement.SetReadOnly(true); - statement.SetParameterType("type", ValueType_Integer64); - statement.SetParameterType("limit", ValueType_Integer64); - statement.SetParameterType("since", ValueType_Integer64); Dictionary args; + + statement.SetParameterType("type", ValueType_Integer64); args.SetIntegerValue("type", static_cast(resourceType)); - args.SetIntegerValue("limit", limit); - args.SetIntegerValue("since", since); + + if (limit > 0) + { + statement.SetParameterType("limit", ValueType_Integer64); + statement.SetParameterType("since", ValueType_Integer64); + args.SetIntegerValue("limit", limit); + args.SetIntegerValue("since", since); + } ReadListOfStrings(target, statement, args); } - - /* Use GetOutput().AnswerChange() */ void IndexBackend::GetChanges(IDatabaseBackendOutput& output, bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) + uint32_t limit) { - std::string suffix; + std::set changeTypes; + GetChangesExtended(output, done, manager, since, -1, changeTypes, limit); + } + + /* Use GetOutput().AnswerChange() */ + void IndexBackend::GetChangesExtended(IDatabaseBackendOutput& output, + bool& done /*out*/, + DatabaseManager& manager, + int64_t since, + int64_t to, + const std::set& changeTypes, + uint32_t limit) + { + std::string limitSuffix; if (manager.GetDialect() == Dialect_MSSQL) { - suffix = "OFFSET 0 ROWS FETCH FIRST ${limit} ROWS ONLY"; + limitSuffix = "OFFSET 0 ROWS FETCH FIRST ${limit} ROWS ONLY"; } else { - suffix = "LIMIT ${limit}"; + limitSuffix = "LIMIT ${limit}"; } - DatabaseManager::CachedStatement statement( - STATEMENT_FROM_HERE, manager, - "SELECT Changes.seq, Changes.changeType, Changes.resourceType, Resources.publicId, " - "Changes.date FROM Changes INNER JOIN Resources " - "ON Changes.internalId = Resources.internalId WHERE seq>${since} ORDER BY seq " + suffix); - + std::vector filters; + bool hasSince = false; + bool hasTo = false; + + if (since > 0) + { + hasSince = true; + filters.push_back("seq>${since}"); + } + if (to != -1) + { + hasTo = true; + filters.push_back("seq<=${to}"); + } +#if ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED == 1 + if (changeTypes.size() > 0) + { + filters.push_back("changeType IN (" + JoinChanges(changeTypes) + ") "); + } +#endif + + std::string filtersString; + if (filters.size() > 0) + { + Orthanc::Toolbox::JoinStrings(filtersString, filters, " AND "); + filtersString = "WHERE " + filtersString; + } + + std::string sql; + bool returnFirstResults; + if (hasTo && !hasSince) + { + // in this case, we want the largest values but we want them ordered in ascending order + sql = "SELECT * FROM (SELECT Changes.seq, Changes.changeType, Changes.resourceType, Resources.publicId, Changes.date " + "FROM Changes INNER JOIN Resources " + "ON Changes.internalId = Resources.internalId " + filtersString + " ORDER BY seq DESC " + limitSuffix + + ") AS FilteredChanges ORDER BY seq ASC"; + + returnFirstResults = false; + } + else + { + // default query: we want the smallest values ordered in ascending order + sql = "SELECT Changes.seq, Changes.changeType, Changes.resourceType, Resources.publicId, " + "Changes.date FROM Changes INNER JOIN Resources " + "ON Changes.internalId = Resources.internalId " + filtersString + " ORDER BY seq ASC " + limitSuffix; + returnFirstResults = true; + } + + DatabaseManager::CachedStatement statement(STATEMENT_FROM_HERE_DYNAMIC(sql), manager, sql); statement.SetReadOnly(true); + Dictionary args; + statement.SetParameterType("limit", ValueType_Integer64); - statement.SetParameterType("since", ValueType_Integer64); - - Dictionary args; - args.SetIntegerValue("limit", maxResults + 1); - args.SetIntegerValue("since", since); - - ReadChangesInternal(output, done, manager, statement, args, maxResults); + args.SetIntegerValue("limit", limit + 1); // we take limit+1 because we use the +1 to know if "Done" must be set to true + + if (hasSince) + { + statement.SetParameterType("since", ValueType_Integer64); + args.SetIntegerValue("since", since); + } + + if (hasTo) + { + statement.SetParameterType("to", ValueType_Integer64); + args.SetIntegerValue("to", to); + } + + ReadChangesInternal(output, done, manager, statement, args, limit, returnFirstResults); } @@ -636,7 +760,7 @@ bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) + uint32_t limit) { std::string suffix; if (manager.GetDialect() == Dialect_MSSQL) @@ -657,10 +781,10 @@ statement.SetParameterType("since", ValueType_Integer64); Dictionary args; - args.SetIntegerValue("limit", maxResults + 1); + args.SetIntegerValue("limit", limit + 1); args.SetIntegerValue("since", since); - ReadExportedResourcesInternal(output, done, statement, args, maxResults); + ReadExportedResourcesInternal(output, done, statement, args, limit); } @@ -689,7 +813,7 @@ Dictionary args; bool done; // Ignored - ReadChangesInternal(output, done, manager, statement, args, 1); + ReadChangesInternal(output, done, manager, statement, args, 1, true); } @@ -764,7 +888,7 @@ if (statement.IsDone()) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource, "No public id found for internal id"); } else { @@ -832,7 +956,7 @@ if (statement.IsDone()) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource, "No resource type found for internal id."); } else { @@ -1017,7 +1141,14 @@ void IndexBackend::LogExportedResource(DatabaseManager& manager, - const OrthancPluginExportedResource& resource) + OrthancPluginResourceType resourceType, + const char* publicId, + const char* modality, + const char* date, + const char* patientId, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid) { DatabaseManager::CachedStatement statement( STATEMENT_FROM_HERE, manager, @@ -1034,14 +1165,14 @@ statement.SetParameterType("date", ValueType_Utf8String); Dictionary args; - args.SetIntegerValue("type", resource.resourceType); - args.SetUtf8Value("publicId", resource.publicId); - args.SetUtf8Value("modality", resource.modality); - args.SetUtf8Value("patient", resource.patientId); - args.SetUtf8Value("study", resource.studyInstanceUid); - args.SetUtf8Value("series", resource.seriesInstanceUid); - args.SetUtf8Value("instance", resource.sopInstanceUid); - args.SetUtf8Value("date", resource.date); + args.SetIntegerValue("type", resourceType); + args.SetUtf8Value("publicId", publicId); + args.SetUtf8Value("modality", modality); + args.SetUtf8Value("patient", patientId); + args.SetUtf8Value("study", studyInstanceUid); + args.SetUtf8Value("series", seriesInstanceUid); + args.SetUtf8Value("instance", sopInstanceUid); + args.SetUtf8Value("date", date); statement.Execute(args); } @@ -1185,7 +1316,41 @@ } } - + bool IndexBackend::HasAtomicIncrementGlobalProperty() + { + return false; // currently only implemented in Postgres + } + + int64_t IndexBackend::IncrementGlobalProperty(DatabaseManager& manager, + const char* serverIdentifier, + int32_t property, + int64_t increment) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + bool IndexBackend::HasUpdateAndGetStatistics() + { + return false; // currently only implemented in Postgres + } + + void IndexBackend::UpdateAndGetStatistics(DatabaseManager& manager, + int64_t& patientsCount, + int64_t& studiesCount, + int64_t& seriesCount, + int64_t& instancesCount, + int64_t& compressedSize, + int64_t& uncompressedSize) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + bool IndexBackend::HasMeasureLatency() + { + return true; + } + + void IndexBackend::LookupIdentifier(std::list& target /*out*/, DatabaseManager& manager, OrthancPluginResourceType resourceType, @@ -1963,7 +2128,7 @@ #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 - class IndexBackend::LookupFormatter : public Orthanc::ISqlLookupFormatter + class IndexBackend::LookupFormatter : public ISqlLookupFormatter { private: Dialect dialect_; @@ -1994,7 +2159,7 @@ virtual std::string FormatResourceType(Orthanc::ResourceType level) { - return boost::lexical_cast(Orthanc::Plugins::Convert(level)); + return boost::lexical_cast(MessagesToolbox::ConvertToPlainC(level)); } virtual std::string FormatWildcardEscape() @@ -2014,10 +2179,47 @@ } } + virtual std::string FormatLimits(uint64_t since, uint64_t count) + { + std::string sql; + + switch (dialect_) + { + case Dialect_MSSQL: + { + if (since > 0) + { + sql += " OFFSET " + boost::lexical_cast(since) + " ROWS "; + } + if (count > 0) + { + sql += " FETCH NEXT " + boost::lexical_cast(count) + " ROWS ONLY "; + } + }; break; + case Dialect_SQLite: + case Dialect_PostgreSQL: + case Dialect_MySQL: + { + if (count > 0) + { + sql += " LIMIT " + boost::lexical_cast(count); + } + if (since > 0) + { + sql += " OFFSET " + boost::lexical_cast(since); + } + }; break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + return sql; + } + virtual bool IsEscapeBrackets() const { // This was initially done at a bad location by the following changeset: - // https://hg.orthanc-server.com/orthanc-databases/rev/389c037387ea + // https://orthanc.uclouvain.be/hg/orthanc-databases/rev/389c037387ea return (dialect_ == Dialect_MSSQL); } @@ -2043,49 +2245,106 @@ // New primitive since Orthanc 1.5.2 void IndexBackend::LookupResources(IDatabaseBackendOutput& output, DatabaseManager& manager, - const std::vector& lookup, - OrthancPluginResourceType queryLevel, + const DatabaseConstraints& lookup, + OrthancPluginResourceType queryLevel_, + const std::set& labels, + LabelsConstraint labelsConstraint, uint32_t limit, bool requestSomeInstance) { LookupFormatter formatter(manager.GetDialect()); + Orthanc::ResourceType queryLevel = MessagesToolbox::Convert(queryLevel_); + Orthanc::ResourceType lowerLevel, upperLevel; + ISqlLookupFormatter::GetLookupLevels(lowerLevel, upperLevel, queryLevel, lookup); std::string sql; - Orthanc::ISqlLookupFormatter::Apply(sql, formatter, lookup, - Orthanc::Plugins::Convert(queryLevel), limit); - - if (requestSomeInstance) + bool enableNewStudyCode = true; + + if (enableNewStudyCode && lowerLevel == queryLevel && upperLevel == queryLevel) { - // Composite query to find some instance if requested - switch (queryLevel) + ISqlLookupFormatter::ApplySingleLevel(sql, formatter, lookup, queryLevel, labels, labelsConstraint, limit); + + if (requestSomeInstance) { - case OrthancPluginResourceType_Patient: - sql = ("SELECT patients.publicId, MIN(instances.publicId) FROM (" + sql + ") patients " - "INNER JOIN Resources studies ON studies.parentId = patients.internalId " - "INNER JOIN Resources series ON series.parentId = studies.internalId " - "INNER JOIN Resources instances ON instances.parentId = series.internalId " - "GROUP BY patients.publicId"); - break; - - case OrthancPluginResourceType_Study: - sql = ("SELECT studies.publicId, MIN(instances.publicId) FROM (" + sql + ") studies " - "INNER JOIN Resources series ON series.parentId = studies.internalId " - "INNER JOIN Resources instances ON instances.parentId = series.internalId " - "GROUP BY studies.publicId"); - break; - - case OrthancPluginResourceType_Series: - sql = ("SELECT series.publicId, MIN(instances.publicId) FROM (" + sql + ") series " - "INNER JOIN Resources instances ON instances.parentId = series.internalId " - "GROUP BY series.publicId"); - break; - - case OrthancPluginResourceType_Instance: - sql = ("SELECT instances.publicId, instances.publicId FROM (" + sql + ") instances"); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + // Composite query to find some instance if requested + switch (queryLevel) + { + case Orthanc::ResourceType_Patient: + sql = ("SELECT patients_studies.patients_public_id, MIN(instances.publicId) AS instances_public_id " + "FROM (SELECT patients.publicId AS patients_public_id, MIN(studies.internalId) AS studies_internal_id " + "FROM (" + sql + + ") AS patients " + "INNER JOIN Resources studies ON studies.parentId = patients.internalId " + "GROUP BY patients.publicId " + ") AS patients_studies " + "INNER JOIN Resources series ON series.parentId = patients_studies.studies_internal_id " + "INNER JOIN Resources instances ON instances.parentId = series.internalId " + "GROUP BY patients_studies.patients_public_id"); + break; + case Orthanc::ResourceType_Study: + sql = ("SELECT studies_series.studies_public_id, MIN(instances.publicId) AS instances_public_id " + "FROM (SELECT studies.publicId AS studies_public_id, MIN(series.internalId) AS series_internal_id " + "FROM (" + sql + + ") AS studies " + "INNER JOIN Resources series ON series.parentId = studies.internalId " + "GROUP BY studies.publicId " + ") AS studies_series " + "INNER JOIN Resources instances ON instances.parentId = studies_series.series_internal_id " + "GROUP BY studies_series.studies_public_id"); + break; + case Orthanc::ResourceType_Series: + sql = ("SELECT series.publicId AS series_public_id, MIN(instances.publicId) AS instances_public_id " + "FROM (" + sql + + ") AS series " + "INNER JOIN Resources instances ON instances.parentId = series.internalId " + "GROUP BY series.publicId "); + break; + + case Orthanc::ResourceType_Instance: + sql = ("SELECT instances.publicId, instances.publicId FROM (" + sql + ") instances"); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + } + else + { + ISqlLookupFormatter::Apply(sql, formatter, lookup, queryLevel, labels, labelsConstraint, limit); + + if (requestSomeInstance) + { + // Composite query to find some instance if requested + switch (queryLevel) + { + case Orthanc::ResourceType_Patient: + sql = ("SELECT patients.publicId, MIN(instances.publicId) FROM (" + sql + ") patients " + "INNER JOIN Resources studies ON studies.parentId = patients.internalId " + "INNER JOIN Resources series ON series.parentId = studies.internalId " + "INNER JOIN Resources instances ON instances.parentId = series.internalId " + "GROUP BY patients.publicId"); + break; + + case Orthanc::ResourceType_Study: + sql = ("SELECT studies.publicId, MIN(instances.publicId) FROM (" + sql + ") studies " + "INNER JOIN Resources series ON series.parentId = studies.internalId " + "INNER JOIN Resources instances ON instances.parentId = series.internalId " + "GROUP BY studies.publicId"); + break; + case Orthanc::ResourceType_Series: + sql = ("SELECT series.publicId, MIN(instances.publicId) FROM (" + sql + ") series " + "INNER JOIN Resources instances ON instances.parentId = series.internalId " + "GROUP BY series.publicId"); + break; + + case Orthanc::ResourceType_Instance: + sql = ("SELECT instances.publicId, instances.publicId FROM (" + sql + ") instances"); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } } } @@ -2363,9 +2622,7 @@ } -#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in 1.3.1 -# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) - // New primitive since Orthanc 1.5.4 +// New primitive since Orthanc 1.5.4 bool IndexBackend::LookupResourceAndParent(int64_t& id, OrthancPluginResourceType& type, std::string& parentPublicId, @@ -2423,12 +2680,8 @@ return true; } } -# endif -#endif -#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in 1.3.1 -# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) // New primitive since Orthanc 1.5.4 void IndexBackend::GetAllMetadata(std::map& result, DatabaseManager& manager, @@ -2465,8 +2718,6 @@ } } } -# endif -#endif #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 @@ -2589,6 +2840,96 @@ #endif + void IndexBackend::AddLabel(DatabaseManager& manager, + int64_t resource, + const std::string& label) + { + std::unique_ptr statement; + + switch (manager.GetDialect()) + { + case Dialect_PostgreSQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager, + "INSERT INTO Labels VALUES(${id}, ${label}) ON CONFLICT DO NOTHING")); + break; + + case Dialect_SQLite: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager, + "INSERT OR IGNORE INTO Labels VALUES(${id}, ${label})")); + break; + + case Dialect_MySQL: + statement.reset(new DatabaseManager::CachedStatement( + STATEMENT_FROM_HERE, manager, + "INSERT IGNORE INTO Labels VALUES(${id}, ${label})")); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + statement->SetParameterType("id", ValueType_Integer64); + statement->SetParameterType("label", ValueType_Utf8String); + + Dictionary args; + args.SetIntegerValue("id", resource); + args.SetUtf8Value("label", label); + + statement->Execute(args); + } + + + void IndexBackend::RemoveLabel(DatabaseManager& manager, + int64_t resource, + const std::string& label) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "DELETE FROM Labels WHERE id=${id} AND label=${label}"); + + statement.SetParameterType("id", ValueType_Integer64); + statement.SetParameterType("label", ValueType_Utf8String); + + Dictionary args; + args.SetIntegerValue("id", resource); + args.SetUtf8Value("label", label); + + statement.Execute(args); + } + + + void IndexBackend::ListLabels(std::list& target, + DatabaseManager& manager, + int64_t resource) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "SELECT label FROM Labels WHERE id=${id}"); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", resource); + + ReadListOfStrings(target, statement, args); + } + + + void IndexBackend::ListAllLabels(std::list& target, + DatabaseManager& manager) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "SELECT DISTINCT label FROM Labels"); + + Dictionary args; + ReadListOfStrings(target, statement, args); + } + + void IndexBackend::Register(IndexBackend* backend, size_t countConnections, unsigned int maxDatabaseRetries) @@ -2598,35 +2939,31 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } - bool hasLoadedV3OrAbove = false; + LOG(WARNING) << "The index plugin will use " << countConnections << " connection(s) to the database, " + << "and will retry up to " << maxDatabaseRetries << " time(s) in the case of a collision"; #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 # if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) if (OrthancPluginCheckVersionAdvanced(backend->GetContext(), 1, 12, 0) == 1) { - LOG(WARNING) << "The index plugin will use " << countConnections << " connection(s) to the database, " - << "and will retry up to " << maxDatabaseRetries << " time(s) in the case of a collision"; - - OrthancDatabases::DatabaseBackendAdapterV4::Register(backend, countConnections, maxDatabaseRetries); - hasLoadedV3OrAbove = true; - } -# elif ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) - if (OrthancPluginCheckVersionAdvanced(backend->GetContext(), 1, 9, 2) == 1) - { - LOG(WARNING) << "The index plugin will use " << countConnections << " connection(s) to the database, " - << "and will retry up to " << maxDatabaseRetries << " time(s) in the case of a collision"; - - OrthancDatabases::DatabaseBackendAdapterV3::Register(backend, countConnections, maxDatabaseRetries); - hasLoadedV3OrAbove = true; + DatabaseBackendAdapterV4::Register(backend, countConnections, maxDatabaseRetries); + return; } # endif #endif - if (!hasLoadedV3OrAbove) +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) + if (OrthancPluginCheckVersionAdvanced(backend->GetContext(), 1, 9, 2) == 1) { - LOG(WARNING) << "Performance warning: Your version of the Orthanc core or SDK doesn't support multiple readers/writers"; - OrthancDatabases::DatabaseBackendAdapterV2::Register(backend); + DatabaseBackendAdapterV3::Register(backend, countConnections, maxDatabaseRetries); + return; } +# endif +#endif + + LOG(WARNING) << "Performance warning: Your version of the Orthanc core or SDK doesn't support multiple readers/writers"; + DatabaseBackendAdapterV2::Register(backend); } @@ -2646,7 +2983,7 @@ } catch (boost::bad_lexical_cast&) { - LOG(ERROR) << "Corrupted PostgreSQL database"; + LOG(ERROR) << "Corrupted database"; throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); } } @@ -2669,20 +3006,858 @@ void IndexBackend::Finalize() { - OrthancDatabases::DatabaseBackendAdapterV2::Finalize(); + DatabaseBackendAdapterV2::Finalize(); #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 # if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) - OrthancDatabases::DatabaseBackendAdapterV3::Finalize(); + DatabaseBackendAdapterV3::Finalize(); +# endif +#endif + +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) + DatabaseBackendAdapterV4::Finalize(); # endif #endif } - DatabaseManager* IndexBackend::CreateSingleDatabaseManager(IDatabaseBackend& backend) + uint64_t IndexBackend::MeasureLatency(DatabaseManager& manager) + { + // execute 11x the simplest statement and return the median value + std::vector measures; + + for (int i = 0; i < 11; i++) + { + DatabaseManager::StandaloneStatement statement(manager, "SELECT 1"); + + Orthanc::Toolbox::ElapsedTimer timer; + + statement.ExecuteWithoutResult(); + + measures.push_back(timer.GetElapsedMicroseconds()); + } + + std::sort(measures.begin(), measures.end()); + + return measures[measures.size() / 2]; + } + + + DatabaseManager* IndexBackend::CreateSingleDatabaseManager(IDatabaseBackend& backend, + bool hasIdentifierTags, + const std::list& identifierTags) { std::unique_ptr manager(new DatabaseManager(backend.CreateDatabaseFactory())); - backend.ConfigureDatabase(*manager); + backend.ConfigureDatabase(*manager, hasIdentifierTags, identifierTags); return manager.release(); } + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + bool IndexBackend::HasFindSupport() const + { + // TODO-FIND move to child plugins ? + return true; + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + Orthanc::DatabasePluginMessages::Find_Response_ResourceContent* GetResourceContent( + Orthanc::DatabasePluginMessages::Find_Response* response, + Orthanc::DatabasePluginMessages::ResourceType level) + { + Orthanc::DatabasePluginMessages::Find_Response_ResourceContent* content = NULL; // the protobuf response will be the owner + + switch (level) + { + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_PATIENT: + content = response->mutable_patient_content(); + break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_STUDY: + content = response->mutable_study_content(); + break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_SERIES: + content =response->mutable_series_content(); + break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_INSTANCE: + content = response->mutable_instance_content(); + break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + return content; + } + + Orthanc::DatabasePluginMessages::Find_Response_ChildrenContent* GetChildrenContent( + Orthanc::DatabasePluginMessages::Find_Response* response, + Orthanc::DatabasePluginMessages::ResourceType childrenLevel) + { + Orthanc::DatabasePluginMessages::Find_Response_ChildrenContent* content = NULL; // the protobuf response will be the owner + + switch (childrenLevel) + { + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_STUDY: + content = response->mutable_children_studies_content(); + break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_SERIES: + content =response->mutable_children_series_content(); + break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_INSTANCE: + content = response->mutable_children_instances_content(); + break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + return content; + } + + std::string JoinRequestedMetadata(const Orthanc::DatabasePluginMessages::Find_Request_ChildrenSpecification* childrenSpec) + { + std::set metadataTypes; + for (int i = 0; i < childrenSpec->retrieve_metadata_size(); ++i) + { + metadataTypes.insert(boost::lexical_cast(childrenSpec->retrieve_metadata(i))); + } + std::string joinedMetadataTypes; + Orthanc::Toolbox::JoinStrings(joinedMetadataTypes, metadataTypes, ", "); + + return joinedMetadataTypes; + } + + std::string JoinRequestedTags(const Orthanc::DatabasePluginMessages::Find_Request_ChildrenSpecification* childrenSpec) + { + std::set tags; + for (int i = 0; i < childrenSpec->retrieve_main_dicom_tags_size(); ++i) + { + tags.insert("(" + boost::lexical_cast(childrenSpec->retrieve_main_dicom_tags(i).group()) + + ", " + boost::lexical_cast(childrenSpec->retrieve_main_dicom_tags(i).element()) + ")"); + } + std::string joinedTags; + Orthanc::Toolbox::JoinStrings(joinedTags, tags, ", "); + + return joinedTags; + } + + +#define C0_QUERY_ID 0 +#define C1_INTERNAL_ID 1 +#define C2_ROW_NUMBER 2 +#define C3_STRING_1 3 +#define C4_STRING_2 4 +#define C5_STRING_3 5 +#define C6_INT_1 6 +#define C7_INT_2 7 +#define C8_BIG_INT_1 8 +#define C9_BIG_INT_2 9 + +#define QUERY_LOOKUP 1 +#define QUERY_MAIN_DICOM_TAGS 2 +#define QUERY_ATTACHMENTS 3 +#define QUERY_METADATA 4 +#define QUERY_LABELS 5 +#define QUERY_PARENT_MAIN_DICOM_TAGS 10 +#define QUERY_PARENT_IDENTIFIER 11 +#define QUERY_PARENT_METADATA 12 +#define QUERY_GRAND_PARENT_MAIN_DICOM_TAGS 15 +#define QUERY_GRAND_PARENT_METADATA 16 +#define QUERY_CHILDREN_IDENTIFIERS 20 +#define QUERY_CHILDREN_MAIN_DICOM_TAGS 21 +#define QUERY_CHILDREN_METADATA 22 +#define QUERY_GRAND_CHILDREN_IDENTIFIERS 30 +#define QUERY_GRAND_CHILDREN_MAIN_DICOM_TAGS 31 +#define QUERY_GRAND_CHILDREN_METADATA 32 +#define QUERY_GRAND_GRAND_CHILDREN_IDENTIFIERS 40 +#define QUERY_ONE_INSTANCE_IDENTIFIER 50 +#define QUERY_ONE_INSTANCE_METADATA 51 +#define QUERY_ONE_INSTANCE_ATTACHMENTS 52 + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + + void IndexBackend::ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, + DatabaseManager& manager, + const Orthanc::DatabasePluginMessages::Find_Request& request) + { + // TODO-FIND move to child plugins ? + + + // If we want the Find to use a read-only transaction, we can not create temporary tables with + // the lookup results. So we must use a CTE (Common Table Expression). + // However, a CTE can only be used in a single query -> we must unionize all the following + // queries to retrieve values from various tables. + // However, to use UNION, all tables must have the same columns (numbers and types). That's + // why we have generic column names. + // So, at the end we'll have only one very big query ! + + std::string sql; + + // extract the resource id of interest by executing the lookup in a CTE + LookupFormatter formatter(manager.GetDialect()); + std::string lookupSql; + ISqlLookupFormatter::Apply(lookupSql, formatter, request); + + // base query, retrieve the ordered internalId and publicId of the selected resources + sql = "WITH Lookup AS (" + lookupSql + ") " + "SELECT " + " " TOSTRING(QUERY_LOOKUP) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " Lookup.rowNumber AS c2_rowNumber, " + " Lookup.publicId AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " NULL::INT AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + " FROM Lookup "; + + // need MainDicomTags from resource ? + if (request.retrieve_main_dicom_tags()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_MAIN_DICOM_TAGS) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " value AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " tagGroup AS c6_int1, " + " tagElement AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + "INNER JOIN MainDicomTags ON MainDicomTags.id = Lookup.internalId "; + } + + // need resource metadata ? + if (request.retrieve_metadata()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_METADATA) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " value AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " type AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + "INNER JOIN Metadata ON Metadata.id = Lookup.internalId "; + } + + // need resource attachments ? + if (request.retrieve_attachments()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_ATTACHMENTS) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " uuid AS c3_string1, " + " uncompressedHash AS c4_string2, " + " compressedHash AS c5_string3, " + " fileType AS c6_int1, " + " compressionType AS c7_int2, " + " compressedSize AS c8_big_int1, " + " uncompressedSize AS c9_big_int2 " + "FROM Lookup " + "INNER JOIN AttachedFiles ON AttachedFiles.id = Lookup.internalId "; + } + + // need resource labels ? + if (request.retrieve_labels()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_LABELS) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " label AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " NULL::INT AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + "INNER JOIN Labels ON Labels.id = Lookup.internalId "; + } + + // need MainDicomTags from parent ? + if (request.level() > Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_PATIENT) + { + const Orthanc::DatabasePluginMessages::Find_Request_ParentSpecification* parentSpec = NULL; + switch (request.level()) + { + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_STUDY: + parentSpec = &(request.parent_patient()); + break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_SERIES: + parentSpec = &(request.parent_study()); + break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_INSTANCE: + parentSpec = &(request.parent_series()); + break; + + default: + break; + } + + if (parentSpec->retrieve_main_dicom_tags()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_PARENT_MAIN_DICOM_TAGS) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " value AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " tagGroup AS c6_int1, " + " tagElement AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId " + "INNER JOIN MainDicomTags ON MainDicomTags.id = currentLevel.parentId "; + } + + if (parentSpec->retrieve_metadata()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_PARENT_METADATA) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " value AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " type AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId " + "INNER JOIN Metadata ON Metadata.id = currentLevel.parentId "; + } + + // need MainDicomTags from grandparent ? + if (request.level() > Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_STUDY) + { + const Orthanc::DatabasePluginMessages::Find_Request_ParentSpecification* grandparentSpec = NULL; + switch (request.level()) + { + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_SERIES: + grandparentSpec = &(request.parent_patient()); + break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_INSTANCE: + grandparentSpec = &(request.parent_study()); + break; + + default: + break; + } + + if (grandparentSpec->retrieve_main_dicom_tags()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_GRAND_PARENT_MAIN_DICOM_TAGS) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " value AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " tagGroup AS c6_int1, " + " tagElement AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId " + "INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId " + "INNER JOIN MainDicomTags ON MainDicomTags.id = parentLevel.parentId "; + } + + if (grandparentSpec->retrieve_metadata()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_GRAND_PARENT_METADATA) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " value AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " type AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId " + "INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId " + "INNER JOIN Metadata ON Metadata.id = parentLevel.parentId "; + } + } + } + + // need MainDicomTags from children ? + if (request.level() <= Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_SERIES) + { + const Orthanc::DatabasePluginMessages::Find_Request_ChildrenSpecification* childrenSpec = NULL; + switch (request.level()) + { + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_PATIENT: + childrenSpec = &(request.children_studies()); + break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_STUDY: + childrenSpec = &(request.children_series()); + break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_SERIES: + childrenSpec = &(request.children_instances()); + break; + + default: + break; + } + + if (childrenSpec->retrieve_main_dicom_tags_size() > 0) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_CHILDREN_MAIN_DICOM_TAGS) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " value AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " tagGroup AS c6_int1, " + " tagElement AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + " INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId " + " INNER JOIN MainDicomTags ON MainDicomTags.id = childLevel.internalId AND (tagGroup, tagElement) IN (" + JoinRequestedTags(childrenSpec) + ")"; + } + + // need children identifiers ? + if (childrenSpec->retrieve_identifiers()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_CHILDREN_IDENTIFIERS) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " childLevel.publicId AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " NULL::INT AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + " INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId "; + } + + if (childrenSpec->retrieve_metadata_size() > 0) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_CHILDREN_METADATA) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " value AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " type AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + " INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId " + " INNER JOIN Metadata ON Metadata.id = childLevel.internalId AND Metadata.type IN (" + JoinRequestedMetadata(childrenSpec) + ") "; + } + + if (request.level() <= Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_STUDY) + { + const Orthanc::DatabasePluginMessages::Find_Request_ChildrenSpecification* grandchildrenSpec = NULL; + switch (request.level()) + { + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_PATIENT: + grandchildrenSpec = &(request.children_series()); + break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_STUDY: + grandchildrenSpec = &(request.children_instances()); + break; + + default: + break; + } + + // need grand children identifiers ? + if (grandchildrenSpec->retrieve_identifiers()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_GRAND_CHILDREN_IDENTIFIERS) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " grandChildLevel.publicId AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " NULL::INT AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + "INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId " + "INNER JOIN Resources grandChildLevel ON childLevel.internalId = grandChildLevel.parentId "; + } + + if (grandchildrenSpec->retrieve_main_dicom_tags_size() > 0) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_GRAND_CHILDREN_MAIN_DICOM_TAGS) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " value AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " tagGroup AS c6_int1, " + " tagElement AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + " INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId " + " INNER JOIN Resources grandChildLevel ON grandChildLevel.parentId = childLevel.internalId " + " INNER JOIN MainDicomTags ON MainDicomTags.id = grandChildLevel.internalId AND (tagGroup, tagElement) IN (" + JoinRequestedTags(grandchildrenSpec) + ")"; + } + + if (grandchildrenSpec->retrieve_metadata_size() > 0) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_GRAND_CHILDREN_METADATA) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " value AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " type AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + " INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId " + " INNER JOIN Resources grandChildLevel ON grandChildLevel.parentId = childLevel.internalId " + " INNER JOIN Metadata ON Metadata.id = grandChildLevel.internalId AND Metadata.type IN (" + JoinRequestedMetadata(grandchildrenSpec) + ") "; + } + + if (request.level() == Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_PATIENT) + { + const Orthanc::DatabasePluginMessages::Find_Request_ChildrenSpecification* grandgrandchildrenSpec = &(request.children_instances()); + + // need grand children identifiers ? + if (grandgrandchildrenSpec->retrieve_identifiers()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_GRAND_GRAND_CHILDREN_IDENTIFIERS) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " grandGrandChildLevel.publicId AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " NULL::INT AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + "INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId " + "INNER JOIN Resources grandChildLevel ON childLevel.internalId = grandChildLevel.parentId " + "INNER JOIN Resources grandGrandChildLevel ON grandChildLevel.internalId = grandGrandChildLevel.parentId "; + } + } + } + } + + // need parent identifier ? + if (request.retrieve_parent_identifier()) + { + sql += "UNION SELECT " + " " TOSTRING(QUERY_PARENT_IDENTIFIER) " AS c0_queryId, " + " Lookup.internalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " parentLevel.publicId AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " NULL::INT AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + "FROM Lookup " + " INNER JOIN Resources currentLevel ON currentLevel.internalId = Lookup.internalId " + " INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId "; + } + + // need one instance info ? + if (request.level() != Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_INSTANCE && + request.retrieve_one_instance_metadata_and_attachments()) + { + // Here, we create a nested CTE 'OneInstance' with one instance ID to join with metadata and main + sql += "UNION" + " (WITH OneInstance AS"; + + switch (request.level()) + { + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_SERIES: + { + sql+= " (SELECT DISTINCT ON (Lookup.internalId) Lookup.internalId AS parentInternalId, childLevel.publicId AS instancePublicId, childLevel.internalId AS instanceInternalId" + " FROM Resources AS childLevel " + " INNER JOIN Lookup ON childLevel.parentId = Lookup.internalId) "; + }; break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_STUDY: + { + sql+= " (SELECT DISTINCT ON (Lookup.internalId) Lookup.internalId AS parentInternalId, grandChildLevel.publicId AS instancePublicId, grandChildLevel.internalId AS instanceInternalId" + " FROM Resources AS grandChildLevel " + " INNER JOIN Resources childLevel ON grandChildLevel.parentId = childLevel.internalId " + " INNER JOIN Lookup ON childLevel.parentId = Lookup.internalId) "; + }; break; + case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_PATIENT: + { + sql+= " (SELECT DISTINCT ON (Lookup.internalId) Lookup.internalId AS parentInternalId, grandGrandChildLevel.publicId AS instancePublicId, grandGrandChildLevel.internalId AS instanceInternalId" + " FROM Resources AS grandGrandChildLevel " + " INNER JOIN Resources grandChildLevel ON grandGrandChildLevel.parentId = grandChildLevel.internalId " + " INNER JOIN Resources childLevel ON grandChildLevel.parentId = childLevel.internalId " + " INNER JOIN Lookup ON childLevel.parentId = Lookup.internalId) "; + }; break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + sql += " SELECT" + " " TOSTRING(QUERY_ONE_INSTANCE_IDENTIFIER) " AS c0_queryId, " + " parentInternalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " instancePublicId AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " NULL::INT AS c6_int1, " + " NULL::INT AS c7_int2, " + " instanceInternalId AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + " FROM OneInstance "; + + sql += " UNION SELECT" + " " TOSTRING(QUERY_ONE_INSTANCE_METADATA) " AS c0_queryId, " + " parentInternalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " Metadata.value AS c3_string1, " + " NULL::TEXT AS c4_string2, " + " NULL::TEXT AS c5_string3, " + " Metadata.type AS c6_int1, " + " NULL::INT AS c7_int2, " + " NULL::BIGINT AS c8_big_int1, " + " NULL::BIGINT AS c9_big_int2 " + " FROM Metadata " + " INNER JOIN OneInstance ON Metadata.id = OneInstance.instanceInternalId"; + + sql += " UNION SELECT" + " " TOSTRING(QUERY_ONE_INSTANCE_ATTACHMENTS) " AS c0_queryId, " + " parentInternalId AS c1_internalId, " + " NULL::BIGINT AS c2_rowNumber, " + " uuid AS c3_string1, " + " uncompressedHash AS c4_string2, " + " compressedHash AS c5_string3, " + " fileType AS c6_int1, " + " compressionType AS c7_int2, " + " compressedSize AS c8_big_int1, " + " uncompressedSize AS c9_big_int2 " + " FROM AttachedFiles " + " INNER JOIN OneInstance ON AttachedFiles.id = OneInstance.instanceInternalId"; + + sql += " ) "; + + } + + sql += " ORDER BY c0_queryId, c2_rowNumber"; // this is really important to make sure that the Lookup query is the first one to provide results since we use it to create the responses element ! + + DatabaseManager::StandaloneStatement statement(manager, sql); // TODO-FIND: cache dynamic statement ? Probably worth it since it can be very complex queries ! + formatter.PrepareStatement(statement); + statement.Execute(formatter.GetDictionary()); + + + std::map responses; + + while (!statement.IsDone()) + { + int32_t queryId = statement.ReadInteger32(C0_QUERY_ID); + int64_t internalId = statement.ReadInteger64(C1_INTERNAL_ID); + + assert(queryId == QUERY_LOOKUP || responses.find(internalId) != responses.end()); // the QUERY_LOOKUP must be read first and must create the response before any other query tries to populate the fields + + switch (queryId) + { + case QUERY_LOOKUP: + responses[internalId] = response.add_find(); + responses[internalId]->set_public_id(statement.ReadString(C3_STRING_1)); + responses[internalId]->set_internal_id(internalId); + break; + + case QUERY_LABELS: + responses[internalId]->add_labels(statement.ReadString(C3_STRING_1)); + break; + + case QUERY_MAIN_DICOM_TAGS: + { + Orthanc::DatabasePluginMessages::Find_Response_ResourceContent* content = GetResourceContent(responses[internalId], request.level()); + Orthanc::DatabasePluginMessages::Find_Response_Tag* tag = content->add_main_dicom_tags(); + + tag->set_value(statement.ReadString(C3_STRING_1)); + tag->set_group(statement.ReadInteger32(C6_INT_1)); + tag->set_element(statement.ReadInteger32(C7_INT_2)); + }; break; + + case QUERY_PARENT_MAIN_DICOM_TAGS: + { + Orthanc::DatabasePluginMessages::Find_Response_ResourceContent* content = GetResourceContent(responses[internalId], static_cast(request.level() - 1)); + Orthanc::DatabasePluginMessages::Find_Response_Tag* tag = content->add_main_dicom_tags(); + + tag->set_value(statement.ReadString(C3_STRING_1)); + tag->set_group(statement.ReadInteger32(C6_INT_1)); + tag->set_element(statement.ReadInteger32(C7_INT_2)); + }; break; + + case QUERY_GRAND_PARENT_MAIN_DICOM_TAGS: + { + Orthanc::DatabasePluginMessages::Find_Response_ResourceContent* content = GetResourceContent(responses[internalId], static_cast(request.level() - 2)); + Orthanc::DatabasePluginMessages::Find_Response_Tag* tag = content->add_main_dicom_tags(); + + tag->set_value(statement.ReadString(C3_STRING_1)); + tag->set_group(statement.ReadInteger32(C6_INT_1)); + tag->set_element(statement.ReadInteger32(C7_INT_2)); + }; break; + + case QUERY_CHILDREN_IDENTIFIERS: + { + Orthanc::DatabasePluginMessages::Find_Response_ChildrenContent* content = GetChildrenContent(responses[internalId], static_cast(request.level() + 1)); + content->add_identifiers(statement.ReadString(C3_STRING_1)); + }; break; + + case QUERY_CHILDREN_MAIN_DICOM_TAGS: + { + Orthanc::DatabasePluginMessages::Find_Response_ChildrenContent* content = GetChildrenContent(responses[internalId], static_cast(request.level() + 1)); + Orthanc::DatabasePluginMessages::Find_Response_MultipleTags* tag = content->add_main_dicom_tags(); + tag->add_values(statement.ReadString(C3_STRING_1)); // TODO: handle sequences ?? + tag->set_group(statement.ReadInteger32(C6_INT_1)); + tag->set_element(statement.ReadInteger32(C7_INT_2)); + }; break; + + case QUERY_CHILDREN_METADATA: + { + Orthanc::DatabasePluginMessages::Find_Response_ChildrenContent* content = GetChildrenContent(responses[internalId], static_cast(request.level() + 1)); + Orthanc::DatabasePluginMessages::Find_Response_MultipleMetadata* metadata = content->add_metadata(); + + metadata->add_values(statement.ReadString(C3_STRING_1)); + metadata->set_key(statement.ReadInteger32(C6_INT_1)); + }; break; + + case QUERY_GRAND_CHILDREN_IDENTIFIERS: + { + Orthanc::DatabasePluginMessages::Find_Response_ChildrenContent* content = GetChildrenContent(responses[internalId], static_cast(request.level() + 2)); + content->add_identifiers(statement.ReadString(C3_STRING_1)); + }; break; + + case QUERY_GRAND_CHILDREN_MAIN_DICOM_TAGS: + { + Orthanc::DatabasePluginMessages::Find_Response_ChildrenContent* content = GetChildrenContent(responses[internalId], static_cast(request.level() + 2)); + Orthanc::DatabasePluginMessages::Find_Response_MultipleTags* tag = content->add_main_dicom_tags(); + + tag->add_values(statement.ReadString(C3_STRING_1)); // TODO: handle sequences ?? + tag->set_group(statement.ReadInteger32(C6_INT_1)); + tag->set_element(statement.ReadInteger32(C7_INT_2)); + }; break; + + case QUERY_GRAND_CHILDREN_METADATA: + { + Orthanc::DatabasePluginMessages::Find_Response_ChildrenContent* content = GetChildrenContent(responses[internalId], static_cast(request.level() + 2)); + Orthanc::DatabasePluginMessages::Find_Response_MultipleMetadata* metadata = content->add_metadata(); + + metadata->add_values(statement.ReadString(C3_STRING_1)); + metadata->set_key(statement.ReadInteger32(C6_INT_1)); + }; break; + + case QUERY_GRAND_GRAND_CHILDREN_IDENTIFIERS: + { + Orthanc::DatabasePluginMessages::Find_Response_ChildrenContent* content = GetChildrenContent(responses[internalId], static_cast(request.level() + 3)); + content->add_identifiers(statement.ReadString(C3_STRING_1)); + }; break; + + case QUERY_ATTACHMENTS: + { + Orthanc::DatabasePluginMessages::FileInfo* attachment = responses[internalId]->add_attachments(); + + attachment->set_uuid(statement.ReadString(C3_STRING_1)); + attachment->set_uncompressed_hash(statement.ReadString(C4_STRING_2)); + attachment->set_compressed_hash(statement.ReadString(C5_STRING_3)); + attachment->set_content_type(statement.ReadInteger32(C6_INT_1)); + attachment->set_compression_type(statement.ReadInteger32(C7_INT_2)); + attachment->set_compressed_size(statement.ReadInteger64(C8_BIG_INT_1)); + attachment->set_uncompressed_size(statement.ReadInteger64(C9_BIG_INT_2)); + }; break; + + case QUERY_METADATA: + { + Orthanc::DatabasePluginMessages::Find_Response_ResourceContent* content = GetResourceContent(responses[internalId], request.level()); + Orthanc::DatabasePluginMessages::Find_Response_Metadata* metadata = content->add_metadata(); + + metadata->set_value(statement.ReadString(C3_STRING_1)); + metadata->set_key(statement.ReadInteger32(C6_INT_1)); + }; break; + + case QUERY_PARENT_METADATA: + { + Orthanc::DatabasePluginMessages::Find_Response_ResourceContent* content = GetResourceContent(responses[internalId], static_cast(request.level() - 1)); + Orthanc::DatabasePluginMessages::Find_Response_Metadata* metadata = content->add_metadata(); + + metadata->set_value(statement.ReadString(C3_STRING_1)); + metadata->set_key(statement.ReadInteger32(C6_INT_1)); + }; break; + + case QUERY_GRAND_PARENT_METADATA: + { + Orthanc::DatabasePluginMessages::Find_Response_ResourceContent* content = GetResourceContent(responses[internalId], static_cast(request.level() - 2)); + Orthanc::DatabasePluginMessages::Find_Response_Metadata* metadata = content->add_metadata(); + + metadata->set_value(statement.ReadString(C3_STRING_1)); + metadata->set_key(statement.ReadInteger32(C6_INT_1)); + }; break; + + case QUERY_PARENT_IDENTIFIER: + { + responses[internalId]->set_parent_public_id(statement.ReadString(C3_STRING_1)); + }; break; + + case QUERY_ONE_INSTANCE_IDENTIFIER: + { + responses[internalId]->set_one_instance_public_id(statement.ReadString(C3_STRING_1)); + }; break; + case QUERY_ONE_INSTANCE_METADATA: + { + Orthanc::DatabasePluginMessages::Find_Response_Metadata* metadata = responses[internalId]->add_one_instance_metadata(); + + metadata->set_value(statement.ReadString(C3_STRING_1)); + metadata->set_key(statement.ReadInteger32(C6_INT_1)); + }; break; + case QUERY_ONE_INSTANCE_ATTACHMENTS: + { + Orthanc::DatabasePluginMessages::FileInfo* attachment = responses[internalId]->add_one_instance_attachments(); + + attachment->set_uuid(statement.ReadString(C3_STRING_1)); + attachment->set_uncompressed_hash(statement.ReadString(C4_STRING_2)); + attachment->set_compressed_hash(statement.ReadString(C5_STRING_3)); + attachment->set_content_type(statement.ReadInteger32(C6_INT_1)); + attachment->set_compression_type(statement.ReadInteger32(C7_INT_2)); + attachment->set_compressed_size(statement.ReadInteger64(C8_BIG_INT_1)); + attachment->set_uncompressed_size(statement.ReadInteger64(C9_BIG_INT_2)); + }; break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + statement.Next(); + } + } +#endif + } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/IndexBackend.h --- a/Framework/Plugins/IndexBackend.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/IndexBackend.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -45,9 +47,11 @@ std::unique_ptr outputFactory_; protected: - void ClearDeletedFiles(DatabaseManager& manager); + virtual void ClearDeletedFiles(DatabaseManager& manager); - void ClearDeletedResources(DatabaseManager& manager); + virtual void ClearDeletedResources(DatabaseManager& manager); + + virtual void ClearRemainingAncestor(DatabaseManager& manager); void SignalDeletedFiles(IDatabaseBackendOutput& output, DatabaseManager& manager); @@ -61,13 +65,14 @@ DatabaseManager& manager, DatabaseManager::CachedStatement& statement, const Dictionary& args, - uint32_t maxResults); + uint32_t limit, + bool returnFirstResults); void ReadExportedResourcesInternal(IDatabaseBackendOutput& output, bool& done, DatabaseManager::CachedStatement& statement, const Dictionary& args, - uint32_t maxResults); + uint32_t limit); public: explicit IndexBackend(OrthancPluginContext* context); @@ -127,15 +132,23 @@ virtual void GetAllPublicIds(std::list& target, DatabaseManager& manager, OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) ORTHANC_OVERRIDE; + int64_t since, + uint32_t limit) ORTHANC_OVERRIDE; virtual void GetChanges(IDatabaseBackendOutput& output, bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) ORTHANC_OVERRIDE; - + uint32_t limit) ORTHANC_OVERRIDE; + + virtual void GetChangesExtended(IDatabaseBackendOutput& output, + bool& done /*out*/, + DatabaseManager& manager, + int64_t since, + int64_t to, + const std::set& changeTypes, + uint32_t limit) ORTHANC_OVERRIDE; + virtual void GetChildrenInternalId(std::list& target /*out*/, DatabaseManager& manager, int64_t id) ORTHANC_OVERRIDE; @@ -148,7 +161,7 @@ bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) ORTHANC_OVERRIDE; + uint32_t limit) ORTHANC_OVERRIDE; virtual void GetLastChange(IDatabaseBackendOutput& output, DatabaseManager& manager) ORTHANC_OVERRIDE; @@ -194,7 +207,14 @@ const char* date) ORTHANC_OVERRIDE; virtual void LogExportedResource(DatabaseManager& manager, - const OrthancPluginExportedResource& resource) ORTHANC_OVERRIDE; + OrthancPluginResourceType resourceType, + const char* publicId, + const char* modality, + const char* date, + const char* patientId, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid) ORTHANC_OVERRIDE; virtual bool LookupAttachment(IDatabaseBackendOutput& output, int64_t& revision /*out*/, @@ -301,8 +321,10 @@ // New primitive since Orthanc 1.5.2 virtual void LookupResources(IDatabaseBackendOutput& output, DatabaseManager& manager, - const std::vector& lookup, + const DatabaseConstraints& lookup, OrthancPluginResourceType queryLevel, + const std::set& labels, + LabelsConstraint labelsConstraint, uint32_t limit, bool requestSomeInstance) ORTHANC_OVERRIDE; #endif @@ -328,25 +350,17 @@ virtual void TagMostRecentPatient(DatabaseManager& manager, int64_t patient) ORTHANC_OVERRIDE; -#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in 1.3.1 -# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) // New primitive since Orthanc 1.5.4 virtual bool LookupResourceAndParent(int64_t& id, OrthancPluginResourceType& type, std::string& parentPublicId, DatabaseManager& manager, const char* publicId) ORTHANC_OVERRIDE; -# endif -#endif -#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in 1.3.1 -# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) // New primitive since Orthanc 1.5.4 virtual void GetAllMetadata(std::map& result, DatabaseManager& manager, int64_t id) ORTHANC_OVERRIDE; -# endif -#endif virtual bool HasCreateInstance() const ORTHANC_OVERRIDE { @@ -388,6 +402,61 @@ int32_t property, int value); + virtual void AddLabel(DatabaseManager& manager, + int64_t resource, + const std::string& label) ORTHANC_OVERRIDE; + + virtual void RemoveLabel(DatabaseManager& manager, + int64_t resource, + const std::string& label) ORTHANC_OVERRIDE; + + virtual void ListLabels(std::list& target, + DatabaseManager& manager, + int64_t resource) ORTHANC_OVERRIDE; + + virtual void ListAllLabels(std::list& target, + DatabaseManager& manager) ORTHANC_OVERRIDE; + + virtual bool HasAtomicIncrementGlobalProperty() ORTHANC_OVERRIDE; + + virtual int64_t IncrementGlobalProperty(DatabaseManager& manager, + const char* serverIdentifier, + int32_t property, + int64_t increment) ORTHANC_OVERRIDE; + + virtual bool HasUpdateAndGetStatistics() ORTHANC_OVERRIDE; + + virtual void UpdateAndGetStatistics(DatabaseManager& manager, + int64_t& patientsCount, + int64_t& studiesCount, + int64_t& seriesCount, + int64_t& instancesCount, + int64_t& compressedSize, + int64_t& uncompressedSize) ORTHANC_OVERRIDE; + + virtual bool HasMeasureLatency() ORTHANC_OVERRIDE; + + virtual uint64_t MeasureLatency(DatabaseManager& manager) ORTHANC_OVERRIDE; + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + // New primitive since Orthanc 1.12.5 + virtual bool HasExtendedChanges() const ORTHANC_OVERRIDE + { + return true; + } +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + virtual bool HasFindSupport() const ORTHANC_OVERRIDE; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + virtual void ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, + DatabaseManager& manager, + const Orthanc::DatabasePluginMessages::Find_Request& request) ORTHANC_OVERRIDE; +#endif + + /** * "maxDatabaseRetries" is to handle * "OrthancPluginErrorCode_DatabaseCannotSerialize" if there is a @@ -400,6 +469,8 @@ static void Finalize(); - static DatabaseManager* CreateSingleDatabaseManager(IDatabaseBackend& backend); + static DatabaseManager* CreateSingleDatabaseManager(IDatabaseBackend& backend, + bool hasIdentifierTags, + const std::list& identifierTags); }; } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/IndexConnectionsPool.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/IndexConnectionsPool.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,175 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "IndexConnectionsPool.h" + +namespace OrthancDatabases +{ + class IndexConnectionsPool::ManagerReference : public Orthanc::IDynamicObject + { + private: + DatabaseManager* manager_; + + public: + explicit ManagerReference(DatabaseManager& manager) : + manager_(&manager) + { + } + + DatabaseManager& GetManager() + { + assert(manager_ != NULL); + return *manager_; + } + }; + + + IndexConnectionsPool::IndexConnectionsPool(IndexBackend* backend, + size_t countConnections) : + backend_(backend), + countConnections_(countConnections) + { + if (countConnections == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "There must be a non-zero number of connections to the database"); + } + else if (backend == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + context_ = backend_->GetContext(); + } + } + + + IndexConnectionsPool::~IndexConnectionsPool() + { + for (std::list::iterator + it = connections_.begin(); it != connections_.end(); ++it) + { + assert(*it != NULL); + delete *it; + } + } + + + void IndexConnectionsPool::OpenConnections(bool hasIdentifierTags, + const std::list& identifierTags) + { + boost::unique_lock lock(connectionsMutex_); + + if (connections_.size() == 0) + { + assert(backend_.get() != NULL); + + { + std::unique_ptr manager(new DatabaseManager(backend_->CreateDatabaseFactory())); + manager->GetDatabase(); // Make sure to open the database connection + + backend_->ConfigureDatabase(*manager, hasIdentifierTags, identifierTags); + connections_.push_back(manager.release()); + } + + for (size_t i = 1; i < countConnections_; i++) + { + connections_.push_back(new DatabaseManager(backend_->CreateDatabaseFactory())); + connections_.back()->GetDatabase(); // Make sure to open the database connection + } + + for (std::list::iterator + it = connections_.begin(); it != connections_.end(); ++it) + { + assert(*it != NULL); + availableConnections_.Enqueue(new ManagerReference(**it)); + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void IndexConnectionsPool::CloseConnections() + { + boost::unique_lock lock(connectionsMutex_); + + if (connections_.size() != countConnections_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else if (availableConnections_.GetSize() != countConnections_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Some connections are still in use, bug in the Orthanc core"); + } + else + { + for (std::list::iterator + it = connections_.begin(); it != connections_.end(); ++it) + { + assert(*it != NULL); + (*it)->Close(); + } + } + } + + + IndexConnectionsPool::Accessor::Accessor(IndexConnectionsPool& pool) : + lock_(pool.connectionsMutex_), + pool_(pool), + manager_(NULL) + { + for (;;) + { + std::unique_ptr manager(pool.availableConnections_.Dequeue(100)); + if (manager.get() != NULL) + { + manager_ = &dynamic_cast(*manager).GetManager(); + return; + } + } + } + + + IndexConnectionsPool::Accessor::~Accessor() + { + assert(manager_ != NULL); + pool_.availableConnections_.Enqueue(new ManagerReference(*manager_)); + } + + + IndexBackend& IndexConnectionsPool::Accessor::GetBackend() const + { + return *pool_.backend_; + } + + + DatabaseManager& IndexConnectionsPool::Accessor::GetManager() const + { + assert(manager_ != NULL); + return *manager_; + } +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/IndexConnectionsPool.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/IndexConnectionsPool.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,80 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "IdentifierTag.h" +#include "IndexBackend.h" + +#include + +#include + +namespace OrthancDatabases +{ + class IndexConnectionsPool : public boost::noncopyable + { + private: + class ManagerReference; + + std::unique_ptr backend_; + OrthancPluginContext* context_; + boost::shared_mutex connectionsMutex_; + size_t countConnections_; + std::list connections_; + Orthanc::SharedMessageQueue availableConnections_; + + public: + IndexConnectionsPool(IndexBackend* backend /* takes ownership */, + size_t countConnections); + + ~IndexConnectionsPool(); + + OrthancPluginContext* GetContext() const + { + return context_; + } + + void OpenConnections(bool hasIdentifierTags, + const std::list& identifierTags); + + void CloseConnections(); + + class Accessor : public boost::noncopyable + { + private: + boost::shared_lock lock_; + IndexConnectionsPool& pool_; + DatabaseManager* manager_; + + public: + explicit Accessor(IndexConnectionsPool& pool); + + ~Accessor(); + + IndexBackend& GetBackend() const; + + DatabaseManager& GetManager() const; + }; + }; +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/IndexUnitTests.h --- a/Framework/Plugins/IndexUnitTests.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/IndexUnitTests.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -27,8 +29,6 @@ #include // For std::unique_ptr<> -#include - #include #include @@ -56,7 +56,7 @@ /** * Mock enumeration inspired from the source code of Orthanc... only * for use in the unit tests! - * https://hg.orthanc-server.com/orthanc/file/default/OrthancServer/Sources/ServerEnumerations.h + * https://orthanc.uclouvain.be/hg/orthanc/file/default/OrthancServer/Sources/ServerEnumerations.h **/ enum MetadataType { @@ -245,7 +245,8 @@ db.SetOutputFactory(new DatabaseBackendAdapterV2::Factory(&context, NULL)); - std::unique_ptr manager(IndexBackend::CreateSingleDatabaseManager(db)); + std::list identifierTags; + std::unique_ptr manager(IndexBackend::CreateSingleDatabaseManager(db, false, identifierTags)); std::unique_ptr output(db.CreateOutput()); @@ -509,20 +510,19 @@ ASSERT_EQ(0u, ci.size()); - OrthancPluginExportedResource exp; - exp.seq = -1; - exp.resourceType = OrthancPluginResourceType_Study; - exp.publicId = "id"; - exp.modality = "remote"; - exp.date = "date"; - exp.patientId = "patient"; - exp.studyInstanceUid = "study"; - exp.seriesInstanceUid = "series"; - exp.sopInstanceUid = "instance"; - db.LogExportedResource(*manager, exp); + db.LogExportedResource(*manager, OrthancPluginResourceType_Study, "id", "remote", "date", + "patient", "study", "series", "instance"); expectedExported.reset(new OrthancPluginExportedResource()); - *expectedExported = exp; + expectedExported->seq = -1; + expectedExported->resourceType = OrthancPluginResourceType_Study; + expectedExported->publicId = "id"; + expectedExported->modality = "remote"; + expectedExported->date = "date"; + expectedExported->patientId = "patient"; + expectedExported->studyInstanceUid = "study"; + expectedExported->seriesInstanceUid = "series"; + expectedExported->sopInstanceUid = "instance"; bool done; db.GetExportedResources(*output, done, *manager, 0, 10); diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/MessagesToolbox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/MessagesToolbox.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,172 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "MessagesToolbox.h" + + +namespace OrthancDatabases +{ + namespace MessagesToolbox + { + Orthanc::ResourceType Convert(Orthanc::DatabasePluginMessages::ResourceType resourceType) + { + switch (resourceType) + { + case Orthanc::DatabasePluginMessages::RESOURCE_PATIENT: + return Orthanc::ResourceType_Patient; + + case Orthanc::DatabasePluginMessages::RESOURCE_STUDY: + return Orthanc::ResourceType_Study; + + case Orthanc::DatabasePluginMessages::RESOURCE_SERIES: + return Orthanc::ResourceType_Series; + + case Orthanc::DatabasePluginMessages::RESOURCE_INSTANCE: + return Orthanc::ResourceType_Instance; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + OrthancPluginResourceType ConvertToPlainC(Orthanc::ResourceType type) + { + switch (type) + { + case Orthanc::ResourceType_Patient: + return OrthancPluginResourceType_Patient; + + case Orthanc::ResourceType_Study: + return OrthancPluginResourceType_Study; + + case Orthanc::ResourceType_Series: + return OrthancPluginResourceType_Series; + + case Orthanc::ResourceType_Instance: + return OrthancPluginResourceType_Instance; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + Orthanc::ResourceType Convert(OrthancPluginResourceType type) + { + switch (type) + { + case OrthancPluginResourceType_Patient: + return Orthanc::ResourceType_Patient; + + case OrthancPluginResourceType_Study: + return Orthanc::ResourceType_Study; + + case OrthancPluginResourceType_Series: + return Orthanc::ResourceType_Series; + + case OrthancPluginResourceType_Instance: + return Orthanc::ResourceType_Instance; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + OrthancPluginConstraintType ConvertToPlainC(ConstraintType constraint) + { + switch (constraint) + { + case ConstraintType_Equal: + return OrthancPluginConstraintType_Equal; + + case ConstraintType_GreaterOrEqual: + return OrthancPluginConstraintType_GreaterOrEqual; + + case ConstraintType_SmallerOrEqual: + return OrthancPluginConstraintType_SmallerOrEqual; + + case ConstraintType_Wildcard: + return OrthancPluginConstraintType_Wildcard; + + case ConstraintType_List: + return OrthancPluginConstraintType_List; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } +#endif + + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + ConstraintType Convert(OrthancPluginConstraintType constraint) + { + switch (constraint) + { + case OrthancPluginConstraintType_Equal: + return ConstraintType_Equal; + + case OrthancPluginConstraintType_GreaterOrEqual: + return ConstraintType_GreaterOrEqual; + + case OrthancPluginConstraintType_SmallerOrEqual: + return ConstraintType_SmallerOrEqual; + + case OrthancPluginConstraintType_Wildcard: + return ConstraintType_Wildcard; + + case OrthancPluginConstraintType_List: + return ConstraintType_List; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } +#endif + + + Orthanc::DatabasePluginMessages::ResourceType ConvertToProtobuf(OrthancPluginResourceType resourceType) + { + switch (resourceType) + { + case OrthancPluginResourceType_Patient: + return Orthanc::DatabasePluginMessages::RESOURCE_PATIENT; + + case OrthancPluginResourceType_Study: + return Orthanc::DatabasePluginMessages::RESOURCE_STUDY; + + case OrthancPluginResourceType_Series: + return Orthanc::DatabasePluginMessages::RESOURCE_SERIES; + + case OrthancPluginResourceType_Instance: + return Orthanc::DatabasePluginMessages::RESOURCE_INSTANCE; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + } +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/MessagesToolbox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/MessagesToolbox.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,92 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include + +// Ensure that "ORTHANC_PLUGINS_VERSION_IS_ABOVE" is defined +#include "../../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" + +#define ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT 0 + +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 2) +# undef ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT +# define ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT 1 +# endif +#endif + + +#define ORTHANC_PLUGINS_HAS_INTEGRATED_FIND 0 + +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) +# undef ORTHANC_PLUGINS_HAS_INTEGRATED_FIND +# define ORTHANC_PLUGINS_HAS_INTEGRATED_FIND 1 +# endif +#endif + + +#define ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED 0 + +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) +# undef ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED +# define ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED 1 +# endif +#endif + + +#include + + +namespace OrthancDatabases +{ + enum ConstraintType + { + ConstraintType_Equal, + ConstraintType_SmallerOrEqual, + ConstraintType_GreaterOrEqual, + ConstraintType_Wildcard, + ConstraintType_List + }; + + namespace MessagesToolbox + { + Orthanc::ResourceType Convert(Orthanc::DatabasePluginMessages::ResourceType resourceType); + + OrthancPluginResourceType ConvertToPlainC(Orthanc::ResourceType type); + + Orthanc::ResourceType Convert(OrthancPluginResourceType type); + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + OrthancPluginConstraintType ConvertToPlainC(ConstraintType constraint); +#endif + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + ConstraintType Convert(OrthancPluginConstraintType constraint); +#endif + } +} diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/PluginInitialization.cpp --- a/Framework/Plugins/PluginInitialization.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/PluginInitialization.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -42,10 +44,13 @@ bool InitializePlugin(OrthancPluginContext* context, + const std::string& pluginName, const std::string& dbms, bool isIndex) { -#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2) +#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 12, 4) + Orthanc::Logging::InitializePluginContext(context, pluginName); +#elif ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2) Orthanc::Logging::InitializePluginContext(context); #else Orthanc::Logging::Initialize(context); @@ -58,6 +63,19 @@ assert(DisplayPerformanceWarning(dbms, isIndex)); /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(context) == 0) + { + LOG(ERROR) << "Your version of Orthanc (" + << context->orthancVersion << ") must be above " + << ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER << "." + << ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER << "." + << ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER + << " to run this plugin"; + return false; + } + + + /* Warn the user if the Orthanc runtime has not an optimal version */ bool useFallback = true; bool isOptimal = false; @@ -88,18 +106,6 @@ # endif #endif - if (useFallback && - OrthancPluginCheckVersion(context) == 0) - { - LOG(ERROR) << "Your version of Orthanc (" - << context->orthancVersion << ") must be above " - << ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER << "." - << ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER << "." - << ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER - << " to run this plugin"; - return false; - } - if (useFallback) { std::string v(context->orthancVersion); @@ -149,7 +155,7 @@ std::string(isIndex ? "index" : "storage area") + " into a " + dbms + " database"); - OrthancPluginSetDescription(context, description.c_str()); + OrthancPlugins::SetDescription(pluginName, description.c_str()); return true; } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/PluginInitialization.h --- a/Framework/Plugins/PluginInitialization.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/PluginInitialization.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -28,6 +30,7 @@ namespace OrthancDatabases { bool InitializePlugin(OrthancPluginContext* context, + const std::string& pluginName, const std::string& dbms, bool isIndex); } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/StorageBackend.cpp --- a/Framework/Plugins/StorageBackend.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/StorageBackend.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -138,7 +140,7 @@ if (statement.IsDone()) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource, "ReadWhole: No content found for storage."); } else if (statement.GetResultFieldsCount() != 1) { @@ -207,7 +209,7 @@ if (statement.IsDone()) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource, "ReadRange: No content found for storage."); } else if (statement.GetResultFieldsCount() != 1) { diff -r 82f73188b58d -r f18e46d7dbf8 Framework/Plugins/StorageBackend.h --- a/Framework/Plugins/StorageBackend.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/Plugins/StorageBackend.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLDatabase.cpp --- a/Framework/PostgreSQL/PostgreSQLDatabase.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLDatabase.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -158,7 +160,10 @@ void PostgreSQLDatabase::ExecuteMultiLines(const std::string& sql) { - LOG(TRACE) << "PostgreSQL: " << sql; + if (IsVerboseEnabled()) + { + LOG(INFO) << "PostgreSQL: " << sql; + } Open(); PGresult* result = PQexec(reinterpret_cast(pg_), sql.c_str()); @@ -317,13 +322,15 @@ PostgreSQLDatabase::TransientAdvisoryLock::TransientAdvisoryLock( PostgreSQLDatabase& database, - int32_t lock) : + int32_t lock, + unsigned int retries, + unsigned int retryInterval) : database_(database), lock_(lock) { bool locked = true; - for (unsigned int i = 0; i < 10; i++) + for (unsigned int i = 0; i < retries; i++) { if (database_.AcquireAdvisoryLock(lock_)) { @@ -332,7 +339,7 @@ } else { - boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + boost::this_thread::sleep(boost::posix_time::milliseconds(retryInterval)); } } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLDatabase.h --- a/Framework/PostgreSQL/PostgreSQLDatabase.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLDatabase.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -35,6 +37,7 @@ private: friend class PostgreSQLStatement; friend class PostgreSQLLargeObject; + friend class PostgreSQLTransaction; class Factory; @@ -58,6 +61,11 @@ void Open(); + bool IsVerboseEnabled() const + { + return parameters_.IsVerboseEnabled(); + } + bool AcquireAdvisoryLock(int32_t lock); bool ReleaseAdvisoryLock(int32_t lock); @@ -90,7 +98,9 @@ public: TransientAdvisoryLock(PostgreSQLDatabase& database, - int32_t lock); + int32_t lock, + unsigned int retries = 10, + unsigned int retryInterval = 500); ~TransientAdvisoryLock(); }; @@ -98,5 +108,16 @@ static IDatabaseFactory* CreateDatabaseFactory(const PostgreSQLParameters& parameters); static PostgreSQLDatabase* CreateDatabaseConnection(const PostgreSQLParameters& parameters); + + protected: + const std::string GetReadWriteTransactionStatement() const + { + return parameters_.GetReadWriteTransactionStatement(); + } + + const std::string GetReadOnlyTransactionStatement() const + { + return parameters_.GetReadOnlyTransactionStatement(); + } }; } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLIncludes.h --- a/Framework/PostgreSQL/PostgreSQLIncludes.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLIncludes.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLLargeObject.cpp --- a/Framework/PostgreSQL/PostgreSQLLargeObject.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLLargeObject.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLLargeObject.h --- a/Framework/PostgreSQL/PostgreSQLLargeObject.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLLargeObject.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLParameters.cpp --- a/Framework/PostgreSQL/PostgreSQLParameters.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLParameters.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -41,6 +43,8 @@ lock_ = true; maxConnectionRetries_ = 10; connectionRetryInterval_ = 5; + isVerboseEnabled_ = false; + isolationMode_ = IsolationMode_Serializable; } @@ -93,8 +97,26 @@ lock_ = configuration.GetBooleanValue("Lock", true); // Use locking by default + isVerboseEnabled_ = configuration.GetBooleanValue("EnableVerboseLogs", false); + maxConnectionRetries_ = configuration.GetUnsignedIntegerValue("MaximumConnectionRetries", 10); connectionRetryInterval_ = configuration.GetUnsignedIntegerValue("ConnectionRetryInterval", 5); + + std::string transactionMode = configuration.GetStringValue("TransactionMode", "Serializable"); + if (transactionMode == "ReadCommitted") + { + LOG(WARNING) << "PostgreSQL: using READ COMMITTED transaction mode"; + SetIsolationMode(IsolationMode_ReadCommited); + } + else if (transactionMode == "Serializable") + { + LOG(WARNING) << "PostgreSQL: using SERIALIZABLE transaction mode"; + SetIsolationMode(IsolationMode_Serializable); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, std::string("Invalid value for 'TransactionMode': ") + transactionMode); + } } @@ -178,6 +200,36 @@ database_ = database; } + const std::string PostgreSQLParameters::GetReadWriteTransactionStatement() const + { + switch (isolationMode_) + { + case IsolationMode_ReadCommited: + return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED READ WRITE"; + + case IsolationMode_Serializable: + return "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE"; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + const std::string PostgreSQLParameters::GetReadOnlyTransactionStatement() const + { + switch (isolationMode_) + { + case IsolationMode_ReadCommited: + return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED READ ONLY"; + + case IsolationMode_Serializable: + return "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY"; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + void PostgreSQLParameters::Format(std::string& target) const { if (uri_.empty()) diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLParameters.h --- a/Framework/PostgreSQL/PostgreSQLParameters.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLParameters.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -29,6 +31,12 @@ namespace OrthancDatabases { + enum IsolationMode + { + IsolationMode_Serializable = 0, + IsolationMode_ReadCommited = 1 + }; + class PostgreSQLParameters { private: @@ -42,7 +50,8 @@ bool lock_; unsigned int maxConnectionRetries_; unsigned int connectionRetryInterval_; - + bool isVerboseEnabled_; + IsolationMode isolationMode_; void Reset(); public: @@ -124,6 +133,26 @@ return connectionRetryInterval_; } + void SetIsolationMode(IsolationMode isolationMode) + { + isolationMode_ = isolationMode; + } + + const std::string GetReadWriteTransactionStatement() const; + + const std::string GetReadOnlyTransactionStatement() const; + + void SetVerboseEnabled(bool enabled) + { + isVerboseEnabled_ = enabled; + } + + bool IsVerboseEnabled() const + { + return isVerboseEnabled_; + } + + void Format(std::string& target) const; }; } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLResult.cpp --- a/Framework/PostgreSQL/PostgreSQLResult.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLResult.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -83,6 +85,11 @@ position_(0), database_(statement.GetDatabase()) { + if (database_.IsVerboseEnabled()) + { + LOG(INFO) << "PostgreSQL: " << statement.sql_; + } + result_ = statement.Execute(); assert(result_ != NULL); // An exception would have been thrown otherwise @@ -254,6 +261,9 @@ case OIDOID: return new LargeObjectResult(database_, GetLargeObjectOid(column)); + case VOIDOID: + return NULL; + default: throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLResult.h --- a/Framework/PostgreSQL/PostgreSQLResult.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLResult.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLStatement.cpp --- a/Framework/PostgreSQL/PostgreSQLStatement.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLStatement.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -24,6 +26,7 @@ #include "../Common/BinaryStringValue.h" #include "../Common/InputFileValue.h" +#include "../Common/Integer32Value.h" #include "../Common/Integer64Value.h" #include "../Common/NullValue.h" #include "../Common/ResultBase.h" @@ -286,7 +289,8 @@ } #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) - throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseCannotSerialize); + std::string errorString(PQresultErrorMessage(result)); + throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseCannotSerialize, errorString, false); // don't log here, it is handled at higher level #else throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Collision between multiple writers"); #endif @@ -307,7 +311,10 @@ inputs_(new Inputs), formatter_(Dialect_PostgreSQL) { - LOG(TRACE) << "PostgreSQL: " << sql; + if (database.IsVerboseEnabled()) + { + LOG(TRACE) << "PostgreSQL: " << sql; + } } @@ -318,7 +325,11 @@ formatter_(Dialect_PostgreSQL) { query.Format(sql_, formatter_); - LOG(TRACE) << "PostgreSQL: " << sql_; + + if (database.IsVerboseEnabled()) + { + LOG(TRACE) << "PostgreSQL: " << sql_; + } for (size_t i = 0; i < formatter_.GetParametersCount(); i++) { @@ -328,6 +339,10 @@ DeclareInputInteger64(i); break; + case ValueType_Integer32: + DeclareInputInteger(i); + break; + case ValueType_Utf8String: DeclareInputString(i); break; @@ -519,6 +534,10 @@ BindInteger64(i, dynamic_cast(parameters.GetValue(name)).GetValue()); break; + case ValueType_Integer32: + BindInteger(i, dynamic_cast(parameters.GetValue(name)).GetValue()); + break; + case ValueType_Null: BindNull(i); break; diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLStatement.h --- a/Framework/PostgreSQL/PostgreSQLStatement.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLStatement.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLTransaction.cpp --- a/Framework/PostgreSQL/PostgreSQLTransaction.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLTransaction.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -63,23 +65,28 @@ LOG(ERROR) << "PostgreSQL: Beginning a transaction twice!"; throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - - database_.ExecuteMultiLines("BEGIN"); + std::string transactionStatement; // if not defined, will use the default DB transaction isolation level switch (type) { case TransactionType_ReadWrite: - database_.ExecuteMultiLines("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE"); + { + transactionStatement = database_.GetReadWriteTransactionStatement(); break; + } case TransactionType_ReadOnly: - database_.ExecuteMultiLines("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY"); + { + transactionStatement = database_.GetReadOnlyTransactionStatement(); break; + } default: throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - + + database_.ExecuteMultiLines("BEGIN; " + transactionStatement); + isOpen_ = true; } diff -r 82f73188b58d -r f18e46d7dbf8 Framework/PostgreSQL/PostgreSQLTransaction.h --- a/Framework/PostgreSQL/PostgreSQLTransaction.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/PostgreSQL/PostgreSQLTransaction.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/SQLite/SQLiteDatabase.cpp --- a/Framework/SQLite/SQLiteDatabase.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/SQLite/SQLiteDatabase.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/SQLite/SQLiteDatabase.h --- a/Framework/SQLite/SQLiteDatabase.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/SQLite/SQLiteDatabase.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/SQLite/SQLiteResult.cpp --- a/Framework/SQLite/SQLiteResult.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/SQLite/SQLiteResult.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/SQLite/SQLiteResult.h --- a/Framework/SQLite/SQLiteResult.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/SQLite/SQLiteResult.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/SQLite/SQLiteStatement.cpp --- a/Framework/SQLite/SQLiteStatement.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/SQLite/SQLiteStatement.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/SQLite/SQLiteStatement.h --- a/Framework/SQLite/SQLiteStatement.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/SQLite/SQLiteStatement.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/SQLite/SQLiteTransaction.cpp --- a/Framework/SQLite/SQLiteTransaction.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/SQLite/SQLiteTransaction.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Framework/SQLite/SQLiteTransaction.h --- a/Framework/SQLite/SQLiteTransaction.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Framework/SQLite/SQLiteTransaction.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 MySQL/CMakeLists.txt --- a/MySQL/CMakeLists.txt Wed Feb 01 16:24:37 2023 +0100 +++ b/MySQL/CMakeLists.txt Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License @@ -22,22 +24,33 @@ set(ORTHANC_PLUGIN_VERSION "mainline") +# This is the preferred version of the Orthanc SDK for this plugin +set(ORTHANC_SDK_DEFAULT_VERSION "1.12.0") + +# This is the list of the versions of the Orthanc SDK against which +# this plugin will compile +set(ORTHANC_SDK_COMPATIBLE_VERSIONS "0.9.5" "1.4.0" "1.5.2" "1.5.4" "1.9.2" "1.12.0" "1.12.3" "1.12.4") + +# This is the minimal version of the Orthanc runtime that will provide +# best performance. If the version of the Orthanc runtime is below +# this minimal version, a warning message will be printed (but the +# plugin will still start). set(ORTHANC_OPTIMAL_VERSION_MAJOR 1) -set(ORTHANC_OPTIMAL_VERSION_MINOR 9) -set(ORTHANC_OPTIMAL_VERSION_REVISION 2) +set(ORTHANC_OPTIMAL_VERSION_MINOR 12) +set(ORTHANC_OPTIMAL_VERSION_REVISION 0) if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline") set(ORTHANC_FRAMEWORK_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_VERSION "1.9.6") + set(ORTHANC_FRAMEWORK_VERSION "1.12.4") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif() include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DatabasesPluginParameters.cmake) set(ENABLE_MYSQL_BACKEND ON) -set(OPENSSL_STATIC_VERSION "1.1.1" CACHE STRING "Force the use of OpenSSL 1.1.1" FORCE) +set(OPENSSL_STATIC_VERSION "3.0" CACHE STRING "Force the use of OpenSSL 3.0.x" FORCE) include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DatabasesPluginConfiguration.cmake) @@ -78,11 +91,28 @@ MYSQL_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql MYSQL_GET_LAST_CHANGE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/GetLastChangeIndex.sql MYSQL_CREATE_INSTANCE ${CMAKE_SOURCE_DIR}/Plugins/CreateInstance.sql - - MYSQL_INSTALL_REVISION_AND_CUSTOM_DATA - ${CMAKE_SOURCE_DIR}/Plugins/InstallRevisionAndCustomData.sql + MYSQL_INSTALL_REVISION_AND_CUSTOM_DATA ${CMAKE_SOURCE_DIR}/Plugins/InstallRevisionAndCustomData.sql + MYSQL_DELETE_RESOURCES ${CMAKE_SOURCE_DIR}/Plugins/DeleteResources.sql ) +if (EXISTS ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto) + add_custom_command( + COMMAND + ${PROTOC_EXECUTABLE} ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto --cpp_out=${AUTOGENERATED_DIR} -I${ORTHANC_SDK_ROOT}/orthanc/ + DEPENDS + ProtobufCompiler + ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto + OUTPUT + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.h + ) + + list(APPEND AUTOGENERATED_SOURCES + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ) +endif() + + add_custom_target( AutogeneratedTarget DEPENDS @@ -158,3 +188,10 @@ set_target_properties(UnitTests PROPERTIES COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=0 ) + +if (COMMAND DefineSourceBasenameForTarget) + DefineSourceBasenameForTarget(FrameworkForPlugins) + DefineSourceBasenameForTarget(OrthancMySQLIndex) + DefineSourceBasenameForTarget(OrthancMySQLStorage) + DefineSourceBasenameForTarget(UnitTests) +endif() diff -r 82f73188b58d -r f18e46d7dbf8 MySQL/NEWS --- a/MySQL/NEWS Wed Feb 01 16:24:37 2023 +0100 +++ b/MySQL/NEWS Tue Sep 24 15:04:21 2024 +0200 @@ -3,6 +3,37 @@ * Added support for customData in AttachedFiles * Added support for revision in AttachedFiles & Metadata +* Added support for ExtendedChanges: + - changes?type=...&to=... +* Fixed a memory leak when executing non cached SQL statements (rarely used) + + +Release 5.2 (2024-06-06) +======================== + +Minimum plugin SDK: 0.9.5 +Optimal plugin SDK: 1.12.0+ +Minimum Orthanc runtime: 0.9.5 +Optimal Orthanc runtime: 1.12.0+ + +* Fix check of Orthanc runtime version +* Optimized resources deletion (https://discourse.orthanc-server.org/t/image-insert-are-too-slow-databse-performance-too-poor-when-using-mysql-mariadb/3820/19) + + +Release 5.1 (2023-06-27) +======================== + +* Optimization of LookupResources mainly used in tools/find, C-Find and QIDO-RS. + + +Release 5.0 (2023-04-16) +======================== + +* Added support for labels +* Compatibility with Orthanc SDK 1.12.0 (communications between the + Orthanc core and the database plugin using Google Protocol Buffers) +* Upgraded dependencies for static builds (notably on Windows and LSB): + - openssl 3.1.0 Release 4.3 (2021-07-22) diff -r 82f73188b58d -r f18e46d7dbf8 MySQL/Plugins/DeleteResources.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MySQL/Plugins/DeleteResources.sql Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,23 @@ +DROP PROCEDURE IF EXISTS DeleteResources; + +CREATE PROCEDURE DeleteResources( + IN p_id BIGINT +) +BEGIN + DECLARE v_internalId BIGINT@ + DECLARE done INT DEFAULT FALSE@ + DECLARE cur1 CURSOR FOR + SELECT internalId FROM DeletedResources@ + DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = TRUE@ + set done=FALSE@ + + OPEN cur1@ + REPEAT + FETCH cur1 INTO v_internalId@ + IF NOT done THEN + DELETE FROM Resources WHERE internalId=v_internalId@ + END IF@ + UNTIL done END REPEAT@ + CLOSE cur1@ + +END; \ No newline at end of file diff -r 82f73188b58d -r f18e46d7dbf8 MySQL/Plugins/IndexPlugin.cpp --- a/MySQL/Plugins/IndexPlugin.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/MySQL/Plugins/IndexPlugin.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -27,12 +29,17 @@ #include #include +#include + +#define ORTHANC_PLUGIN_NAME "mysql-index" extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { - if (!OrthancDatabases::InitializePlugin(context, "MySQL", true)) + GOOGLE_PROTOBUF_VERIFY_VERSION; + + if (!OrthancDatabases::InitializePlugin(context, ORTHANC_PLUGIN_NAME, "MySQL", true)) { return -1; } @@ -92,12 +99,13 @@ OrthancDatabases::MySQLDatabase::GlobalFinalization(); Orthanc::HttpClient::GlobalFinalize(); Orthanc::Toolbox::FinalizeOpenSsl(); + google::protobuf::ShutdownProtobufLibrary(); } ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { - return "mysql-index"; + return ORTHANC_PLUGIN_NAME; } diff -r 82f73188b58d -r f18e46d7dbf8 MySQL/Plugins/MySQLDefinitions.h --- a/MySQL/Plugins/MySQLDefinitions.h Wed Feb 01 16:24:37 2023 +0100 +++ b/MySQL/Plugins/MySQLDefinitions.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 MySQL/Plugins/MySQLIndex.cpp --- a/MySQL/Plugins/MySQLIndex.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/MySQL/Plugins/MySQLIndex.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -62,7 +64,9 @@ } - void MySQLIndex::ConfigureDatabase(DatabaseManager& manager) + void MySQLIndex::ConfigureDatabase(DatabaseManager& manager, + bool hasIdentifierTags, + const std::list& identifierTags) { uint32_t expectedVersion = 6; @@ -234,7 +238,7 @@ // to the LONGTEXT type (up to 4GB). This might be important // for applications such as the Osimis Web viewer that stores // large amount of metadata. - // http://book.orthanc-server.com/faq/features.html#central-registry-of-metadata-and-attachments + // https://orthanc.uclouvain.be/book/faq/features.html#central-registry-of-metadata-and-attachments t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE Metadata MODIFY value LONGTEXT"); revision = 4; @@ -294,7 +298,46 @@ t.Commit(); } - if (revision == 6) + if (revision == 6) + { + // Added new table "Labels" since release 5.0 to deal with + // labels that were introduced in Orthanc 1.12.0 + DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); + + t.GetDatabaseTransaction().ExecuteMultiLines( + "CREATE TABLE Labels(id BIGINT NOT NULL," + "label VARCHAR(64) NOT NULL," + "PRIMARY KEY(id, label)," + "CONSTRAINT Labels1 FOREIGN KEY (id) REFERENCES Resources(internalId) ON DELETE CASCADE);" + "CREATE INDEX LabelsIndex1 ON Labels(id);" + "CREATE INDEX LabelsIndex2 ON Labels(label);"); + + revision = 7; + SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, revision); + + t.Commit(); + } + + if (revision == 7) + { + DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); + + // Install the "CreateInstance" extension + std::string query; + + Orthanc::EmbeddedResources::GetFileResource + (query, Orthanc::EmbeddedResources::MYSQL_DELETE_RESOURCES); + + // Need to escape arobases: Don't use "t.GetDatabaseTransaction().ExecuteMultiLines()" here + db.ExecuteMultiLines(query, true); + + revision = 8; + SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, revision); + + t.Commit(); + } + + if (revision == 8) { DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); @@ -307,13 +350,13 @@ // Need to escape arobases: Don't use "t.GetDatabaseTransaction().ExecuteMultiLines()" here db.ExecuteMultiLines(query, true); - revision = 7; + revision = 9; SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, revision); t.Commit(); } - if (revision != 7) + if (revision != 9) { LOG(ERROR) << "MySQL plugin is incompatible with database schema revision: " << revision; throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); @@ -467,11 +510,25 @@ lookupResourcesToDelete.Execute(args); } + // { + // DatabaseManager::CachedStatement deleteHierarchy( + // STATEMENT_FROM_HERE, manager, + // "DELETE FROM Resources WHERE internalId IN (SELECT internalId FROM DeletedResources)"); + // deleteHierarchy.Execute(); + // } + + { - DatabaseManager::CachedStatement deleteHierarchy( + DatabaseManager::CachedStatement deleteResources( STATEMENT_FROM_HERE, manager, - "DELETE FROM Resources WHERE internalId IN (SELECT internalId FROM DeletedResources)"); - deleteHierarchy.Execute(); + "CALL DeleteResources(${id})"); + + deleteResources.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + + deleteResources.Execute(args); } SignalDeletedResources(output, manager); @@ -554,4 +611,24 @@ } } #endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + bool MySQLIndex::HasFindSupport() const + { + // TODO-FIND + return false; + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + void MySQLIndex::ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, + DatabaseManager& manager, + const Orthanc::DatabasePluginMessages::Find_Request& request) + { + // TODO-FIND + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } +#endif } diff -r 82f73188b58d -r f18e46d7dbf8 MySQL/Plugins/MySQLIndex.h --- a/MySQL/Plugins/MySQLIndex.h Wed Feb 01 16:24:37 2023 +0100 +++ b/MySQL/Plugins/MySQLIndex.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -43,7 +45,9 @@ virtual IDatabaseFactory* CreateDatabaseFactory() ORTHANC_OVERRIDE; - virtual void ConfigureDatabase(DatabaseManager& database) ORTHANC_OVERRIDE; + virtual void ConfigureDatabase(DatabaseManager& database, + bool hasIdentifierTags, + const std::list& identifierTags) ORTHANC_OVERRIDE; virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE { @@ -80,5 +84,21 @@ const char* hashInstance) ORTHANC_OVERRIDE; #endif + + // New primitive since Orthanc 1.12.0 + virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE + { + return true; + } + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + virtual bool HasFindSupport() const ORTHANC_OVERRIDE; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + virtual void ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, + DatabaseManager& manager, + const Orthanc::DatabasePluginMessages::Find_Request& request) ORTHANC_OVERRIDE; +#endif }; } diff -r 82f73188b58d -r f18e46d7dbf8 MySQL/Plugins/MySQLStorageArea.cpp --- a/MySQL/Plugins/MySQLStorageArea.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/MySQL/Plugins/MySQLStorageArea.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 MySQL/Plugins/MySQLStorageArea.h --- a/MySQL/Plugins/MySQLStorageArea.h Wed Feb 01 16:24:37 2023 +0100 +++ b/MySQL/Plugins/MySQLStorageArea.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 MySQL/Plugins/StoragePlugin.cpp --- a/MySQL/Plugins/StoragePlugin.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/MySQL/Plugins/StoragePlugin.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -27,12 +29,14 @@ #include #include +#define ORTHANC_PLUGIN_NAME "mysql-storage" + extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { - if (!OrthancDatabases::InitializePlugin(context, "MySQL", false)) + if (!OrthancDatabases::InitializePlugin(context, ORTHANC_PLUGIN_NAME, "MySQL", false)) { return -1; } @@ -94,7 +98,7 @@ ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { - return "mysql-storage"; + return ORTHANC_PLUGIN_NAME; } diff -r 82f73188b58d -r f18e46d7dbf8 MySQL/UnitTests/UnitTestsMain.cpp --- a/MySQL/UnitTests/UnitTestsMain.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/MySQL/UnitTests/UnitTestsMain.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -50,19 +52,21 @@ OrthancDatabases::MySQLIndex db1(NULL, noLock); db1.SetClearAll(true); - std::unique_ptr manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1)); + std::list identifierTags; + + std::unique_ptr manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1, false, identifierTags)); { OrthancDatabases::MySQLIndex db2(NULL, lock); - std::unique_ptr manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2)); + std::unique_ptr manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2, false, identifierTags)); OrthancDatabases::MySQLIndex db3(NULL, lock); - ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3), Orthanc::OrthancException); + ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3, false, identifierTags), Orthanc::OrthancException); } OrthancDatabases::MySQLIndex db4(NULL, lock); - std::unique_ptr manager4(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db4)); + std::unique_ptr manager4(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db4, false, identifierTags)); } diff -r 82f73188b58d -r f18e46d7dbf8 NOTES --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/NOTES Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,494 @@ +Resources: +********* +- PG transaction modes explained: https://www.postgresql.org/files/developer/concurrency.pdf +- Isolation level explained (PG + MySQL): https://amirsoleimani.medium.com/understanding-database-isolation-level-via-examples-mysql-and-postgres-a86b5502d404 +- Message queuing in PG: https://www.crunchydata.com/blog/message-queuing-using-native-postgresql + + +Create and delete instances Internals: +************************************* + +isNewInstance = CreateInstance(...) + +if (!isNewInstance && overwriteInstances) + DeleteResource(instance) + -> ClearDeletedFiles(manager); + DELETE FROM DeletedFiles ------> this is not a temporary table in postgres but it is supposed to be empty before and after each transaction -> it is empty when taking a snapshot of the DB in READ COMMITTED mode!!! + ClearDeletedResources(manager); + DELETE FROM DeletedResources ------> this is not a temporary table in postgres but it is supposed to be empty before and after each transaction !!! + + DELETE FROM RemainingAncestor ------> this is not a temporary table in postgres but it is supposed to be empty before and after each transaction !!! + DELETE FROM Resources WHERE internalId=.. + -> cascades delete the MainDicomTags, the Metadata and the AttachedFiles + -> this triggers AttachedFileDeletedFunc + INSERT INTO DeletedFiles VALUES + (old.uuid, old.filetype, old.compressedSize, + old.uncompressedSize, old.compressionType, + old.uncompressedHash, old.compressedHash); + RETURN NULL; + -> this triggers a SQL trigger: ResourceDeletedFunc + INSERT INTO DeletedResources VALUES (old.resourceType, old.publicId); + IF EXISTS (SELECT 1 FROM Resources WHERE parentId = old.parentId) THEN + -- Signal that the deleted resource has a remaining parent + -- (a parent that must not be deleted but whose LastUpdate must be updated) + INSERT INTO RemainingAncestor + SELECT resourceType, publicId FROM Resources WHERE internalId = old.parentId; + ELSE + -- Delete a parent resource when its unique child is deleted + DELETE FROM Resources WHERE internalId = old.parentId; + END IF; + + SELECT * FROM RemainingAncestor + -> SignalRemainingAncestor() // There is at most 1 remaining ancestor + -> ServerIndex::TransactionContext::SignalRemainingAncestor() + -> stores remainingType and remainingPublicId (this is used in StatelessDatabaseOperations::DeleteResource to build the Rest Response of /delete + and to update the LastUpdate of all parent (only when deleted from /delete)) + + SignalDeletedFiles(output, manager); + SELECT * FROM DeletedFiles + -> SignalDeletedAttachment() + -> ServerIndex::TransactionContext::SignalAttachmentDeleted() + -> pendingFilesToRemove_.push_back(FileToRemove(info)) (files are deleted in CommitFilesToRemove in the ServerIndex::TransactionContext::Commit) + + SignalDeletedResources(output, manager); + SELECT resourceType, publicId FROM DeletedResources + -> SignalDeletedResource() + -> Emit DeletedResource event (lua) + + + if (!CreateInstance(...)) + Error: "No new instance while overwriting; this should not happen" + +if isNewInstance -> LogChange +if isNewSeries -> LogChange +.... + +Sample SQL code that you can execute in DBeaver to test new functions/procedures: + +CreateInstance +************************************************************************ + +CREATE OR REPLACE FUNCTION CreateInstance( + IN patient_public_id TEXT, + IN study_public_id TEXT, + IN series_public_id TEXT, + IN instance_public_id TEXT, + OUT is_new_patient BIGINT, + OUT is_new_study BIGINT, + OUT is_new_series BIGINT, + OUT is_new_instance BIGINT, + OUT patient_internal_id BIGINT, + OUT study_internal_id BIGINT, + OUT series_internal_id BIGINT, + OUT instance_internal_id BIGINT) AS $body$ + +BEGIN + is_new_patient := 1; + is_new_study := 1; + is_new_series := 1; + is_new_instance := 1; + + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 0, patient_public_id, NULL); + EXCEPTION + WHEN unique_violation THEN + is_new_patient := 0; + END; + SELECT internalid INTO patient_internal_id FROM "resources" WHERE publicId = patient_public_id AND resourcetype = 0 FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 1, study_public_id, patient_internal_id); + EXCEPTION + WHEN unique_violation THEN + is_new_study := 0; + END; + SELECT internalid INTO study_internal_id FROM "resources" WHERE publicId = study_public_id AND resourcetype = 1 FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 2, series_public_id, study_internal_id); + EXCEPTION + WHEN unique_violation THEN + is_new_series := 0; + END; + SELECT internalid INTO series_internal_id FROM "resources" WHERE publicId = series_public_id AND resourcetype = 2 FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 3, instance_public_id, series_internal_id); + EXCEPTION + WHEN unique_violation THEN + is_new_instance := 0; + END; + SELECT internalid INTO instance_internal_id FROM "resources" WHERE publicId = instance_public_id AND resourcetype = 3 FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction + + IF is_new_instance > 0 THEN + -- Move the patient to the end of the recycling order. + PERFORM PatientAddedOrUpdated(patient_internal_id, 1); + END IF; +END; + +$body$ LANGUAGE plpgsql; + + +DO $$ +DECLARE + result record; +begin + delete from "resources"; + + SELECT * INTO result from CreateInstance('patient1', 'study1', 'series1', 'instance1'); + + RAISE NOTICE 'Value patientInternalId: %, is_new: %', result.patient_internal_id, result.is_new_patient; + RAISE NOTICE 'Value studyInternalId: %, is_new: %', result.study_internal_id, result.is_new_study; + RAISE NOTICE 'Value seriesInternalId: %, is_new: %', result.series_internal_id, result.is_new_series; + RAISE NOTICE 'Value instanceInternalId: %, is_new: %', result.instance_internal_id, result.is_new_instance; + RAISE NOTICE '--------------'; + + SELECT * INTO result from CreateInstance('patient1', 'study1', 'series1', 'instance2'); + + RAISE NOTICE 'Value patientInternalId: %, is_new: %', result.patient_internal_id, result.is_new_patient; + RAISE NOTICE 'Value studyInternalId: %, is_new: %', result.study_internal_id, result.is_new_study; + RAISE NOTICE 'Value seriesInternalId: %, is_new: %', result.series_internal_id, result.is_new_series; + RAISE NOTICE 'Value instanceInternalId: %, is_new: %', result.instance_internal_id, result.is_new_instance; + RAISE NOTICE '--------------'; + + SELECT * INTO result from CreateInstance('patient1', 'study1', 'series1', 'instance2'); + + RAISE NOTICE 'Value patientInternalId: %, is_new: %', result.patient_internal_id, result.is_new_patient; + RAISE NOTICE 'Value studyInternalId: %, is_new: %', result.study_internal_id, result.is_new_study; + RAISE NOTICE 'Value seriesInternalId: %, is_new: %', result.series_internal_id, result.is_new_series; + RAISE NOTICE 'Value instanceInternalId: %, is_new: %', result.instance_internal_id, result.is_new_instance; + RAISE NOTICE '--------------'; + +END $$; + + +-- \set patient_key 'patient_key' +-- SELECT CreateInstance('patient', 'study', 'series', 'instance', patient_key) ; + +-- drop function CreateInstance +-- select * from "resources"; +-- delete from "resources"; +-- INSERT INTO "resources" VALUES (DEFAULT, 0, 'patient', NULL) + + + +************************************************************************ + +In debug, no verbose logs, 10 connections +Orthanc 1.12.1 + PG 5.1 (serializable mode) : test_concurrent_anonymize_same_study with 4 workers and 10x repeat: 43.957 s +Orthanc mainline + PG mainline (read-committed mode) : test_concurrent_anonymize_same_study with 4 workers and 10x repeat: 15.744 s + test_concurrent_anonymize_same_study deletion took: 18.8 s + +Orthanc 1.12.1 + PG 5.1 (serializable mode) : test_concurrent_uploads_same_study with 20 workers and 1x repeat: 21.214 s +Orthanc mainline + PG mainline (read-committed mode) : test_concurrent_uploads_same_study with 20 workers and 1x repeat: 9.514 s + +Orthanc 1.12.1 + PG 5.1 (serializable mode) : test_upload_delete_same_study_from_multiple_threads with 5 workers and 3x repeat (10x): 23.016 s +Orthanc mainline + PG mainline (read-committed mode) : test_upload_delete_same_study_from_multiple_threads with 5 workers and 3x repeat (10x): 7.129 s + +Orthanc mainline + PG mainline (read-committed mode) : test_upload_multiple_studies_from_multiple_threads with 10 workers and 25 files and repeat 3x: 6.454 s + +With Docker with 10 connections SQL: +osimis/orthanc:24.1.2 : test_concurrent_anonymize_same_study with 4 workers and 10x repeat: 18.956 s FAIL !!! + test_concurrent_anonymize_same_study deletion took: NA +osimis/orthanc:current: test_concurrent_anonymize_same_study with 4 workers and 10x repeat: 6.867 s + test_concurrent_anonymize_same_study deletion took: 9.095 s + +osimis/orthanc:24.1.2 : test_concurrent_uploads_same_study with 20 workers and 1x repeat: 9.822 s +osimis/orthanc:current: test_concurrent_uploads_same_study with 20 workers and 1x repeat: 16.027 s up to 38s ! (slower but the test is not representative of a real life scenario !!!!!) + +osimis/orthanc:24.1.2 : test_upload_delete_same_study_from_multiple_threads with 5 workers and 3x repeat (10x): 12.966 s +osimis/orthanc:current: test_upload_delete_same_study_from_multiple_threads with 5 workers and 3x repeat (10x): 4.196 s + +osimis/orthanc:24.1.2 : test_upload_multiple_studies_from_multiple_threads with 10 workers and 25 files and repeat 3x: 8.957 s +osimis/orthanc:current: test_upload_multiple_studies_from_multiple_threads with 10 workers and 25 files and repeat 3x: 2.671 s + +Testing the connecions (note: Orthanc and PG server running on the same server) +10 connections : test_concurrent_anonymize_same_study with 4 workers and 10x repeat: 15.744 s +1 connection : test_concurrent_anonymize_same_study with 4 workers and 10x repeat: 21.341 s +10 connections : test_concurrent_uploads_same_study with 20 workers and 1x repeat: 6.57 s +1 connection : test_concurrent_uploads_same_study with 20 workers and 1x repeat: 10.223 s +10 connections : test_upload_delete_same_study_from_multiple_threads with 5 workers and 3x repeat (10x): 7.129 s +1 connection : test_upload_delete_same_study_from_multiple_threads with 5 workers and 3x repeat (10x): 11.172 s + + +LARGE QUERIES +------------- + +new extended-api-v1/tools/find requirements (note: extended-api-v1 is a code name:-) ): +- be able to list child-resources like in /studies/.../instances?expand + => we need a "ParentStudy" in the query. Note: a single "Parent" field is not enough since we sometimes want to specify a "Parent" + that is 2 or 3 levels higher (e.g /patients/../instances) + e.g: { + "Level": "Instance", + "Query": { + "ParentStudy": "orthanc-study-id", + }, + } +- be able to search on labels like in the current tools/find implementation + => we need a "ParentStudy" in the query. Note: a single "Parent" field is not enough since we sometimes want to specify a "Parent" + that is 2 or 3 levels higher (e.g /patients/../instances) + e.g: { + "Level": "Study", + "Query": { + "Labels": ["hello"], + "LabelsConstraint" : "All", + }, + } +- be able to select what are the expected fields in the response + => we need a "ExpandContent" field in the query. + If there are no "ExpandContent", we simply return a list of IDs + e.g: { + "Level": "Series", + "Query": { + "ParentStudy": "orthanc-study-id", + }, + "ExpandContent": [ + "Children", // the list of orthanc-ids of the child of the resources (equivalent to e.g /series/../instances) + "Parent", // the resource parent id (e.g. "ParentStudy" for a series) + "Labels", // the resources labels (equivalent to e.g /series/../labels) + "Metadata", // the resources metadata (equivalent to e.g. /series/../metadata?expand) + "MainDicomTags", // the resources MainDicomTags + "FileInfo", // "FileSize" + "FileUuid", applicable only to instances + // TODO: what about "IsStable", "Status", "LastUpdate", "IndexInSeries", "ExpectedNumberOfInstances" ??? + // TODO: right now, we have a "RequestedTags" option that can list the DICOM TAGS and tools/find can fetch the tags from disk if they are not in the ExtraMainDicomTags + ] + } +- be able to filter on DICOM Tags (and compose with a Parent orthanc id) + e.g: get all US series from this study + { + "Level": "Series", + "Query": { + "ParentStudy": "orthanc-study-id", + "DicomTags": { + "Modality": "US" + } + } + } +- be able to filter on a metadata (and compose with a Parent orthanc id) + e.g: to get all PDF instances of a study + { + "Level": "Instance", + "Query": { + "ParentStudy": "orthanc-study-id", + "Metadata": { + "SopClassUid": "1.2.840.10008.5.1.4.1.1.104.1" + } + } + } +- be able to order results on one or more DicomTags or Metadata + limit results + offset (for pagination) + e.g: to get page 5 of all studies from this month + { + "Level": "Study", + "Query": { + "DicomTags": { + "StudyDate": "20240101-20240131" + }, + "OrderBy": [ + { + "DicomTag": "StudyDate", + "Order": "Desc" + }, + { + "Metadata": "LastUpdate", + "Order": "Desc" + } + ], + "Limit": 1000, + "Since": 5000 + } + } + + +current queries - single level: +------------------------------ + +Search: +sql = ("SELECT publicId, internalId " + "FROM Resources " + "WHERE resourceType = " + formatter.FormatResourceType(queryLevel) + + " "); + +if (dicomIdentifiersComparisons.size() > 0) + sql += (" AND internalId IN (SELECT id FROM DicomIdentifiers WHERE " + *it + ") "); + +if (mainDicomTagsComparisons.size() > 0) + sql += (" AND internalId IN (SELECT id FROM MainDicomTags WHERE " + *it + ") "); + +if (!labels.empty()) + sql += (" AND internalId " + inOrNotIn + " (SELECT id" + " FROM (SELECT id, COUNT(1) AS labelsCount " + "FROM Labels " + "WHERE label IN (" + Join(formattedLabels, "", ", ") + ") GROUP BY id" + ") AS temp " + " WHERE labelsCount " + condition + ")"); +if (limit != 0) + sql += " LIMIT " + boost::lexical_cast(limit); + + +Patients: +"SELECT patients_studies.patients_public_id, MIN(instances.publicId) AS instances_public_id " + "FROM (SELECT patients.publicId AS patients_public_id, MIN(studies.internalId) AS studies_internal_id " + "FROM (" + sql + + ") AS patients " + "INNER JOIN Resources studies ON studies.parentId = patients.internalId " + "GROUP BY patients.publicId " + ") AS patients_studies " + "INNER JOIN Resources series ON series.parentId = patients_studies.studies_internal_id " + "INNER JOIN Resources instances ON instances.parentId = series.internalId " + "GROUP BY patients_studies.patients_public_id" + +Studies: +"SELECT studies_series.studies_public_id, MIN(instances.publicId) AS instances_public_id " + "FROM (SELECT studies.publicId AS studies_public_id, MIN(series.internalId) AS series_internal_id " + "FROM (" + sql + + ") AS studies " + "INNER JOIN Resources series ON series.parentId = studies.internalId " + "GROUP BY studies.publicId " + ") AS studies_series " + "INNER JOIN Resources instances ON instances.parentId = studies_series.series_internal_id " + "GROUP BY studies_series.studies_public_id" + +Series: +"SELECT series.publicId AS series_public_id, MIN(instances.publicId) AS instances_public_id " + "FROM (" + sql + + ") AS series " + "INNER JOIN Resources instances ON instances.parentId = series.internalId " + "GROUP BY series.publicId " + +Instances: +"SELECT instances.publicId, instances.publicId FROM (" + sql + ") instances" + + +current queries - multi level: +------------------------------ +Search: ISqlLookupFormatter::Apply + + + +New queries: +----------- + +- Get all series ids where PatientID (16, 32)="ID-DYL-WAL-00000008" + StudyDescription (8, 4144) %LIKE% "000000" + order by StudyDate(8, 32) DESC + + Current: curl http://localhost:8044/tools/find -d '{"Level": "Series", "Query": {"StudyDescription": "*000000*", "PatientID": "ID-DYL-WAL-00000008"}}' + SELECT series.publicId, MIN(instances.publicId) + FROM (SELECT series.publicId, series.internalId + FROM Resources AS series + INNER JOIN Resources studies ON studies.internalId=series.parentId + INNER JOIN DicomIdentifiers t0 ON t0.id = studies.internalId AND t0.tagGroup = 16 AND t0.tagElement = 32 -- Patient tags are copied at study level ! + INNER JOIN DicomIdentifiers t1 ON t1.id = studies.internalId AND t1.tagGroup = 8 AND t1.tagElement = 4144 + WHERE series.resourceType = 2 AND t0.value = 'ID-DYL-WAL-00000008' AND t1.value like '%000000%' ESCAPE '\' LIMIT 101) series + INNER JOIN Resources instances ON instances.parentId = series.internalId GROUP BY series.publicId + + + With order by, we must not retrieve the instance anymore: + SELECT series.publicId, series.internalId + FROM Resources AS series + INNER JOIN Resources studies ON studies.internalId=series.parentId + INNER JOIN DicomIdentifiers t0 ON t0.id = studies.internalId AND t0.tagGroup = 16 AND t0.tagElement = 32 -- Patient tags are copied at study level ! + INNER JOIN DicomIdentifiers t1 ON t1.id = studies.internalId AND t1.tagGroup = 8 AND t1.tagElement = 4144 + INNER JOIN DicomIdentifiers t2 ON t2.id = studies.internalId AND t2.tagGroup = 8 AND t2.tagElement = 32 + WHERE series.resourceType = 2 AND t0.value = 'ID-DYL-WAL-00000008' AND t1.value like '%000000%' ESCAPE '\' + ORDER BY t2 DESC + LIMIT 101 + + Combine multiple search criteria: + Search studies with a label constraints + a Metadata filter + a DICOM Tag filter + a ParentPatient filter + + SELECT studies.publicId, studies.internalId + FROM Resources AS studies + INNER JOIN + + WHERE resourceType = 1 AND internalId IN (SELECT id FROM DicomIdentifiers WHERE tagGroup = 16 AND tagElement = 32 AND value = $1) AND internalId IN (SELECT id FROM DicomIdentifiers WHERE tagGroup = 8 AND tagElement = 32 AND value >= $2) AND internalId IN (SELECT id FROM DicomIdentifiers WHERE tagGroup = 8 AND tagElement = 4144 AND value LIKE $3 ESCAPE '\') LIMIT 101) AS studies INNER JOIN Resources series ON series.parentId = studies.internalId GROUP BY studies.publicId ) AS studies_series INNER JOIN Resources instances ON instances.parentId = studies_series.series_internal_id GROUP BY studies_series.studies_public_id + + + -- current search at study level (single level -> we use IN instead of INNER JOIN) + SELECT publicId, internalId + FROM Resources + WHERE resourceType = 1 + AND internalId IN (SELECT id + FROM DicomIdentifiers WHERE tagGroup = 16 AND tagElement = 32 AND value = 'ID-DYL-WAL-00000008') + AND internalId IN (SELECT id + FROM DicomIdentifiers WHERE tagGroup = 8 AND tagElement = 32 AND value >= '19700101') + AND internalId IN (SELECT id + FROM DicomIdentifiers WHERE tagGroup = 8 AND tagElement = 4144 AND value LIKE '%000000%' ESCAPE '\') + AND internalId IN (SELECT id + FROM (SELECT id, COUNT(1) AS labelsCount FROM Labels WHERE label IN ('Label2', 'Label3') GROUP BY id) AS temp + WHERE labelsCount >= 1) + AND internalId IN (SELECT id + FROM Metadata WHERE type=7 AND value >= '20240415T120000') + LIMIT 101 + + + -- same query with INNER JOIN to compare performance + SELECT studies.publicId, studies.internalId + FROM Resources AS studies + INNER JOIN DicomIdentifiers t0 ON t0.id = studies.internalId AND t0.tagGroup = 16 AND t0.tagElement = 32 AND t0.value = 'ID-DYL-WAL-00000008' -- Patient tags are copied at study level ! + INNER JOIN DicomIdentifiers t1 ON t1.id = studies.internalId AND t1.tagGroup = 8 AND t1.tagElement = 4144 AND t1.value like '%000000%' ESCAPE '\' + INNER JOIN DicomIdentifiers t2 ON t2.id = studies.internalId AND t2.tagGroup = 8 AND t2.tagElement = 32 AND t2.value >= '19700101' + INNER JOIN Metadata m ON m.id = studies.internalId AND m.type = 7 AND m.value >= '20240415T120000' + WHERE studies.resourceType = 1 +-- AND studies.internalId IN (SELECT id FROM (SELECT id, COUNT(1) AS labelsCount FROM labels WHERE label IN ('Label2', 'Label6') GROUP BY id) AS temp WHERE labelsCount = 2) -- All labels +-- AND studies.internalId IN (SELECT id FROM (SELECT id, COUNT(1) AS labelsCount FROM labels WHERE label IN ('Label2', 'Label6') GROUP BY id) AS temp WHERE labelsCount >= 1) -- Any labels + AND studies.internalId NOT IN (SELECT id FROM (SELECT id, COUNT(1) AS labelsCount FROM labels WHERE label IN ('Label2', 'Label6') GROUP BY id) AS temp WHERE labelsCount >= 1) -- None of labels + ORDER BY t2.value DESC, m.value ASC + LIMIT 100 OFFSET 0 + + + We may store this in a temporary table that we can reuse e.g to retrieve, the labels, the metadata, the MainDicomTags ... + CREATE TEMPORARY TABLE FilteredResourcesIds AS + SELECT series.publicId, series.internalId + FROM Resources AS series + INNER JOIN Resources studies ON studies.internalId=series.parentId + INNER JOIN DicomIdentifiers t0 ON t0.id = studies.internalId AND t0.tagGroup = 16 AND t0.tagElement = 32 -- Patient tags are copied at study level ! + INNER JOIN DicomIdentifiers t1 ON t1.id = studies.internalId AND t1.tagGroup = 8 AND t1.tagElement = 4144 + INNER JOIN DicomIdentifiers t2 ON t2.id = studies.internalId AND t2.tagGroup = 8 AND t2.tagElement = 32 + WHERE series.resourceType = 2 AND t0.value = 'ID-DYL-WAL-00000008' AND t1.value like '%000000%' ESCAPE '\' + ORDER BY t2 DESC + LIMIT 101; + + Note: we can probably only retrieve the internalId as the key -> since it is an integer, it is more efficient than a uuid to populate the responses + Retrieve all MainDicomTags at the series level + SELECT internalId, publicId, tagGroup, tagElement, value FROM MainDicomTags AS tags + INNER JOIN FilteredResourcesIds ON tags.id = FilteredResourcesIds.internalId; + + Retrieve all MainDicomTags at the study level too but attach them to the series ids + SELECT series.internalId, series.publicId, tagGroup, tagElement, value + FROM Resources as series + INNER JOIN Resources studies ON studies.internalId=series.parentId + INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = series.internalId + INNER JOIN MainDicomTags tags ON tags.id=studies.internalId + + Retrieve all Metadata from all the series + SELECT metadata.id, series.publicId, metadata.type, metadata.value, metadata.revision + FROM Metadata + INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = Metadata.id + + Retrieve all Labels from all the series (not tested yet) + SELECT labels.id, series.publicId, label + FROM Labels + INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = Labels.id + + Retrieve all parents from all the series + SELECT filtered.internalId, filtered.publicId, parentLevel.internalId as parentInternalId, parentLevel.publicId as parentPublicId + FROM Resources as currentLevel + INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = currentLevel.internalId + INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId + + Retrieve all children from all the series + SELECT filtered.internalId, filtered.publicId, childLevel.internalId as childInternalId, childLevel.publicId as childPublicId + FROM Resources as currentLevel + INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = currentLevel.internalId + INNER JOIN Resources childLevel ON childLevel.parentId = currentLevel.internalId + + Retrieve one instanceId by series + SELECT filtered.internalId, filtered.publicId, MIN(c0.publicId) as instancePublicId + FROM Resources as currentLevel + INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = currentLevel.internalId + INNER JOIN Resources c0 ON c0.parentId = currentLevel.internalId + GROUP BY filtered.internalId, filtered.publicId + + TODO Retrieve all attachments from all the series diff -r 82f73188b58d -r f18e46d7dbf8 Odbc/CMakeLists.txt --- a/Odbc/CMakeLists.txt Wed Feb 01 16:24:37 2023 +0100 +++ b/Odbc/CMakeLists.txt Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License @@ -20,17 +22,28 @@ cmake_minimum_required(VERSION 2.8) project(OrthancOdbc) -set(ORTHANC_PLUGIN_VERSION "1.1") +set(ORTHANC_PLUGIN_VERSION "mainline") + +# This is the preferred version of the Orthanc SDK for this plugin +set(ORTHANC_SDK_DEFAULT_VERSION "1.12.0") +# This is the list of the versions of the Orthanc SDK against which +# this plugin will compile +set(ORTHANC_SDK_COMPATIBLE_VERSIONS "0.9.5" "1.4.0" "1.5.2" "1.5.4" "1.9.2" "1.12.0" "1.12.3" "1.12.4") + +# This is the minimal version of the Orthanc runtime that will provide +# best performance. If the version of the Orthanc runtime is below +# this minimal version, a warning message will be printed (but the +# plugin will still start). set(ORTHANC_OPTIMAL_VERSION_MAJOR 1) -set(ORTHANC_OPTIMAL_VERSION_MINOR 9) -set(ORTHANC_OPTIMAL_VERSION_REVISION 2) +set(ORTHANC_OPTIMAL_VERSION_MINOR 12) +set(ORTHANC_OPTIMAL_VERSION_REVISION 0) if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline") set(ORTHANC_FRAMEWORK_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_VERSION "1.9.6") + set(ORTHANC_FRAMEWORK_VERSION "1.12.4") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif() @@ -78,6 +91,23 @@ ODBC_PREPARE_STORAGE ${CMAKE_SOURCE_DIR}/Plugins/PrepareStorage.sql ) +if (EXISTS ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto) + add_custom_command( + COMMAND + ${PROTOC_EXECUTABLE} ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto --cpp_out=${AUTOGENERATED_DIR} -I${ORTHANC_SDK_ROOT}/orthanc/ + DEPENDS + ProtobufCompiler + ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto + OUTPUT + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.h + ) + + list(APPEND AUTOGENERATED_SOURCES + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ) +endif() + add_custom_target( AutogeneratedTarget DEPENDS @@ -156,3 +186,10 @@ set_target_properties(UnitTests PROPERTIES COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=0 ) + +if (COMMAND DefineSourceBasenameForTarget) + DefineSourceBasenameForTarget(FrameworkForPlugins) + DefineSourceBasenameForTarget(OrthancOdbcIndex) + DefineSourceBasenameForTarget(OrthancOdbcStorage) + DefineSourceBasenameForTarget(UnitTests) +endif() diff -r 82f73188b58d -r f18e46d7dbf8 Odbc/NEWS --- a/Odbc/NEWS Wed Feb 01 16:24:37 2023 +0100 +++ b/Odbc/NEWS Tue Sep 24 15:04:21 2024 +0200 @@ -1,8 +1,30 @@ Pending changes in the mainline =============================== +Minimum plugin SDK: 0.9.5 +Optimal plugin SDK: 1.12.0+ +Minimum Orthanc runtime: 0.9.5 +Optimal Orthanc runtime: 1.12.0+ + +* Fix check of Orthanc runtime version +* Added support for ExtendedChanges: + - changes?type=...&to=... * Added support for customData in AttachedFiles +* Fix bug 224, error when using LIMIT with MSSQLServer + https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=224 +* Fixed a memory leak when executing non cached SQL statements (rarely used) + + +Release 1.2 (2024-03-06) +======================== + +* Optimization of LookupResources mainly used in tools/find, C-Find and QIDO-RS. +* Compatibility with Orthanc SDK 1.12.0 (communications between the + Orthanc core and the database plugin using Google Protocol Buffers) +* Now detecting communication link failure with the DB and retrying to connect. +* Fixed "MaximumConnectionRetries" configuration that was not taken into account. + Release 1.1 (2021-12-06) ======================== diff -r 82f73188b58d -r f18e46d7dbf8 Odbc/Plugins/IndexPlugin.cpp --- a/Odbc/Plugins/IndexPlugin.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Odbc/Plugins/IndexPlugin.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -27,6 +29,8 @@ #include +#define ORTHANC_PLUGIN_NAME "odbc-index" + #if defined(_WIN32) # ifdef _MSC_VER @@ -41,6 +45,9 @@ #endif +#include + + static const char* const KEY_ODBC = "Odbc"; @@ -53,7 +60,9 @@ ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { - if (!OrthancDatabases::InitializePlugin(context, "ODBC", true)) + GOOGLE_PROTOBUF_VERIFY_VERSION; + + if (!OrthancDatabases::InitializePlugin(context, ORTHANC_PLUGIN_NAME, "ODBC", true)) { return -1; } @@ -95,7 +104,7 @@ { const std::string connectionString = odbc.GetStringValue("IndexConnectionString", ""); const unsigned int countConnections = odbc.GetUnsignedIntegerValue("IndexConnectionsCount", 1); - const unsigned int maxConnectionRetries = odbc.GetUnsignedIntegerValue("MaxConnectionRetries", 10); + const unsigned int maxConnectionRetries = odbc.GetUnsignedIntegerValue("MaximumConnectionRetries", 10); const unsigned int connectionRetryInterval = odbc.GetUnsignedIntegerValue("ConnectionRetryInterval", 5); if (connectionString.empty()) @@ -129,12 +138,13 @@ { LOG(WARNING) << "ODBC index is finalizing"; OrthancDatabases::IndexBackend::Finalize(); + google::protobuf::ShutdownProtobufLibrary(); } ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { - return "odbc-index"; + return ORTHANC_PLUGIN_NAME; } diff -r 82f73188b58d -r f18e46d7dbf8 Odbc/Plugins/OdbcIndex.cpp --- a/Odbc/Plugins/OdbcIndex.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Odbc/Plugins/OdbcIndex.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -204,7 +206,9 @@ } - void OdbcIndex::ConfigureDatabase(DatabaseManager& manager) + void OdbcIndex::ConfigureDatabase(DatabaseManager& manager, + bool hasIdentifierTags, + const std::list& identifierTags) { uint32_t expectedVersion = 6; @@ -712,4 +716,24 @@ SignalDeletedFiles(output, manager); } + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + bool OdbcIndex::HasFindSupport() const + { + // TODO-FIND + return false; + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + void OdbcIndex::ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, + DatabaseManager& manager, + const Orthanc::DatabasePluginMessages::Find_Request& request) + { + // TODO-FIND + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } +#endif } diff -r 82f73188b58d -r f18e46d7dbf8 Odbc/Plugins/OdbcIndex.h --- a/Odbc/Plugins/OdbcIndex.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Odbc/Plugins/OdbcIndex.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -55,7 +57,9 @@ virtual IDatabaseFactory* CreateDatabaseFactory() ORTHANC_OVERRIDE; - virtual void ConfigureDatabase(DatabaseManager& manager) ORTHANC_OVERRIDE; + virtual void ConfigureDatabase(DatabaseManager& manager, + bool hasIdentifierTags, + const std::list& identifierTags) ORTHANC_OVERRIDE; virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE { @@ -87,5 +91,21 @@ DatabaseManager& manager, int64_t id, int32_t attachment) ORTHANC_OVERRIDE; + + // New primitive since Orthanc 1.12.0 + virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE + { + return false; + } + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + virtual bool HasFindSupport() const ORTHANC_OVERRIDE; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + virtual void ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, + DatabaseManager& manager, + const Orthanc::DatabasePluginMessages::Find_Request& request) ORTHANC_OVERRIDE; +#endif }; } diff -r 82f73188b58d -r f18e46d7dbf8 Odbc/Plugins/StoragePlugin.cpp --- a/Odbc/Plugins/StoragePlugin.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Odbc/Plugins/StoragePlugin.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -28,6 +30,8 @@ #include +#define ORTHANC_PLUGIN_NAME "odbc-storage" + namespace OrthancDatabases { @@ -112,7 +116,7 @@ ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { - if (!OrthancDatabases::InitializePlugin(context, "ODBC", false)) + if (!OrthancDatabases::InitializePlugin(context, ORTHANC_PLUGIN_NAME, "ODBC", false)) { return -1; } @@ -190,7 +194,7 @@ ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { - return "odbc-storage"; + return ORTHANC_PLUGIN_NAME; } diff -r 82f73188b58d -r f18e46d7dbf8 Odbc/UnitTests/UnitTestsMain.cpp --- a/Odbc/UnitTests/UnitTestsMain.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Odbc/UnitTests/UnitTestsMain.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/CMakeLists.txt --- a/PostgreSQL/CMakeLists.txt Wed Feb 01 16:24:37 2023 +0100 +++ b/PostgreSQL/CMakeLists.txt Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License @@ -22,15 +24,26 @@ set(ORTHANC_PLUGIN_VERSION "mainline") +# This is the preferred version of the Orthanc SDK for this plugin +set(ORTHANC_SDK_DEFAULT_VERSION "1.12.3") + +# This is the list of the versions of the Orthanc SDK against which +# this plugin will compile +set(ORTHANC_SDK_COMPATIBLE_VERSIONS "1.12.3" "1.12.4") + +# This is the minimal version of the Orthanc runtime that will provide +# best performance. If the version of the Orthanc runtime is below +# this minimal version, a warning message will be printed (but the +# plugin will still start). set(ORTHANC_OPTIMAL_VERSION_MAJOR 1) -set(ORTHANC_OPTIMAL_VERSION_MINOR 9) -set(ORTHANC_OPTIMAL_VERSION_REVISION 2) +set(ORTHANC_OPTIMAL_VERSION_MINOR 12) +set(ORTHANC_OPTIMAL_VERSION_REVISION 3) if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline") set(ORTHANC_FRAMEWORK_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_VERSION "1.9.6") + set(ORTHANC_FRAMEWORK_VERSION "1.12.4") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif() @@ -38,7 +51,7 @@ include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DatabasesPluginParameters.cmake) set(ENABLE_POSTGRESQL_BACKEND ON) -set(OPENSSL_STATIC_VERSION "1.1.1" CACHE STRING "Force the use of OpenSSL 1.1.1" FORCE) +set(OPENSSL_STATIC_VERSION "3.0" CACHE STRING "Force the use of OpenSSL 3.0.x" FORCE) include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DatabasesPluginConfiguration.cmake) @@ -76,13 +89,31 @@ EmbedResources( - POSTGRESQL_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql - POSTGRESQL_CREATE_INSTANCE ${CMAKE_SOURCE_DIR}/Plugins/CreateInstance.sql - POSTGRESQL_FAST_TOTAL_SIZE ${CMAKE_SOURCE_DIR}/Plugins/FastTotalSize.sql - POSTGRESQL_FAST_COUNT_RESOURCES ${CMAKE_SOURCE_DIR}/Plugins/FastCountResources.sql - POSTGRESQL_GET_LAST_CHANGE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/GetLastChangeIndex.sql + POSTGRESQL_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/SQL/PrepareIndex.sql + POSTGRESQL_UPGRADE_UNKNOWN_TO_REV1 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/UnknownToRev1.sql + POSTGRESQL_UPGRADE_REV1_TO_REV2 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev1ToRev2.sql + POSTGRESQL_UPGRADE_REV2_TO_REV3 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev2ToRev3.sql ) + +if (EXISTS ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto) + add_custom_command( + COMMAND + ${PROTOC_EXECUTABLE} ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto --cpp_out=${AUTOGENERATED_DIR} -I${ORTHANC_SDK_ROOT}/orthanc/ + DEPENDS + ProtobufCompiler + ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto + OUTPUT + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.h + ) + + list(APPEND AUTOGENERATED_SOURCES + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ) +endif() + + add_custom_target( AutogeneratedTarget DEPENDS @@ -135,6 +166,7 @@ COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=1 ) + install( TARGETS OrthancPostgreSQLIndex OrthancPostgreSQLStorage RUNTIME DESTINATION lib # Destination for Windows @@ -158,3 +190,10 @@ set_target_properties(UnitTests PROPERTIES COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=0 ) + +if (COMMAND DefineSourceBasenameForTarget) + DefineSourceBasenameForTarget(FrameworkForPlugins) + DefineSourceBasenameForTarget(OrthancPostgreSQLIndex) + DefineSourceBasenameForTarget(OrthancPostgreSQLStorage) + DefineSourceBasenameForTarget(UnitTests) +endif() diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/NEWS --- a/PostgreSQL/NEWS Wed Feb 01 16:24:37 2023 +0100 +++ b/PostgreSQL/NEWS Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,75 @@ Pending changes in the mainline =============================== +DB schema revision: 2 +Minimum plugin SDK (for build): 1.12.3 +Minimum Orthanc runtime: 1.12.3 + +* Fix updates from plugin version 3.3 to latest version +* Added support for ExtendedChanges: + - changes?type=...&to=... * Added support for customData in AttachedFiles +* Performance optimizations (to be summarized before release): + - using more prepared SQL statements: + - InsertOrUpdateMetadata + - ExecuteSetResourcesContentTags + - merged BEGIN and SET TRANSACTION statements + - reduced the number of round-trips between Orthanc and the PostgreSQL server: + - e.g: when receiving an instance in an existing series, reduced the number of SQL queries from 13 to 9 +* Fixed a memory leak when executing non cached SQL statements (rarely used) + + +Release 6.2 (2024-03-25) +======================== + +DB schema revision: 2 +Minimum plugin SDK (for build): 1.12.3 +Minimum Orthanc runtime: 1.12.3 + +* Fix check of Orthanc runtime version + + +Release 6.1 (2024-02-14) +======================== + +DB schema revision: 2 + +* Fix handling of MaximumStorageSize & MaximumPatientCount. + + +Release 6.0 (2024-02-08) +======================== + +DB schema revision: 2 + +* The DB schema has been updated to Revision 2. If you need to reinstall the previous + version of the plugin, you should run this script: + https://orthanc.uclouvain.be/hg/orthanc-databases/file/tip/PostgreSQL/Plugins/SQL/Downgrades/Rev2ToRev1.sql +* Transaction Mode: + Introduced a new configuration "TransactionMode" to select the transaction isolation level. + Allowed values: "Serializable", "ReadCommitted". + The "Serializable" mode was the only available value up to now. It is still the default + value now. + The "ReadCommitted" is possible now due to rewrites of SQL queries and notably improves + the Orthanc ability to ingest data from multiple sources in parallel. +* New "EnableVerboseLogs" configuration to show SQL statements being executed. + + +Release 5.1 (2023-06-27) +======================== + +DB schema revision: 1 + +* Optimization of LookupResources mainly used in tools/find, C-Find and QIDO-RS. + +Release 5.0 (2023-04-15) +======================== + +* Added support for labels +* Compatibility with Orthanc SDK 1.12.0 (communications between the + Orthanc core and the database plugin using Google Protocol Buffers) +* Upgraded dependencies for static builds (notably on Windows and LSB): + - openssl 3.1.0 Release 4.0 (2021-04-22) @@ -13,7 +81,7 @@ * Support of multiple readers/writers, by handling retries from Orthanc SDK 1.9.2 * Support of range reads for the storage area, from Orthanc SDK 1.9.0 * Fix issue #193 (LSB binaries crash with PostgreSQL + SSL) by changeset - in OrthancFramework: https://hg.orthanc-server.com/orthanc/rev/9a9118406484 + in OrthancFramework: https://orthanc.uclouvain.be/hg/orthanc/rev/9a9118406484 * Fix issue #151 (Storage failures when running with two instances and PG_LOCK=false) diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/CreateInstance.sql --- a/PostgreSQL/Plugins/CreateInstance.sql Wed Feb 01 16:24:37 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -CREATE FUNCTION CreateInstance( - IN patient TEXT, - IN study TEXT, - IN series TEXT, - IN instance TEXT, - OUT isNewPatient BIGINT, - OUT isNewStudy BIGINT, - OUT isNewSeries BIGINT, - OUT isNewInstance BIGINT, - OUT patientKey BIGINT, - OUT studyKey BIGINT, - OUT seriesKey BIGINT, - OUT instanceKey BIGINT) AS $body$ - -DECLARE - patientSeq BIGINT; - countRecycling BIGINT; - -BEGIN - SELECT internalId FROM Resources INTO instanceKey WHERE publicId = instance AND resourceType = 3; - - IF NOT (instanceKey IS NULL) THEN - -- This instance already exists, stop here - isNewInstance := 0; - ELSE - SELECT internalId FROM Resources INTO patientKey WHERE publicId = patient AND resourceType = 0; - SELECT internalId FROM Resources INTO studyKey WHERE publicId = study AND resourceType = 1; - SELECT internalId FROM Resources INTO seriesKey WHERE publicId = series AND resourceType = 2; - - IF patientKey IS NULL THEN - -- Must create a new patient - IF NOT (studyKey IS NULL AND seriesKey IS NULL AND instanceKey IS NULL) THEN - RAISE EXCEPTION 'Broken invariant'; - END IF; - - INSERT INTO Resources VALUES (DEFAULT, 0, patient, NULL) RETURNING internalId INTO patientKey; - isNewPatient := 1; - ELSE - isNewPatient := 0; - END IF; - - IF (patientKey IS NULL) THEN - RAISE EXCEPTION 'Broken invariant'; - END IF; - - IF studyKey IS NULL THEN - -- Must create a new study - IF NOT (seriesKey IS NULL AND instanceKey IS NULL) THEN - RAISE EXCEPTION 'Broken invariant'; - END IF; - - INSERT INTO Resources VALUES (DEFAULT, 1, study, patientKey) RETURNING internalId INTO studyKey; - isNewStudy := 1; - ELSE - isNewStudy := 0; - END IF; - - IF (studyKey IS NULL) THEN - RAISE EXCEPTION 'Broken invariant'; - END IF; - - IF seriesKey IS NULL THEN - -- Must create a new series - IF NOT (instanceKey IS NULL) THEN - RAISE EXCEPTION 'Broken invariant'; - END IF; - - INSERT INTO Resources VALUES (DEFAULT, 2, series, studyKey) RETURNING internalId INTO seriesKey; - isNewSeries := 1; - ELSE - isNewSeries := 0; - END IF; - - IF (seriesKey IS NULL OR NOT instanceKey IS NULL) THEN - RAISE EXCEPTION 'Broken invariant'; - END IF; - - INSERT INTO Resources VALUES (DEFAULT, 3, instance, seriesKey) RETURNING internalId INTO instanceKey; - isNewInstance := 1; - - -- Move the patient to the end of the recycling order - SELECT seq FROM PatientRecyclingOrder WHERE patientId = patientKey INTO patientSeq; - - IF NOT (patientSeq IS NULL) THEN - -- The patient is not protected - SELECT COUNT(*) FROM (SELECT * FROM PatientRecyclingOrder WHERE seq >= patientSeq LIMIT 2) AS tmp INTO countRecycling; - IF countRecycling = 2 THEN - -- The patient was not at the end of the recycling order - DELETE FROM PatientRecyclingOrder WHERE seq = patientSeq; - INSERT INTO PatientRecyclingOrder VALUES(DEFAULT, patientKey); - END IF; - END IF; - END IF; -END; - -$body$ LANGUAGE plpgsql; diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/FastCountResources.sql --- a/PostgreSQL/Plugins/FastCountResources.sql Wed Feb 01 16:24:37 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ --- https://wiki.postgresql.org/wiki/Count_estimate - -INSERT INTO GlobalIntegers -SELECT 2, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 0; -- Count patients - -INSERT INTO GlobalIntegers -SELECT 3, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 1; -- Count studies - -INSERT INTO GlobalIntegers -SELECT 4, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 2; -- Count series - -INSERT INTO GlobalIntegers -SELECT 5, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 3; -- Count instances - - -CREATE OR REPLACE FUNCTION CountResourcesTrackerFunc() -RETURNS TRIGGER AS $$ -BEGIN - IF TG_OP = 'INSERT' THEN - UPDATE GlobalIntegers SET value = value + 1 WHERE key = new.resourceType + 2; - RETURN new; - ELSIF TG_OP = 'DELETE' THEN - UPDATE GlobalIntegers SET value = value - 1 WHERE key = old.resourceType + 2; - RETURN old; - END IF; -END; -$$ LANGUAGE plpgsql; - - -CREATE TRIGGER CountResourcesTracker -AFTER INSERT OR DELETE ON Resources -FOR EACH ROW -EXECUTE PROCEDURE CountResourcesTrackerFunc(); diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/FastTotalSize.sql --- a/PostgreSQL/Plugins/FastTotalSize.sql Wed Feb 01 16:24:37 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -CREATE TABLE GlobalIntegers( - key INTEGER PRIMARY KEY, - value BIGINT); - -INSERT INTO GlobalIntegers -SELECT 0, CAST(COALESCE(SUM(compressedSize), 0) AS BIGINT) FROM AttachedFiles; - -INSERT INTO GlobalIntegers -SELECT 1, CAST(COALESCE(SUM(uncompressedSize), 0) AS BIGINT) FROM AttachedFiles; - - - -CREATE FUNCTION AttachedFileIncrementSizeFunc() -RETURNS TRIGGER AS $body$ -BEGIN - UPDATE GlobalIntegers SET value = value + new.compressedSize WHERE key = 0; - UPDATE GlobalIntegers SET value = value + new.uncompressedSize WHERE key = 1; - RETURN NULL; -END; -$body$ LANGUAGE plpgsql; - -CREATE FUNCTION AttachedFileDecrementSizeFunc() -RETURNS TRIGGER AS $body$ -BEGIN - UPDATE GlobalIntegers SET value = value - old.compressedSize WHERE key = 0; - UPDATE GlobalIntegers SET value = value - old.uncompressedSize WHERE key = 1; - RETURN NULL; -END; -$body$ LANGUAGE plpgsql; - - - -CREATE TRIGGER AttachedFileIncrementSize -AFTER INSERT ON AttachedFiles -FOR EACH ROW -EXECUTE PROCEDURE AttachedFileIncrementSizeFunc(); - -CREATE TRIGGER AttachedFileDecrementSize -AFTER DELETE ON AttachedFiles -FOR EACH ROW -EXECUTE PROCEDURE AttachedFileDecrementSizeFunc(); diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/GetLastChangeIndex.sql --- a/PostgreSQL/Plugins/GetLastChangeIndex.sql Wed Feb 01 16:24:37 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ --- In PostgreSQL, the most straightforward query would be to run: - --- SELECT currval(pg_get_serial_sequence('Changes', 'seq'))". - --- Unfortunately, this raises the error message "currval of sequence --- "changes_seq_seq" is not yet defined in this session" if no change --- has been inserted before the SELECT. We thus track the sequence --- index with a trigger. --- http://www.neilconway.org/docs/sequences/ - -INSERT INTO GlobalIntegers -SELECT 6, CAST(COALESCE(MAX(seq), 0) AS BIGINT) FROM Changes; - - -CREATE FUNCTION InsertedChangeFunc() -RETURNS TRIGGER AS $body$ -BEGIN - UPDATE GlobalIntegers SET value = new.seq WHERE key = 6; - RETURN NULL; -END; -$body$ LANGUAGE plpgsql; - - -CREATE TRIGGER InsertedChange -AFTER INSERT ON Changes -FOR EACH ROW -EXECUTE PROCEDURE InsertedChangeFunc(); diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/IndexPlugin.cpp --- a/PostgreSQL/Plugins/IndexPlugin.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/PostgreSQL/Plugins/IndexPlugin.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -25,12 +27,18 @@ #include #include +#include + +#define ORTHANC_PLUGIN_NAME "postgresql-index" + extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { - if (!OrthancDatabases::InitializePlugin(context, "PostgreSQL", true)) + GOOGLE_PROTOBUF_VERIFY_VERSION; + + if (!OrthancDatabases::InitializePlugin(context, ORTHANC_PLUGIN_NAME, "PostgreSQL", true)) { return -1; } @@ -86,12 +94,13 @@ LOG(WARNING) << "PostgreSQL index is finalizing"; OrthancDatabases::IndexBackend::Finalize(); Orthanc::Toolbox::FinalizeOpenSsl(); + google::protobuf::ShutdownProtobufLibrary(); } ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { - return "postgresql-index"; + return ORTHANC_PLUGIN_NAME; } diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/PostgreSQLDefinitions.h --- a/PostgreSQL/Plugins/PostgreSQLDefinitions.h Wed Feb 01 16:24:37 2023 +0100 +++ b/PostgreSQL/Plugins/PostgreSQLDefinitions.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -47,3 +49,10 @@ * https://groups.google.com/d/msg/orthanc-users/yV3LSTh_TjI/h3PRApJFBAAJ **/ static const int32_t POSTGRESQL_LOCK_DATABASE_SETUP = 44; + +/** + * Transient advisory lock to protect the instance creation, + * because it is not 100% resilient to concurrency in, e.g, READ COMIITED + * transaction isolation level. + **/ +static const int32_t POSTGRESQL_LOCK_CREATE_INSTANCE = 45; diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/PostgreSQLIndex.cpp --- a/PostgreSQL/Plugins/PostgreSQLIndex.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/PostgreSQL/Plugins/PostgreSQLIndex.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -29,6 +31,7 @@ #include // Auto-generated file #include // For std::unique_ptr<> +#include #include #include @@ -59,8 +62,18 @@ return PostgreSQLDatabase::CreateDatabaseFactory(parameters_); } + void PostgreSQLIndex::ApplyPrepareIndex(DatabaseManager::Transaction& t, DatabaseManager& manager) + { + std::string query; + + Orthanc::EmbeddedResources::GetFileResource + (query, Orthanc::EmbeddedResources::POSTGRESQL_PREPARE_INDEX); + t.GetDatabaseTransaction().ExecuteMultiLines(query); + } - void PostgreSQLIndex::ConfigureDatabase(DatabaseManager& manager) + void PostgreSQLIndex::ConfigureDatabase(DatabaseManager& manager, + bool hasIdentifierTags, + const std::list& identifierTags) { uint32_t expectedVersion = 6; @@ -73,7 +86,7 @@ if (expectedVersion != 6) { LOG(ERROR) << "This database plugin is incompatible with your version of Orthanc " - << "expecting the DB schema version " << expectedVersion + << "expecting the Orthanc DB schema version " << expectedVersion << ", but this plugin is only compatible with version 6"; throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin); } @@ -86,6 +99,7 @@ } { + // lock the full DB while checking if it needs to be create/ugraded PostgreSQLDatabase::TransientAdvisoryLock lock(db, POSTGRESQL_LOCK_DATABASE_SETUP); if (clearAll_) @@ -98,243 +112,121 @@ if (!t.GetDatabaseTransaction().DoesTableExist("Resources")) { - std::string query; - - Orthanc::EmbeddedResources::GetFileResource - (query, Orthanc::EmbeddedResources::POSTGRESQL_PREPARE_INDEX); - t.GetDatabaseTransaction().ExecuteMultiLines(query); - - SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabaseSchemaVersion, expectedVersion); - SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, 1); - SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_HasTrigramIndex, 0); - } - - if (!t.GetDatabaseTransaction().DoesTableExist("Resources")) - { - LOG(ERROR) << "Corrupted PostgreSQL database"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } + LOG(WARNING) << "PostgreSQL is creating the database schema"; - int version = 0; - if (!LookupGlobalIntegerProperty(version, manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabaseSchemaVersion) || - version != 6) - { - LOG(ERROR) << "PostgreSQL plugin is incompatible with database schema version: " << version; - throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); - } - - int revision; - if (!LookupGlobalIntegerProperty(revision, manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel)) - { - revision = 1; - SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, revision); - } - - if (revision != 1) - { - LOG(ERROR) << "PostgreSQL plugin is incompatible with database schema revision: " << revision; - throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); - } + ApplyPrepareIndex(t, manager); - t.Commit(); - } - - { - DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); - - int hasTrigram = 0; - if (!LookupGlobalIntegerProperty(hasTrigram, manager, MISSING_SERVER_IDENTIFIER, - Orthanc::GlobalProperty_HasTrigramIndex) || - hasTrigram != 1) - { - /** - * Apply fix for performance issue (speed up wildcard search - * by using GIN trigrams). This implements the patch suggested - * in issue #47, BUT we also keep the original - * "DicomIdentifiersIndexValues", as it leads to better - * performance for "strict" searches (i.e. searches involving - * no wildcard). - * https://www.postgresql.org/docs/current/static/pgtrgm.html - * https://bugs.orthanc-server.com/show_bug.cgi?id=47 - **/ - try + if (!t.GetDatabaseTransaction().DoesTableExist("Resources")) { - // We've observed 9 minutes on DB with 100000 studies - LOG(WARNING) << "Trying to enable trigram matching on the PostgreSQL database " - << "to speed up wildcard searches. This may take several minutes"; - - t.GetDatabaseTransaction().ExecuteMultiLines( - "CREATE EXTENSION IF NOT EXISTS pg_trgm; " - "CREATE INDEX DicomIdentifiersIndexValues2 ON DicomIdentifiers USING gin(value gin_trgm_ops);"); - - SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_HasTrigramIndex, 1); - LOG(WARNING) << "Trigram index has been created"; - - t.Commit(); - } - catch (Orthanc::OrthancException&) - { - LOG(WARNING) << "Performance warning: Your PostgreSQL server does " - << "not support trigram matching"; - LOG(WARNING) << "-> Consider installing the \"pg_trgm\" extension on the " - << "PostgreSQL server, e.g. on Debian: sudo apt install postgresql-contrib"; + LOG(ERROR) << "Corrupted PostgreSQL database or failed to create the database schema"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } } else { - t.Commit(); - } - } - - { - DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); + LOG(WARNING) << "The database schema already exists, checking if it needs to be updated"; - int property = 0; - if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, - Orthanc::GlobalProperty_HasCreateInstance) || - property != 2) - { - LOG(INFO) << "Installing the CreateInstance extension"; - - if (property == 1) + int version = 0; + if (!LookupGlobalIntegerProperty(version, manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabaseSchemaVersion) || + version != 6) { - // Drop older, experimental versions of this extension - t.GetDatabaseTransaction().ExecuteMultiLines("DROP FUNCTION CreateInstance(" - "IN patient TEXT, IN study TEXT, IN series TEXT, in instance TEXT)"); + LOG(ERROR) << "PostgreSQL plugin is incompatible with Orthanc database schema version: " << version; + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); } - - std::string query; - Orthanc::EmbeddedResources::GetFileResource - (query, Orthanc::EmbeddedResources::POSTGRESQL_CREATE_INSTANCE); - t.GetDatabaseTransaction().ExecuteMultiLines(query); - SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_HasCreateInstance, 2); - } - - - if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, - Orthanc::GlobalProperty_GetTotalSizeIsFast) || - property != 1) - { - LOG(INFO) << "Installing the FastTotalSize extension"; + bool needToRunUpgradeFromUnknownToV1 = false; + bool needToRunUpgradeV1toV2 = false; + bool needToRunUpgradeV2toV3 = false; - std::string query; - Orthanc::EmbeddedResources::GetFileResource - (query, Orthanc::EmbeddedResources::POSTGRESQL_FAST_TOTAL_SIZE); - t.GetDatabaseTransaction().ExecuteMultiLines(query); - - SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_GetTotalSizeIsFast, 1); - } - - - // Installing this extension requires the "GlobalIntegers" table - // created by the "FastTotalSize" extension - property = 0; - if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, - Orthanc::GlobalProperty_HasFastCountResources) || - property != 1) - { - LOG(INFO) << "Installing the FastCountResources extension"; + int revision; + if (!LookupGlobalIntegerProperty(revision, manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel)) + { + LOG(WARNING) << "No DatabasePatchLevel found, assuming it's 1"; + revision = 1; + needToRunUpgradeFromUnknownToV1 = true; + needToRunUpgradeV1toV2 = true; + } + else if (revision == 1) + { + LOG(WARNING) << "DatabasePatchLevel is 1"; + needToRunUpgradeFromUnknownToV1 = true; + needToRunUpgradeV1toV2 = true; + needToRunUpgradeV2toV3 = true; + } + else if (revision == 2) + { + LOG(WARNING) << "DatabasePatchLevel is 2"; + needToRunUpgradeV2toV3 = true; + } - std::string query; - Orthanc::EmbeddedResources::GetFileResource - (query, Orthanc::EmbeddedResources::POSTGRESQL_FAST_COUNT_RESOURCES); - t.GetDatabaseTransaction().ExecuteMultiLines(query); - - SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_HasFastCountResources, 1); - } - - - // Installing this extension requires the "GlobalIntegers" table - // created by the "GetLastChangeIndex" extension - property = 0; - if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, - Orthanc::GlobalProperty_GetLastChangeIndex) || - property != 1) - { - LOG(INFO) << "Installing the GetLastChangeIndex extension"; - - std::string query; - Orthanc::EmbeddedResources::GetFileResource - (query, Orthanc::EmbeddedResources::POSTGRESQL_GET_LAST_CHANGE_INDEX); - t.GetDatabaseTransaction().ExecuteMultiLines(query); + int hasTrigram = 0; + if (!LookupGlobalIntegerProperty(hasTrigram, manager, MISSING_SERVER_IDENTIFIER, + Orthanc::GlobalProperty_HasTrigramIndex) || + hasTrigram != 1) + { + // We've observed 9 minutes on DB with 100000 studies + LOG(WARNING) << "The DB schema update will try to enable trigram matching on the PostgreSQL database " + << "to speed up wildcard searches. This may take several minutes"; + needToRunUpgradeV1toV2 = true; + } - SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_GetLastChangeIndex, 1); - } - - t.Commit(); - } - - - { - // New in release 4.0 to deal with multiple writers - DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); - - if (!t.GetDatabaseTransaction().DoesTableExist("ServerProperties")) - { - t.GetDatabaseTransaction().ExecuteMultiLines("CREATE TABLE ServerProperties(server VARCHAR(64) NOT NULL, " - "property INTEGER, value TEXT, PRIMARY KEY(server, property))"); - } + int property = 0; + if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, + Orthanc::GlobalProperty_HasFastCountResources) || + property != 1) + { + needToRunUpgradeV1toV2 = true; + } + if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, + Orthanc::GlobalProperty_GetTotalSizeIsFast) || + property != 1) + { + needToRunUpgradeV1toV2 = true; + } + if (!LookupGlobalIntegerProperty(property, manager, MISSING_SERVER_IDENTIFIER, + Orthanc::GlobalProperty_GetLastChangeIndex) || + property != 1) + { + needToRunUpgradeV1toV2 = true; + } - /** - * PostgreSQL 9.5: "Adding a column with a default requires - * updating each row of the table (to store the new column - * value). However, if no default is specified, PostgreSQL is - * able to avoid the physical update." => We set no default - * for performance (older entries will be NULL) - * https://www.postgresql.org/docs/9.5/ddl-alter.html - **/ - if (!db.DoesColumnExist("Metadata", "revision")) - { - t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE Metadata ADD COLUMN revision INTEGER"); - } - - if (!db.DoesColumnExist("AttachedFiles", "revision")) - { - t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE AttachedFiles ADD COLUMN revision INTEGER"); - } + if (needToRunUpgradeFromUnknownToV1) + { + LOG(WARNING) << "Upgrading DB schema from unknown to revision 1"; + std::string query; - // new in v 4.X - if (!db.DoesColumnExist("DeletedFiles", "revision")) - { - t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE DeletedFiles ADD COLUMN revision INTEGER"); - } + Orthanc::EmbeddedResources::GetFileResource + (query, Orthanc::EmbeddedResources::POSTGRESQL_UPGRADE_UNKNOWN_TO_REV1); + t.GetDatabaseTransaction().ExecuteMultiLines(query); + } + + if (needToRunUpgradeV1toV2) + { + LOG(WARNING) << "Upgrading DB schema from revision 1 to revision 2"; - if (!db.DoesColumnExist("AttachedFiles", "customData")) - { - t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE AttachedFiles ADD COLUMN customData TEXT"); - } + std::string query; - if (!db.DoesColumnExist("DeletedFiles", "customData")) - { - // add the column and modify the trigger - t.GetDatabaseTransaction().ExecuteMultiLines("ALTER TABLE DeletedFiles ADD COLUMN customData TEXT"); - - t.GetDatabaseTransaction().ExecuteMultiLines( - "DROP TRIGGER AttachedFileDeleted ON AttachedFiles"); + Orthanc::EmbeddedResources::GetFileResource + (query, Orthanc::EmbeddedResources::POSTGRESQL_UPGRADE_REV1_TO_REV2); + t.GetDatabaseTransaction().ExecuteMultiLines(query); - t.GetDatabaseTransaction().ExecuteMultiLines( - "DROP FUNCTION AttachedFileDeletedFunc"); + // apply all idempotent changes that are in the PrepareIndex + ApplyPrepareIndex(t, manager); + } + + if (needToRunUpgradeV2toV3) + { + LOG(WARNING) << "Upgrading DB schema from revision 2 to revision 3"; - t.GetDatabaseTransaction().ExecuteMultiLines( - "CREATE FUNCTION AttachedFileDeletedFunc() " - "RETURNS TRIGGER AS $body$" - "BEGIN" - " INSERT INTO DeletedFiles VALUES" - " (old.uuid, old.filetype, old.compressedSize," - " old.uncompressedSize, old.compressionType," - " old.uncompressedHash, old.compressedHash," - " old.revision, old.customData);" - " RETURN NULL;" - "END;" - "$body$ LANGUAGE plpgsql;"); + std::string query; - t.GetDatabaseTransaction().ExecuteMultiLines( - "CREATE TRIGGER AttachedFileDeleted " - "AFTER DELETE ON AttachedFiles " - "FOR EACH ROW " - "EXECUTE PROCEDURE AttachedFileDeletedFunc();" - ); + Orthanc::EmbeddedResources::GetFileResource + (query, Orthanc::EmbeddedResources::POSTGRESQL_UPGRADE_REV2_TO_REV3); + t.GetDatabaseTransaction().ExecuteMultiLines(query); + + // apply all idempotent changes that are in the PrepareIndex (update triggers + set Patch level to 3) + ApplyPrepareIndex(t, manager); + } } t.Commit(); @@ -372,15 +264,15 @@ { DatabaseManager::CachedStatement statement( STATEMENT_FROM_HERE, manager, - "SELECT value FROM GlobalIntegers WHERE key = 0"); + "SELECT * FROM UpdateSingleStatistic(0)"); - statement.SetReadOnly(true); statement.Execute(); result = static_cast(statement.ReadInteger64(0)); } - assert(result == IndexBackend::GetTotalCompressedSize(manager)); + // disabled because this is not alway true while transactions are being executed in READ COMITTED TRANSACTION. This is however true when no files are being delete/added + //assert(result == IndexBackend::GetTotalCompressedSize(manager)); return result; } @@ -393,18 +285,170 @@ { DatabaseManager::CachedStatement statement( STATEMENT_FROM_HERE, manager, - "SELECT value FROM GlobalIntegers WHERE key = 1"); + "SELECT * FROM UpdateSingleStatistic(1)"); - statement.SetReadOnly(true); statement.Execute(); result = static_cast(statement.ReadInteger64(0)); } - assert(result == IndexBackend::GetTotalUncompressedSize(manager)); + // disabled because this is not alway true while transactions are being executed in READ COMITTED TRANSACTION. This is however true when no files are being delete/added + // assert(result == IndexBackend::GetTotalUncompressedSize(manager)); return result; } + int64_t PostgreSQLIndex::IncrementGlobalProperty(DatabaseManager& manager, + const char* serverIdentifier, + int32_t property, + int64_t increment) + { + if (serverIdentifier == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + if (strlen(serverIdentifier) == 0) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "INSERT INTO GlobalProperties (property, value) VALUES(${property}, ${increment}) " + " ON CONFLICT (property) DO UPDATE SET value = CAST(GlobalProperties.value AS BIGINT) + ${increment}" + " RETURNING CAST(value AS BIGINT)"); + + statement.SetParameterType("property", ValueType_Integer64); + statement.SetParameterType("increment", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("property", property); + args.SetIntegerValue("increment", increment); + + statement.Execute(args); + + return statement.ReadInteger64(0); + } + else + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "INSERT INTO ServerProperties (server, property, value) VALUES(${server}, ${property}, ${increment}) " + " ON CONFLICT (server, property) DO UPDATE SET value = CAST(ServerProperties.value AS BIGINT) + ${increment}" + " RETURNING CAST(value AS BIGINT)"); + + statement.SetParameterType("server", ValueType_Utf8String); + statement.SetParameterType("property", ValueType_Integer64); + statement.SetParameterType("increment", ValueType_Integer64); + + Dictionary args; + args.SetUtf8Value("server", serverIdentifier); + args.SetIntegerValue("property", property); + args.SetIntegerValue("increment", increment); + + statement.Execute(args); + + return statement.ReadInteger64(0); + } + } + } + + void PostgreSQLIndex::UpdateAndGetStatistics(DatabaseManager& manager, + int64_t& patientsCount, + int64_t& studiesCount, + int64_t& seriesCount, + int64_t& instancesCount, + int64_t& compressedSize, + int64_t& uncompressedSize) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "SELECT * FROM UpdateStatistics()"); + + statement.Execute(); + + patientsCount = statement.ReadInteger64(0); + studiesCount = statement.ReadInteger64(1); + seriesCount = statement.ReadInteger64(2); + instancesCount = statement.ReadInteger64(3); + compressedSize = statement.ReadInteger64(4); + uncompressedSize = statement.ReadInteger64(5); + } + + void PostgreSQLIndex::ClearDeletedFiles(DatabaseManager& manager) + { + { // note: the temporary table lifespan is the session, not the transaction -> that's why we need the IF NOT EXISTS + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "SELECT CreateDeletedFilesTemporaryTable()" + ); + statement.ExecuteWithoutResult(); + } + + } + + void PostgreSQLIndex::ClearDeletedResources(DatabaseManager& manager) + { + { // note: the temporary table lifespan is the session, not the transaction -> that's why we need the IF NOT EXISTS + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "CREATE TEMPORARY TABLE IF NOT EXISTS DeletedResources(" + "resourceType INTEGER NOT NULL," + "publicId VARCHAR(64) NOT NULL" + ");" + ); + statement.Execute(); + } + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "DELETE FROM DeletedResources;" + ); + + statement.Execute(); + } + + } + + void PostgreSQLIndex::ClearRemainingAncestor(DatabaseManager& manager) + { + } + + void PostgreSQLIndex::DeleteResource(IDatabaseBackendOutput& output, + DatabaseManager& manager, + int64_t id) + { + // clearing of temporary table is now implemented in the funcion DeleteResource + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager, + "SELECT * FROM DeleteResource(${id})"); + + statement.SetParameterType("id", ValueType_Integer64); + + Dictionary args; + args.SetIntegerValue("id", id); + + statement.Execute(args); + + if (statement.IsDone() || + statement.GetResultFieldsCount() != 2) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); + } + + statement.SetResultFieldType(0, ValueType_Integer64); + statement.SetResultFieldType(1, ValueType_Utf8String); + + if (!statement.IsNull(0)) + { + output.SignalRemainingAncestor( + statement.ReadString(1), + static_cast(statement.ReadInteger32(0))); + } + + SignalDeletedFiles(output, manager); + SignalDeletedResources(output, manager); + } + + #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 void PostgreSQLIndex::CreateInstance(OrthancPluginCreateInstanceResult& result, @@ -428,7 +472,7 @@ args.SetUtf8Value("study", hashStudy); args.SetUtf8Value("series", hashSeries); args.SetUtf8Value("instance", hashInstance); - + statement.Execute(args); if (statement.IsDone() || @@ -442,6 +486,8 @@ statement.SetResultFieldType(i, ValueType_Integer64); } + // LOG(INFO) << statement.ReadInteger64(0) << statement.ReadInteger64(1) << statement.ReadInteger64(2) << statement.ReadInteger64(3); + result.isNewInstance = (statement.ReadInteger64(3) == 1); result.instanceId = statement.ReadInteger64(7); @@ -458,6 +504,156 @@ #endif +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + static void ExecuteSetResourcesContentTags( + DatabaseManager& manager, + const std::string& table, + uint32_t count, + const OrthancPluginResourcesContentTags* tags) + { + std::string sql; + + std::vector resourceIds; + std::vector groups; + std::vector elements; + std::vector values; + + Dictionary args; + + for (uint32_t i = 0; i < count; i++) + { + std::string resourceArgName = "r" + boost::lexical_cast(i); + std::string groupArgName = "g" + boost::lexical_cast(i); + std::string elementArgName = "e" + boost::lexical_cast(i); + std::string valueArgName = "v" + boost::lexical_cast(i); + + args.SetIntegerValue(resourceArgName, tags[i].resource); + args.SetInteger32Value(elementArgName, tags[i].element); + args.SetInteger32Value(groupArgName, tags[i].group); + args.SetUtf8Value(valueArgName, tags[i].value); + + std::string insert = ("(${" + resourceArgName + "}, ${" + + groupArgName + "}, ${" + + elementArgName + "}, " + + "${" + valueArgName + "})"); + + if (sql.empty()) + { + sql = "INSERT INTO " + table + " VALUES " + insert; + } + else + { + sql += ", " + insert; + } + } + + if (!sql.empty()) + { + DatabaseManager::CachedStatement statement(STATEMENT_FROM_HERE_DYNAMIC(sql), manager, sql); + + for (uint32_t i = 0; i < count; i++) + { + statement.SetParameterType("r" + boost::lexical_cast(i), + ValueType_Integer64); + statement.SetParameterType("g" + boost::lexical_cast(i), + ValueType_Integer32); + statement.SetParameterType("e" + boost::lexical_cast(i), + ValueType_Integer32); + statement.SetParameterType("v" + boost::lexical_cast(i), + ValueType_Utf8String); + } + + statement.Execute(args); + } + } +#endif + + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + static void ExecuteSetResourcesContentMetadata( + DatabaseManager& manager, + bool hasRevisionsSupport, + uint32_t count, + const OrthancPluginResourcesContentMetadata* metadata) + { + if (count < 1) + { + return; + } + + std::vector resourceIds; + std::vector metadataTypes; + std::vector metadataValues; + std::vector revisions; + + Dictionary args; + + for (uint32_t i = 0; i < count; i++) + { + std::string resourceArgName = "r" + boost::lexical_cast(i); + std::string typeArgName = "t" + boost::lexical_cast(i); + std::string valueArgName = "v" + boost::lexical_cast(i); + + args.SetIntegerValue(resourceArgName, metadata[i].resource); + args.SetInteger32Value(typeArgName, metadata[i].metadata); + args.SetUtf8Value(valueArgName, metadata[i].value); + + resourceIds.push_back("${" + resourceArgName + "}"); + metadataTypes.push_back("${" + typeArgName + "}"); + metadataValues.push_back("${" + valueArgName + "}"); + revisions.push_back("0"); + } + + std::string joinedResourceIds; + std::string joinedMetadataTypes; + std::string joinedMetadataValues; + std::string joinedRevisions; + + Orthanc::Toolbox::JoinStrings(joinedResourceIds, resourceIds, ","); + Orthanc::Toolbox::JoinStrings(joinedMetadataTypes, metadataTypes, ","); + Orthanc::Toolbox::JoinStrings(joinedMetadataValues, metadataValues, ","); + Orthanc::Toolbox::JoinStrings(joinedRevisions, revisions, ","); + + std::string sql = std::string("SELECT InsertOrUpdateMetadata(ARRAY[") + + joinedResourceIds + "], ARRAY[" + + joinedMetadataTypes + "], ARRAY[" + + joinedMetadataValues + "], ARRAY[" + + joinedRevisions + "])"; + + DatabaseManager::CachedStatement statement(STATEMENT_FROM_HERE_DYNAMIC(sql), manager, sql); + + for (uint32_t i = 0; i < count; i++) + { + statement.SetParameterType("v" + boost::lexical_cast(i), + ValueType_Utf8String); + statement.SetParameterType("r" + boost::lexical_cast(i), + ValueType_Integer64); + statement.SetParameterType("t" + boost::lexical_cast(i), + ValueType_Integer32); + } + + statement.Execute(args); + } +#endif + + + void PostgreSQLIndex::SetResourcesContent(DatabaseManager& manager, + uint32_t countIdentifierTags, + const OrthancPluginResourcesContentTags* identifierTags, + uint32_t countMainDicomTags, + const OrthancPluginResourcesContentTags* mainDicomTags, + uint32_t countMetadata, + const OrthancPluginResourcesContentMetadata* metadata) + { + ExecuteSetResourcesContentTags(manager, "DicomIdentifiers", countIdentifierTags, identifierTags); + + ExecuteSetResourcesContentTags(manager, "MainDicomTags", countMainDicomTags, mainDicomTags); + + ExecuteSetResourcesContentMetadata(manager, HasRevisionsSupport(), countMetadata, metadata); + + } + + uint64_t PostgreSQLIndex::GetResourcesCount(DatabaseManager& manager, OrthancPluginResourceType resourceType) { @@ -471,24 +667,18 @@ uint64_t result; { - DatabaseManager::CachedStatement statement( - STATEMENT_FROM_HERE, manager, - "SELECT value FROM GlobalIntegers WHERE key = ${key}"); - - statement.SetParameterType("key", ValueType_Integer64); + DatabaseManager::StandaloneStatement statement( + manager, + std::string("SELECT * FROM UpdateSingleStatistic(") + boost::lexical_cast(resourceType + 2) + ")"); // For an explanation of the "+ 2" below, check out "PrepareIndex.sql" - Dictionary args; - - // For an explanation of the "+ 2" below, check out "FastCountResources.sql" - args.SetIntegerValue("key", static_cast(resourceType + 2)); - - statement.SetReadOnly(true); - statement.Execute(args); + statement.Execute(); result = static_cast(statement.ReadInteger64(0)); } + // disabled because this is not alway true while transactions are being executed in READ COMITTED TRANSACTION. This is however true when no files are being delete/added assert(result == IndexBackend::GetResourcesCount(manager, resourceType)); + return result; } @@ -513,4 +703,24 @@ // backward compatibility is necessary throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); } + + +// #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) +// bool PostgreSQLIndex::HasFindSupport() const +// { +// // TODO-FIND +// return false; +// } +// #endif + + +// #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) +// void PostgreSQLIndex::ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, +// DatabaseManager& manager, +// const Orthanc::DatabasePluginMessages::Find_Request& request) +// { +// // TODO-FIND +// throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); +// } +// #endif } diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/PostgreSQLIndex.h --- a/PostgreSQL/Plugins/PostgreSQLIndex.h Wed Feb 01 16:24:37 2023 +0100 +++ b/PostgreSQL/Plugins/PostgreSQLIndex.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -32,6 +34,15 @@ PostgreSQLParameters parameters_; bool clearAll_; + protected: + virtual void ClearDeletedFiles(DatabaseManager& manager) ORTHANC_OVERRIDE; + + virtual void ClearDeletedResources(DatabaseManager& manager) ORTHANC_OVERRIDE; + + virtual void ClearRemainingAncestor(DatabaseManager& manager) ORTHANC_OVERRIDE; + + void ApplyPrepareIndex(DatabaseManager::Transaction& t, DatabaseManager& manager); + public: PostgreSQLIndex(OrthancPluginContext* context, const PostgreSQLParameters& parameters); @@ -43,7 +54,9 @@ virtual IDatabaseFactory* CreateDatabaseFactory() ORTHANC_OVERRIDE; - virtual void ConfigureDatabase(DatabaseManager& manager) ORTHANC_OVERRIDE; + virtual void ConfigureDatabase(DatabaseManager& manager, + bool hasIdentifierTags, + const std::list& identifierTags) ORTHANC_OVERRIDE; virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE { @@ -57,8 +70,19 @@ virtual int64_t CreateResource(DatabaseManager& manager, const char* publicId, - OrthancPluginResourceType type) - ORTHANC_OVERRIDE; + OrthancPluginResourceType type) ORTHANC_OVERRIDE; + + virtual void DeleteResource(IDatabaseBackendOutput& output, + DatabaseManager& manager, + int64_t id) ORTHANC_OVERRIDE; + + virtual void SetResourcesContent(DatabaseManager& manager, + uint32_t countIdentifierTags, + const OrthancPluginResourcesContentTags* identifierTags, + uint32_t countMainDicomTags, + const OrthancPluginResourcesContentTags* mainDicomTags, + uint32_t countMetadata, + const OrthancPluginResourcesContentMetadata* metadata) ORTHANC_OVERRIDE; virtual uint64_t GetTotalCompressedSize(DatabaseManager& manager) ORTHANC_OVERRIDE; @@ -86,5 +110,44 @@ virtual void TagMostRecentPatient(DatabaseManager& manager, int64_t patient) ORTHANC_OVERRIDE; + + // New primitive since Orthanc 1.12.0 + virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE + { + return true; + } + + virtual bool HasAtomicIncrementGlobalProperty() ORTHANC_OVERRIDE + { + return true; + } + + virtual int64_t IncrementGlobalProperty(DatabaseManager& manager, + const char* serverIdentifier, + int32_t property, + int64_t increment) ORTHANC_OVERRIDE; + + virtual bool HasUpdateAndGetStatistics() ORTHANC_OVERRIDE + { + return true; + } + + virtual void UpdateAndGetStatistics(DatabaseManager& manager, + int64_t& patientsCount, + int64_t& studiesCount, + int64_t& seriesCount, + int64_t& instancesCount, + int64_t& compressedSize, + int64_t& uncompressedSize) ORTHANC_OVERRIDE; + +// #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) +// virtual bool HasFindSupport() const ORTHANC_OVERRIDE; +// #endif + +// #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) +// virtual void ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, +// DatabaseManager& manager, +// const Orthanc::DatabasePluginMessages::Find_Request& request) ORTHANC_OVERRIDE; +// #endif }; } diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/PostgreSQLStorageArea.cpp --- a/PostgreSQL/Plugins/PostgreSQLStorageArea.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/PostgreSQL/Plugins/PostgreSQLStorageArea.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/PostgreSQLStorageArea.h --- a/PostgreSQL/Plugins/PostgreSQLStorageArea.h Wed Feb 01 16:24:37 2023 +0100 +++ b/PostgreSQL/Plugins/PostgreSQLStorageArea.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/PrepareIndex.sql --- a/PostgreSQL/Plugins/PrepareIndex.sql Wed Feb 01 16:24:37 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,173 +0,0 @@ -CREATE TABLE GlobalProperties( - property INTEGER PRIMARY KEY, - value TEXT - ); - -CREATE TABLE Resources( - internalId BIGSERIAL NOT NULL PRIMARY KEY, - resourceType INTEGER NOT NULL, - publicId VARCHAR(64) NOT NULL, - parentId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE - ); - -CREATE TABLE MainDicomTags( - id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, - tagGroup INTEGER, - tagElement INTEGER, - value TEXT, - PRIMARY KEY(id, tagGroup, tagElement) - ); - -CREATE TABLE DicomIdentifiers( - id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, - tagGroup INTEGER, - tagElement INTEGER, - value TEXT, - PRIMARY KEY(id, tagGroup, tagElement) - ); - -CREATE TABLE Metadata( - id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, - type INTEGER NOT NULL, - value TEXT, - -- revision INTEGER, -- new in v4.0 (this column is added in PostgreSQLIndex::ConfigureDatabase) - PRIMARY KEY(id, type) - ); - -CREATE TABLE AttachedFiles( - id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, - fileType INTEGER, - uuid VARCHAR(64) NOT NULL, - compressedSize BIGINT, - uncompressedSize BIGINT, - compressionType INTEGER, - uncompressedHash VARCHAR(40), - compressedHash VARCHAR(40), - -- revision BIGINT, -- new in v 4.0 (this column is added in PostgreSQLIndex::ConfigureDatabase) - -- customData TEXT, -- new in v 4.X (this column is added in PostgreSQLIndex::ConfigureDatabase) - PRIMARY KEY(id, fileType) - ); - -CREATE TABLE Changes( - seq BIGSERIAL NOT NULL PRIMARY KEY, - changeType INTEGER, - internalId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, - resourceType INTEGER, - date VARCHAR(64) - ); - -CREATE TABLE ExportedResources( - seq BIGSERIAL NOT NULL PRIMARY KEY, - resourceType INTEGER, - publicId VARCHAR(64), - remoteModality TEXT, - patientId VARCHAR(64), - studyInstanceUid TEXT, - seriesInstanceUid TEXT, - sopInstanceUid TEXT, - date VARCHAR(64) - ); - -CREATE TABLE PatientRecyclingOrder( - seq BIGSERIAL NOT NULL PRIMARY KEY, - patientId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE - ); - -CREATE INDEX ChildrenIndex ON Resources(parentId); -CREATE INDEX PublicIndex ON Resources(publicId); -CREATE INDEX ResourceTypeIndex ON Resources(resourceType); -CREATE INDEX PatientRecyclingIndex ON PatientRecyclingOrder(patientId); - -CREATE INDEX MainDicomTagsIndex ON MainDicomTags(id); -CREATE INDEX DicomIdentifiersIndex1 ON DicomIdentifiers(id); -CREATE INDEX DicomIdentifiersIndex2 ON DicomIdentifiers(tagGroup, tagElement); -CREATE INDEX DicomIdentifiersIndexValues ON DicomIdentifiers(value); - -CREATE INDEX ChangesIndex ON Changes(internalId); - - --- New tables wrt. Orthanc core -CREATE TABLE DeletedFiles( - uuid VARCHAR(64) NOT NULL, -- 0 - fileType INTEGER, -- 1 - compressedSize BIGINT, -- 2 - uncompressedSize BIGINT, -- 3 - compressionType INTEGER, -- 4 - uncompressedHash VARCHAR(40), -- 5 - compressedHash VARCHAR(40) -- 6 - ); - -CREATE TABLE RemainingAncestor( - resourceType INTEGER NOT NULL, - publicId VARCHAR(64) NOT NULL - ); - -CREATE TABLE DeletedResources( - resourceType INTEGER NOT NULL, - publicId VARCHAR(64) NOT NULL - ); --- End of differences - - -CREATE FUNCTION AttachedFileDeletedFunc() -RETURNS TRIGGER AS $body$ -BEGIN - INSERT INTO DeletedFiles VALUES - (old.uuid, old.filetype, old.compressedSize, - old.uncompressedSize, old.compressionType, - old.uncompressedHash, old.compressedHash - -- old.customData -- new in v 4.X (this column is added in PostgreSQLIndex::ConfigureDatabase) - ); - RETURN NULL; -END; -$body$ LANGUAGE plpgsql; - -CREATE TRIGGER AttachedFileDeleted -AFTER DELETE ON AttachedFiles -FOR EACH ROW -EXECUTE PROCEDURE AttachedFileDeletedFunc(); - - --- The following trigger combines 2 triggers from SQLite: --- ResourceDeleted + ResourceDeletedParentCleaning -CREATE FUNCTION ResourceDeletedFunc() -RETURNS TRIGGER AS $body$ -BEGIN - --RAISE NOTICE 'Delete resource %', old.parentId; - INSERT INTO DeletedResources VALUES (old.resourceType, old.publicId); - - -- http://stackoverflow.com/a/11299968/881731 - IF EXISTS (SELECT 1 FROM Resources WHERE parentId = old.parentId) THEN - -- Signal that the deleted resource has a remaining parent - INSERT INTO RemainingAncestor - SELECT resourceType, publicId FROM Resources WHERE internalId = old.parentId; - ELSE - -- Delete a parent resource when its unique child is deleted - DELETE FROM Resources WHERE internalId = old.parentId; - END IF; - RETURN NULL; -END; -$body$ LANGUAGE plpgsql; - -CREATE TRIGGER ResourceDeleted -AFTER DELETE ON Resources -FOR EACH ROW -EXECUTE PROCEDURE ResourceDeletedFunc(); - - - -CREATE FUNCTION PatientAddedFunc() -RETURNS TRIGGER AS $body$ -BEGIN - -- The "0" corresponds to "OrthancPluginResourceType_Patient" - IF new.resourceType = 0 THEN - INSERT INTO PatientRecyclingOrder VALUES (DEFAULT, new.internalId); - END IF; - RETURN NULL; -END; -$body$ LANGUAGE plpgsql; - -CREATE TRIGGER PatientAdded -AFTER INSERT ON Resources -FOR EACH ROW -EXECUTE PROCEDURE PatientAddedFunc(); diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/SQL/Downgrades/Rev2ToRev1.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PostgreSQL/Plugins/SQL/Downgrades/Rev2ToRev1.sql Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,252 @@ +-- This file contains an SQL procedure to downgrade from schema Rev2 to Rev1 (version = 6, revision = 1). +-- It reinstalls all triggers and temporary tables that have been removed or replaced in Rev2 + +-- note: we don't not remove the unique constraints that have been added - they should not +-- create any issues. + +-- these constraints were introduced in Rev2 +ALTER TABLE Resources DROP CONSTRAINT UniquePublicId; +ALTER TABLE PatientRecyclingOrder DROP CONSTRAINT UniquePatientId; + +-- the CreateInstance has been replaced in Rev2, reinstall the Rev1 +DROP FUNCTION CreateInstance; +CREATE FUNCTION CreateInstance( + IN patient TEXT, + IN study TEXT, + IN series TEXT, + IN instance TEXT, + OUT isNewPatient BIGINT, + OUT isNewStudy BIGINT, + OUT isNewSeries BIGINT, + OUT isNewInstance BIGINT, + OUT patientKey BIGINT, + OUT studyKey BIGINT, + OUT seriesKey BIGINT, + OUT instanceKey BIGINT) AS $body$ + +DECLARE + patientSeq BIGINT; + countRecycling BIGINT; + +BEGIN + SELECT internalId FROM Resources INTO instanceKey WHERE publicId = instance AND resourceType = 3; + + IF NOT (instanceKey IS NULL) THEN + -- This instance already exists, stop here + isNewInstance := 0; + ELSE + SELECT internalId FROM Resources INTO patientKey WHERE publicId = patient AND resourceType = 0; + SELECT internalId FROM Resources INTO studyKey WHERE publicId = study AND resourceType = 1; + SELECT internalId FROM Resources INTO seriesKey WHERE publicId = series AND resourceType = 2; + + IF patientKey IS NULL THEN + -- Must create a new patient + IF NOT (studyKey IS NULL AND seriesKey IS NULL AND instanceKey IS NULL) THEN + RAISE EXCEPTION 'Broken invariant'; + END IF; + + INSERT INTO Resources VALUES (DEFAULT, 0, patient, NULL) RETURNING internalId INTO patientKey; + isNewPatient := 1; + ELSE + isNewPatient := 0; + END IF; + + IF (patientKey IS NULL) THEN + RAISE EXCEPTION 'Broken invariant'; + END IF; + + IF studyKey IS NULL THEN + -- Must create a new study + IF NOT (seriesKey IS NULL AND instanceKey IS NULL) THEN + RAISE EXCEPTION 'Broken invariant'; + END IF; + + INSERT INTO Resources VALUES (DEFAULT, 1, study, patientKey) RETURNING internalId INTO studyKey; + isNewStudy := 1; + ELSE + isNewStudy := 0; + END IF; + + IF (studyKey IS NULL) THEN + RAISE EXCEPTION 'Broken invariant'; + END IF; + + IF seriesKey IS NULL THEN + -- Must create a new series + IF NOT (instanceKey IS NULL) THEN + RAISE EXCEPTION 'Broken invariant'; + END IF; + + INSERT INTO Resources VALUES (DEFAULT, 2, series, studyKey) RETURNING internalId INTO seriesKey; + isNewSeries := 1; + ELSE + isNewSeries := 0; + END IF; + + IF (seriesKey IS NULL OR NOT instanceKey IS NULL) THEN + RAISE EXCEPTION 'Broken invariant'; + END IF; + + INSERT INTO Resources VALUES (DEFAULT, 3, instance, seriesKey) RETURNING internalId INTO instanceKey; + isNewInstance := 1; + + -- Move the patient to the end of the recycling order + SELECT seq FROM PatientRecyclingOrder WHERE patientId = patientKey INTO patientSeq; + + IF NOT (patientSeq IS NULL) THEN + -- The patient is not protected + SELECT COUNT(*) FROM (SELECT * FROM PatientRecyclingOrder WHERE seq >= patientSeq LIMIT 2) AS tmp INTO countRecycling; + IF countRecycling = 2 THEN + -- The patient was not at the end of the recycling order + DELETE FROM PatientRecyclingOrder WHERE seq = patientSeq; + INSERT INTO PatientRecyclingOrder VALUES(DEFAULT, patientKey); + END IF; + END IF; + END IF; +END; + +$body$ LANGUAGE plpgsql; + + +-- these tables have been deleted in Rev2: +CREATE TABLE DeletedFiles( + uuid VARCHAR(64) NOT NULL, -- 0 + fileType INTEGER, -- 1 + compressedSize BIGINT, -- 2 + uncompressedSize BIGINT, -- 3 + compressionType INTEGER, -- 4 + uncompressedHash VARCHAR(40), -- 5 + compressedHash VARCHAR(40) -- 6 + ); + +CREATE TABLE RemainingAncestor( + resourceType INTEGER NOT NULL, + publicId VARCHAR(64) NOT NULL + ); + +CREATE TABLE DeletedResources( + resourceType INTEGER NOT NULL, + publicId VARCHAR(64) NOT NULL + ); + + +-- these triggers have been introduced in Rev2, remove them +DROP TRIGGER IF EXISTS IncrementResourcesTracker on Resources; +DROP TRIGGER IF EXISTS DecrementResourcesTracker on Resources; +DROP FUNCTION IF EXISTS IncrementResourcesTrackerFunc; +DROP FUNCTION IF EXISTS DecrementResourcesTrackerFunc; + +-- this trigger has been removed in Rev2, reinstall it +CREATE OR REPLACE FUNCTION CountResourcesTrackerFunc() +RETURNS TRIGGER AS $$ +BEGIN + IF TG_OP = 'INSERT' THEN + UPDATE GlobalIntegers SET value = value + 1 WHERE key = new.resourceType + 2; + RETURN new; + ELSIF TG_OP = 'DELETE' THEN + UPDATE GlobalIntegers SET value = value - 1 WHERE key = old.resourceType + 2; + RETURN old; + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER CountResourcesTracker +AFTER INSERT OR DELETE ON Resources +FOR EACH ROW +EXECUTE PROCEDURE CountResourcesTrackerFunc(); + +-- this trigger was introduced in Rev2, remove it: +DROP FUNCTION IF EXISTS InsertOrUpdateMetadata; + +-- reinstall old triggers: +CREATE OR REPLACE FUNCTION AttachedFileIncrementSizeFunc() +RETURNS TRIGGER AS $body$ +BEGIN + UPDATE GlobalIntegers SET value = value + new.compressedSize WHERE key = 0; + UPDATE GlobalIntegers SET value = value + new.uncompressedSize WHERE key = 1; + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION AttachedFileDecrementSizeFunc() +RETURNS TRIGGER AS $body$ +BEGIN + UPDATE GlobalIntegers SET value = value - old.compressedSize WHERE key = 0; + UPDATE GlobalIntegers SET value = value - old.uncompressedSize WHERE key = 1; + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +DROP TRIGGER AttachedFileIncrementSize ON AttachedFiles; +CREATE TRIGGER AttachedFileIncrementSize +AFTER INSERT ON AttachedFiles +FOR EACH ROW +EXECUTE PROCEDURE AttachedFileIncrementSizeFunc(); + +DROP TRIGGER AttachedFileDecrementSize ON AttachedFiles; +CREATE TRIGGER AttachedFileDecrementSize +AFTER DELETE ON AttachedFiles +FOR EACH ROW +EXECUTE PROCEDURE AttachedFileDecrementSizeFunc(); + +-- these functions have been introduced in Rev2: +DROP FUNCTION IF EXISTS UpdateStatistics; +DROP FUNCTION IF EXISTS UpdateSingleStatistic; + +-- this table has been introduced in Rev2: +DROP TABLE IF EXISTS GlobalIntegersChanges; + +-- these functions have been introduced in Rev2: +DROP FUNCTION IF EXISTS CreateDeletedFilesTemporaryTable; +DROP FUNCTION IF EXISTS DeleteResource; + +-- reinstall this old trigger: +CREATE OR REPLACE FUNCTION ResourceDeletedFunc() +RETURNS TRIGGER AS $body$ +BEGIN + --RAISE NOTICE 'Delete resource %', old.parentId; + INSERT INTO DeletedResources VALUES (old.resourceType, old.publicId); + + -- http://stackoverflow.com/a/11299968/881731 + IF EXISTS (SELECT 1 FROM Resources WHERE parentId = old.parentId) THEN + -- Signal that the deleted resource has a remaining parent + INSERT INTO RemainingAncestor + SELECT resourceType, publicId FROM Resources WHERE internalId = old.parentId; + ELSE + -- Delete a parent resource when its unique child is deleted + DELETE FROM Resources WHERE internalId = old.parentId; + END IF; + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS ResourceDeleted ON Resources; +CREATE TRIGGER ResourceDeleted +AFTER DELETE ON Resources +FOR EACH ROW +EXECUTE PROCEDURE ResourceDeletedFunc(); + +-- reinstall this old trigger: +CREATE OR REPLACE FUNCTION PatientAddedFunc() +RETURNS TRIGGER AS $body$ +BEGIN + -- The "0" corresponds to "OrthancPluginResourceType_Patient" + IF new.resourceType = 0 THEN + INSERT INTO PatientRecyclingOrder VALUES (DEFAULT, new.internalId); + END IF; + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS PatientAdded ON Resources; +CREATE TRIGGER PatientAdded +AFTER INSERT ON Resources +FOR EACH ROW +EXECUTE PROCEDURE PatientAddedFunc(); + + +-- set the global properties that actually documents the DB version, revision and some of the capabilities +-- modify only the ones that have changed +DELETE FROM GlobalProperties WHERE property IN (4, 11); +INSERT INTO GlobalProperties VALUES (4, 1); -- GlobalProperty_DatabasePatchLevel +INSERT INTO GlobalProperties VALUES (11, 2); -- GlobalProperty_HasCreateInstance \ No newline at end of file diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/SQL/PrepareIndex.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PostgreSQL/Plugins/SQL/PrepareIndex.sql Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,565 @@ +-- This SQL file creates a DB in Rev2 directly +-- It is also run after upgrade scripts to create new tables and or create/replace triggers and functions. +-- This script is self contained, it contains everything that needs to be run to create an Orthanc DB. +-- Note to developers: +-- - it is and must stay idempotent. +-- - it is executed when the DB is "locked", only one Orthanc instance can execute it at a given time. + +CREATE TABLE IF NOT EXISTS GlobalProperties( + property INTEGER PRIMARY KEY, + value TEXT + ); + +CREATE TABLE IF NOT EXISTS Resources( + internalId BIGSERIAL NOT NULL PRIMARY KEY, + resourceType INTEGER NOT NULL, + publicId VARCHAR(64) NOT NULL, + parentId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, + CONSTRAINT UniquePublicId UNIQUE (publicId) + ); + +CREATE TABLE IF NOT EXISTS MainDicomTags( + id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, + tagGroup INTEGER, + tagElement INTEGER, + value TEXT, + PRIMARY KEY(id, tagGroup, tagElement) + ); + +CREATE TABLE IF NOT EXISTS DicomIdentifiers( + id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, + tagGroup INTEGER, + tagElement INTEGER, + value TEXT, + PRIMARY KEY(id, tagGroup, tagElement) + ); + +CREATE TABLE IF NOT EXISTS Metadata( + id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, + type INTEGER NOT NULL, + value TEXT, + revision INTEGER, + PRIMARY KEY(id, type) + ); + +CREATE TABLE IF NOT EXISTS AttachedFiles( + id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, + fileType INTEGER, + uuid VARCHAR(64) NOT NULL, + compressedSize BIGINT, + uncompressedSize BIGINT, + compressionType INTEGER, + uncompressedHash VARCHAR(40), + compressedHash VARCHAR(40), + revision INTEGER, + customData TEXT, -- new in schema rev 3 + PRIMARY KEY(id, fileType) + ); + +CREATE TABLE IF NOT EXISTS Changes( + seq BIGSERIAL NOT NULL PRIMARY KEY, + changeType INTEGER, + internalId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, + resourceType INTEGER, + date VARCHAR(64) + ); + +CREATE TABLE IF NOT EXISTS ExportedResources( + seq BIGSERIAL NOT NULL PRIMARY KEY, + resourceType INTEGER, + publicId VARCHAR(64), + remoteModality TEXT, + patientId VARCHAR(64), + studyInstanceUid TEXT, + seriesInstanceUid TEXT, + sopInstanceUid TEXT, + date VARCHAR(64) + ); + +CREATE TABLE IF NOT EXISTS PatientRecyclingOrder( + seq BIGSERIAL NOT NULL PRIMARY KEY, + patientId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, + CONSTRAINT UniquePatientId UNIQUE (patientId) + ); + +CREATE TABLE IF NOT EXISTS Labels( + id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, + label TEXT, + PRIMARY KEY(id, label) + ); + +CREATE TABLE IF NOT EXISTS GlobalIntegers( + key INTEGER PRIMARY KEY, + value BIGINT); +-- GlobalIntegers keys: +-- 0: CompressedSize +-- 1: UncompressedSize +-- 2: PatientsCount +-- 3: StudiesCount +-- 4: SeriesCount +-- 5: InstancesCount +-- 6: ChangeSeq +-- 7: PatientRecyclingOrderSeq + +CREATE TABLE IF NOT EXISTS ServerProperties( + server VARCHAR(64) NOT NULL, + property INTEGER, value TEXT, + PRIMARY KEY(server, property) + ); + +CREATE INDEX IF NOT EXISTS ChildrenIndex ON Resources(parentId); +CREATE INDEX IF NOT EXISTS PublicIndex ON Resources(publicId); +CREATE INDEX IF NOT EXISTS ResourceTypeIndex ON Resources(resourceType); +CREATE INDEX IF NOT EXISTS PatientRecyclingIndex ON PatientRecyclingOrder(patientId); + +CREATE INDEX IF NOT EXISTS MainDicomTagsIndex ON MainDicomTags(id); +CREATE INDEX IF NOT EXISTS DicomIdentifiersIndex1 ON DicomIdentifiers(id); +CREATE INDEX IF NOT EXISTS DicomIdentifiersIndex2 ON DicomIdentifiers(tagGroup, tagElement); +CREATE INDEX IF NOT EXISTS DicomIdentifiersIndexValues ON DicomIdentifiers(value); + +CREATE INDEX IF NOT EXISTS ChangesIndex ON Changes(internalId); +CREATE INDEX IF NOT EXISTS LabelsIndex1 ON LABELS(id); +CREATE INDEX IF NOT EXISTS LabelsIndex2 ON LABELS(label); + +------------------- Trigram index creation ------------------- + + +-- Apply fix for performance issue (speed up wildcard search by using GIN trigrams). This implements the patch suggested +-- in issue #47, BUT we also keep the original "DicomIdentifiersIndexValues", as it leads to better +-- performance for "strict" searches (i.e. searches involving no wildcard). +-- https://www.postgresql.org/docs/current/static/pgtrgm.html +-- https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=47 + +DO $body$ +begin + IF EXISTS (SELECT 1 FROM pg_available_extensions WHERE name='pg_trgm') THEN + CREATE EXTENSION IF NOT EXISTS pg_trgm; + CREATE INDEX IF NOT EXISTS DicomIdentifiersIndexValues2 ON DicomIdentifiers USING gin(value gin_trgm_ops); + ELSE + RAISE NOTICE 'pg_trgm extension is not available on you system'; + END IF; +END $body$; + + +------------------- PatientAdded trigger & PatientRecyclingOrder ------------------- +DROP TRIGGER IF EXISTS PatientAdded ON Resources; + +CREATE OR REPLACE FUNCTION PatientAddedOrUpdated( + IN patient_id BIGINT, + IN is_update BIGINT + ) +RETURNS VOID AS $body$ +BEGIN + DECLARE + newSeq BIGINT; + BEGIN + UPDATE GlobalIntegers SET value = value + 1 WHERE key = 7 RETURNING value INTO newSeq; + IF is_update > 0 THEN + -- Note: Protected patients are not listed in this table ! So, they won't be updated + UPDATE PatientRecyclingOrder SET seq = newSeq WHERE PatientRecyclingOrder.patientId = patient_id; + ELSE + INSERT INTO PatientRecyclingOrder VALUES (newSeq, patient_id); + END IF; + END; +END; +$body$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION PatientAddedFunc() +RETURNS TRIGGER AS $body$ +BEGIN + -- The "0" corresponds to "OrthancPluginResourceType_Patient" + IF new.resourceType = 0 THEN + PERFORM PatientAddedOrUpdated(new.internalId, 0); + END IF; + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +CREATE TRIGGER PatientAdded +AFTER INSERT ON Resources +FOR EACH ROW +EXECUTE PROCEDURE PatientAddedFunc(); + +-- initial population of PatientRecyclingOrderSeq +INSERT INTO GlobalIntegers + SELECT 7, CAST(COALESCE(MAX(seq), 0) AS BIGINT) FROM PatientRecyclingOrder + ON CONFLICT DO NOTHING; + + +------------------- ResourceDeleted trigger ------------------- +DROP TRIGGER IF EXISTS ResourceDeleted ON Resources; + +-- The following trigger combines 2 triggers from SQLite: +-- ResourceDeleted + ResourceDeletedParentCleaning +CREATE OR REPLACE FUNCTION ResourceDeletedFunc() +RETURNS TRIGGER AS $body$ +BEGIN + -- RAISE NOTICE 'ResourceDeletedFunc %', old.publicId; + INSERT INTO DeletedResources VALUES (old.resourceType, old.publicId); + + -- If this resource is the latest child, delete the parent + DELETE FROM Resources WHERE internalId = old.parentId + AND NOT EXISTS (SELECT 1 FROM Resources WHERE parentId = old.parentId); + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +CREATE TRIGGER ResourceDeleted +AFTER DELETE ON Resources +FOR EACH ROW +EXECUTE PROCEDURE ResourceDeletedFunc(); + + +------------------- DeleteResource function ------------------- + +CREATE OR REPLACE FUNCTION DeleteResource( + IN id BIGINT, + OUT remaining_ancestor_resource_type INTEGER, + OUT remaining_anncestor_public_id TEXT) AS $body$ + +DECLARE + deleted_row RECORD; + locked_row RECORD; + +BEGIN + + SET client_min_messages = warning; -- suppress NOTICE: relation "deletedresources" already exists, skipping + + -- note: temporary tables are created at session (connection) level -> they are likely to exist + -- these tables are used by the triggers + CREATE TEMPORARY TABLE IF NOT EXISTS DeletedResources( + resourceType INTEGER NOT NULL, + publicId VARCHAR(64) NOT NULL + ); + + RESET client_min_messages; + + -- clear the temporary table in case it has been created earlier in the session + DELETE FROM DeletedResources; + + -- create/clear the DeletedFiles temporary table + PERFORM CreateDeletedFilesTemporaryTable(); + + -- Before deleting an object, we need to lock its parent until the end of the transaction to avoid that + -- 2 threads deletes the last 2 instances of a series at the same time -> none of them would realize + -- that they are deleting the last instance and the parent resources would not be deleted. + -- Locking only the immediate parent is sufficient to prevent from this. + SELECT * INTO locked_row FROM resources WHERE internalid = (SELECT parentid FROM resources WHERE internalid = id) FOR UPDATE; + + -- delete the resource itself + DELETE FROM Resources WHERE internalId=id RETURNING * INTO deleted_row; + -- note: there is a ResourceDeletedFunc trigger that will execute here and delete the parents if there are no remaining children + + + -- If this resource still has siblings, keep track of the remaining parent + -- (a parent that must not be deleted but whose LastUpdate must be updated) + SELECT resourceType, publicId INTO remaining_ancestor_resource_type, remaining_anncestor_public_id + FROM Resources + WHERE internalId = deleted_row.parentId + AND EXISTS (SELECT 1 FROM Resources WHERE parentId = deleted_row.parentId); + +END; + +$body$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION CreateDeletedFilesTemporaryTable( +) RETURNS VOID AS $body$ + +BEGIN + + SET client_min_messages = warning; -- suppress NOTICE: relation "deletedresources" already exists, skipping + + -- note: temporary tables are created at session (connection) level -> they are likely to exist + CREATE TEMPORARY TABLE IF NOT EXISTS DeletedFiles( + uuid VARCHAR(64) NOT NULL, + fileType INTEGER, + compressedSize BIGINT, + uncompressedSize BIGINT, + compressionType INTEGER, + uncompressedHash VARCHAR(40), + compressedHash VARCHAR(40), + revision INTEGER, + customData TEXT + ); + + RESET client_min_messages; + + -- clear the temporary table in case it has been created earlier in the session + DELETE FROM DeletedFiles; +END; + +$body$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION AttachedFileDeletedFunc() +RETURNS TRIGGER AS $body$ +BEGIN + INSERT INTO DeletedFiles VALUES + (old.uuid, old.filetype, old.compressedSize, + old.uncompressedSize, old.compressionType, + old.uncompressedHash, old.compressedHash, + old.revision, old.customData); + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS AttachedFileDeleted on AttachedFiles; +CREATE TRIGGER AttachedFileDeleted +AFTER DELETE ON AttachedFiles +FOR EACH ROW +EXECUTE PROCEDURE AttachedFileDeletedFunc(); + + +------------------- Fast Statistics ------------------- + +-- initial population of GlobalIntegers if not already there +INSERT INTO GlobalIntegers + SELECT 0, CAST(COALESCE(SUM(compressedSize), 0) AS BIGINT) FROM AttachedFiles + ON CONFLICT DO NOTHING; + +INSERT INTO GlobalIntegers + SELECT 1, CAST(COALESCE(SUM(uncompressedSize), 0) AS BIGINT) FROM AttachedFiles + ON CONFLICT DO NOTHING; + +INSERT INTO GlobalIntegers + SELECT 2, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 0 -- Count patients + ON CONFLICT DO NOTHING; + +INSERT INTO GlobalIntegers + SELECT 3, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 1 -- Count studies + ON CONFLICT DO NOTHING; + +INSERT INTO GlobalIntegers + SELECT 4, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 2 -- Count series + ON CONFLICT DO NOTHING; + +INSERT INTO GlobalIntegers + SELECT 5, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 3 -- Count instances + ON CONFLICT DO NOTHING; + + +-- this table stores all changes that needs to be performed to the GlobalIntegers table +-- This way, each transaction can add row independently in this table without having to lock +-- any row (which was the case with previous FastTotalSize). +-- These changes will be applied at regular interval by an external thread or when someone +-- requests the statistics +CREATE TABLE IF NOT EXISTS GlobalIntegersChanges( + key INTEGER, + value BIGINT); + +CREATE OR REPLACE FUNCTION UpdateSingleStatistic( + IN statistics_key INTEGER, + OUT new_value BIGINT +) AS $body$ +BEGIN + + -- Delete the current changes, sum them and update the GlobalIntegers row. + -- New rows can be added in the meantime, they won't be deleted or summed. + WITH deleted_rows AS ( + DELETE FROM GlobalIntegersChanges + WHERE GlobalIntegersChanges.key = statistics_key + RETURNING value + ) + UPDATE GlobalIntegers + SET value = value + ( + SELECT COALESCE(SUM(value), 0) + FROM deleted_rows + ) + WHERE GlobalIntegers.key = statistics_key + RETURNING value INTO new_value; + +END; +$body$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION UpdateStatistics( + OUT patients_cunt BIGINT, + OUT studies_count BIGINT, + OUT series_count BIGINT, + OUT instances_count BIGINT, + OUT total_compressed_size BIGINT, + OUT total_uncompressed_size BIGINT +) AS $body$ +BEGIN + + SELECT UpdateSingleStatistic(0) INTO total_compressed_size; + SELECT UpdateSingleStatistic(1) INTO total_uncompressed_size; + SELECT UpdateSingleStatistic(2) INTO patients_cunt; + SELECT UpdateSingleStatistic(3) INTO studies_count; + SELECT UpdateSingleStatistic(4) INTO series_count; + SELECT UpdateSingleStatistic(5) INTO instances_count; + +END; +$body$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION IncrementResourcesTrackerFunc() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO GlobalIntegersChanges VALUES(new.resourceType + 2, 1); + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION DecrementResourcesTrackerFunc() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO GlobalIntegersChanges VALUES(old.resourceType + 2, -1); + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION AttachedFileIncrementSizeFunc() +RETURNS TRIGGER AS $body$ +BEGIN + INSERT INTO GlobalIntegersChanges VALUES(0, new.compressedSize); + INSERT INTO GlobalIntegersChanges VALUES(1, new.uncompressedSize); + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION AttachedFileDecrementSizeFunc() +RETURNS TRIGGER AS $body$ +BEGIN + INSERT INTO GlobalIntegersChanges VALUES(0, -old.compressedSize); + INSERT INTO GlobalIntegersChanges VALUES(1, -old.uncompressedSize); + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS AttachedFileIncrementSize on AttachedFiles; +CREATE TRIGGER AttachedFileIncrementSize +AFTER INSERT ON AttachedFiles +FOR EACH ROW +EXECUTE PROCEDURE AttachedFileIncrementSizeFunc(); + +DROP TRIGGER IF EXISTS AttachedFileDecrementSize on AttachedFiles; +CREATE TRIGGER AttachedFileDecrementSize +AFTER DELETE ON AttachedFiles +FOR EACH ROW +EXECUTE PROCEDURE AttachedFileDecrementSizeFunc(); + +DROP TRIGGER IF EXISTS IncrementResourcesTracker on Resources; +CREATE TRIGGER IncrementResourcesTracker +AFTER INSERT ON Resources +FOR EACH ROW +EXECUTE PROCEDURE IncrementResourcesTrackerFunc(); + +DROP TRIGGER IF EXISTS DecrementResourcesTracker on Resources; +CREATE TRIGGER DecrementResourcesTracker +AFTER DELETE ON Resources +FOR EACH ROW +EXECUTE PROCEDURE DecrementResourcesTrackerFunc(); + + +------------------- InsertOrUpdateMetadata function ------------------- +CREATE OR REPLACE FUNCTION InsertOrUpdateMetadata(resource_ids BIGINT[], + metadata_types INTEGER[], + metadata_values TEXT[], + revisions INTEGER[]) +RETURNS VOID AS $body$ +BEGIN + FOR i IN 1 .. ARRAY_LENGTH(resource_ids, 1) LOOP + -- RAISE NOTICE 'Parameter %: % % %', i, resource_ids[i], metadata_types[i], metadata_values[i]; + INSERT INTO Metadata VALUES(resource_ids[i], metadata_types[i], metadata_values[i], revisions[i]) + ON CONFLICT (id, type) DO UPDATE SET value = EXCLUDED.value, revision = EXCLUDED.revision; + END LOOP; + +END; +$body$ LANGUAGE plpgsql; + + +------------------- GetLastChange function ------------------- +DROP TRIGGER IF EXISTS InsertedChange ON Changes; + +-- insert the value if not already there +INSERT INTO GlobalIntegers + SELECT 6, CAST(COALESCE(MAX(seq), 0) AS BIGINT) FROM Changes + ON CONFLICT DO NOTHING; + +CREATE OR REPLACE FUNCTION InsertedChangeFunc() +RETURNS TRIGGER AS $body$ +BEGIN + UPDATE GlobalIntegers SET value = new.seq WHERE key = 6; + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +CREATE TRIGGER InsertedChange +AFTER INSERT ON Changes +FOR EACH ROW +EXECUTE PROCEDURE InsertedChangeFunc(); + + +------------------- CreateInstance function ------------------- +CREATE OR REPLACE FUNCTION CreateInstance( + IN patient_public_id TEXT, + IN study_public_id TEXT, + IN series_public_id TEXT, + IN instance_public_id TEXT, + OUT is_new_patient BIGINT, + OUT is_new_study BIGINT, + OUT is_new_series BIGINT, + OUT is_new_instance BIGINT, + OUT patient_internal_id BIGINT, + OUT study_internal_id BIGINT, + OUT series_internal_id BIGINT, + OUT instance_internal_id BIGINT) AS $body$ + +BEGIN + is_new_patient := 1; + is_new_study := 1; + is_new_series := 1; + is_new_instance := 1; + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 0, patient_public_id, NULL) RETURNING internalid INTO patient_internal_id; + EXCEPTION + WHEN unique_violation THEN + is_new_patient := 0; + SELECT internalid INTO patient_internal_id FROM "resources" WHERE publicId = patient_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction + END; + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 1, study_public_id, patient_internal_id) RETURNING internalid INTO study_internal_id; + EXCEPTION + WHEN unique_violation THEN + is_new_study := 0; + SELECT internalid INTO study_internal_id FROM "resources" WHERE publicId = study_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction END; + END; + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 2, series_public_id, study_internal_id) RETURNING internalid INTO series_internal_id; + EXCEPTION + WHEN unique_violation THEN + is_new_series := 0; + SELECT internalid INTO series_internal_id FROM "resources" WHERE publicId = series_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction END; + END; + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 3, instance_public_id, series_internal_id) RETURNING internalid INTO instance_internal_id; + EXCEPTION + WHEN unique_violation THEN + is_new_instance := 0; + SELECT internalid INTO instance_internal_id FROM "resources" WHERE publicId = instance_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction + END; + + IF is_new_instance > 0 THEN + -- Move the patient to the end of the recycling order. + PERFORM PatientAddedOrUpdated(patient_internal_id, 1); + END IF; +END; + +$body$ LANGUAGE plpgsql; + + + +-- set the global properties that actually documents the DB version, revision and some of the capabilities +DELETE FROM GlobalProperties WHERE property IN (1, 4, 6, 10, 11, 12, 13); +INSERT INTO GlobalProperties VALUES (1, 6); -- GlobalProperty_DatabaseSchemaVersion +INSERT INTO GlobalProperties VALUES (4, 3); -- GlobalProperty_DatabasePatchLevel +INSERT INTO GlobalProperties VALUES (6, 1); -- GlobalProperty_GetTotalSizeIsFast +INSERT INTO GlobalProperties VALUES (10, 1); -- GlobalProperty_HasTrigramIndex +INSERT INTO GlobalProperties VALUES (11, 3); -- GlobalProperty_HasCreateInstance -- this is actually the 3rd version of HasCreateInstance +INSERT INTO GlobalProperties VALUES (12, 1); -- GlobalProperty_HasFastCountResources +INSERT INTO GlobalProperties VALUES (13, 1); -- GlobalProperty_GetLastChangeIndex diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/SQL/Upgrades/Rev1ToRev2.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PostgreSQL/Plugins/SQL/Upgrades/Rev1ToRev2.sql Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,48 @@ +-- This file contains part of the changes required to upgrade from Revision 1 to Revision 2 (DB version 6) +-- It actually contains only the changes that: + -- can not be executed with an idempotent statement in SQL + -- or would polute the PrepareIndex.sql +-- This file is executed only if the current schema is in revision 1 and it is executed +-- before PrepareIndex.sql that is idempotent. + + +-- add unique constraints if they do not exists +DO $body$ +BEGIN + + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'resources' + AND constraint_name = 'uniquepublicid') + THEN + ALTER TABLE Resources ADD CONSTRAINT UniquePublicId UNIQUE (publicId); + RAISE NOTICE 'UniquePublicId constraint added to Resources.'; + END IF; + + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'patientrecyclingorder' + AND constraint_name = 'uniquepatientid') + THEN + ALTER TABLE PatientRecyclingOrder ADD CONSTRAINT UniquePatientId UNIQUE (patientId); + RAISE NOTICE 'UniquePatientId constraint added to PatientRecyclingOrder.'; + END IF; + +END $body$ LANGUAGE plpgsql; + + +-- In Rev2, we'll now use temporary tables so we need to remove the old tables that might have been used in previous revisions ! +-- these statements, although idempotent, are not part of PrepareIndexV2.sql to keep it clean +DROP TABLE IF EXISTS DeletedFiles; +DROP TABLE IF EXISTS RemainingAncestor; +DROP TABLE IF EXISTS DeletedResources; + +-- These triggers disappears and are not replaced in Rev2 +DROP TRIGGER IF EXISTS CountResourcesTracker ON Resources; + +-- The signature has changed so we must delete the function before replacing it. +DROP FUNCTION IF EXISTS CreateInstance; diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/SQL/Upgrades/UnknownToRev1.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PostgreSQL/Plugins/SQL/Upgrades/UnknownToRev1.sql Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,18 @@ +-- add the revision columns if not yet done + +DO $body$ +BEGIN + IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema='public' AND table_name='metadata' AND column_name='revision') THEN + ALTER TABLE Metadata ADD COLUMN revision INTEGER; + ELSE + raise notice 'the metadata.revision column already exists'; + END IF; + + IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema='public' AND table_name='attachedfiles' AND column_name='revision') THEN + ALTER TABLE AttachedFiles ADD COLUMN revision INTEGER; + ELSE + raise notice 'the attachedfiles.revision column already exists'; + END IF; + +END $body$; + diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/Plugins/StoragePlugin.cpp --- a/PostgreSQL/Plugins/StoragePlugin.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/PostgreSQL/Plugins/StoragePlugin.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -25,11 +27,14 @@ #include #include +#define ORTHANC_PLUGIN_NAME "postgresql-storage" + + extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { - if (!OrthancDatabases::InitializePlugin(context, "PostgreSQL", false)) + if (!OrthancDatabases::InitializePlugin(context, ORTHANC_PLUGIN_NAME, "PostgreSQL", false)) { return -1; } @@ -87,7 +92,7 @@ ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { - return "postgresql-storage"; + return ORTHANC_PLUGIN_NAME; } diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/UnitTests/PostgreSQLTests.cpp --- a/PostgreSQL/UnitTests/PostgreSQLTests.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/PostgreSQL/UnitTests/PostgreSQLTests.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -541,7 +543,8 @@ OrthancDatabases::PostgreSQLIndex db(NULL, globalParameters_); db.SetClearAll(true); - std::unique_ptr manager(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db)); + std::list tags; + std::unique_ptr manager(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db, false, tags)); std::string s; ASSERT_TRUE(db.LookupGlobalProperty(s, *manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabaseInternal1)); diff -r 82f73188b58d -r f18e46d7dbf8 PostgreSQL/UnitTests/UnitTestsMain.cpp --- a/PostgreSQL/UnitTests/UnitTestsMain.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/PostgreSQL/UnitTests/UnitTestsMain.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -94,18 +96,19 @@ OrthancDatabases::PostgreSQLIndex db1(NULL, noLock); db1.SetClearAll(true); - std::unique_ptr manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1)); + std::list identifierTags; + std::unique_ptr manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1, false, identifierTags)); { OrthancDatabases::PostgreSQLIndex db2(NULL, lock); - std::unique_ptr manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2)); + std::unique_ptr manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2, false, identifierTags)); OrthancDatabases::PostgreSQLIndex db3(NULL, lock); - ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3), Orthanc::OrthancException); + ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3, false, identifierTags), Orthanc::OrthancException); } OrthancDatabases::PostgreSQLIndex db4(NULL, lock); - std::unique_ptr manager4(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db4)); + std::unique_ptr manager4(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db4, false, identifierTags)); } diff -r 82f73188b58d -r f18e46d7dbf8 README --- a/README Wed Feb 01 16:24:37 2023 +0100 +++ b/README Tue Sep 24 15:04:21 2024 +0200 @@ -24,7 +24,7 @@ (.tar.gz) focused on one given RDBMS, you will only find the folders that are related to this specific RDBMS. The full source code is available at: -https://hg.orthanc-server.com/orthanc-databases/ +https://orthanc.uclouvain.be/hg/orthanc-databases/ Compilation and usage @@ -33,9 +33,9 @@ The compilation and usage of the plugins is available in the Orthanc Book: -* MySQL/MariaDB : http://book.orthanc-server.com/plugins/mysql.html -* PostgreSQL : http://book.orthanc-server.com/plugins/postgresql.html -* ODBC : http://book.orthanc-server.com/plugins/odbc.html +* MySQL/MariaDB : https://orthanc.uclouvain.be/book/plugins/mysql.html +* PostgreSQL : https://orthanc.uclouvain.be/book/plugins/postgresql.html +* ODBC : https://orthanc.uclouvain.be/book/plugins/odbc.html Older releases of PostgreSQL @@ -46,7 +46,15 @@ Releases <= 2.1 of the PostgreSQL plugins can still be found in the following legacy repository: -https://hg.orthanc-server.com/orthanc-postgresql/ +https://orthanc.uclouvain.be/hg/orthanc-postgresql/ + + +Contributing +------------ + +Instructions for contributing to the Orthanc project are included in +the Orthanc Book: +https://orthanc.uclouvain.be/book/developers/repositories.html Development diff -r 82f73188b58d -r f18e46d7dbf8 Resources/CMake/DatabasesFrameworkConfiguration.cmake --- a/Resources/CMake/DatabasesFrameworkConfiguration.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/CMake/DatabasesFrameworkConfiguration.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License @@ -29,10 +31,7 @@ if (ENABLE_POSTGRESQL_BACKEND) set(ENABLE_CRYPTO_OPTIONS ON) set(ENABLE_ZLIB ON) - - if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") - set(ENABLE_OPENSSL_ENGINES ON) - endif() + set(ENABLE_OPENSSL_ENGINES ON) endif() if (ENABLE_MYSQL_BACKEND) @@ -41,10 +40,7 @@ set(ENABLE_ZLIB ON) set(ENABLE_LOCALE ON) # iconv is needed set(ENABLE_WEB_CLIENT ON) # libcurl is needed - - if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") - set(ENABLE_OPENSSL_ENGINES ON) - endif() + set(ENABLE_OPENSSL_ENGINES ON) endif() if (ENABLE_ODBC_BACKEND) @@ -73,11 +69,21 @@ if (ENABLE_SQLITE_BACKEND) add_definitions(-DORTHANC_ENABLE_SQLITE=1) endif() - + + # These parameters should *NOT* be modified by the user: System-wide + # installations expect only dynamic linking set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") + set(USE_SYSTEM_OPENSSL ON CACHE BOOL "Use the system version of OpenSSL") + set(USE_SYSTEM_PROTOBUF ON CACHE BOOL "Use the system version of Google Protocol Buffers") + set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) + + set(ENABLE_OPENSSL_ENGINES ON CACHE INTERNAL "") + include(${CMAKE_CURRENT_LIST_DIR}/../Orthanc/CMake/GoogleTestConfiguration.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/../Orthanc/CMake/OpenSslConfiguration.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/../Orthanc/CMake/ProtobufConfiguration.cmake) else() # Those modules of the Orthanc framework are not needed when dealing @@ -107,6 +113,7 @@ ${ORTHANC_DATABASES_ROOT}/Framework/Common/IResult.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Common/ImplicitTransaction.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Common/InputFileValue.cpp + ${ORTHANC_DATABASES_ROOT}/Framework/Common/Integer32Value.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Common/Integer64Value.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Common/NullValue.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Common/Query.cpp @@ -114,7 +121,7 @@ ${ORTHANC_DATABASES_ROOT}/Framework/Common/ResultFileValue.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Common/RetryDatabaseFactory.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Common/RetryDatabaseFactory.cpp - ${ORTHANC_DATABASES_ROOT}/Framework/Common/StatementLocation.cpp + ${ORTHANC_DATABASES_ROOT}/Framework/Common/StatementId.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Common/Utf8StringValue.cpp ) diff -r 82f73188b58d -r f18e46d7dbf8 Resources/CMake/DatabasesFrameworkParameters.cmake --- a/Resources/CMake/DatabasesFrameworkParameters.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/CMake/DatabasesFrameworkParameters.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License @@ -50,4 +52,6 @@ set(ENABLE_MYSQL_BACKEND OFF) set(ENABLE_ODBC_BACKEND OFF) set(ENABLE_POSTGRESQL_BACKEND OFF) +set(ENABLE_PROTOBUF ON) +set(ENABLE_PROTOBUF_COMPILER ON) set(ENABLE_SQLITE_BACKEND OFF) diff -r 82f73188b58d -r f18e46d7dbf8 Resources/CMake/DatabasesPluginConfiguration.cmake --- a/Resources/CMake/DatabasesPluginConfiguration.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/CMake/DatabasesPluginConfiguration.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License @@ -22,35 +24,58 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Orthanc/CMake/AutoGeneratedCode.cmake) include(${CMAKE_CURRENT_LIST_DIR}/../Orthanc/Plugins/OrthancPluginsExports.cmake) +if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK) + if (NOT ORTHANC_SDK_VERSION STREQUAL "framework") + list(FIND ORTHANC_SDK_COMPATIBLE_VERSIONS ${ORTHANC_SDK_VERSION} tmp) + if (tmp EQUAL -1) + message(FATAL_ERROR "This database plugin is not compatible with Orthanc SDK ${ORTHANC_SDK_VERSION}") + endif() + endif() -if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK) if (ORTHANC_SDK_VERSION STREQUAL "0.9.5") - include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-0.9.5) + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-0.9.5) elseif (ORTHANC_SDK_VERSION STREQUAL "1.4.0") - include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.4.0) + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.4.0) elseif (ORTHANC_SDK_VERSION STREQUAL "1.5.2") - include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.2) + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.2) elseif (ORTHANC_SDK_VERSION STREQUAL "1.5.4") - include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.4) + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.4) elseif (ORTHANC_SDK_VERSION STREQUAL "1.9.2") - include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.9.2) + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.9.2) + elseif (ORTHANC_SDK_VERSION STREQUAL "1.12.0") + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.12.0) + elseif (ORTHANC_SDK_VERSION STREQUAL "1.12.3") + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.12.3) + elseif (ORTHANC_SDK_VERSION STREQUAL "1.12.4") + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.12.4) elseif (ORTHANC_SDK_VERSION STREQUAL "framework") set(tmp ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Include/) message(${tmp}) if (NOT EXISTS ${tmp}/orthanc/OrthancCDatabasePlugin.h) message(FATAL_ERROR "Your copy of the Orthanc framework doesn't contain the Orthanc plugin SDK") endif() - include_directories(${tmp}) + set(ORTHANC_SDK_ROOT ${tmp}) else() message(FATAL_ERROR "Unsupported version of the Orthanc plugin SDK: ${ORTHANC_SDK_VERSION}") endif() else () - CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCDatabasePlugin.h HAVE_ORTHANC_H) + find_path(ORTHANC_SDK_ROOT orthanc/OrthancCDatabasePlugin.h + /usr/include + /usr/local/include + ) + + if (NOT ORTHANC_SDK_ROOT) + message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK") + endif() + + check_include_file(${ORTHANC_SDK_ROOT}/orthanc/OrthancCDatabasePlugin.h HAVE_ORTHANC_H) if (NOT HAVE_ORTHANC_H) message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK") endif() endif() +include_directories(${ORTHANC_SDK_ROOT}) + if (NOT DEFINED ORTHANC_OPTIMAL_VERSION_MAJOR) message(FATAL_ERROR "ORTHANC_OPTIMAL_VERSION_MAJOR is not defined") @@ -67,22 +92,31 @@ add_definitions( -DHAS_ORTHANC_EXCEPTION=1 - -DORTHANC_BUILDING_SERVER_LIBRARY=0 - -DORTHANC_ENABLE_PLUGINS=1 -DORTHANC_OPTIMAL_VERSION_MAJOR=${ORTHANC_OPTIMAL_VERSION_MAJOR} -DORTHANC_OPTIMAL_VERSION_MINOR=${ORTHANC_OPTIMAL_VERSION_MINOR} -DORTHANC_OPTIMAL_VERSION_REVISION=${ORTHANC_OPTIMAL_VERSION_REVISION} ) +if ((STATIC_BUILD OR NOT USE_SYSTEM_PROTOBUF) AND + CMAKE_SYSTEM_VERSION STREQUAL "LinuxStandardBase") + # This is necessary, at least on LSB (Linux Standard Base), + # otherwise the following error is generated: "undefined reference + # to `__tls_get_addr'" + add_definitions(-DGOOGLE_PROTOBUF_NO_THREADLOCAL=1) +endif() + + list(APPEND DATABASES_SOURCES ${ORTHANC_CORE_SOURCES} ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV2.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV3.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV4.cpp + ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseConstraint.cpp + ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/ISqlLookupFormatter.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/IndexBackend.cpp + ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/IndexConnectionsPool.cpp + ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/MessagesToolbox.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/StorageBackend.cpp - ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Databases/DatabaseConstraint.cpp - ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Databases/ISqlLookupFormatter.cpp ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp ) diff -r 82f73188b58d -r f18e46d7dbf8 Resources/CMake/DatabasesPluginParameters.cmake --- a/Resources/CMake/DatabasesPluginParameters.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/CMake/DatabasesPluginParameters.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License @@ -25,7 +27,16 @@ # Advanced parameters to fine-tune linking against system libraries set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK") -set(ORTHANC_SDK_VERSION "1.9.2" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"0.9.5\", \"1.4.0\", \"1.5.2\", \"1.5.4\", \"1.9.2\" or \"framework\")") + +# Generate the documentation about the "ORTHANC_SDK_VERSION" option +set(tmp "Version of the Orthanc plugin SDK to use, if not using the system version (can be") +foreach(version IN LISTS ORTHANC_SDK_COMPATIBLE_VERSIONS) + set(tmp "${tmp} ${version},") +endforeach() +set(tmp "${tmp} or \"framework\")") + +set(ORTHANC_SDK_VERSION "${ORTHANC_SDK_DEFAULT_VERSION}" CACHE STRING "${tmp}") + include(${CMAKE_CURRENT_LIST_DIR}/DatabasesFrameworkParameters.cmake) diff -r 82f73188b58d -r f18e46d7dbf8 Resources/CMake/MariaDBConfiguration.cmake --- a/Resources/CMake/MariaDBConfiguration.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/CMake/MariaDBConfiguration.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License @@ -24,7 +26,7 @@ set(MARIADB_PACKAGE_VERSION "3.1.11") set(MARIADB_CLIENT_SOURCES_DIR ${CMAKE_BINARY_DIR}/mariadb-connector-c-${MARIADB_PACKAGE_VERSION}-src) set(MARIADB_CLIENT_MD5 "cf9da5f0ac9ec72dd8309bdc1d1c6c2f") - set(MARIADB_CLIENT_URL "http://orthanc.osimis.io/ThirdPartyDownloads/mariadb-connector-c-${MARIADB_PACKAGE_VERSION}-src.tar.gz") + set(MARIADB_CLIENT_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/mariadb-connector-c-${MARIADB_PACKAGE_VERSION}-src.tar.gz") if (IS_DIRECTORY "${MARIADB_CLIENT_SOURCES_DIR}") set(FirstRun OFF) diff -r 82f73188b58d -r f18e46d7dbf8 Resources/CMake/PostgreSQLConfiguration.cmake --- a/Resources/CMake/PostgreSQLConfiguration.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/CMake/PostgreSQLConfiguration.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License @@ -53,7 +55,7 @@ SET(LIBPQ_SOURCES_DIR ${CMAKE_BINARY_DIR}/postgresql-${LIBPQ_VERSION}) DownloadPackage( "551302a823a1ab48b4ed14166beebba9" - "http://orthanc.osimis.io/ThirdPartyDownloads/postgresql-${LIBPQ_VERSION}.tar.gz" + "https://orthanc.uclouvain.be/downloads/third-party-downloads/postgresql-${LIBPQ_VERSION}.tar.gz" "${LIBPQ_SOURCES_DIR}") @@ -440,7 +442,7 @@ else() set(PostgreSQL_ADDITIONAL_VERSIONS - "13" "12" "11" "10" "9.6" "9.5" "9.4" "9.3" "9.2" "9.1" "9.0" "8.4" "8.3" "8.2" "8.1" "8.0") + "16" "15" "14" "13" "12" "11" "10" "9.6" "9.5" "9.4" "9.3" "9.2" "9.1" "9.0" "8.4" "8.3" "8.2" "8.1" "8.0") if (NOT WIN32) foreach (suffix ${PostgreSQL_ADDITIONAL_VERSIONS}) list(APPEND PostgreSQL_ADDITIONAL_SEARCH_PATHS diff -r 82f73188b58d -r f18e46d7dbf8 Resources/CMake/UnixOdbcConfiguration.cmake --- a/Resources/CMake/UnixOdbcConfiguration.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/CMake/UnixOdbcConfiguration.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License @@ -24,7 +26,7 @@ set(VERSION "2.3.9") # Used in "config.h.in" set(UNIX_ODBC_SOURCES_DIR ${CMAKE_BINARY_DIR}/unixODBC-${VERSION}) set(UNIX_ODBC_MD5 "06f76e034bb41df5233554abe961a16f") - set(UNIX_ODBC_URL "http://orthanc.osimis.io/ThirdPartyDownloads/unixODBC-${VERSION}.tar.gz") + set(UNIX_ODBC_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/unixODBC-${VERSION}.tar.gz") DownloadPackage(${UNIX_ODBC_MD5} ${UNIX_ODBC_URL} "${UNIX_ODBC_SOURCES_DIR}") diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/CMake/AutoGeneratedCode.cmake --- a/Resources/Orthanc/CMake/AutoGeneratedCode.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/CMake/AutoGeneratedCode.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/CMake/Compiler.cmake --- a/Resources/Orthanc/CMake/Compiler.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/CMake/Compiler.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License @@ -42,6 +44,13 @@ # use by "ExternalProject" in CMake SET(CMAKE_LSB_CC $ENV{LSB_CC} CACHE STRING "") SET(CMAKE_LSB_CXX $ENV{LSB_CXX} CACHE STRING "") + + # This is necessary to build "Orthanc mainline - Framework LSB + # Release" on "buildbot-worker-debian11" + set(LSB_PTHREAD_NONSHARED "${LSB_PATH}/lib64-${LSB_TARGET_VERSION}/libpthread_nonshared.a") + if (EXISTS ${LSB_PTHREAD_NONSHARED}) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LSB_PTHREAD_NONSHARED}") + endif() endif() @@ -123,12 +132,17 @@ ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") - if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" AND + if (# NOT ${CMAKE_SYSTEM_VERSION} STREQUAL "LinuxStandardBase" AND + NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") # The "--no-undefined" linker flag makes the shared libraries # (plugins ModalityWorklists and ServeFolders) fail to compile on # OpenBSD, and make the PostgreSQL plugin complain about missing - # "environ" global variable in FreeBSD + # "environ" global variable in FreeBSD. + # + # TODO - Furthermore, on Linux Standard Base running on Debian 12, + # the "-Wl,--no-undefined" seems to break the compilation (added + # after Orthanc 1.12.2). This is disabled for now. set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") endif() @@ -218,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 ) @@ -236,7 +254,8 @@ if (DEFINED ENABLE_PROFILING AND ENABLE_PROFILING) - if (CMAKE_COMPILER_IS_GNUCXX) + if (CMAKE_COMPILER_IS_GNUCXX OR + CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg") @@ -261,3 +280,24 @@ # preceding batches. https://cmake.org/Bug/view.php?id=14874 set(CMAKE_CXX_ARCHIVE_APPEND " q ") endif() + + +# This function defines macro "__ORTHANC_FILE__" as a replacement to +# macro "__FILE__", as the latter leaks the full path of the source +# files in the binaries +# https://stackoverflow.com/questions/8487986/file-macro-shows-full-path +# https://twitter.com/wget42/status/1676877802375634944?s=20 +function(DefineSourceBasenameForTarget targetname) + # Microsoft Visual Studio is extremely slow if using + # "set_property()", we only enable this feature for gcc and clang + if (CMAKE_COMPILER_IS_GNUCXX OR + CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + get_target_property(source_files "${targetname}" SOURCES) + foreach(sourcefile ${source_files}) + get_filename_component(basename "${sourcefile}" NAME) + set_property( + SOURCE "${sourcefile}" APPEND + PROPERTY COMPILE_DEFINITIONS "__ORTHANC_FILE__=\"${basename}\"") + endforeach() + endif() +endfunction() diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/CMake/DownloadOrthancFramework.cmake --- a/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License @@ -71,7 +73,10 @@ if (NOT ORTHANC_FRAMEWORK_MAJOR MATCHES "^[0-9]+$" OR NOT ORTHANC_FRAMEWORK_MINOR MATCHES "^[0-9]+$" OR NOT ORTHANC_FRAMEWORK_REVISION MATCHES "^[0-9]+$") - message("Bad version of the Orthanc framework: ${ORTHANC_FRAMEWORK_VERSION}") + message("Bad version of the Orthanc framework, assuming a pre-release: ${ORTHANC_FRAMEWORK_VERSION}") + set(ORTHANC_FRAMEWORK_MAJOR 999) + set(ORTHANC_FRAMEWORK_MINOR 999) + set(ORTHANC_FRAMEWORK_REVISION 999) endif() if (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.1") @@ -138,6 +143,28 @@ set(ORTHANC_FRAMEWORK_MD5 "4b5d05683d747c29b2860ad79d11e62e") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.7") set(ORTHANC_FRAMEWORK_MD5 "c912bbb860d640d3ae3003b5c9698205") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.10.0") + set(ORTHANC_FRAMEWORK_MD5 "8610c82d9153f22e929f2110f8f60279") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.10.1") + set(ORTHANC_FRAMEWORK_MD5 "caf667fc5ea452b3d0c2f70bfd02599c") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.0") + set(ORTHANC_FRAMEWORK_MD5 "962c4a4a706a2ef28b390d8515dd7091") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.1") + set(ORTHANC_FRAMEWORK_MD5 "a39661c406adf22cf574fde290cf4bbf") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.2") + set(ORTHANC_FRAMEWORK_MD5 "ede3de356493a8868545f8cb4b8bc8b5") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.3") + set(ORTHANC_FRAMEWORK_MD5 "e48fc0cb09c4856803791a1be28c07dc") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.0") + set(ORTHANC_FRAMEWORK_MD5 "d32a0cde03b6eb603d8dd2b33d38bf1b") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.1") + set(ORTHANC_FRAMEWORK_MD5 "8a435140efc8ff4a01d8242f092f21de") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.2") + set(ORTHANC_FRAMEWORK_MD5 "d2476b9e796e339ac320b5333489bdb3") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.3") + set(ORTHANC_FRAMEWORK_MD5 "975f5bf2142c22cb1777b4f6a0a614c5") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.4") + set(ORTHANC_FRAMEWORK_MD5 "1e61779ea4a7cd705720bdcfed8a6a73") # Below this point are development snapshots that were used to # release some plugin, before an official release of the Orthanc @@ -148,16 +175,28 @@ # elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "ae0e3fd609df") # DICOMweb 1.1 (framework pre-1.6.0) + set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) set(ORTHANC_FRAMEWORK_MD5 "7e09e9b530a2f527854f0b782d7e0645") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "82652c5fc04f") # Stone Web viewer 1.0 (framework pre-1.8.1) + set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) set(ORTHANC_FRAMEWORK_MD5 "d77331d68917e66a3f4f9b807bbdab7f") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "4a3ba4bf4ba7") # PostgreSQL 3.3 (framework pre-1.8.2) + set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) set(ORTHANC_FRAMEWORK_MD5 "2d82bddf06f9cfe82095495cb3b8abde") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "23ad1b9c7800") # For "Toolbox::ReadJson()" and "Toolbox::Write{...}Json()" (pre-1.9.0) + set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) set(ORTHANC_FRAMEWORK_MD5 "9af92080e57c60dd288eba46ce606c00") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "b2e08d83e21d") + # WSI 1.1 (framework pre-1.10.0), to remove "-std=c++11" + set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) + set(ORTHANC_FRAMEWORK_MD5 "2eaa073cbb4b44ffba199ad93393b2b1") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "daf4807631c5") + # DICOMweb 1.15 (framework pre-1.12.2) + set(ORTHANC_FRAMEWORK_PRE_RELEASE ON) + set(ORTHANC_FRAMEWORK_MD5 "ebe8bdf388319f1c9536b2b680451848") endif() endif() endif() @@ -245,7 +284,7 @@ else() message("Forking the Orthanc source repository using Mercurial") execute_process( - COMMAND ${ORTHANC_FRAMEWORK_HG} clone "https://hg.orthanc-server.com/orthanc/" + COMMAND ${ORTHANC_FRAMEWORK_HG} clone "https://orthanc.uclouvain.be/hg/orthanc/" WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE Failure ) @@ -294,7 +333,11 @@ else() # Default case: Download from the official Web site set(ORTHANC_FRAMEMORK_FILENAME Orthanc-${ORTHANC_FRAMEWORK_VERSION}.tar.gz) - set(ORTHANC_FRAMEWORK_URL "http://orthanc.osimis.io/ThirdPartyDownloads/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}") + if (ORTHANC_FRAMEWORK_PRE_RELEASE) + set(ORTHANC_FRAMEWORK_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}") + else() + set(ORTHANC_FRAMEWORK_URL "https://orthanc.uclouvain.be/downloads/sources/orthanc/${ORTHANC_FRAMEMORK_FILENAME}") + endif() endif() set(ORTHANC_FRAMEWORK_ARCHIVE "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${ORTHANC_FRAMEMORK_FILENAME}") @@ -500,35 +543,6 @@ message(FATAL_ERROR "Please install the libjsoncpp-dev package") endif() - # Switch to the C++11 standard if the version of JsonCpp is 1.y.z - # (same as variable JSONCPP_CXX11 in the source code of Orthanc) - if (EXISTS ${JSONCPP_INCLUDE_DIR}/json/version.h) - file(STRINGS - "${JSONCPP_INCLUDE_DIR}/json/version.h" - JSONCPP_VERSION_MAJOR1 REGEX - ".*define JSONCPP_VERSION_MAJOR.*") - - if (NOT JSONCPP_VERSION_MAJOR1) - message(FATAL_ERROR "Unable to extract the major version of JsonCpp") - endif() - - string(REGEX REPLACE - ".*JSONCPP_VERSION_MAJOR.*([0-9]+)$" "\\1" - JSONCPP_VERSION_MAJOR ${JSONCPP_VERSION_MAJOR1}) - message("JsonCpp major version: ${JSONCPP_VERSION_MAJOR}") - - if (JSONCPP_VERSION_MAJOR GREATER 0) - message("Switching to C++11 standard, as version of JsonCpp is >= 1.0.0") - if (CMAKE_COMPILER_IS_GNUCXX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") - elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - endif() - endif() - else() - message("Unable to detect the major version of JsonCpp, assuming < 1.0.0") - endif() - # Look for Orthanc framework shared library include(CheckCXXSymbolExists) diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/CMake/DownloadPackage.cmake --- a/Resources/Orthanc/CMake/DownloadPackage.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/CMake/DownloadPackage.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License @@ -100,19 +102,26 @@ message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON") endif() - if ("${MD5}" STREQUAL "no-check") - message(WARNING "Not checking the MD5 of: ${Url}") - file(DOWNLOAD "${Url}" "${TMP_PATH}" - SHOW_PROGRESS TIMEOUT 300 INACTIVITY_TIMEOUT 60 - STATUS Failure) - else() - file(DOWNLOAD "${Url}" "${TMP_PATH}" - SHOW_PROGRESS TIMEOUT 300 INACTIVITY_TIMEOUT 60 - EXPECTED_MD5 "${MD5}" STATUS Failure) - endif() + foreach (retry RANGE 1 5) # Retries 5 times + if ("${MD5}" STREQUAL "no-check") + message(WARNING "Not checking the MD5 of: ${Url}") + file(DOWNLOAD "${Url}" "${TMP_PATH}" + SHOW_PROGRESS TIMEOUT 30 INACTIVITY_TIMEOUT 10 + STATUS Failure) + else() + file(DOWNLOAD "${Url}" "${TMP_PATH}" + SHOW_PROGRESS TIMEOUT 30 INACTIVITY_TIMEOUT 10 + EXPECTED_MD5 "${MD5}" STATUS Failure) + endif() - list(GET Failure 0 Status) + list(GET Failure 0 Status) + if (Status EQUAL 0) + break() # Successful download + endif() + endforeach() + if (NOT Status EQUAL 0) + file(REMOVE ${TMP_PATH}) message(FATAL_ERROR "Cannot download file: ${Url}") endif() diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/CMake/GoogleTestConfiguration.cmake --- a/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License @@ -49,7 +51,7 @@ elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST) set(GOOGLE_TEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/googletest-release-1.8.1) - set(GOOGLE_TEST_URL "http://orthanc.osimis.io/ThirdPartyDownloads/gtest-1.8.1.tar.gz") + set(GOOGLE_TEST_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/gtest-1.8.1.tar.gz") set(GOOGLE_TEST_MD5 "2e6fbeb6a91310a16efe181886c59596") DownloadPackage(${GOOGLE_TEST_MD5} ${GOOGLE_TEST_URL} "${GOOGLE_TEST_SOURCES_DIR}") diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/CMake/OpenSslConfiguration.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Orthanc/CMake/OpenSslConfiguration.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,69 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program. If not, see +# . + + +if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL) + if (OPENSSL_STATIC_VERSION STREQUAL "1.1.1") + # Still used by orthanc-gcp (Google Cloud Platform) as of its release 1.0 + include(${CMAKE_CURRENT_LIST_DIR}/OpenSslConfigurationStatic-1.1.1.cmake) + elseif (OPENSSL_STATIC_VERSION STREQUAL "3.0") + include(${CMAKE_CURRENT_LIST_DIR}/OpenSslConfigurationStatic-3.0.cmake) + else() + message(FATAL_ERROR "Unsupported version of OpenSSL: ${OPENSSL_STATIC_VERSION}") + endif() + + source_group(ThirdParty\\OpenSSL REGULAR_EXPRESSION ${OPENSSL_SOURCES_DIR}/.*) + +elseif (CMAKE_CROSSCOMPILING AND + "${CMAKE_SYSTEM_VERSION}" STREQUAL "CrossToolNg") + + CHECK_INCLUDE_FILE_CXX(openssl/opensslv.h HAVE_OPENSSL_H) + if (NOT HAVE_OPENSSL_H) + message(FATAL_ERROR "Please install the libopenssl-dev package") + endif() + + CHECK_LIBRARY_EXISTS(crypto "OPENSSL_init" "" HAVE_OPENSSL_CRYPTO_LIB) + if (NOT HAVE_OPENSSL_CRYPTO_LIB) + message(FATAL_ERROR "Please install the libopenssl package") + endif() + + # The "SSL_library_init" is for OpenSSL <= 1.0.2, whereas + # "OPENSSL_init_ssl" is for OpenSSL >= 1.1.0 + CHECK_LIBRARY_EXISTS(ssl "SSL_library_init" "" HAVE_OPENSSL_SSL_LIB) + if (NOT HAVE_OPENSSL_SSL_LIB) + CHECK_LIBRARY_EXISTS(ssl "OPENSSL_init_ssl" "" HAVE_OPENSSL_SSL_LIB_2) + if (NOT HAVE_OPENSSL_SSL_LIB_2) + message(FATAL_ERROR "Please install the libopenssl package") + endif() + endif() + + link_libraries(crypto ssl) + +else() + include(FindOpenSSL) + + if (NOT OPENSSL_FOUND) + message(FATAL_ERROR "Unable to find OpenSSL") + endif() + + include_directories(${OPENSSL_INCLUDE_DIR}) + link_libraries(${OPENSSL_LIBRARIES}) +endif() diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/CMake/ProtobufConfiguration.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Orthanc/CMake/ProtobufConfiguration.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,86 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program. If not, see +# . + + +if (STATIC_BUILD OR NOT USE_SYSTEM_PROTOBUF) + if (ENABLE_PROTOBUF_COMPILER) + include(ExternalProject) + externalproject_add(ProtobufCompiler + SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../ProtocolBuffers" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/ProtobufCompiler-build" + # this helps triggering build when changing the external project + BUILD_ALWAYS 1 + CMAKE_ARGS + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR} + ) + + # The "protoc" compiler is built using "externalproject_add", + # which builds for the host platform, not for the target platform + if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + set(Suffix ".exe") + else() + set(Suffix "") + endif() + + set(PROTOC_EXECUTABLE ${CMAKE_CURRENT_BINARY_DIR}/protoc${Suffix}) + endif() + + include(${CMAKE_CURRENT_LIST_DIR}/../ProtocolBuffers/ProtobufLibrary.cmake) + source_group(ThirdParty\\Protobuf REGULAR_EXPRESSION ${PROTOBUF_SOURCE_DIR}/.*) + +else() + if (CMAKE_CROSSCOMPILING) + message(FATAL_ERROR "If cross-compiling, the static version of Protocol Buffers should be used to avoid version mismatch") + endif() + + if (ENABLE_PROTOBUF_COMPILER) + find_program(PROTOC_EXECUTABLE protoc) + if (${PROTOC_EXECUTABLE} MATCHES "PROTOC_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the 'protoc' compiler for Protocol Buffers (package 'protobuf-compiler' on Debian/Ubuntu)") + endif() + add_custom_target(ProtobufCompiler) + endif() + + check_include_file_cxx(google/protobuf/any.h HAVE_PROTOBUF_H) + if (NOT HAVE_PROTOBUF_H) + message(FATAL_ERROR "Please install the libprotobuf-dev package") + endif() + + set(CMAKE_REQUIRED_LIBRARIES "protobuf") + + include(CheckCXXSourceCompiles) + check_cxx_source_compiles( + " +#include +int main() +{ + google::protobuf::FieldDescriptor::TypeName(google::protobuf::FieldDescriptor::TYPE_FLOAT); +} +" HAVE_PROTOBUF_LIB) + if (NOT HAVE_PROTOBUF_LIB) + message(FATAL_ERROR "Cannot find the protobuf library") + endif() + + unset(CMAKE_REQUIRED_LIBRARIES) + + link_libraries(protobuf) +endif() diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Databases/DatabaseConstraint.cpp --- a/Resources/Orthanc/Databases/DatabaseConstraint.cpp Wed Feb 01 16:24:37 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,246 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., 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 . - **/ - - -#if !defined(ORTHANC_BUILDING_SERVER_LIBRARY) -# error Macro ORTHANC_BUILDING_SERVER_LIBRARY must be defined -#endif - -#if ORTHANC_BUILDING_SERVER_LIBRARY == 1 -# include "../PrecompiledHeadersServer.h" -#endif - -#include "DatabaseConstraint.h" - -#if ORTHANC_BUILDING_SERVER_LIBRARY == 1 -# include "../../../OrthancFramework/Sources/OrthancException.h" -#else -# include -#endif - -#include - - -namespace Orthanc -{ - namespace Plugins - { -#if ORTHANC_ENABLE_PLUGINS == 1 - OrthancPluginResourceType Convert(ResourceType type) - { - switch (type) - { - case ResourceType_Patient: - return OrthancPluginResourceType_Patient; - - case ResourceType_Study: - return OrthancPluginResourceType_Study; - - case ResourceType_Series: - return OrthancPluginResourceType_Series; - - case ResourceType_Instance: - return OrthancPluginResourceType_Instance; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } -#endif - - -#if ORTHANC_ENABLE_PLUGINS == 1 - ResourceType Convert(OrthancPluginResourceType type) - { - switch (type) - { - case OrthancPluginResourceType_Patient: - return ResourceType_Patient; - - case OrthancPluginResourceType_Study: - return ResourceType_Study; - - case OrthancPluginResourceType_Series: - return ResourceType_Series; - - case OrthancPluginResourceType_Instance: - return ResourceType_Instance; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } -#endif - - -#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 - OrthancPluginConstraintType Convert(ConstraintType constraint) - { - switch (constraint) - { - case ConstraintType_Equal: - return OrthancPluginConstraintType_Equal; - - case ConstraintType_GreaterOrEqual: - return OrthancPluginConstraintType_GreaterOrEqual; - - case ConstraintType_SmallerOrEqual: - return OrthancPluginConstraintType_SmallerOrEqual; - - case ConstraintType_Wildcard: - return OrthancPluginConstraintType_Wildcard; - - case ConstraintType_List: - return OrthancPluginConstraintType_List; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } -#endif - - -#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 - ConstraintType Convert(OrthancPluginConstraintType constraint) - { - switch (constraint) - { - case OrthancPluginConstraintType_Equal: - return ConstraintType_Equal; - - case OrthancPluginConstraintType_GreaterOrEqual: - return ConstraintType_GreaterOrEqual; - - case OrthancPluginConstraintType_SmallerOrEqual: - return ConstraintType_SmallerOrEqual; - - case OrthancPluginConstraintType_Wildcard: - return ConstraintType_Wildcard; - - case OrthancPluginConstraintType_List: - return ConstraintType_List; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } -#endif - } - - DatabaseConstraint::DatabaseConstraint(ResourceType level, - const DicomTag& tag, - bool isIdentifier, - ConstraintType type, - const std::vector& values, - bool caseSensitive, - bool mandatory) : - level_(level), - tag_(tag), - isIdentifier_(isIdentifier), - constraintType_(type), - values_(values), - caseSensitive_(caseSensitive), - mandatory_(mandatory) - { - if (type != ConstraintType_List && - values_.size() != 1) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - -#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 - DatabaseConstraint::DatabaseConstraint(const OrthancPluginDatabaseConstraint& constraint) : - level_(Plugins::Convert(constraint.level)), - tag_(constraint.tagGroup, constraint.tagElement), - isIdentifier_(constraint.isIdentifierTag), - constraintType_(Plugins::Convert(constraint.type)), - caseSensitive_(constraint.isCaseSensitive), - mandatory_(constraint.isMandatory) - { - if (constraintType_ != ConstraintType_List && - constraint.valuesCount != 1) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - values_.resize(constraint.valuesCount); - - for (uint32_t i = 0; i < constraint.valuesCount; i++) - { - assert(constraint.values[i] != NULL); - values_[i].assign(constraint.values[i]); - } - } -#endif - - - const std::string& DatabaseConstraint::GetValue(size_t index) const - { - if (index >= values_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - return values_[index]; - } - } - - - const std::string& DatabaseConstraint::GetSingleValue() const - { - if (values_.size() != 1) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - return values_[0]; - } - } - - -#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 - void DatabaseConstraint::EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint, - std::vector& tmpValues) const - { - memset(&constraint, 0, sizeof(constraint)); - - tmpValues.resize(values_.size()); - - for (size_t i = 0; i < values_.size(); i++) - { - tmpValues[i] = values_[i].c_str(); - } - - constraint.level = Plugins::Convert(level_); - constraint.tagGroup = tag_.GetGroup(); - constraint.tagElement = tag_.GetElement(); - constraint.isIdentifierTag = isIdentifier_; - constraint.isCaseSensitive = caseSensitive_; - constraint.isMandatory = mandatory_; - constraint.type = Plugins::Convert(constraintType_); - constraint.valuesCount = values_.size(); - constraint.values = (tmpValues.empty() ? NULL : &tmpValues[0]); - } -#endif -} diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Databases/DatabaseConstraint.h --- a/Resources/Orthanc/Databases/DatabaseConstraint.h Wed Feb 01 16:24:37 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., 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 . - **/ - - -#pragma once - -#if !defined(ORTHANC_BUILDING_SERVER_LIBRARY) -# error Macro ORTHANC_BUILDING_SERVER_LIBRARY must be defined -#endif - -#if ORTHANC_BUILDING_SERVER_LIBRARY == 1 -# include "../../../OrthancFramework/Sources/DicomFormat/DicomMap.h" -#else -// This is for the "orthanc-databases" project to reuse this file -# include -#endif - -#define ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT 0 - -#if ORTHANC_ENABLE_PLUGINS == 1 -# include -# if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in 1.3.1 -# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 2) -# undef ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT -# define ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT 1 -# endif -# endif -#endif - -namespace Orthanc -{ - enum ConstraintType - { - ConstraintType_Equal, - ConstraintType_SmallerOrEqual, - ConstraintType_GreaterOrEqual, - ConstraintType_Wildcard, - ConstraintType_List - }; - - namespace Plugins - { -#if ORTHANC_ENABLE_PLUGINS == 1 - OrthancPluginResourceType Convert(ResourceType type); -#endif - -#if ORTHANC_ENABLE_PLUGINS == 1 - ResourceType Convert(OrthancPluginResourceType type); -#endif - -#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 - OrthancPluginConstraintType Convert(ConstraintType constraint); -#endif - -#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 - ConstraintType Convert(OrthancPluginConstraintType constraint); -#endif - } - - - // This class is also used by the "orthanc-databases" project - class DatabaseConstraint - { - private: - ResourceType level_; - DicomTag tag_; - bool isIdentifier_; - ConstraintType constraintType_; - std::vector values_; - bool caseSensitive_; - bool mandatory_; - - public: - DatabaseConstraint(ResourceType level, - const DicomTag& tag, - bool isIdentifier, - ConstraintType type, - const std::vector& values, - bool caseSensitive, - bool mandatory); - -#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 - explicit DatabaseConstraint(const OrthancPluginDatabaseConstraint& constraint); -#endif - - ResourceType GetLevel() const - { - return level_; - } - - const DicomTag& GetTag() const - { - return tag_; - } - - bool IsIdentifier() const - { - return isIdentifier_; - } - - ConstraintType GetConstraintType() const - { - return constraintType_; - } - - size_t GetValuesCount() const - { - return values_.size(); - } - - const std::string& GetValue(size_t index) const; - - const std::string& GetSingleValue() const; - - bool IsCaseSensitive() const - { - return caseSensitive_; - } - - bool IsMandatory() const - { - return mandatory_; - } - - bool IsMatch(const DicomMap& dicom) const; - -#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 - void EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint, - std::vector& tmpValues) const; -#endif - }; -} diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Databases/ISqlLookupFormatter.cpp --- a/Resources/Orthanc/Databases/ISqlLookupFormatter.cpp Wed Feb 01 16:24:37 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,357 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., 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 . - **/ - - -#if !defined(ORTHANC_BUILDING_SERVER_LIBRARY) -# error Macro ORTHANC_BUILDING_SERVER_LIBRARY must be defined -#endif - -#if ORTHANC_BUILDING_SERVER_LIBRARY == 1 -# include "../PrecompiledHeadersServer.h" -#endif - -#include "ISqlLookupFormatter.h" - -#if ORTHANC_BUILDING_SERVER_LIBRARY == 1 -# include "../../../OrthancFramework/Sources/OrthancException.h" -#else -# include -#endif - -#include "DatabaseConstraint.h" - -#include - - -namespace Orthanc -{ - static std::string FormatLevel(ResourceType level) - { - switch (level) - { - case ResourceType_Patient: - return "patients"; - - case ResourceType_Study: - return "studies"; - - case ResourceType_Series: - return "series"; - - case ResourceType_Instance: - return "instances"; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - - static bool FormatComparison(std::string& target, - ISqlLookupFormatter& formatter, - const DatabaseConstraint& constraint, - size_t index, - bool escapeBrackets) - { - std::string tag = "t" + boost::lexical_cast(index); - - std::string comparison; - - switch (constraint.GetConstraintType()) - { - case ConstraintType_Equal: - case ConstraintType_SmallerOrEqual: - case ConstraintType_GreaterOrEqual: - { - std::string op; - switch (constraint.GetConstraintType()) - { - case ConstraintType_Equal: - op = "="; - break; - - case ConstraintType_SmallerOrEqual: - op = "<="; - break; - - case ConstraintType_GreaterOrEqual: - op = ">="; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - std::string parameter = formatter.GenerateParameter(constraint.GetSingleValue()); - - if (constraint.IsCaseSensitive()) - { - comparison = tag + ".value " + op + " " + parameter; - } - else - { - comparison = "lower(" + tag + ".value) " + op + " lower(" + parameter + ")"; - } - - break; - } - - case ConstraintType_List: - { - for (size_t i = 0; i < constraint.GetValuesCount(); i++) - { - if (!comparison.empty()) - { - comparison += ", "; - } - - std::string parameter = formatter.GenerateParameter(constraint.GetValue(i)); - - if (constraint.IsCaseSensitive()) - { - comparison += parameter; - } - else - { - comparison += "lower(" + parameter + ")"; - } - } - - if (constraint.IsCaseSensitive()) - { - comparison = tag + ".value IN (" + comparison + ")"; - } - else - { - comparison = "lower(" + tag + ".value) IN (" + comparison + ")"; - } - - break; - } - - case ConstraintType_Wildcard: - { - const std::string value = constraint.GetSingleValue(); - - if (value == "*") - { - if (!constraint.IsMandatory()) - { - // Universal constraint on an optional tag, ignore it - return false; - } - } - else - { - std::string escaped; - escaped.reserve(value.size()); - - for (size_t i = 0; i < value.size(); i++) - { - if (value[i] == '*') - { - escaped += "%"; - } - else if (value[i] == '?') - { - escaped += "_"; - } - else if (value[i] == '%') - { - escaped += "\\%"; - } - else if (value[i] == '_') - { - escaped += "\\_"; - } - else if (value[i] == '\\') - { - escaped += "\\\\"; - } - else if (escapeBrackets && value[i] == '[') - { - escaped += "\\["; - } - else if (escapeBrackets && value[i] == ']') - { - escaped += "\\]"; - } - else - { - escaped += value[i]; - } - } - - std::string parameter = formatter.GenerateParameter(escaped); - - if (constraint.IsCaseSensitive()) - { - comparison = (tag + ".value LIKE " + parameter + " " + - formatter.FormatWildcardEscape()); - } - else - { - comparison = ("lower(" + tag + ".value) LIKE lower(" + - parameter + ") " + formatter.FormatWildcardEscape()); - } - } - - break; - } - - default: - return false; - } - - if (constraint.IsMandatory()) - { - target = comparison; - } - else if (comparison.empty()) - { - target = tag + ".value IS NULL"; - } - else - { - target = tag + ".value IS NULL OR " + comparison; - } - - return true; - } - - - static void FormatJoin(std::string& target, - const DatabaseConstraint& constraint, - size_t index) - { - std::string tag = "t" + boost::lexical_cast(index); - - if (constraint.IsMandatory()) - { - target = " INNER JOIN "; - } - else - { - target = " LEFT JOIN "; - } - - if (constraint.IsIdentifier()) - { - target += "DicomIdentifiers "; - } - else - { - target += "MainDicomTags "; - } - - target += (tag + " ON " + tag + ".id = " + FormatLevel(constraint.GetLevel()) + - ".internalId AND " + tag + ".tagGroup = " + - boost::lexical_cast(constraint.GetTag().GetGroup()) + - " AND " + tag + ".tagElement = " + - boost::lexical_cast(constraint.GetTag().GetElement())); - } - - - void ISqlLookupFormatter::Apply(std::string& sql, - ISqlLookupFormatter& formatter, - const std::vector& lookup, - ResourceType queryLevel, - size_t limit) - { - assert(ResourceType_Patient < ResourceType_Study && - ResourceType_Study < ResourceType_Series && - ResourceType_Series < ResourceType_Instance); - - ResourceType upperLevel = queryLevel; - ResourceType lowerLevel = queryLevel; - - for (size_t i = 0; i < lookup.size(); i++) - { - ResourceType level = lookup[i].GetLevel(); - - if (level < upperLevel) - { - upperLevel = level; - } - - if (level > lowerLevel) - { - lowerLevel = level; - } - } - - assert(upperLevel <= queryLevel && - queryLevel <= lowerLevel); - - const bool escapeBrackets = formatter.IsEscapeBrackets(); - - std::string joins, comparisons; - - size_t count = 0; - - for (size_t i = 0; i < lookup.size(); i++) - { - std::string comparison; - - if (FormatComparison(comparison, formatter, lookup[i], count, escapeBrackets)) - { - std::string join; - FormatJoin(join, lookup[i], count); - joins += join; - - if (!comparison.empty()) - { - comparisons += " AND " + comparison; - } - - count ++; - } - } - - sql = ("SELECT " + - FormatLevel(queryLevel) + ".publicId, " + - FormatLevel(queryLevel) + ".internalId" + - " FROM Resources AS " + FormatLevel(queryLevel)); - - for (int level = queryLevel - 1; level >= upperLevel; level--) - { - sql += (" INNER JOIN Resources " + - FormatLevel(static_cast(level)) + " ON " + - FormatLevel(static_cast(level)) + ".internalId=" + - FormatLevel(static_cast(level + 1)) + ".parentId"); - } - - for (int level = queryLevel + 1; level <= lowerLevel; level++) - { - sql += (" INNER JOIN Resources " + - FormatLevel(static_cast(level)) + " ON " + - FormatLevel(static_cast(level - 1)) + ".internalId=" + - FormatLevel(static_cast(level)) + ".parentId"); - } - - sql += (joins + " WHERE " + FormatLevel(queryLevel) + ".resourceType = " + - formatter.FormatResourceType(queryLevel) + comparisons); - - if (limit != 0) - { - sql += " LIMIT " + boost::lexical_cast(limit); - } - } -} diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Databases/ISqlLookupFormatter.h --- a/Resources/Orthanc/Databases/ISqlLookupFormatter.h Wed Feb 01 16:24:37 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., 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 . - **/ - - -#pragma once - -#if ORTHANC_BUILDING_SERVER_LIBRARY == 1 -# include "../../../OrthancFramework/Sources/Enumerations.h" -#else -# include -#endif - -#include -#include - -namespace Orthanc -{ - class DatabaseConstraint; - - // This class is also used by the "orthanc-databases" project - class ISqlLookupFormatter : public boost::noncopyable - { - public: - virtual ~ISqlLookupFormatter() - { - } - - virtual std::string GenerateParameter(const std::string& value) = 0; - - virtual std::string FormatResourceType(ResourceType level) = 0; - - virtual std::string FormatWildcardEscape() = 0; - - /** - * Whether to escape '[' and ']', which is only needed for - * MSSQL. New in Orthanc 1.9.8, from the following changeset: - * https://hg.orthanc-server.com/orthanc-databases/rev/389c037387ea - **/ - virtual bool IsEscapeBrackets() const = 0; - - static void Apply(std::string& sql, - ISqlLookupFormatter& formatter, - const std::vector& lookup, - ResourceType queryLevel, - size_t limit); - }; -} diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/EmbedResources.py --- a/Resources/Orthanc/EmbedResources.py Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/EmbedResources.py Tue Sep 24 15:04:21 2024 +0200 @@ -3,7 +3,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/LinuxStandardBaseToolchain.cmake --- a/Resources/Orthanc/LinuxStandardBaseToolchain.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/LinuxStandardBaseToolchain.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License @@ -21,11 +23,11 @@ # # Full build, as used on the BuildBot CIS: # -# $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DBOOST_LOCALE_BACKEND=icu -DENABLE_PKCS11=ON -G Ninja +# $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DUSE_LEGACY_BOOST=ON -DBOOST_LOCALE_BACKEND=icu -DENABLE_PKCS11=ON -G Ninja # # Or, more lightweight version (without libp11 and ICU): # -# $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -G Ninja +# $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake ../OrthancServer/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_BOOST=ON -G Ninja # INCLUDE(CMakeForceCompiler) diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/MinGW-W64-Toolchain32.cmake --- a/Resources/Orthanc/MinGW-W64-Toolchain32.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/MinGW-W64-Toolchain32.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/MinGW-W64-Toolchain64.cmake --- a/Resources/Orthanc/MinGW-W64-Toolchain64.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/MinGW-W64-Toolchain64.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/MinGWToolchain.cmake --- a/Resources/Orthanc/MinGWToolchain.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/MinGWToolchain.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp --- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 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 @@ -60,6 +62,7 @@ namespace OrthancPlugins { static OrthancPluginContext* globalContext_ = NULL; + static std::string pluginName_; void SetGlobalContext(OrthancPluginContext* context) @@ -79,6 +82,20 @@ } + void SetGlobalContext(OrthancPluginContext* context, + const char* pluginName) + { + SetGlobalContext(context); + pluginName_ = pluginName; + } + + + void ResetGlobalContext() + { + globalContext_ = NULL; + pluginName_.clear(); + } + bool HasGlobalContext() { return globalContext_ != NULL; @@ -98,6 +115,66 @@ } +#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1 + void LogMessage(OrthancPluginLogLevel level, + const char* file, + uint32_t line, + const std::string& message) + { + if (HasGlobalContext()) + { +#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1 + const char* pluginName = (pluginName_.empty() ? NULL : pluginName_.c_str()); + OrthancPluginLogMessage(GetGlobalContext(), message.c_str(), pluginName, file, line, OrthancPluginLogCategory_Generic, level); +#else + switch (level) + { + case OrthancPluginLogLevel_Error: + OrthancPluginLogError(GetGlobalContext(), message.c_str()); + break; + + case OrthancPluginLogLevel_Warning: + OrthancPluginLogWarning(GetGlobalContext(), message.c_str()); + break; + + case OrthancPluginLogLevel_Info: + OrthancPluginLogInfo(GetGlobalContext(), message.c_str()); + break; + + default: + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } +#endif + } + } +#endif + + + void LogError(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogError(GetGlobalContext(), message.c_str()); + } + } + + void LogWarning(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogWarning(GetGlobalContext(), message.c_str()); + } + } + + void LogInfo(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogInfo(GetGlobalContext(), message.c_str()); + } + } + + void MemoryBuffer::Check(OrthancPluginErrorCode code) { if (code != OrthancPluginErrorCode_Success) @@ -228,7 +305,7 @@ if (!ReadJson(target, buffer_.data, buffer_.size)) { - LogError("Cannot convert some memory buffer to JSON"); + ORTHANC_PLUGINS_LOG_ERROR("Cannot convert some memory buffer to JSON"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } } @@ -249,26 +326,53 @@ } } + // helper class to convert std::map of headers to the plugin SDK C structure + class PluginHttpHeaders + { + private: + std::vector headersKeys_; + std::vector headersValues_; + + public: + explicit PluginHttpHeaders(const std::map& httpHeaders) + { + for (std::map::const_iterator + it = httpHeaders.begin(); it != httpHeaders.end(); ++it) + { + headersKeys_.push_back(it->first.c_str()); + headersValues_.push_back(it->second.c_str()); + } + } + + const char* const* GetKeys() + { + return (headersKeys_.empty() ? NULL : &headersKeys_[0]); + } + + const char* const* GetValues() + { + return (headersValues_.empty() ? NULL : &headersValues_[0]); + } + + uint32_t GetSize() + { + return static_cast(headersKeys_.size()); + } + }; + bool MemoryBuffer::RestApiGet(const std::string& uri, const std::map& httpHeaders, bool applyPlugins) { Clear(); - std::vector headersKeys; - std::vector headersValues; - - for (std::map::const_iterator - it = httpHeaders.begin(); it != httpHeaders.end(); it++) - { - headersKeys.push_back(it->first.c_str()); - headersValues.push_back(it->second.c_str()); - } + PluginHttpHeaders headers(httpHeaders); return CheckHttp(OrthancPluginRestApiGet2( - GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(), - (headersKeys.empty() ? NULL : &headersKeys[0]), - (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins)); + GetGlobalContext(), &buffer_, uri.c_str(), + headers.GetSize(), + headers.GetKeys(), + headers.GetValues(), applyPlugins)); } bool MemoryBuffer::RestApiPost(const std::string& uri, @@ -291,6 +395,41 @@ } } +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const void* body, + size_t bodySize, + const std::map& httpHeaders, + bool applyPlugins) + { + MemoryBuffer answerHeaders; + uint16_t httpStatus; + + PluginHttpHeaders headers(httpHeaders); + + return CheckHttp(OrthancPluginCallRestApi(GetGlobalContext(), + &buffer_, + *answerHeaders, + &httpStatus, + OrthancPluginHttpMethod_Post, + uri.c_str(), + headers.GetSize(), headers.GetKeys(), headers.GetValues(), + body, bodySize, + applyPlugins)); + } + + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const Json::Value& body, + const std::map& httpHeaders, + bool applyPlugins) + { + std::string s; + WriteFastJson(s, body); + return RestApiPost(uri, s.c_str(), s.size(), httpHeaders, applyPlugins); + } +#endif bool MemoryBuffer::RestApiPut(const std::string& uri, const void* body, @@ -337,7 +476,7 @@ } else { - LogError("Cannot parse JSON: " + std::string(err)); + ORTHANC_PLUGINS_LOG_ERROR("Cannot parse JSON: " + std::string(err)); return false; } #endif @@ -498,13 +637,29 @@ { if (str_ == NULL) { - LogError("Cannot convert an empty memory buffer to JSON"); + ORTHANC_PLUGINS_LOG_ERROR("Cannot convert an empty memory buffer to JSON"); ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); } if (!ReadJson(target, str_)) { - LogError("Cannot convert some memory buffer to JSON"); + ORTHANC_PLUGINS_LOG_ERROR("Cannot convert some memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + void OrthancString::ToJsonWithoutComments(Json::Value& target) const + { + if (str_ == NULL) + { + ORTHANC_PLUGINS_LOG_ERROR("Cannot convert an empty memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + if (!ReadJsonWithoutComments(target, str_)) + { + ORTHANC_PLUGINS_LOG_ERROR("Cannot convert some memory buffer to JSON"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } } @@ -539,6 +694,13 @@ const std::string& password) { Clear(); + + if (body.size() > 0xffffffffu) + { + ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(), body.c_str(), body.size(), username.empty() ? NULL : username.c_str(), @@ -552,6 +714,13 @@ const std::string& password) { Clear(); + + if (body.size() > 0xffffffffu) + { + ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(), body.empty() ? NULL : body.c_str(), body.size(), @@ -591,34 +760,6 @@ } } - - void LogError(const std::string& message) - { - if (HasGlobalContext()) - { - OrthancPluginLogError(GetGlobalContext(), message.c_str()); - } - } - - - void LogWarning(const std::string& message) - { - if (HasGlobalContext()) - { - OrthancPluginLogWarning(GetGlobalContext(), message.c_str()); - } - } - - - void LogInfo(const std::string& message) - { - if (HasGlobalContext()) - { - OrthancPluginLogInfo(GetGlobalContext(), message.c_str()); - } - } - - void OrthancConfiguration::LoadConfiguration() { OrthancString str; @@ -626,15 +767,15 @@ if (str.GetContent() == NULL) { - LogError("Cannot access the Orthanc configuration"); + ORTHANC_PLUGINS_LOG_ERROR("Cannot access the Orthanc configuration"); ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); } - str.ToJson(configuration_); + str.ToJsonWithoutComments(configuration_); if (configuration_.type() != Json::objectValue) { - LogError("Unable to read the Orthanc configuration"); + ORTHANC_PLUGINS_LOG_ERROR("Unable to read the Orthanc configuration"); ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); } } @@ -658,6 +799,12 @@ } } + OrthancConfiguration::OrthancConfiguration(const Json::Value& configuration, const std::string& path) : + configuration_(configuration), + path_(path) + { + } + std::string OrthancConfiguration::GetPath(const std::string& key) const { @@ -696,8 +843,8 @@ { if (configuration_[key].type() != Json::objectValue) { - LogError("The configuration section \"" + target.path_ + - "\" is not an associative array as expected"); + ORTHANC_PLUGINS_LOG_ERROR("The configuration section \"" + target.path_ + + "\" is not an associative array as expected"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } @@ -719,8 +866,8 @@ if (configuration_[key].type() != Json::stringValue) { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a string as expected"); + ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) + + "\" is not a string as expected"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } @@ -751,8 +898,8 @@ return true; default: - LogError("The configuration option \"" + GetPath(key) + - "\" is not an integer as expected"); + ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) + + "\" is not an integer as expected"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } @@ -770,8 +917,8 @@ if (tmp < 0) { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a positive integer as expected"); + ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) + + "\" is not a positive integer as expected"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } @@ -795,8 +942,8 @@ if (configuration_[key].type() != Json::booleanValue) { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a Boolean as expected"); + ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) + + "\" is not a Boolean as expected"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } @@ -831,8 +978,8 @@ return true; default: - LogError("The configuration option \"" + GetPath(key) + - "\" is not an integer as expected"); + ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) + + "\" is not an integer as expected"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } @@ -891,8 +1038,8 @@ break; } - LogError("The configuration option \"" + GetPath(key) + - "\" is not a list of strings as expected"); + ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) + + "\" is not a list of strings as expected"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } @@ -1012,8 +1159,8 @@ if (configuration_[key].type() != Json::objectValue) { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a string as expected"); + ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) + + "\" is not an object as expected"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } @@ -1030,8 +1177,8 @@ } else { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a dictionary mapping strings to strings"); + ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) + + "\" is not a dictionary mapping strings to strings"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } @@ -1053,7 +1200,7 @@ { if (image_ == NULL) { - LogError("Trying to access a NULL image"); + ORTHANC_PLUGINS_LOG_ERROR("Trying to access a NULL image"); ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); } } @@ -1079,7 +1226,7 @@ if (image_ == NULL) { - LogError("Cannot create an image"); + ORTHANC_PLUGINS_LOG_ERROR("Cannot create an image"); ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); } } @@ -1096,7 +1243,7 @@ if (image_ == NULL) { - LogError("Cannot create an image accessor"); + ORTHANC_PLUGINS_LOG_ERROR("Cannot create an image accessor"); ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); } } @@ -1110,7 +1257,7 @@ if (image_ == NULL) { - LogError("Cannot uncompress a PNG image"); + ORTHANC_PLUGINS_LOG_ERROR("Cannot uncompress a PNG image"); ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); } } @@ -1123,7 +1270,7 @@ image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg); if (image_ == NULL) { - LogError("Cannot uncompress a JPEG image"); + ORTHANC_PLUGINS_LOG_ERROR("Cannot uncompress a JPEG image"); ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); } } @@ -1137,7 +1284,7 @@ image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame); if (image_ == NULL) { - LogError("Cannot uncompress a DICOM image"); + ORTHANC_PLUGINS_LOG_ERROR("Cannot uncompress a DICOM image"); ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); } } @@ -1359,6 +1506,27 @@ } + bool RestApiGet(Json::Value& result, + const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiGet(uri, httpHeaders, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + answer.ToJson(result); + } + return true; + } + } + bool RestApiGet(Json::Value& result, const std::string& uri, @@ -1426,6 +1594,30 @@ } } +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + bool RestApiPost(Json::Value& result, + const std::string& uri, + const Json::Value& body, + const std::map& httpHeaders, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiPost(uri, body, httpHeaders, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + answer.ToJson(result); + } + return true; + } + } +#endif + bool RestApiPost(Json::Value& result, const std::string& uri, @@ -1506,44 +1698,39 @@ unsigned int minor, unsigned int revision) { - LogError("Your version of the Orthanc core (" + - std::string(GetGlobalContext()->orthancVersion) + - ") is too old to run this plugin (version " + - boost::lexical_cast(major) + "." + - boost::lexical_cast(minor) + "." + - boost::lexical_cast(revision) + - " is required)"); + ORTHANC_PLUGINS_LOG_ERROR("Your version of the Orthanc core (" + + std::string(GetGlobalContext()->orthancVersion) + + ") is too old to run this plugin (version " + + boost::lexical_cast(major) + "." + + boost::lexical_cast(minor) + "." + + boost::lexical_cast(revision) + + " is required)"); } - - bool CheckMinimalOrthancVersion(unsigned int major, - unsigned int minor, - unsigned int revision) + bool CheckMinimalVersion(const char* version, + unsigned int major, + unsigned int minor, + unsigned int revision) { - if (!HasGlobalContext()) - { - LogError("Bad Orthanc context in the plugin"); - return false; - } - - if (!strcmp(GetGlobalContext()->orthancVersion, "mainline")) + if (!strcmp(version, "mainline")) { // Assume compatibility with the mainline return true; } - // Parse the version of the Orthanc core - int aa, bb, cc; - if ( #ifdef _MSC_VER - sscanf_s +#define ORTHANC_SCANF sscanf_s #else - sscanf +#define ORTHANC_SCANF sscanf #endif - (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 || - aa < 0 || - bb < 0 || - cc < 0) + + // Parse the version + int aa, bb, cc = 0; + if ((ORTHANC_SCANF(version, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 && + ORTHANC_SCANF(version, "%4d.%4d", &aa, &bb) != 2) || + aa < 0 || + bb < 0 || + cc < 0) { return false; } @@ -1564,7 +1751,6 @@ return false; } - // Check the minor version number assert(a == major); @@ -1592,6 +1778,21 @@ } + bool CheckMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision) + { + if (!HasGlobalContext()) + { + ORTHANC_PLUGINS_LOG_ERROR("Bad Orthanc context in the plugin"); + return false; + } + + return CheckMinimalVersion(GetGlobalContext()->orthancVersion, + major, minor, revision); + } + + #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) const char* AutodetectMimeType(const std::string& path) { @@ -1620,7 +1821,7 @@ } else { - LogError("Inexistent peer: " + name); + ORTHANC_PLUGINS_LOG_ERROR("Inexistent peer: " + name); ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource); } } @@ -1761,7 +1962,8 @@ bool OrthancPeers::DoGet(MemoryBuffer& target, size_t index, - const std::string& uri) const + const std::string& uri, + const std::map& headers) const { if (index >= index_.size()) { @@ -1770,10 +1972,12 @@ OrthancPlugins::MemoryBuffer answer; uint16_t status; + PluginHttpHeaders pluginHeaders(headers); + OrthancPluginErrorCode code = OrthancPluginCallPeerApi (GetGlobalContext(), *answer, NULL, &status, peers_, static_cast(index), OrthancPluginHttpMethod_Get, uri.c_str(), - 0, NULL, NULL, NULL, 0, timeout_); + pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_); if (code == OrthancPluginErrorCode_Success) { @@ -1789,21 +1993,23 @@ bool OrthancPeers::DoGet(MemoryBuffer& target, const std::string& name, - const std::string& uri) const + const std::string& uri, + const std::map& headers) const { size_t index; return (LookupName(index, name) && - DoGet(target, index, uri)); + DoGet(target, index, uri, headers)); } bool OrthancPeers::DoGet(Json::Value& target, size_t index, - const std::string& uri) const + const std::string& uri, + const std::map& headers) const { MemoryBuffer buffer; - if (DoGet(buffer, index, uri)) + if (DoGet(buffer, index, uri, headers)) { buffer.ToJson(target); return true; @@ -1817,11 +2023,12 @@ bool OrthancPeers::DoGet(Json::Value& target, const std::string& name, - const std::string& uri) const + const std::string& uri, + const std::map& headers) const { MemoryBuffer buffer; - if (DoGet(buffer, name, uri)) + if (DoGet(buffer, name, uri, headers)) { buffer.ToJson(target); return true; @@ -1836,22 +2043,24 @@ bool OrthancPeers::DoPost(MemoryBuffer& target, const std::string& name, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map& headers) const { size_t index; return (LookupName(index, name) && - DoPost(target, index, uri, body)); + DoPost(target, index, uri, body, headers)); } bool OrthancPeers::DoPost(Json::Value& target, size_t index, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map& headers) const { MemoryBuffer buffer; - if (DoPost(buffer, index, uri, body)) + if (DoPost(buffer, index, uri, body, headers)) { buffer.ToJson(target); return true; @@ -1866,11 +2075,12 @@ bool OrthancPeers::DoPost(Json::Value& target, const std::string& name, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map& headers) const { MemoryBuffer buffer; - if (DoPost(buffer, name, uri, body)) + if (DoPost(buffer, name, uri, body, headers)) { buffer.ToJson(target); return true; @@ -1885,19 +2095,28 @@ bool OrthancPeers::DoPost(MemoryBuffer& target, size_t index, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map& headers) const { if (index >= index_.size()) { ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); } + if (body.size() > 0xffffffffu) + { + ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + OrthancPlugins::MemoryBuffer answer; uint16_t status; + PluginHttpHeaders pluginHeaders(headers); + OrthancPluginErrorCode code = OrthancPluginCallPeerApi (GetGlobalContext(), *answer, NULL, &status, peers_, static_cast(index), OrthancPluginHttpMethod_Post, uri.c_str(), - 0, NULL, NULL, 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) { @@ -1913,19 +2132,28 @@ bool OrthancPeers::DoPut(size_t index, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map& headers) const { if (index >= index_.size()) { ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); } + if (body.size() > 0xffffffffu) + { + ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + OrthancPlugins::MemoryBuffer answer; uint16_t status; + PluginHttpHeaders pluginHeaders(headers); + OrthancPluginErrorCode code = OrthancPluginCallPeerApi (GetGlobalContext(), *answer, NULL, &status, peers_, static_cast(index), OrthancPluginHttpMethod_Put, uri.c_str(), - 0, NULL, NULL, 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) { @@ -1940,16 +2168,18 @@ bool OrthancPeers::DoPut(const std::string& name, const std::string& uri, - const std::string& body) const + const std::string& body, + const std::map& headers) const { size_t index; return (LookupName(index, name) && - DoPut(index, uri, body)); + DoPut(index, uri, body, headers)); } bool OrthancPeers::DoDelete(size_t index, - const std::string& uri) const + const std::string& uri, + const std::map& headers) const { if (index >= index_.size()) { @@ -1958,10 +2188,12 @@ OrthancPlugins::MemoryBuffer answer; uint16_t status; + PluginHttpHeaders pluginHeaders(headers); + OrthancPluginErrorCode code = OrthancPluginCallPeerApi (GetGlobalContext(), *answer, NULL, &status, peers_, static_cast(index), OrthancPluginHttpMethod_Delete, uri.c_str(), - 0, NULL, NULL, NULL, 0, timeout_); + pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_); if (code == OrthancPluginErrorCode_Success) { @@ -1975,11 +2207,12 @@ bool OrthancPeers::DoDelete(const std::string& name, - const std::string& uri) const + const std::string& uri, + const std::map& headers) const { size_t index; return (LookupName(index, name) && - DoDelete(index, uri)); + DoDelete(index, uri, headers)); } #endif @@ -2016,6 +2249,36 @@ } +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + static OrthancPluginErrorCode CopyStringToMemoryBuffer(OrthancPluginMemoryBuffer* target, + const std::string& source) + { + if (OrthancPluginCreateMemoryBuffer(globalContext_, target, source.size()) != OrthancPluginErrorCode_Success) + { + return OrthancPluginErrorCode_NotEnoughMemory; + } + else + { + if (!source.empty()) + { + memcpy(target->data, source.c_str(), source.size()); + } + + return OrthancPluginErrorCode_Success; + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + OrthancPluginErrorCode OrthancJob::CallbackGetContent(OrthancPluginMemoryBuffer* target, + void* job) + { + assert(job != NULL); + OrthancJob& that = *reinterpret_cast(job); + return CopyStringToMemoryBuffer(target, that.content_); + } +#else const char* OrthancJob::CallbackGetContent(void* job) { assert(job != NULL); @@ -2029,8 +2292,33 @@ return 0; } } - - +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + int32_t OrthancJob::CallbackGetSerialized(OrthancPluginMemoryBuffer* target, + void* job) + { + assert(job != NULL); + OrthancJob& that = *reinterpret_cast(job); + + if (that.hasSerialized_) + { + if (CopyStringToMemoryBuffer(target, that.serialized_) == OrthancPluginErrorCode_Success) + { + return 1; + } + else + { + return -1; + } + } + else + { + return 0; + } + } +#else const char* OrthancJob::CallbackGetSerialized(void* job) { assert(job != NULL); @@ -2053,6 +2341,7 @@ return 0; } } +#endif OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job) @@ -2184,10 +2473,15 @@ ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); } - OrthancPluginJob* orthanc = OrthancPluginCreateJob( - GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(), - CallbackGetProgress, CallbackGetContent, CallbackGetSerialized, - CallbackStep, CallbackStop, CallbackReset); + OrthancPluginJob* orthanc = +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + OrthancPluginCreateJob2 +#else + OrthancPluginCreateJob +#endif + (GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(), + CallbackGetProgress, CallbackGetContent, CallbackGetSerialized, + CallbackStep, CallbackStop, CallbackReset); if (orthanc == NULL) { @@ -2214,7 +2508,7 @@ if (id == NULL) { - LogError("Plugin cannot submit job"); + ORTHANC_PLUGINS_LOG_ERROR("Plugin cannot submit job"); OrthancPluginFreeJob(GetGlobalContext(), orthanc); ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); } @@ -2283,7 +2577,7 @@ throw Orthanc::OrthancException(static_cast(status["ErrorCode"].asInt()), status["ErrorDescription"].asString()); #else - LogError("Exception while executing the job: " + status["ErrorDescription"].asString()); + ORTHANC_PLUGINS_LOG_ERROR("Exception while executing the job: " + status["ErrorDescription"].asString()); ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); #endif } @@ -2308,7 +2602,7 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Expected a JSON object in the body"); #else - LogError("Expected a JSON object in the body"); + ORTHANC_PLUGINS_LOG_ERROR("Expected a JSON object in the body"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); #endif } @@ -2324,7 +2618,7 @@ "Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean"); #else - LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean"); + ORTHANC_PLUGINS_LOG_ERROR("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); #endif } @@ -2343,7 +2637,7 @@ "Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean"); #else - LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean"); + ORTHANC_PLUGINS_LOG_ERROR("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); #endif } @@ -2357,14 +2651,14 @@ if (body.isMember(KEY_PRIORITY)) { - if (body[KEY_PRIORITY].type() != Json::booleanValue) + if (body[KEY_PRIORITY].type() != Json::intValue) { #if HAS_ORTHANC_EXCEPTION == 1 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer"); #else - LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer"); + ORTHANC_PLUGINS_LOG_ERROR("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); #endif } @@ -2483,7 +2777,7 @@ } catch (...) { - return OrthancPluginErrorCode_InternalError; + return OrthancPluginErrorCode_Plugin; } } } @@ -2569,8 +2863,8 @@ void HttpClient::ClearCredentials() { - username_.empty(); - password_.empty(); + username_.clear(); + password_.clear(); } @@ -2883,6 +3177,12 @@ MemoryBuffer answerBodyBuffer, answerHeadersBuffer; + if (body.size() > 0xffffffffu) + { + ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + OrthancPluginErrorCode error = OrthancPluginHttpClient( GetGlobalContext(), *answerBodyBuffer, @@ -3024,7 +3324,7 @@ if (!ReadJson(answerBody, body)) { - LogError("Cannot convert HTTP answer body to JSON"); + ORTHANC_PLUGINS_LOG_ERROR("Cannot convert HTTP answer body to JSON"); ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); } } @@ -3499,4 +3799,319 @@ } } #endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 1) + DicomInstance* DicomInstance::Load(const std::string& instanceId, + OrthancPluginLoadDicomInstanceMode mode) + { + OrthancPluginDicomInstance* instance = OrthancPluginLoadDicomInstance( + GetGlobalContext(), instanceId.c_str(), mode); + + if (instance == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + boost::movelib::unique_ptr result(new DicomInstance(instance)); + result->toFree_ = true; + return result.release(); + } + } +#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 + { + if (static_cast(static_cast(size)) != size) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + + *isReadOnly = (that.StoreFile(WebDavConvertPath(pathSize, pathItems), data, + static_cast(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 + + void GetHttpHeaders(std::map& result, const OrthancPluginHttpRequest* request) + { + result.clear(); + + for (uint32_t i = 0; i < request->headersCount; ++i) + { + result[request->headersKeys[i]] = request->headersValues[i]; + } + } + +#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4) + static void SetPluginProperty(const std::string& pluginIdentifier, + _OrthancPluginProperty property, + const std::string& value) + { + _OrthancPluginSetPluginProperty params; + params.plugin = pluginIdentifier.c_str(); + params.property = property; + params.value = value.c_str(); + + GetGlobalContext()->InvokeService(GetGlobalContext(), _OrthancPluginService_SetPluginProperty, ¶ms); + } +#endif + + void SetRootUri(const std::string& pluginIdentifier, + const std::string& uri) + { +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4) + OrthancPluginSetRootUri2(GetGlobalContext(), pluginIdentifier.c_str(), uri.c_str()); +#else + SetPluginProperty(pluginIdentifier, _OrthancPluginProperty_RootUri, uri); +#endif + } + + void SetDescription(const std::string& pluginIdentifier, + const std::string& description) + { +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4) + OrthancPluginSetDescription2(GetGlobalContext(), pluginIdentifier.c_str(), description.c_str()); +#else + SetPluginProperty(pluginIdentifier, _OrthancPluginProperty_Description, description); +#endif + } + + void ExtendOrthancExplorer(const std::string& pluginIdentifier, + const std::string& javascript) + { +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4) + OrthancPluginExtendOrthancExplorer2(GetGlobalContext(), pluginIdentifier.c_str(), javascript.c_str()); +#else + SetPluginProperty(pluginIdentifier, _OrthancPluginProperty_OrthancExplorer, javascript); +#endif + } } diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h --- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 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 @@ -114,6 +116,56 @@ # define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 0 #endif +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) +# define HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API 1 +#else +# define HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API 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 + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4) +# define HAS_ORTHANC_PLUGIN_LOG_MESSAGE 1 +#else +# define HAS_ORTHANC_PLUGIN_LOG_MESSAGE 0 +#endif + + +// Macro to tag a function as having been deprecated +#if (__cplusplus >= 201402L) // C++14 +# define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(f) [[deprecated]] f +#elif defined(__GNUC__) || defined(__clang__) +# define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(f) f __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(f) __declspec(deprecated) f +#else +# define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED +#endif + + +#if !defined(__ORTHANC_FILE__) +# if defined(_MSC_VER) +# pragma message("Warning: Macro __ORTHANC_FILE__ is not defined, this will leak the full path of the source files in the binaries") +# else +# warning Warning: Macro __ORTHANC_FILE__ is not defined, this will leak the full path of the source files in the binaries +# endif +# define __ORTHANC_FILE__ __FILE__ +#endif + + +#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1 +# define ORTHANC_PLUGINS_LOG_ERROR(msg) ::OrthancPlugins::LogMessage(OrthancPluginLogLevel_Error, __ORTHANC_FILE__, __LINE__, msg) +# define ORTHANC_PLUGINS_LOG_WARNING(msg) ::OrthancPlugins::LogMessage(OrthancPluginLogLevel_Warning, __ORTHANC_FILE__, __LINE__, msg) +# define ORTHANC_PLUGINS_LOG_INFO(msg) ::OrthancPlugins::LogMessage(OrthancPluginLogLevel_Info, __ORTHANC_FILE__, __LINE__, msg) +#else +# define ORTHANC_PLUGINS_LOG_ERROR(msg) ::OrthancPlugins::LogError(msg) +# define ORTHANC_PLUGINS_LOG_WARNING(msg) ::OrthancPlugins::LogWarning(msg) +# define ORTHANC_PLUGINS_LOG_INFO(msg) ::OrthancPlugins::LogInfo(msg) +#endif namespace OrthancPlugins @@ -124,6 +176,11 @@ void SetGlobalContext(OrthancPluginContext* context); + void SetGlobalContext(OrthancPluginContext* context, + const char* pluginName); + + void ResetGlobalContext(); + bool HasGlobalContext(); OrthancPluginContext* GetGlobalContext(); @@ -217,6 +274,19 @@ const Json::Value& body, bool applyPlugins); +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + bool RestApiPost(const std::string& uri, + const Json::Value& body, + const std::map& httpHeaders, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const void* body, + size_t bodySize, + const std::map& httpHeaders, + bool applyPlugins); +#endif + bool RestApiPut(const std::string& uri, const Json::Value& body, bool applyPlugins); @@ -296,10 +366,17 @@ return str_; } + bool IsNullOrEmpty() const + { + return str_ == NULL || str_[0] == 0; + } + void ToString(std::string& target) const; void ToJson(Json::Value& target) const; - }; + + void ToJsonWithoutComments(Json::Value& target) const; +}; class OrthancConfiguration : public boost::noncopyable @@ -313,10 +390,12 @@ void LoadConfiguration(); public: - OrthancConfiguration(); + OrthancConfiguration(); // loads the full Orthanc configuration explicit OrthancConfiguration(bool load); + explicit OrthancConfiguration(const Json::Value& configuration, const std::string& path); // e.g. to load a section from a default json content + const Json::Value& GetJson() const { return configuration_; @@ -500,6 +579,11 @@ const std::string& uri, bool applyPlugins); + bool RestApiGet(Json::Value& result, + const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins); + bool RestApiGetString(std::string& result, const std::string& uri, bool applyPlugins); @@ -521,6 +605,14 @@ size_t bodySize, bool applyPlugins); +#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1 + bool RestApiPost(Json::Value& result, + const std::string& uri, + const Json::Value& body, + const std::map& httpHeaders, + bool applyPlugins); +#endif + bool RestApiPost(Json::Value& result, const std::string& uri, const Json::Value& body, @@ -587,11 +679,33 @@ const char* AutodetectMimeType(const std::string& path); #endif +#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1 + void LogMessage(OrthancPluginLogLevel level, + const char* file, + uint32_t line, + const std::string& message); +#endif + +#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1 + // Use macro ORTHANC_PLUGINS_LOG_ERROR() instead + ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(void LogError(const std::string& message)); +#else void LogError(const std::string& message); +#endif +#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1 + // Use macro ORTHANC_PLUGINS_LOG_WARNING() instead + ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(void LogWarning(const std::string& message)); +#else void LogWarning(const std::string& message); +#endif +#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1 + // Use macro ORTHANC_PLUGINS_LOG_INFO() instead + ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(void LogInfo(const std::string& message)); +#else void LogInfo(const std::string& message); +#endif void ReportMinimalOrthancVersion(unsigned int major, unsigned int minor, @@ -601,6 +715,10 @@ unsigned int minor, unsigned int revision); + bool CheckMinimalVersion(const char* version, + unsigned int major, + unsigned int minor, + unsigned int revision); namespace Internals { @@ -710,53 +828,65 @@ bool DoGet(MemoryBuffer& target, size_t index, - const std::string& uri) const; + const std::string& uri, + const std::map& headers) const; bool DoGet(MemoryBuffer& target, const std::string& name, - const std::string& uri) const; + const std::string& uri, + const std::map& headers) const; bool DoGet(Json::Value& target, size_t index, - const std::string& uri) const; + const std::string& uri, + const std::map& headers) const; bool DoGet(Json::Value& target, const std::string& name, - const std::string& uri) const; + const std::string& uri, + const std::map& headers) const; bool DoPost(MemoryBuffer& target, size_t index, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map& headers) const; bool DoPost(MemoryBuffer& target, const std::string& name, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map& headers) const; bool DoPost(Json::Value& target, size_t index, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map& headers) const; bool DoPost(Json::Value& target, const std::string& name, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map& headers) const; bool DoPut(size_t index, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map& headers) const; bool DoPut(const std::string& name, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map& headers) const; bool DoDelete(size_t index, - const std::string& uri) const; + const std::string& uri, + const std::map& headers) const; bool DoDelete(const std::string& name, - const std::string& uri) const; + const std::string& uri, + const std::map& headers) const; }; #endif @@ -776,9 +906,19 @@ static float CallbackGetProgress(void* job); +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + static OrthancPluginErrorCode CallbackGetContent(OrthancPluginMemoryBuffer* target, + void* job); +#else static const char* CallbackGetContent(void* job); +#endif +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3) + static int32_t CallbackGetSerialized(OrthancPluginMemoryBuffer* target, + void* job); +#else static const char* CallbackGetSerialized(void* job); +#endif static OrthancPluginJobStepStatus CallbackStep(void* job); @@ -1190,6 +1330,11 @@ ~DicomInstance(); + const OrthancPluginDicomInstance* GetObject() const + { + return instance_; + } + std::string GetRemoteAet() const; const void* GetBuffer() const @@ -1244,5 +1389,123 @@ size_t size, const std::string& transferSyntax); #endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 1) + static DicomInstance* Load(const std::string& instanceId, + OrthancPluginLoadDicomInstanceMode mode); +#endif }; + +// helper method to convert Http headers from the plugin SDK to a std::map +void GetHttpHeaders(std::map& result, const OrthancPluginHttpRequest* request); + +#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 + + void SetRootUri(const std::string& pluginIdentifier, + const std::string& uri); + + void SetDescription(const std::string& pluginIdentifier, + const std::string& description); + + void ExtendOrthancExplorer(const std::string& pluginIdentifier, + const std::string& javascript); } diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Plugins/OrthancPluginException.h --- a/Resources/Orthanc/Plugins/OrthancPluginException.h Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginException.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 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 diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Plugins/OrthancPluginsExports.cmake --- a/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 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 diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Sdk-1.12.0/orthanc/OrthancCDatabasePlugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Orthanc/Sdk-1.12.0/orthanc/OrthancCDatabasePlugin.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,1370 @@ +/** + * @ingroup CInterface + **/ + +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2021-2023 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 . + **/ + + + +#pragma once + +#include "OrthancCPlugin.h" + + +/** @{ */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + + /** + * Opaque structure that represents the context of a custom database engine. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginDatabaseContext_t OrthancPluginDatabaseContext; + + + /** + * Opaque structure that represents a transaction of a custom database engine. + * New in Orthanc 1.9.2. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginDatabaseTransaction_t OrthancPluginDatabaseTransaction; + + +/*InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChange( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginChange* change) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Change; + params.valueUint32 = 0; + params.valueGeneric = change; + + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChangesDone( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Change; + params.valueUint32 = 1; + params.valueGeneric = NULL; + + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt32( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int32_t value) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Int32; + params.valueInt32 = value; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt64( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int64_t value) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Int64; + params.valueInt64 = value; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginExportedResource* exported) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_ExportedResource; + params.valueUint32 = 0; + params.valueGeneric = exported; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResourcesDone( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_ExportedResource; + params.valueUint32 = 1; + params.valueGeneric = NULL; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerDicomTag( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginDicomTag* tag) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DicomTag; + params.valueGeneric = tag; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerAttachment( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginAttachment* attachment) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Attachment; + params.valueGeneric = attachment; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int64_t id, + OrthancPluginResourceType resourceType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Resource; + params.valueInt64 = id; + params.valueInt32 = (int32_t) resourceType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerMatchingResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginMatchingResource* match) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_MatchingResource; + params.valueGeneric = match; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerMetadata( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int64_t resourceId, + int32_t type, + const char* value) + { + OrthancPluginResourcesContentMetadata metadata; + _OrthancPluginDatabaseAnswer params; + metadata.resource = resourceId; + metadata.metadata = type; + metadata.value = value; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Metadata; + params.valueGeneric = &metadata; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedAttachment( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginAttachment* attachment) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DeletedAttachment; + params.valueGeneric = attachment; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const char* publicId, + OrthancPluginResourceType resourceType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DeletedResource; + params.valueString = publicId; + params.valueInt32 = (int32_t) resourceType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalRemainingAncestor( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const char* ancestorId, + OrthancPluginResourceType ancestorType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_RemainingAncestor; + params.valueString = ancestorId; + params.valueInt32 = (int32_t) ancestorType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + + + + + typedef struct + { + OrthancPluginErrorCode (*addAttachment) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginAttachment* attachment); + + OrthancPluginErrorCode (*attachChild) ( + /* inputs */ + void* payload, + int64_t parent, + int64_t child); + + OrthancPluginErrorCode (*clearChanges) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*clearExportedResources) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*createResource) ( + /* outputs */ + int64_t* id, + /* inputs */ + void* payload, + const char* publicId, + OrthancPluginResourceType resourceType); + + OrthancPluginErrorCode (*deleteAttachment) ( + /* inputs */ + void* payload, + int64_t id, + int32_t contentType); + + OrthancPluginErrorCode (*deleteMetadata) ( + /* inputs */ + void* payload, + int64_t id, + int32_t metadataType); + + OrthancPluginErrorCode (*deleteResource) ( + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getAllPublicIds) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType); + + /* Output: Use OrthancPluginDatabaseAnswerChange() and + * OrthancPluginDatabaseAnswerChangesDone() */ + OrthancPluginErrorCode (*getChanges) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t since, + uint32_t maxResult); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*getChildrenInternalId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getChildrenPublicId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerExportedResource() and + * OrthancPluginDatabaseAnswerExportedResourcesDone() */ + OrthancPluginErrorCode (*getExportedResources) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t since, + uint32_t maxResult); + + /* Output: Use OrthancPluginDatabaseAnswerChange() */ + OrthancPluginErrorCode (*getLastChange) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerExportedResource() */ + OrthancPluginErrorCode (*getLastExportedResource) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerDicomTag() */ + OrthancPluginErrorCode (*getMainDicomTags) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getPublicId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*getResourceCount) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType); + + OrthancPluginErrorCode (*getResourceType) ( + /* outputs */ + OrthancPluginResourceType* resourceType, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*getTotalCompressedSize) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*getTotalUncompressedSize) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*isExistingResource) ( + /* outputs */ + int32_t* existing, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*isProtectedPatient) ( + /* outputs */ + int32_t* isProtected, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerInt32() */ + OrthancPluginErrorCode (*listAvailableMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerInt32() */ + OrthancPluginErrorCode (*listAvailableAttachments) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*logChange) ( + /* inputs */ + void* payload, + const OrthancPluginChange* change); + + OrthancPluginErrorCode (*logExportedResource) ( + /* inputs */ + void* payload, + const OrthancPluginExportedResource* exported); + + /* Output: Use OrthancPluginDatabaseAnswerAttachment() */ + OrthancPluginErrorCode (*lookupAttachment) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id, + int32_t contentType); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*lookupGlobalProperty) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int32_t property); + + /* Use "OrthancPluginDatabaseExtensions::lookupIdentifier3" + instead of this function as of Orthanc 0.9.5 (db v6), can be set to NULL. + Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifier) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const OrthancPluginDicomTag* tag); + + /* Unused starting with Orthanc 0.9.5 (db v6), can be set to NULL. + Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifier2) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const char* value); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*lookupMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id, + int32_t metadata); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupParent) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerResource() */ + OrthancPluginErrorCode (*lookupResource) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const char* publicId); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*selectPatientToRecycle) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*selectPatientToRecycle2) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t patientIdToAvoid); + + OrthancPluginErrorCode (*setGlobalProperty) ( + /* inputs */ + void* payload, + int32_t property, + const char* value); + + OrthancPluginErrorCode (*setMainDicomTag) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginDicomTag* tag); + + OrthancPluginErrorCode (*setIdentifierTag) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginDicomTag* tag); + + OrthancPluginErrorCode (*setMetadata) ( + /* inputs */ + void* payload, + int64_t id, + int32_t metadata, + const char* value); + + OrthancPluginErrorCode (*setProtectedPatient) ( + /* inputs */ + void* payload, + int64_t id, + int32_t isProtected); + + OrthancPluginErrorCode (*startTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*rollbackTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*commitTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*open) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*close) ( + /* inputs */ + void* payload); + + } OrthancPluginDatabaseBackend; + + + typedef struct + { + /** + * Base extensions since Orthanc 1.0.0 + **/ + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getAllPublicIdsWithLimit) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + uint64_t since, + uint64_t limit); + + OrthancPluginErrorCode (*getDatabaseVersion) ( + /* outputs */ + uint32_t* version, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*upgradeDatabase) ( + /* inputs */ + void* payload, + uint32_t targetVersion, + OrthancPluginStorageArea* storageArea); + + OrthancPluginErrorCode (*clearMainDicomTags) ( + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*getAllInternalIds) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifier3) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + const OrthancPluginDicomTag* tag, + OrthancPluginIdentifierConstraint constraint); + + + /** + * Extensions since Orthanc 1.4.0 + **/ + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifierRange) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + uint16_t group, + uint16_t element, + const char* start, + const char* end); + + + /** + * Extensions since Orthanc 1.5.2 + **/ + + /* Ouput: Use OrthancPluginDatabaseAnswerMatchingResource */ + OrthancPluginErrorCode (*lookupResources) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + uint32_t constraintsCount, + const OrthancPluginDatabaseConstraint* constraints, + OrthancPluginResourceType queryLevel, + uint32_t limit, + uint8_t requestSomeInstance); + + OrthancPluginErrorCode (*createInstance) ( + /* output */ + OrthancPluginCreateInstanceResult* output, + /* inputs */ + void* payload, + const char* hashPatient, + const char* hashStudy, + const char* hashSeries, + const char* hashInstance); + + OrthancPluginErrorCode (*setResourcesContent) ( + /* inputs */ + void* payload, + uint32_t countIdentifierTags, + const OrthancPluginResourcesContentTags* identifierTags, + uint32_t countMainDicomTags, + const OrthancPluginResourcesContentTags* mainDicomTags, + uint32_t countMetadata, + const OrthancPluginResourcesContentMetadata* metadata); + + /* Ouput: Use OrthancPluginDatabaseAnswerString */ + OrthancPluginErrorCode (*getChildrenMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t resourceId, + int32_t metadata); + + OrthancPluginErrorCode (*getLastChangeIndex) ( + /* outputs */ + int64_t* target, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*tagMostRecentPatient) ( + /* inputs */ + void* payload, + int64_t patientId); + + + /** + * Extensions since Orthanc 1.5.4 + **/ + + /* Ouput: Use OrthancPluginDatabaseAnswerMetadata */ + OrthancPluginErrorCode (*getAllMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t resourceId); + + /* Ouput: Use OrthancPluginDatabaseAnswerString to send + the public ID of the parent (if the resource is not a patient) */ + OrthancPluginErrorCode (*lookupResourceAndParent) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + uint8_t* isExisting, + int64_t* id, + OrthancPluginResourceType* type, + + /* inputs */ + void* payload, + const char* publicId); + + } OrthancPluginDatabaseExtensions; + +/*InvokeService(context, _OrthancPluginService_RegisterDatabaseBackend, ¶ms) || + result == NULL) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + typedef struct + { + OrthancPluginDatabaseContext** result; + const OrthancPluginDatabaseBackend* backend; + void* payload; + const OrthancPluginDatabaseExtensions* extensions; + uint32_t extensionsSize; + } _OrthancPluginRegisterDatabaseBackendV2; + + + /** + * Register a custom database back-end. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param backend The callbacks of the custom database engine. + * @param payload Pointer containing private information for the database engine. + * @param extensions Extensions to the base database SDK that was shipped until Orthanc 0.9.3. + * @return The context of the database engine (it must not be manually freed). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDatabaseContext* OrthancPluginRegisterDatabaseBackendV2( + OrthancPluginContext* context, + const OrthancPluginDatabaseBackend* backend, + const OrthancPluginDatabaseExtensions* extensions, + void* payload) + { + OrthancPluginDatabaseContext* result = NULL; + _OrthancPluginRegisterDatabaseBackendV2 params; + + if (sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType)) + { + return NULL; + } + + memset(¶ms, 0, sizeof(params)); + params.backend = backend; + params.result = &result; + params.payload = payload; + params.extensions = extensions; + params.extensionsSize = sizeof(OrthancPluginDatabaseExtensions); + + if (context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV2, ¶ms) || + result == NULL) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + /** + * New interface starting with Orthanc 1.9.2 + **/ + +/*InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV3, ¶ms); + } + +#ifdef __cplusplus +} +#endif + + +/** @} */ + diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Sdk-1.12.0/orthanc/OrthancCPlugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Orthanc/Sdk-1.12.0/orthanc/OrthancCPlugin.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,9234 @@ +/** + * \mainpage + * + * This C/C++ SDK allows external developers to create plugins that + * can be loaded into Orthanc to extend its functionality. Each + * Orthanc plugin must expose 4 public functions with the following + * signatures: + * + * -# int32_t OrthancPluginInitialize(const OrthancPluginContext* context): + * This function is invoked by Orthanc when it loads the plugin on startup. + * The plugin must: + * - Check its compatibility with the Orthanc version using + * ::OrthancPluginCheckVersion(). + * - Store the context pointer so that it can use the plugin + * services of Orthanc. + * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). + * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). + * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). + * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea2(). + * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV4(). + * - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback(). + * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). + * - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback(). + * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). + * - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter2(). + * - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer(). + * - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback(). + * - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback(). + * - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback(). + * - 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. + * -# const char* OrthancPluginGetName(): + * The plugin must return a short string to identify itself. + * -# const char* OrthancPluginGetVersion(): + * The plugin must return a string containing its version number. + * + * The name and the version of a plugin is only used to prevent it + * from being loaded twice. Note that, in C++, it is mandatory to + * declare these functions within an extern "C" section. + * + * To ensure multi-threading safety, the various REST callbacks are + * guaranteed to be executed in mutual exclusion since Orthanc + * 0.8.5. If this feature is undesired (notably when developing + * high-performance plugins handling simultaneous requests), use + * ::OrthancPluginRegisterRestCallbackNoLock(). + **/ + + + +/** + * @defgroup Images Images and compression + * @brief Functions to deal with images and compressed buffers. + * + * @defgroup REST REST + * @brief Functions to answer REST requests in a callback. + * + * @defgroup Callbacks Callbacks + * @brief Functions to register and manage callbacks by the plugins. + * + * @defgroup DicomCallbacks DicomCallbacks + * @brief Functions to register and manage DICOM callbacks (worklists, C-FIND, C-MOVE, storage commitment). + * + * @defgroup Orthanc Orthanc + * @brief Functions to access the content of the Orthanc server. + * + * @defgroup DicomInstance DicomInstance + * @brief Functions to access DICOM images that are managed by the Orthanc core. + **/ + + + +/** + * @defgroup Toolbox Toolbox + * @brief Generic functions to help with the creation of plugins. + **/ + + + +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2021-2023 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 . + **/ + + + +#pragma once + + +#include +#include + +#ifdef WIN32 +# define ORTHANC_PLUGINS_API __declspec(dllexport) +#elif __GNUC__ >= 4 +# define ORTHANC_PLUGINS_API __attribute__ ((visibility ("default"))) +#else +# define ORTHANC_PLUGINS_API +#endif + +#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 +#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 12 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 + + +#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) +#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \ + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) +#endif + + + +/******************************************************************** + ** Check that function inlining is properly supported. The use of + ** inlining is required, to avoid the duplication of object code + ** between two compilation modules that would use the Orthanc Plugin + ** API. + ********************************************************************/ + +/* If the auto-detection of the "inline" keyword below does not work + automatically and that your compiler is known to properly support + inlining, uncomment the following #define and adapt the definition + of "static inline". */ + +/* #define ORTHANC_PLUGIN_INLINE static inline */ + +#ifndef ORTHANC_PLUGIN_INLINE +# if __STDC_VERSION__ >= 199901L +/* This is C99 or above: http://predef.sourceforge.net/prestd.html */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__cplusplus) +/* This is C++ */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__GNUC__) +/* This is GCC running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# elif defined(_MSC_VER) +/* This is Visual Studio running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# else +# error Your compiler is not known to support the "inline" keyword +# endif +#endif + + + +/******************************************************************** + ** Inclusion of standard libraries. + ********************************************************************/ + +/** + * For Microsoft Visual Studio, a compatibility "stdint.h" can be + * downloaded at the following URL: + * https://hg.orthanc-server.com/orthanc/raw-file/tip/Resources/ThirdParty/VisualStudio/stdint.h + **/ +#include + +#include + + + +/******************************************************************** + ** Definition of the Orthanc Plugin API. + ********************************************************************/ + +/** @{ */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * The various error codes that can be returned by the Orthanc core. + **/ + typedef enum + { + OrthancPluginErrorCode_InternalError = -1 /*!< Internal error */, + OrthancPluginErrorCode_Success = 0 /*!< Success */, + OrthancPluginErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, + OrthancPluginErrorCode_NotImplemented = 2 /*!< Not implemented yet */, + OrthancPluginErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, + OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< The server hosting Orthanc is running out of memory */, + OrthancPluginErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, + OrthancPluginErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, + OrthancPluginErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, + OrthancPluginErrorCode_BadRequest = 8 /*!< Bad request */, + OrthancPluginErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */, + OrthancPluginErrorCode_SystemCommand = 10 /*!< Error while calling a system command */, + OrthancPluginErrorCode_Database = 11 /*!< Error with the database engine */, + OrthancPluginErrorCode_UriSyntax = 12 /*!< Badly formatted URI */, + OrthancPluginErrorCode_InexistentFile = 13 /*!< Inexistent file */, + OrthancPluginErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */, + OrthancPluginErrorCode_BadFileFormat = 15 /*!< Bad file format */, + OrthancPluginErrorCode_Timeout = 16 /*!< Timeout */, + OrthancPluginErrorCode_UnknownResource = 17 /*!< Unknown resource */, + OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */, + OrthancPluginErrorCode_FullStorage = 19 /*!< The file storage is full */, + OrthancPluginErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */, + OrthancPluginErrorCode_InexistentTag = 21 /*!< Inexistent tag */, + OrthancPluginErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */, + OrthancPluginErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */, + OrthancPluginErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */, + OrthancPluginErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */, + OrthancPluginErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */, + OrthancPluginErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */, + OrthancPluginErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */, + OrthancPluginErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */, + OrthancPluginErrorCode_BadFont = 30 /*!< Badly formatted font file */, + OrthancPluginErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, + OrthancPluginErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, + OrthancPluginErrorCode_EmptyRequest = 33 /*!< The request is empty */, + OrthancPluginErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, + OrthancPluginErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */, + OrthancPluginErrorCode_DatabaseUnavailable = 36 /*!< The database is currently not available (probably a transient situation) */, + OrthancPluginErrorCode_CanceledJob = 37 /*!< This job was canceled */, + OrthancPluginErrorCode_BadGeometry = 38 /*!< Geometry error encountered in Stone */, + OrthancPluginErrorCode_SslInitialization = 39 /*!< Cannot initialize SSL encryption, check out your certificates */, + OrthancPluginErrorCode_DiscontinuedAbi = 40 /*!< Calling a function that has been removed from the Orthanc Framework */, + OrthancPluginErrorCode_BadRange = 41 /*!< Incorrect range request */, + OrthancPluginErrorCode_DatabaseCannotSerialize = 42 /*!< Database could not serialize access due to concurrent update, the transaction should be retried */, + OrthancPluginErrorCode_Revision = 43 /*!< A bad revision number was provided, which might indicate conflict between multiple writers */, + OrthancPluginErrorCode_MainDicomTagsMultiplyDefined = 44 /*!< A main DICOM Tag has been defined multiple times for the same resource level */, + OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, + OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, + OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, + OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */, + OrthancPluginErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */, + OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */, + OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */, + OrthancPluginErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */, + OrthancPluginErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, + OrthancPluginErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, + OrthancPluginErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, + OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, + OrthancPluginErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, + OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, + OrthancPluginErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, + OrthancPluginErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */, + OrthancPluginErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, + OrthancPluginErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, + OrthancPluginErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, + OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is privileged or already in use */, + OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is privileged or already in use */, + OrthancPluginErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, + OrthancPluginErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, + OrthancPluginErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, + OrthancPluginErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */, + OrthancPluginErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */, + OrthancPluginErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */, + OrthancPluginErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */, + OrthancPluginErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */, + OrthancPluginErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */, + OrthancPluginErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */, + OrthancPluginErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */, + OrthancPluginErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */, + OrthancPluginErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */, + OrthancPluginErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */, + OrthancPluginErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */, + OrthancPluginErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */, + OrthancPluginErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */, + OrthancPluginErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */, + OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */, + OrthancPluginErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */, + OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */, + OrthancPluginErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */, + OrthancPluginErrorCode_UnknownModality = 2027 /*!< Unknown modality */, + OrthancPluginErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */, + OrthancPluginErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */, + OrthancPluginErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */, + OrthancPluginErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */, + OrthancPluginErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */, + OrthancPluginErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */, + OrthancPluginErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */, + OrthancPluginErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */, + OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, + OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, + OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, + OrthancPluginErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, + OrthancPluginErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, + OrthancPluginErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */, + OrthancPluginErrorCode_AlreadyExistingTag = 2042 /*!< Cannot override the value of a tag that already exists */, + OrthancPluginErrorCode_NoStorageCommitmentHandler = 2043 /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */, + OrthancPluginErrorCode_NoCGetHandler = 2044 /*!< No request handler factory for DICOM C-GET SCP */, + OrthancPluginErrorCode_UnsupportedMediaType = 3000 /*!< Unsupported media type */, + + _OrthancPluginErrorCode_INTERNAL = 0x7fffffff + } OrthancPluginErrorCode; + + + /** + * Forward declaration of one of the mandatory functions for Orthanc + * plugins. + **/ + ORTHANC_PLUGINS_API const char* OrthancPluginGetName(); + + + /** + * The various HTTP methods for a REST call. + **/ + typedef enum + { + OrthancPluginHttpMethod_Get = 1, /*!< GET request */ + OrthancPluginHttpMethod_Post = 2, /*!< POST request */ + OrthancPluginHttpMethod_Put = 3, /*!< PUT request */ + OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */ + + _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff + } OrthancPluginHttpMethod; + + + /** + * @brief The parameters of a REST request. + * @ingroup Callbacks + **/ + typedef struct + { + /** + * @brief The HTTP method. + **/ + OrthancPluginHttpMethod method; + + /** + * @brief The number of groups of the regular expression. + **/ + uint32_t groupsCount; + + /** + * @brief The matched values for the groups of the regular expression. + **/ + const char* const* groups; + + /** + * @brief For a GET request, the number of GET parameters. + **/ + uint32_t getCount; + + /** + * @brief For a GET request, the keys of the GET parameters. + **/ + const char* const* getKeys; + + /** + * @brief For a GET request, the values of the GET parameters. + **/ + const char* const* getValues; + + /** + * @brief For a PUT or POST request, the content of the body. + **/ + const void* body; + + /** + * @brief For a PUT or POST request, the number of bytes of the body. + **/ + uint32_t bodySize; + + + /* -------------------------------------------------- + New in version 0.8.1 + -------------------------------------------------- */ + + /** + * @brief The number of HTTP headers. + **/ + uint32_t headersCount; + + /** + * @brief The keys of the HTTP headers (always converted to low-case). + **/ + const char* const* headersKeys; + + /** + * @brief The values of the HTTP headers. + **/ + const char* const* headersValues; + + } OrthancPluginHttpRequest; + + + typedef enum + { + /* Generic services */ + _OrthancPluginService_LogInfo = 1, + _OrthancPluginService_LogWarning = 2, + _OrthancPluginService_LogError = 3, + _OrthancPluginService_GetOrthancPath = 4, + _OrthancPluginService_GetOrthancDirectory = 5, + _OrthancPluginService_GetConfigurationPath = 6, + _OrthancPluginService_SetPluginProperty = 7, + _OrthancPluginService_GetGlobalProperty = 8, + _OrthancPluginService_SetGlobalProperty = 9, + _OrthancPluginService_GetCommandLineArgumentsCount = 10, + _OrthancPluginService_GetCommandLineArgument = 11, + _OrthancPluginService_GetExpectedDatabaseVersion = 12, + _OrthancPluginService_GetConfiguration = 13, + _OrthancPluginService_BufferCompression = 14, + _OrthancPluginService_ReadFile = 15, + _OrthancPluginService_WriteFile = 16, + _OrthancPluginService_GetErrorDescription = 17, + _OrthancPluginService_CallHttpClient = 18, + _OrthancPluginService_RegisterErrorCode = 19, + _OrthancPluginService_RegisterDictionaryTag = 20, + _OrthancPluginService_DicomBufferToJson = 21, + _OrthancPluginService_DicomInstanceToJson = 22, + _OrthancPluginService_CreateDicom = 23, + _OrthancPluginService_ComputeMd5 = 24, + _OrthancPluginService_ComputeSha1 = 25, + _OrthancPluginService_LookupDictionary = 26, + _OrthancPluginService_CallHttpClient2 = 27, + _OrthancPluginService_GenerateUuid = 28, + _OrthancPluginService_RegisterPrivateDictionaryTag = 29, + _OrthancPluginService_AutodetectMimeType = 30, + _OrthancPluginService_SetMetricsValue = 31, + _OrthancPluginService_EncodeDicomWebJson = 32, + _OrthancPluginService_EncodeDicomWebXml = 33, + _OrthancPluginService_ChunkedHttpClient = 34, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_GetTagName = 35, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_EncodeDicomWebJson2 = 36, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_EncodeDicomWebXml2 = 37, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_CreateMemoryBuffer = 38, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GenerateRestApiAuthorizationToken = 39, /* New in Orthanc 1.8.1 */ + _OrthancPluginService_CreateMemoryBuffer64 = 40, /* New in Orthanc 1.9.0 */ + _OrthancPluginService_CreateDicom2 = 41, /* New in Orthanc 1.9.0 */ + _OrthancPluginService_GetDatabaseServerIdentifier = 42, /* New in Orthanc 1.11.1 */ + + /* Registration of callbacks */ + _OrthancPluginService_RegisterRestCallback = 1000, + _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, + _OrthancPluginService_RegisterStorageArea = 1002, + _OrthancPluginService_RegisterOnChangeCallback = 1003, + _OrthancPluginService_RegisterRestCallbackNoLock = 1004, + _OrthancPluginService_RegisterWorklistCallback = 1005, + _OrthancPluginService_RegisterDecodeImageCallback = 1006, + _OrthancPluginService_RegisterIncomingHttpRequestFilter = 1007, + _OrthancPluginService_RegisterFindCallback = 1008, + _OrthancPluginService_RegisterMoveCallback = 1009, + _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010, + _OrthancPluginService_RegisterRefreshMetricsCallback = 1011, + _OrthancPluginService_RegisterChunkedRestCallback = 1012, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_RegisterStorageCommitmentScpCallback = 1013, + _OrthancPluginService_RegisterIncomingDicomInstanceFilter = 1014, + _OrthancPluginService_RegisterTranscoderCallback = 1015, /* New in Orthanc 1.7.0 */ + _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, + _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */ + _OrthancPluginService_Redirect = 2002, + _OrthancPluginService_SendHttpStatusCode = 2003, + _OrthancPluginService_SendUnauthorized = 2004, + _OrthancPluginService_SendMethodNotAllowed = 2005, + _OrthancPluginService_SetCookie = 2006, + _OrthancPluginService_SetHttpHeader = 2007, + _OrthancPluginService_StartMultipartAnswer = 2008, + _OrthancPluginService_SendMultipartItem = 2009, + _OrthancPluginService_SendHttpStatus = 2010, + _OrthancPluginService_CompressAndAnswerImage = 2011, + _OrthancPluginService_SendMultipartItem2 = 2012, + _OrthancPluginService_SetHttpErrorDetails = 2013, + + /* Access to the Orthanc database and API */ + _OrthancPluginService_GetDicomForInstance = 3000, + _OrthancPluginService_RestApiGet = 3001, + _OrthancPluginService_RestApiPost = 3002, + _OrthancPluginService_RestApiDelete = 3003, + _OrthancPluginService_RestApiPut = 3004, + _OrthancPluginService_LookupPatient = 3005, + _OrthancPluginService_LookupStudy = 3006, + _OrthancPluginService_LookupSeries = 3007, + _OrthancPluginService_LookupInstance = 3008, + _OrthancPluginService_LookupStudyWithAccessionNumber = 3009, + _OrthancPluginService_RestApiGetAfterPlugins = 3010, + _OrthancPluginService_RestApiPostAfterPlugins = 3011, + _OrthancPluginService_RestApiDeleteAfterPlugins = 3012, + _OrthancPluginService_RestApiPutAfterPlugins = 3013, + _OrthancPluginService_ReconstructMainDicomTags = 3014, + _OrthancPluginService_RestApiGet2 = 3015, + _OrthancPluginService_CallRestApi = 3016, /* New in Orthanc 1.9.2 */ + + /* Access to DICOM instances */ + _OrthancPluginService_GetInstanceRemoteAet = 4000, + _OrthancPluginService_GetInstanceSize = 4001, + _OrthancPluginService_GetInstanceData = 4002, + _OrthancPluginService_GetInstanceJson = 4003, + _OrthancPluginService_GetInstanceSimplifiedJson = 4004, + _OrthancPluginService_HasInstanceMetadata = 4005, + _OrthancPluginService_GetInstanceMetadata = 4006, + _OrthancPluginService_GetInstanceOrigin = 4007, + _OrthancPluginService_GetInstanceTransferSyntaxUid = 4008, + _OrthancPluginService_HasInstancePixelData = 4009, + _OrthancPluginService_CreateDicomInstance = 4010, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_FreeDicomInstance = 4011, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceFramesCount = 4012, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceRawFrame = 4013, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceDecodedFrame = 4014, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_TranscodeDicomInstance = 4015, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_SerializeDicomInstance = 4016, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceAdvancedJson = 4017, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceDicomWebJson = 4018, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceDicomWebXml = 4019, /* New in Orthanc 1.7.0 */ + + /* Services for plugins implementing a database back-end */ + _OrthancPluginService_RegisterDatabaseBackend = 5000, /* New in Orthanc 0.8.6 */ + _OrthancPluginService_DatabaseAnswer = 5001, + _OrthancPluginService_RegisterDatabaseBackendV2 = 5002, /* New in Orthanc 0.9.4 */ + _OrthancPluginService_StorageAreaCreate = 5003, + _OrthancPluginService_StorageAreaRead = 5004, + _OrthancPluginService_StorageAreaRemove = 5005, + _OrthancPluginService_RegisterDatabaseBackendV3 = 5006, /* New in Orthanc 1.9.2 */ + _OrthancPluginService_RegisterDatabaseBackendV4 = 5007, /* New in Orthanc 1.12.0 */ + + /* Primitives for handling images */ + _OrthancPluginService_GetImagePixelFormat = 6000, + _OrthancPluginService_GetImageWidth = 6001, + _OrthancPluginService_GetImageHeight = 6002, + _OrthancPluginService_GetImagePitch = 6003, + _OrthancPluginService_GetImageBuffer = 6004, + _OrthancPluginService_UncompressImage = 6005, + _OrthancPluginService_FreeImage = 6006, + _OrthancPluginService_CompressImage = 6007, + _OrthancPluginService_ConvertPixelFormat = 6008, + _OrthancPluginService_GetFontsCount = 6009, + _OrthancPluginService_GetFontInfo = 6010, + _OrthancPluginService_DrawText = 6011, + _OrthancPluginService_CreateImage = 6012, + _OrthancPluginService_CreateImageAccessor = 6013, + _OrthancPluginService_DecodeDicomImage = 6014, + + /* Primitives for handling C-Find, C-Move and worklists */ + _OrthancPluginService_WorklistAddAnswer = 7000, + _OrthancPluginService_WorklistMarkIncomplete = 7001, + _OrthancPluginService_WorklistIsMatch = 7002, + _OrthancPluginService_WorklistGetDicomQuery = 7003, + _OrthancPluginService_FindAddAnswer = 7004, + _OrthancPluginService_FindMarkIncomplete = 7005, + _OrthancPluginService_GetFindQuerySize = 7006, + _OrthancPluginService_GetFindQueryTag = 7007, + _OrthancPluginService_GetFindQueryTagName = 7008, + _OrthancPluginService_GetFindQueryValue = 7009, + _OrthancPluginService_CreateFindMatcher = 7010, + _OrthancPluginService_FreeFindMatcher = 7011, + _OrthancPluginService_FindMatcherIsMatch = 7012, + + /* Primitives for accessing Orthanc Peers (new in 1.4.2) */ + _OrthancPluginService_GetPeers = 8000, + _OrthancPluginService_FreePeers = 8001, + _OrthancPluginService_GetPeersCount = 8003, + _OrthancPluginService_GetPeerName = 8004, + _OrthancPluginService_GetPeerUrl = 8005, + _OrthancPluginService_CallPeerApi = 8006, + _OrthancPluginService_GetPeerUserProperty = 8007, + + /* Primitives for handling jobs (new in 1.4.2) */ + _OrthancPluginService_CreateJob = 9000, /* Deprecated since SDK 1.11.3 */ + _OrthancPluginService_FreeJob = 9001, + _OrthancPluginService_SubmitJob = 9002, + _OrthancPluginService_RegisterJobsUnserializer = 9003, + _OrthancPluginService_CreateJob2 = 9004, /* New in SDK 1.11.3 */ + + _OrthancPluginService_INTERNAL = 0x7fffffff + } _OrthancPluginService; + + + typedef enum + { + _OrthancPluginProperty_Description = 1, + _OrthancPluginProperty_RootUri = 2, + _OrthancPluginProperty_OrthancExplorer = 3, + + _OrthancPluginProperty_INTERNAL = 0x7fffffff + } _OrthancPluginProperty; + + + + /** + * The memory layout of the pixels of an image. + * @ingroup Images + **/ + typedef enum + { + /** + * @brief Graylevel 8bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * one byte. + **/ + OrthancPluginPixelFormat_Grayscale8 = 1, + + /** + * @brief Graylevel, unsigned 16bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * two bytes. + **/ + OrthancPluginPixelFormat_Grayscale16 = 2, + + /** + * @brief Graylevel, signed 16bpp image. + * + * The image is graylevel. Each pixel is signed and stored in two + * bytes. + **/ + OrthancPluginPixelFormat_SignedGrayscale16 = 3, + + /** + * @brief Color image in RGB24 format. + * + * This format describes a color image. The pixels are stored in 3 + * consecutive bytes. The memory layout is RGB. + **/ + OrthancPluginPixelFormat_RGB24 = 4, + + /** + * @brief Color image in RGBA32 format. + * + * This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is RGBA. + **/ + OrthancPluginPixelFormat_RGBA32 = 5, + + OrthancPluginPixelFormat_Unknown = 6, /*!< Unknown pixel format */ + + /** + * @brief Color image in RGB48 format. + * + * This format describes a color image. The pixels are stored in 6 + * consecutive bytes. The memory layout is RRGGBB. + **/ + OrthancPluginPixelFormat_RGB48 = 7, + + /** + * @brief Graylevel, unsigned 32bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * four bytes. + **/ + OrthancPluginPixelFormat_Grayscale32 = 8, + + /** + * @brief Graylevel, floating-point 32bpp image. + * + * The image is graylevel. Each pixel is floating-point and stored + * in four bytes. + **/ + OrthancPluginPixelFormat_Float32 = 9, + + /** + * @brief Color image in BGRA32 format. + * + * This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is BGRA. + **/ + OrthancPluginPixelFormat_BGRA32 = 10, + + /** + * @brief Graylevel, unsigned 64bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * eight bytes. + **/ + OrthancPluginPixelFormat_Grayscale64 = 11, + + _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff + } OrthancPluginPixelFormat; + + + + /** + * The content types that are supported by Orthanc plugins. + **/ + typedef enum + { + OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */ + OrthancPluginContentType_Dicom = 1, /*!< DICOM */ + OrthancPluginContentType_DicomAsJson = 2, /*!< JSON summary of a DICOM file */ + OrthancPluginContentType_DicomUntilPixelData = 3, /*!< DICOM Header till pixel data */ + + _OrthancPluginContentType_INTERNAL = 0x7fffffff + } OrthancPluginContentType; + + + + /** + * The supported types of DICOM resources. + **/ + typedef enum + { + OrthancPluginResourceType_Patient = 0, /*!< Patient */ + OrthancPluginResourceType_Study = 1, /*!< Study */ + OrthancPluginResourceType_Series = 2, /*!< Series */ + OrthancPluginResourceType_Instance = 3, /*!< Instance */ + OrthancPluginResourceType_None = 4, /*!< Unavailable resource type */ + + _OrthancPluginResourceType_INTERNAL = 0x7fffffff + } OrthancPluginResourceType; + + + + /** + * The supported types of changes that can be signaled to the change callback. + * @ingroup Callbacks + **/ + typedef enum + { + OrthancPluginChangeType_CompletedSeries = 0, /*!< Series is now complete */ + OrthancPluginChangeType_Deleted = 1, /*!< Deleted resource */ + OrthancPluginChangeType_NewChildInstance = 2, /*!< A new instance was added to this resource */ + OrthancPluginChangeType_NewInstance = 3, /*!< New instance received */ + OrthancPluginChangeType_NewPatient = 4, /*!< New patient created */ + OrthancPluginChangeType_NewSeries = 5, /*!< New series created */ + OrthancPluginChangeType_NewStudy = 6, /*!< New study created */ + OrthancPluginChangeType_StablePatient = 7, /*!< Timeout: No new instance in this patient */ + OrthancPluginChangeType_StableSeries = 8, /*!< Timeout: No new instance in this series */ + OrthancPluginChangeType_StableStudy = 9, /*!< Timeout: No new instance in this study */ + OrthancPluginChangeType_OrthancStarted = 10, /*!< Orthanc has started */ + OrthancPluginChangeType_OrthancStopped = 11, /*!< Orthanc is stopping */ + OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */ + OrthancPluginChangeType_UpdatedMetadata = 13, /*!< Some user-defined metadata has changed for this resource */ + OrthancPluginChangeType_UpdatedPeers = 14, /*!< The list of Orthanc peers has changed */ + OrthancPluginChangeType_UpdatedModalities = 15, /*!< The list of DICOM modalities has changed */ + OrthancPluginChangeType_JobSubmitted = 16, /*!< New Job submitted */ + OrthancPluginChangeType_JobSuccess = 17, /*!< A Job has completed successfully */ + OrthancPluginChangeType_JobFailure = 18, /*!< A Job has failed */ + + _OrthancPluginChangeType_INTERNAL = 0x7fffffff + } OrthancPluginChangeType; + + + /** + * The compression algorithms that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginCompressionType_Zlib = 0, /*!< Standard zlib compression */ + OrthancPluginCompressionType_ZlibWithSize = 1, /*!< zlib, prefixed with uncompressed size (uint64_t) */ + OrthancPluginCompressionType_Gzip = 2, /*!< Standard gzip compression */ + OrthancPluginCompressionType_GzipWithSize = 3, /*!< gzip, prefixed with uncompressed size (uint64_t) */ + + _OrthancPluginCompressionType_INTERNAL = 0x7fffffff + } OrthancPluginCompressionType; + + + /** + * The image formats that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */ + OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */ + OrthancPluginImageFormat_Dicom = 2, /*!< Image compressed using DICOM */ + + _OrthancPluginImageFormat_INTERNAL = 0x7fffffff + } OrthancPluginImageFormat; + + + /** + * The value representations present in the DICOM standard (version 2013). + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginValueRepresentation_AE = 1, /*!< Application Entity */ + OrthancPluginValueRepresentation_AS = 2, /*!< Age String */ + OrthancPluginValueRepresentation_AT = 3, /*!< Attribute Tag */ + OrthancPluginValueRepresentation_CS = 4, /*!< Code String */ + OrthancPluginValueRepresentation_DA = 5, /*!< Date */ + OrthancPluginValueRepresentation_DS = 6, /*!< Decimal String */ + OrthancPluginValueRepresentation_DT = 7, /*!< Date Time */ + OrthancPluginValueRepresentation_FD = 8, /*!< Floating Point Double */ + OrthancPluginValueRepresentation_FL = 9, /*!< Floating Point Single */ + OrthancPluginValueRepresentation_IS = 10, /*!< Integer String */ + OrthancPluginValueRepresentation_LO = 11, /*!< Long String */ + OrthancPluginValueRepresentation_LT = 12, /*!< Long Text */ + OrthancPluginValueRepresentation_OB = 13, /*!< Other Byte String */ + OrthancPluginValueRepresentation_OF = 14, /*!< Other Float String */ + OrthancPluginValueRepresentation_OW = 15, /*!< Other Word String */ + OrthancPluginValueRepresentation_PN = 16, /*!< Person Name */ + OrthancPluginValueRepresentation_SH = 17, /*!< Short String */ + OrthancPluginValueRepresentation_SL = 18, /*!< Signed Long */ + OrthancPluginValueRepresentation_SQ = 19, /*!< Sequence of Items */ + OrthancPluginValueRepresentation_SS = 20, /*!< Signed Short */ + OrthancPluginValueRepresentation_ST = 21, /*!< Short Text */ + OrthancPluginValueRepresentation_TM = 22, /*!< Time */ + OrthancPluginValueRepresentation_UI = 23, /*!< Unique Identifier (UID) */ + OrthancPluginValueRepresentation_UL = 24, /*!< Unsigned Long */ + OrthancPluginValueRepresentation_UN = 25, /*!< Unknown */ + OrthancPluginValueRepresentation_US = 26, /*!< Unsigned Short */ + OrthancPluginValueRepresentation_UT = 27, /*!< Unlimited Text */ + + _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff + } OrthancPluginValueRepresentation; + + + /** + * The possible output formats for a DICOM-to-JSON conversion. + * @ingroup Toolbox + * @see OrthancPluginDicomToJson() + **/ + typedef enum + { + OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */ + OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */ + OrthancPluginDicomToJsonFormat_Human = 3, /*!< Human-readable JSON */ + + _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff + } OrthancPluginDicomToJsonFormat; + + + /** + * Flags to customize a DICOM-to-JSON conversion. By default, binary + * tags are formatted using Data URI scheme. + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginDicomToJsonFlags_None = 0, + OrthancPluginDicomToJsonFlags_IncludeBinary = (1 << 0), /*!< Include the binary tags */ + OrthancPluginDicomToJsonFlags_IncludePrivateTags = (1 << 1), /*!< Include the private tags */ + OrthancPluginDicomToJsonFlags_IncludeUnknownTags = (1 << 2), /*!< Include the tags unknown by the dictionary */ + OrthancPluginDicomToJsonFlags_IncludePixelData = (1 << 3), /*!< Include the pixel data */ + OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii = (1 << 4), /*!< Output binary tags as-is, dropping non-ASCII */ + OrthancPluginDicomToJsonFlags_ConvertBinaryToNull = (1 << 5), /*!< Signal binary tags as null values */ + OrthancPluginDicomToJsonFlags_StopAfterPixelData = (1 << 6), /*!< Stop processing after pixel data (new in 1.9.1) */ + OrthancPluginDicomToJsonFlags_SkipGroupLengths = (1 << 7), /*!< Skip tags whose element is zero (new in 1.9.1) */ + + _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff + } OrthancPluginDicomToJsonFlags; + + + /** + * Flags to the creation of a DICOM file. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom() + **/ + typedef enum + { + OrthancPluginCreateDicomFlags_None = 0, + OrthancPluginCreateDicomFlags_DecodeDataUriScheme = (1 << 0), /*!< Decode fields encoded using data URI scheme */ + OrthancPluginCreateDicomFlags_GenerateIdentifiers = (1 << 1), /*!< Automatically generate DICOM identifiers */ + + _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff + } OrthancPluginCreateDicomFlags; + + + /** + * The constraints on the DICOM identifiers that must be supported + * by the database plugins. + * @deprecated Plugins using OrthancPluginConstraintType will be faster + **/ + typedef enum + { + OrthancPluginIdentifierConstraint_Equal = 1, /*!< Equal */ + OrthancPluginIdentifierConstraint_SmallerOrEqual = 2, /*!< Less or equal */ + OrthancPluginIdentifierConstraint_GreaterOrEqual = 3, /*!< More or equal */ + OrthancPluginIdentifierConstraint_Wildcard = 4, /*!< Case-sensitive wildcard matching (with * and ?) */ + + _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff + } OrthancPluginIdentifierConstraint; + + + /** + * The constraints on the tags (main DICOM tags and identifier tags) + * that must be supported by the database plugins. + **/ + typedef enum + { + OrthancPluginConstraintType_Equal = 1, /*!< Equal */ + OrthancPluginConstraintType_SmallerOrEqual = 2, /*!< Less or equal */ + OrthancPluginConstraintType_GreaterOrEqual = 3, /*!< More or equal */ + OrthancPluginConstraintType_Wildcard = 4, /*!< Wildcard matching */ + OrthancPluginConstraintType_List = 5, /*!< List of values */ + + _OrthancPluginConstraintType_INTERNAL = 0x7fffffff + } OrthancPluginConstraintType; + + + /** + * The origin of a DICOM instance that has been received by Orthanc. + **/ + typedef enum + { + OrthancPluginInstanceOrigin_Unknown = 1, /*!< Unknown origin */ + OrthancPluginInstanceOrigin_DicomProtocol = 2, /*!< Instance received through DICOM protocol */ + OrthancPluginInstanceOrigin_RestApi = 3, /*!< Instance received through REST API of Orthanc */ + OrthancPluginInstanceOrigin_Plugin = 4, /*!< Instance added to Orthanc by a plugin */ + OrthancPluginInstanceOrigin_Lua = 5, /*!< Instance added to Orthanc by a Lua script */ + OrthancPluginInstanceOrigin_WebDav = 6, /*!< Instance received through WebDAV (new in 1.8.0) */ + + _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff + } OrthancPluginInstanceOrigin; + + + /** + * The possible status for one single step of a job. + **/ + typedef enum + { + OrthancPluginJobStepStatus_Success = 1, /*!< The job has successfully executed all its steps */ + OrthancPluginJobStepStatus_Failure = 2, /*!< The job has failed while executing this step */ + OrthancPluginJobStepStatus_Continue = 3 /*!< The job has still data to process after this step */ + } OrthancPluginJobStepStatus; + + + /** + * Explains why the job should stop and release the resources it has + * allocated. This is especially important to disambiguate between + * the "paused" condition and the "final" conditions (success, + * failure, or canceled). + **/ + typedef enum + { + OrthancPluginJobStopReason_Success = 1, /*!< The job has succeeded */ + OrthancPluginJobStopReason_Paused = 2, /*!< The job was paused, and will be resumed later */ + OrthancPluginJobStopReason_Failure = 3, /*!< The job has failed, and might be resubmitted later */ + OrthancPluginJobStopReason_Canceled = 4 /*!< The job was canceled, and might be resubmitted later */ + } OrthancPluginJobStopReason; + + + /** + * The available types of metrics. + **/ + typedef enum + { + OrthancPluginMetricsType_Default = 0, /*!< Default metrics */ + + /** + * This metrics represents a time duration. Orthanc will keep the + * maximum value of the metrics over a sliding window of ten + * seconds, which is useful if the metrics is sampled frequently. + **/ + OrthancPluginMetricsType_Timer = 1 + } OrthancPluginMetricsType; + + + /** + * The available modes to export a binary DICOM tag into a DICOMweb + * JSON or XML document. + **/ + typedef enum + { + OrthancPluginDicomWebBinaryMode_Ignore = 0, /*!< Don't include binary tags */ + OrthancPluginDicomWebBinaryMode_InlineBinary = 1, /*!< Inline encoding using Base64 */ + OrthancPluginDicomWebBinaryMode_BulkDataUri = 2 /*!< Use a bulk data URI field */ + } OrthancPluginDicomWebBinaryMode; + + + /** + * The available values for the Failure Reason (0008,1197) during + * storage commitment. + * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 + **/ + typedef enum + { + OrthancPluginStorageCommitmentFailureReason_Success = 0, + /*!< Success: The DICOM instance is properly stored in the SCP */ + + OrthancPluginStorageCommitmentFailureReason_ProcessingFailure = 1, + /*!< 0110H: A general failure in processing the operation was encountered */ + + OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance = 2, + /*!< 0112H: One or more of the elements in the Referenced SOP + Instance Sequence was not available */ + + OrthancPluginStorageCommitmentFailureReason_ResourceLimitation = 3, + /*!< 0213H: The SCP does not currently have enough resources to + store the requested SOP Instance(s) */ + + OrthancPluginStorageCommitmentFailureReason_ReferencedSOPClassNotSupported = 4, + /*!< 0122H: Storage Commitment has been requested for a SOP + Instance with a SOP Class that is not supported by the SCP */ + + OrthancPluginStorageCommitmentFailureReason_ClassInstanceConflict = 5, + /*!< 0119H: The SOP Class of an element in the Referenced SOP + Instance Sequence did not correspond to the SOP class registered + for this SOP Instance at the SCP */ + + OrthancPluginStorageCommitmentFailureReason_DuplicateTransactionUID = 6 + /*!< 0131H: The Transaction UID of the Storage Commitment Request + is already in use */ + } OrthancPluginStorageCommitmentFailureReason; + + + /** + * The action to be taken after ReceivedInstanceCallback is triggered + **/ + typedef enum + { + OrthancPluginReceivedInstanceAction_KeepAsIs = 1, /*!< Keep the instance as is */ + OrthancPluginReceivedInstanceAction_Modify = 2, /*!< Modify the instance */ + OrthancPluginReceivedInstanceAction_Discard = 3, /*!< Discard the instance */ + + _OrthancPluginReceivedInstanceAction_INTERNAL = 0x7fffffff + } OrthancPluginReceivedInstanceAction; + + + /** + * @brief A 32-bit memory buffer allocated by the core system of Orthanc. + * + * A memory buffer allocated by the core system of Orthanc. When the + * content of the buffer is not useful anymore, it must be free by a + * call to ::OrthancPluginFreeMemoryBuffer(). + **/ + typedef struct + { + /** + * @brief The content of the buffer. + **/ + void* data; + + /** + * @brief The number of bytes in the buffer. + **/ + uint32_t size; + } OrthancPluginMemoryBuffer; + + + + /** + * @brief A 64-bit memory buffer allocated by the core system of Orthanc. + * + * A memory buffer allocated by the core system of Orthanc. When the + * content of the buffer is not useful anymore, it must be free by a + * call to ::OrthancPluginFreeMemoryBuffer64(). + **/ + typedef struct + { + /** + * @brief The content of the buffer. + **/ + void* data; + + /** + * @brief The number of bytes in the buffer. + **/ + uint64_t size; + } OrthancPluginMemoryBuffer64; + + + + + /** + * @brief Opaque structure that represents the HTTP connection to the client application. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput; + + + + /** + * @brief Opaque structure that represents a DICOM instance that is managed by the Orthanc core. + * @ingroup DicomInstance + **/ + typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance; + + + + /** + * @brief Opaque structure that represents an image that is uncompressed in memory. + * @ingroup Images + **/ + typedef struct _OrthancPluginImage_t OrthancPluginImage; + + + + /** + * @brief Opaque structure that represents the storage area that is actually used by Orthanc. + * @ingroup Images + **/ + typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea; + + + + /** + * @brief Opaque structure to an object that represents a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery; + + + + /** + * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers; + + + + /** + * @brief Opaque structure to an object that represents a C-Find query. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginFindQuery_t OrthancPluginFindQuery; + + + + /** + * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindAnswers; + + + + /** + * @brief Opaque structure to an object that can be used to check whether a DICOM instance matches a C-Find query. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginFindMatcher_t OrthancPluginFindMatcher; + + + + /** + * @brief Opaque structure to the set of remote Orthanc Peers that are known to the local Orthanc server. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginPeers_t OrthancPluginPeers; + + + + /** + * @brief Opaque structure to a job to be executed by Orthanc. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginJob_t OrthancPluginJob; + + + + /** + * @brief Opaque structure that represents a node in a JSON or XML + * document used in DICOMweb. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginDicomWebNode_t OrthancPluginDicomWebNode; + + + + /** + * @brief Signature of a callback function that answers to a REST request. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) ( + OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + + + /** + * @brief Signature of a callback function that is triggered when Orthanc stores a new DICOM instance. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) ( + const OrthancPluginDicomInstance* instance, + const char* instanceId); + + + + /** + * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) ( + OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId); + + + + /** + * @brief Signature of a callback function to decode a DICOM instance as an image. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) ( + OrthancPluginImage** target, + const void* dicom, + const uint32_t size, + uint32_t frameIndex); + + + + /** + * @brief Signature of a function to free dynamic memory. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginFree) (void* buffer); + + + + /** + * @brief Signature of a function to set the content of a node + * encoding a binary DICOM tag, into a JSON or XML document + * generated for DICOMweb. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginDicomWebSetBinaryNode) ( + OrthancPluginDicomWebNode* node, + OrthancPluginDicomWebBinaryMode mode, + const char* bulkDataUri); + + + + /** + * @brief Callback for writing to the storage area. + * + * Signature of a callback function that is triggered when Orthanc writes a file to the storage area. + * + * @param uuid The UUID of the file. + * @param content The content of the file. + * @param size The size of the file. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) ( + const char* uuid, + const void* content, + int64_t size, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading from the storage area. + * + * Signature of a callback function that is triggered when Orthanc reads a file from the storage area. + * + * @param content The content of the file (output). + * @param size The size of the file (output). + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated New plugins should use OrthancPluginStorageRead2 + * + * @warning The "content" buffer *must* have been allocated using + * the "malloc()" function of your C standard library (i.e. nor + * "new[]", neither a pointer to a buffer). The "free()" function of + * your C standard library will automatically be invoked on the + * "content" pointer. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) ( + void** content, + int64_t* size, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading a whole file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc + * reads a whole file from the storage area. + * + * @param target Memory buffer where to store the content of the file. It must be allocated by the + * plugin using OrthancPluginCreateMemoryBuffer64(). The core of Orthanc will free it. + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageReadWhole) ( + OrthancPluginMemoryBuffer64* target, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading a range of a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc + * reads a portion of a file from the storage area. Orthanc + * indicates the start position and the length of the range. + * + * @param target Memory buffer where to store the content of the range. + * The memory buffer is allocated and freed by Orthanc. The length of the range + * of interest corresponds to the size of this buffer. + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @param rangeStart Start position of the requested range in the file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageReadRange) ( + OrthancPluginMemoryBuffer64* target, + const char* uuid, + OrthancPluginContentType type, + uint64_t rangeStart); + + + + /** + * @brief Callback for removing a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area. + * + * @param uuid The UUID of the file to be removed. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) ( + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback to handle the C-Find SCP requests for worklists. + * + * Signature of a callback function that is triggered when Orthanc + * receives a C-Find SCP request against modality worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) ( + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const char* issuerAet, + const char* calledAet); + + + + /** + * @brief Callback to filter incoming HTTP requests received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives an HTTP/REST request, and that answers whether + * this request should be allowed. If the callback returns "0" + * ("false"), the server answers with HTTP status code 403 + * (Forbidden). + * + * Pay attention to the fact that this function may be invoked + * concurrently by different threads of the Web server of + * Orthanc. You must implement proper locking if applicable. + * + * @param method The HTTP method used by the request. + * @param uri The URI of interest. + * @param ip The IP address of the HTTP client. + * @param headersCount The number of HTTP headers. + * @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 Callbacks + * @deprecated Please instead use OrthancPluginIncomingHttpRequestFilter2() + **/ + typedef int32_t (*OrthancPluginIncomingHttpRequestFilter) ( + OrthancPluginHttpMethod method, + const char* uri, + const char* ip, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues); + + + + /** + * @brief Callback to filter incoming HTTP requests received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives an HTTP/REST request, and that answers whether + * this request should be allowed. If the callback returns "0" + * ("false"), the server answers with HTTP status code 403 + * (Forbidden). + * + * Pay attention to the fact that this function may be invoked + * concurrently by different threads of the Web server of + * Orthanc. You must implement proper locking if applicable. + * + * @param method The HTTP method used by the request. + * @param uri The URI of interest. + * @param ip The IP address of the HTTP client. + * @param headersCount The number of HTTP headers. + * @param headersKeys The keys of the HTTP headers (always converted to low-case). + * @param headersValues The values of the HTTP headers. + * @param getArgumentsCount The number of GET arguments (only for the GET HTTP method). + * @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 Callbacks + **/ + typedef int32_t (*OrthancPluginIncomingHttpRequestFilter2) ( + OrthancPluginHttpMethod method, + const char* uri, + const char* ip, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + uint32_t getArgumentsCount, + const char* const* getArgumentsKeys, + const char* const* getArgumentsValues); + + + + /** + * @brief Callback to handle incoming C-Find SCP requests. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Find SCP request not concerning modality + * worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) ( + OrthancPluginFindAnswers* answers, + const OrthancPluginFindQuery* query, + const char* issuerAet, + const char* calledAet); + + + + /** + * @brief Callback to handle incoming C-Move SCP requests. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Move SCP request. The callback receives the + * type of the resource of interest (study, series, instance...) + * together with the DICOM tags containing its identifiers. In turn, + * the plugin must create a driver object that will be responsible + * for driving the successive move suboperations. + * + * @param resourceType The type of the resource of interest. Note + * that this might be set to ResourceType_None if the + * QueryRetrieveLevel (0008,0052) tag was not provided by the + * issuer (i.e. the originator modality). + * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL. + * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL. + * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL. + * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL. + * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL. + * @param originatorAet The Application Entity Title (AET) of the + * modality from which the request originates. + * @param sourceAet The Application Entity Title (AET) of the + * modality that should send its DICOM files to another modality. + * @param targetAet The Application Entity Title (AET) of the + * modality that should receive the DICOM files. + * @param originatorId The Message ID issued by the originator modality, + * as found in tag (0000,0110) of the DICOM query emitted by the issuer. + * + * @return The NULL value if the plugin cannot deal with this query, + * or a pointer to the driver object that is responsible for + * handling the successive move suboperations. + * + * @note If targetAet equals sourceAet, this is actually a query/retrieve operation. + * @ingroup DicomCallbacks + **/ + typedef void* (*OrthancPluginMoveCallback) ( + OrthancPluginResourceType resourceType, + const char* patientId, + const char* accessionNumber, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid, + const char* originatorAet, + const char* sourceAet, + const char* targetAet, + uint16_t originatorId); + + + /** + * @brief Callback to read the size of a C-Move driver. + * + * Signature of a callback function that returns the number of + * C-Move suboperations that are to be achieved by the given C-Move + * driver. This driver is the return value of a previous call to the + * OrthancPluginMoveCallback() callback. + * + * @param moveDriver The C-Move driver of interest. + * @return The number of suboperations. + * @ingroup DicomCallbacks + **/ + typedef uint32_t (*OrthancPluginGetMoveSize) (void* moveDriver); + + + /** + * @brief Callback to apply one C-Move suboperation. + * + * Signature of a callback function that applies the next C-Move + * suboperation that os to be achieved by the given C-Move + * driver. This driver is the return value of a previous call to the + * OrthancPluginMoveCallback() callback. + * + * @param moveDriver The C-Move driver of interest. + * @return 0 if success, or the error code if failure. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginApplyMove) (void* moveDriver); + + + /** + * @brief Callback to free one C-Move driver. + * + * Signature of a callback function that releases the resources + * allocated by the given C-Move driver. This driver is the return + * value of a previous call to the OrthancPluginMoveCallback() + * callback. + * + * @param moveDriver The C-Move driver of interest. + * @ingroup DicomCallbacks + **/ + typedef void (*OrthancPluginFreeMove) (void* moveDriver); + + + /** + * @brief Callback to finalize one custom job. + * + * Signature of a callback function that releases all the resources + * allocated by the given job. This job is the argument provided to + * OrthancPluginCreateJob(). + * + * @param job The job of interest. + * @ingroup Toolbox + **/ + typedef void (*OrthancPluginJobFinalize) (void* job); + + + /** + * @brief Callback to check the progress of one custom job. + * + * Signature of a callback function that returns the progress of the + * job. + * + * @param job The job of interest. + * @return The progress, as a floating-point number ranging from 0 to 1. + * @ingroup Toolbox + **/ + typedef float (*OrthancPluginJobGetProgress) (void* job); + + + /** + * @brief Callback to retrieve the content of one custom job. + * + * Signature of a callback function that returns human-readable + * statistics about the job. This statistics must be formatted as a + * JSON object. This information is notably displayed in the "Jobs" + * tab of "Orthanc Explorer". + * + * @param job The job of interest. + * @return The statistics, as a JSON object encoded as a string. + * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. + **/ + typedef const char* (*OrthancPluginJobGetContent) (void* job); + + + /** + * @brief Callback to retrieve the content of one custom job. + * + * Signature of a callback function that returns human-readable + * statistics about the job. This statistics must be formatted as a + * JSON object. This information is notably displayed in the "Jobs" + * tab of "Orthanc Explorer". + * + * @param target The target memory buffer where to store the JSON string. + * This buffer must be allocated using OrthancPluginCreateMemoryBuffer() + * and will be freed by the Orthanc core. + * @param job The job of interest. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobGetContent2) (OrthancPluginMemoryBuffer* target, + void* job); + + + /** + * @brief Callback to serialize one custom job. + * + * Signature of a callback function that returns a serialized + * version of the job, formatted as a JSON object. This + * serialization is stored in the Orthanc database, and is used to + * reload the job on the restart of Orthanc. The "unserialization" + * callback (with OrthancPluginJobsUnserializer signature) will + * receive this serialized object. + * + * @param job The job of interest. + * @return The serialized job, as a JSON object encoded as a string. + * @see OrthancPluginRegisterJobsUnserializer() + * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. + **/ + typedef const char* (*OrthancPluginJobGetSerialized) (void* job); + + + /** + * @brief Callback to serialize one custom job. + * + * Signature of a callback function that returns a serialized + * version of the job, formatted as a JSON object. This + * serialization is stored in the Orthanc database, and is used to + * reload the job on the restart of Orthanc. The "unserialization" + * callback (with OrthancPluginJobsUnserializer signature) will + * receive this serialized object. + * + * @param target The target memory buffer where to store the JSON string. + * This buffer must be allocated using OrthancPluginCreateMemoryBuffer() + * and will be freed by the Orthanc core. + * @param job The job of interest. + * @return 1 if the serialization has succeeded, 0 if serialization is + * not implemented for this type of job, or -1 in case of error. + **/ + typedef int32_t (*OrthancPluginJobGetSerialized2) (OrthancPluginMemoryBuffer* target, + void* job); + + + /** + * @brief Callback to execute one step of a custom job. + * + * Signature of a callback function that executes one step in the + * job. The jobs engine of Orthanc will make successive calls to + * this method, as long as it returns + * OrthancPluginJobStepStatus_Continue. + * + * @param job The job of interest. + * @return The status of execution. + * @ingroup Toolbox + **/ + typedef OrthancPluginJobStepStatus (*OrthancPluginJobStep) (void* job); + + + /** + * @brief Callback executed once one custom job leaves the "running" state. + * + * Signature of a callback function that is invoked once a job + * leaves the "running" state. This can happen if the previous call + * to OrthancPluginJobStep has failed/succeeded, if the host Orthanc + * server is being stopped, or if the user manually tags the job as + * paused/canceled. This callback allows the plugin to free + * resources allocated for running this custom job (e.g. to stop + * threads, or to remove temporary files). + * + * Note that handling pauses might involves a specific treatment + * (such a stopping threads, but keeping temporary files on the + * disk). This "paused" situation can be checked by looking at the + * "reason" parameter. + * + * @param job The job of interest. + * @param reason The reason for leaving the "running" state. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobStop) (void* job, + OrthancPluginJobStopReason reason); + + + /** + * @brief Callback executed once one stopped custom job is started again. + * + * Signature of a callback function that is invoked once a job + * leaves the "failure/canceled" state, to be started again. This + * function will typically reset the progress to zero. Note that + * before being actually executed, the job would first be tagged as + * "pending" in the Orthanc jobs engine. + * + * @param job The job of interest. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobReset) (void* job); + + + /** + * @brief Callback executed to unserialize a custom job. + * + * Signature of a callback function that unserializes a job that was + * saved in the Orthanc database. + * + * @param jobType The type of the job, as provided to OrthancPluginCreateJob(). + * @param serialized The serialization of the job, as provided by OrthancPluginJobGetSerialized. + * @return The unserialized job (as created by OrthancPluginCreateJob()), or NULL + * if this unserializer cannot handle this job type. + * @see OrthancPluginRegisterJobsUnserializer() + * @ingroup Callbacks + **/ + typedef OrthancPluginJob* (*OrthancPluginJobsUnserializer) (const char* jobType, + const char* serialized); + + + + /** + * @brief Callback executed to update the metrics of the plugin. + * + * Signature of a callback function that is called by Orthanc + * whenever a monitoring tool (such as Prometheus) asks the current + * values of the metrics. This callback gives the plugin a chance to + * update its metrics, by calling OrthancPluginSetMetricsValue(). + * This is typically useful for metrics that are expensive to + * acquire. + * + * @see OrthancPluginRegisterRefreshMetrics() + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginRefreshMetricsCallback) (); + + + + /** + * @brief Callback executed to encode a binary tag in DICOMweb. + * + * Signature of a callback function that is called by Orthanc + * whenever a DICOM tag that contains a binary value must be written + * to a JSON or XML node, while a DICOMweb document is being + * generated. The value representation (VR) of the DICOM tag can be + * OB, OD, OF, OL, OW, or UN. + * + * @see OrthancPluginEncodeDicomWebJson() and OrthancPluginEncodeDicomWebXml() + * @param node The node being generated, as provided by Orthanc. + * @param setter The setter to be used to encode the content of the node. If + * the setter is not called, the binary tag is not written to the output document. + * @param levelDepth The depth of the node in the DICOM hierarchy of sequences. + * This parameter gives the number of elements in the "levelTagGroup", + * "levelTagElement", and "levelIndex" arrays. + * @param levelTagGroup The group of the parent DICOM tags in the hierarchy. + * @param levelTagElement The element of the parent DICOM tags in the hierarchy. + * @param levelIndex The index of the node in the parent sequences of the hierarchy. + * @param tagGroup The group of the DICOM tag of interest. + * @param tagElement The element of the DICOM tag of interest. + * @param vr The value representation of the binary DICOM node. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginDicomWebBinaryCallback) ( + OrthancPluginDicomWebNode* node, + OrthancPluginDicomWebSetBinaryNode setter, + uint32_t levelDepth, + const uint16_t* levelTagGroup, + const uint16_t* levelTagElement, + const uint32_t* levelIndex, + uint16_t tagGroup, + uint16_t tagElement, + OrthancPluginValueRepresentation vr); + + + + /** + * @brief Callback executed to encode a binary tag in DICOMweb. + * + * Signature of a callback function that is called by Orthanc + * whenever a DICOM tag that contains a binary value must be written + * to a JSON or XML node, while a DICOMweb document is being + * generated. The value representation (VR) of the DICOM tag can be + * OB, OD, OF, OL, OW, or UN. + * + * @see OrthancPluginEncodeDicomWebJson() and OrthancPluginEncodeDicomWebXml() + * @param node The node being generated, as provided by Orthanc. + * @param setter The setter to be used to encode the content of the node. If + * the setter is not called, the binary tag is not written to the output document. + * @param levelDepth The depth of the node in the DICOM hierarchy of sequences. + * This parameter gives the number of elements in the "levelTagGroup", + * "levelTagElement", and "levelIndex" arrays. + * @param levelTagGroup The group of the parent DICOM tags in the hierarchy. + * @param levelTagElement The element of the parent DICOM tags in the hierarchy. + * @param levelIndex The index of the node in the parent sequences of the hierarchy. + * @param tagGroup The group of the DICOM tag of interest. + * @param tagElement The element of the DICOM tag of interest. + * @param vr The value representation of the binary DICOM node. + * @param payload The user payload. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginDicomWebBinaryCallback2) ( + OrthancPluginDicomWebNode* node, + OrthancPluginDicomWebSetBinaryNode setter, + uint32_t levelDepth, + const uint16_t* levelTagGroup, + const uint16_t* levelTagElement, + const uint32_t* levelIndex, + uint16_t tagGroup, + uint16_t tagElement, + OrthancPluginValueRepresentation vr, + void* payload); + + + + /** + * @brief Data structure that contains information about the Orthanc core. + **/ + typedef struct _OrthancPluginContext_t + { + void* pluginsManager; + const char* orthancVersion; + OrthancPluginFree Free; + OrthancPluginErrorCode (*InvokeService) (struct _OrthancPluginContext_t* context, + _OrthancPluginService service, + const void* params); + } OrthancPluginContext; + + + + /** + * @brief An entry in the dictionary of DICOM tags. + **/ + typedef struct + { + uint16_t group; /*!< The group of the tag */ + uint16_t element; /*!< The element of the tag */ + OrthancPluginValueRepresentation vr; /*!< The value representation of the tag */ + uint32_t minMultiplicity; /*!< The minimum multiplicity of the tag */ + uint32_t maxMultiplicity; /*!< The maximum multiplicity of the tag (0 means arbitrary) */ + } OrthancPluginDictionaryEntry; + + + + /** + * @brief Free a string. + * + * Free a string that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param str The string to be freed. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeString( + OrthancPluginContext* context, + char* str) + { + if (str != NULL) + { + context->Free(str); + } + } + + + /** + * @brief Check that the version of the hosting Orthanc is above a given version. + * + * This function checks whether the version of the Orthanc server + * running this plugin, is above the given version. Contrarily to + * OrthancPluginCheckVersion(), it is up to the developer of the + * plugin to make sure that all the Orthanc SDK services called by + * the plugin are actually implemented in the given version of + * Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param expectedMajor Expected major version. + * @param expectedMinor Expected minor version. + * @param expectedRevision Expected revision. + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @see OrthancPluginCheckVersion + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersionAdvanced( + OrthancPluginContext* context, + int expectedMajor, + int expectedMinor, + int expectedRevision) + { + int major, minor, revision; + + if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) || + sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) || + sizeof(int32_t) != sizeof(_OrthancPluginService) || + sizeof(int32_t) != sizeof(_OrthancPluginProperty) || + sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) || + sizeof(int32_t) != sizeof(OrthancPluginContentType) || + sizeof(int32_t) != sizeof(OrthancPluginResourceType) || + sizeof(int32_t) != sizeof(OrthancPluginChangeType) || + sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || + sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || + sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) || + sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) || + sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) || + sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) || + sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) || + sizeof(int32_t) != sizeof(OrthancPluginConstraintType) || + sizeof(int32_t) != sizeof(OrthancPluginMetricsType) || + sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) || + sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) || + sizeof(int32_t) != sizeof(OrthancPluginReceivedInstanceAction)) + { + /* Mismatch in the size of the enumerations */ + return 0; + } + + /* Assume compatibility with the mainline */ + if (!strcmp(context->orthancVersion, "mainline")) + { + return 1; + } + + /* Parse the version of the Orthanc core */ + if ( +#ifdef _MSC_VER + sscanf_s +#else + sscanf +#endif + (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3) + { + return 0; + } + + /* Check the major number of the version */ + + if (major > expectedMajor) + { + return 1; + } + + if (major < expectedMajor) + { + return 0; + } + + /* Check the minor number of the version */ + + if (minor > expectedMinor) + { + return 1; + } + + if (minor < expectedMinor) + { + return 0; + } + + /* Check the revision number of the version */ + + if (revision >= expectedRevision) + { + return 1; + } + else + { + return 0; + } + } + + + /** + * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. + * + * This function checks whether the version of the Orthanc server + * running this plugin, is above the version of the current Orthanc + * SDK header. This guarantees that the plugin is compatible with + * the hosting Orthanc (i.e. it will not call unavailable services). + * The result of this function should always be checked in the + * OrthancPluginInitialize() entry point of the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @see OrthancPluginCheckVersionAdvanced + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( + OrthancPluginContext* context) + { + return OrthancPluginCheckVersionAdvanced( + context, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + } + + + /** + * @brief Free a memory buffer. + * + * Free a memory buffer that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer to release. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* buffer) + { + context->Free(buffer->data); + } + + + /** + * @brief Free a memory buffer. + * + * Free a memory buffer that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer to release. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer64( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer64* buffer) + { + context->Free(buffer->data); + } + + + /** + * @brief Log an error. + * + * Log an error message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogError( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogError, message); + } + + + /** + * @brief Log a warning. + * + * Log a warning message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogWarning, message); + } + + + /** + * @brief Log an information. + * + * Log an information message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogInfo, message); + } + + + + typedef struct + { + const char* pathRegularExpression; + OrthancPluginRestCallback callback; + } _OrthancPluginRestCallback; + + /** + * @brief Register a REST callback. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Each REST callback is guaranteed to run in mutual exclusion. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallbackNoLock() + * + * @note + * The regular expression is case sensitive and must follow the + * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html). + * + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, ¶ms); + } + + + + /** + * @brief Register a REST callback, without locking. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Contrarily to OrthancPluginRegisterRestCallback(), the callback + * will NOT be invoked in mutual exclusion. This can be useful for + * high-performance plugins that must handle concurrent requests + * (Orthanc uses a pool of threads, one thread being assigned to + * each incoming HTTP request). Of course, if using this function, + * it is up to the plugin to implement the required locking + * mechanisms. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallback() + * + * @note + * The regular expression is case sensitive and must follow the + * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html). + * + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, ¶ms); + } + + + + typedef struct + { + OrthancPluginOnStoredInstanceCallback callback; + } _OrthancPluginOnStoredInstanceCallback; + + /** + * @brief Register a callback for received instances. + * + * This function registers a callback function that is called + * whenever a new DICOM instance is stored into the Orthanc core. + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback( + OrthancPluginContext* context, + OrthancPluginOnStoredInstanceCallback callback) + { + _OrthancPluginOnStoredInstanceCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const void* answer; + uint32_t answerSize; + const char* mimeType; + } _OrthancPluginAnswerBuffer; + + /** + * @brief Answer to a REST request. + * + * This function answers to a REST request with the content of a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the answer. + * @param answerSize Number of bytes of the answer. + * @param mimeType The MIME type of the answer. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const void* answer, + uint32_t answerSize, + const char* mimeType) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = mimeType; + context->InvokeService(context, _OrthancPluginService_AnswerBuffer, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + } _OrthancPluginCompressAndAnswerPngImage; + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressAndAnswerImage; + + + /** + * @brief Answer to a REST request with a PNG image. + * + * This function answers to a REST request with a PNG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a PNG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* No quality for PNG */ + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* instanceId; + } _OrthancPluginGetDicomForInstance; + + /** + * @brief Retrieve a DICOM instance using its Orthanc identifier. + * + * Retrieve a DICOM instance using its Orthanc identifier. The DICOM + * file is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instanceId The Orthanc identifier of the DICOM instance of interest. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetDicomForInstance( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* instanceId) + { + _OrthancPluginGetDicomForInstance params; + params.target = target; + params.instanceId = instanceId; + return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + } _OrthancPluginRestApiGet; + + /** + * @brief Make a GET call to the built-in Orthanc REST API. + * + * Make a GET call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiGetAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGet, ¶ms); + } + + + + /** + * @brief Make a GET call to the REST API, as tainted by the plugins. + * + * Make a GET call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiGet + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGetAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + const void* body; + uint32_t bodySize; + } _OrthancPluginRestApiPostPut; + + /** + * @brief Make a POST call to the built-in Orthanc REST API. + * + * Make a POST call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPostAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPost, ¶ms); + } + + + /** + * @brief Make a POST call to the REST API, as tainted by the plugins. + * + * Make a POST call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPost + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPostAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, ¶ms); + } + + + + /** + * @brief Make a DELETE call to the built-in Orthanc REST API. + * + * Make a DELETE call to the built-in Orthanc REST API. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiDeleteAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDelete( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri); + } + + + /** + * @brief Make a DELETE call to the REST API, as tainted by the plugins. + * + * Make a DELETE call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiDelete + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDeleteAfterPlugins( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri); + } + + + + /** + * @brief Make a PUT call to the built-in Orthanc REST API. + * + * Make a PUT call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPutAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPut, ¶ms); + } + + + + /** + * @brief Make a PUT call to the REST API, as tainted by the plugins. + * + * Make a PUT call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPut + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPutAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* argument; + } _OrthancPluginOutputPlusArgument; + + /** + * @brief Redirect a REST request. + * + * This function answers to a REST request by redirecting the user + * to another URI using HTTP status 301. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param redirection Where to redirect. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* redirection) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = redirection; + context->InvokeService(context, _OrthancPluginService_Redirect, ¶ms); + } + + + + typedef struct + { + char** result; + const char* argument; + } _OrthancPluginRetrieveDynamicString; + + /** + * @brief Look for a patient. + * + * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored patients). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param patientID The Patient ID of interest. + * @return The NULL value if the patient is non-existent, or a string containing the + * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient( + OrthancPluginContext* context, + const char* patientID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = patientID; + + if (context->InvokeService(context, _OrthancPluginService_LookupPatient, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study. + * + * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param studyUID The Study Instance UID of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy( + OrthancPluginContext* context, + const char* studyUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = studyUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudy, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study, using the accession number. + * + * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param accessionNumber The Accession Number of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber( + OrthancPluginContext* context, + const char* accessionNumber) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = accessionNumber; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a series. + * + * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored series). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param seriesUID The Series Instance UID of interest. + * @return The NULL value if the series is non-existent, or a string containing the + * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries( + OrthancPluginContext* context, + const char* seriesUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = seriesUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupSeries, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for an instance. + * + * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored instances). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param sopInstanceUID The SOP Instance UID of interest. + * @return The NULL value if the instance is non-existent, or a string containing the + * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance( + OrthancPluginContext* context, + const char* sopInstanceUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = sopInstanceUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + } _OrthancPluginSendHttpStatusCode; + + /** + * @brief Send a HTTP status code. + * + * This function answers to a REST request by sending a HTTP status + * code (such as "400 - Bad Request"). Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @ingroup REST + * @see OrthancPluginSendHttpStatus() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status) + { + _OrthancPluginSendHttpStatusCode params; + params.output = output; + params.status = status; + context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, ¶ms); + } + + + /** + * @brief Signal that a REST request is not authorized. + * + * This function answers to a REST request by signaling that it is + * not authorized. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param realm The realm for the authorization process. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* realm) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = realm; + context->InvokeService(context, _OrthancPluginService_SendUnauthorized, ¶ms); + } + + + /** + * @brief Signal that this URI does not support this HTTP method. + * + * This function answers to a REST request by signaling that the + * queried URI does not support this method. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* allowedMethods) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = allowedMethods; + context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* key; + const char* value; + } _OrthancPluginSetHttpHeader; + + /** + * @brief Set a cookie. + * + * This function sets a cookie in the HTTP client. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param cookie The cookie to be set. + * @param value The value of the cookie. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* cookie, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = cookie; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetCookie, ¶ms); + } + + + /** + * @brief Set some HTTP header. + * + * This function sets a HTTP header in the HTTP answer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param key The HTTP header to be set. + * @param value The value of the HTTP header. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* key, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = key; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetHttpHeader, ¶ms); + } + + + typedef struct + { + char** resultStringToFree; + const char** resultString; + int64_t* resultInt64; + const char* key; + const OrthancPluginDicomInstance* instance; + OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */ + } _OrthancPluginAccessDicomInstance; + + + /** + * @brief Get the AET of a DICOM instance. + * + * This function returns the Application Entity Title (AET) of the + * DICOM modality from which a DICOM instance originates. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The AET if success, NULL if error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the size of a DICOM file. + * + * This function returns the number of bytes of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The size of the file, -1 in case of error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + int64_t size; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &size; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return size; + } + } + + + /** + * @brief Get the data of a DICOM file. + * + * This function returns a pointer to the content of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The pointer to the DICOM data, NULL in case of error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE const void* OrthancPluginGetInstanceData( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file. + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file (with simplification). + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. In contrast with + * ::OrthancPluginGetInstanceJson(), the returned JSON file is in + * its simplified version. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Check whether a DICOM instance is associated with some metadata. + * + * This function checks whether the DICOM instance of interest is + * associated with some metadata. As of Orthanc 0.8.1, in the + * callbacks registered by + * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only + * possibly available metadata are "ReceptionDate", "RemoteAET" and + * "IndexInSeries". + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginHasInstanceMetadata( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + const char* metadata) + { + int64_t result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return (result != 0); + } + } + + + /** + * @brief Get the value of some metadata associated with a given DICOM instance. + * + * This functions returns the value of some metadata that is associated with the DICOM instance of interest. + * Before calling this function, the existence of the metadata must have been checked with + * ::OrthancPluginHasInstanceMetadata(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return The metadata value if success, NULL if error. Please note that the + * returned string belongs to the instance object and must NOT be + * deallocated. Please make a copy of the string if you wish to access + * it later. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + const char* metadata) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginStorageCreate create; + OrthancPluginStorageRead read; + OrthancPluginStorageRemove remove; + OrthancPluginFree free; + } _OrthancPluginRegisterStorageArea; + + /** + * @brief Register a custom storage area. + * + * This function registers a custom storage area, to replace the + * built-in way Orthanc stores its files on the filesystem. 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 create The callback function to store a file on the custom storage area. + * @param read The callback function to read a file from the custom storage area. + * @param remove The callback function to remove a file from the custom storage area. + * @ingroup Callbacks + * @deprecated Please use OrthancPluginRegisterStorageArea2() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageRead read, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea params; + params.create = create; + params.read = read; + params.remove = remove; + +#ifdef __cplusplus + params.free = ::free; +#else + params.free = free; +#endif + + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, ¶ms); + } + + + + /** + * @brief Return the path to the Orthanc executable. + * + * This function returns the path to the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the directory containing the Orthanc. + * + * This function returns the path to the directory containing the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the path to the configuration file(s). + * + * This function returns the path to the configuration file(s) that + * was specified when starting Orthanc. Since version 0.9.1, this + * path can refer to a folder that stores a set of configuration + * files. This function is deprecated in favor of + * OrthancPluginGetConfiguration(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + * @see OrthancPluginGetConfiguration() + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginOnChangeCallback callback; + } _OrthancPluginOnChangeCallback; + + /** + * @brief Register a callback to monitor changes. + * + * This function registers a callback function that is called + * whenever a change happens to some DICOM resource. + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( + OrthancPluginContext* context, + OrthancPluginOnChangeCallback callback) + { + _OrthancPluginOnChangeCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); + } + + + + typedef struct + { + const char* plugin; + _OrthancPluginProperty property; + const char* value; + } _OrthancPluginSetPluginProperty; + + + /** + * @brief Set the URI where the plugin provides its Web interface. + * + * For plugins that come with a Web interface, this function + * declares the entry path where to find this interface. This + * information is notably used in the "Plugins" page of Orthanc + * Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The root URI for this plugin. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri( + OrthancPluginContext* context, + const char* uri) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_RootUri; + params.value = uri; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Set a description for this plugin. + * + * Set a description for this plugin. It is displayed in the + * "Plugins" page of Orthanc Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param description The description. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription( + OrthancPluginContext* context, + const char* description) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_Description; + params.value = description; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Extend the JavaScript code of Orthanc Explorer. + * + * Add JavaScript code to customize the default behavior of Orthanc + * Explorer. This can for instance be used to add new buttons. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param javascript The custom JavaScript code. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer( + OrthancPluginContext* context, + const char* javascript) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_OrthancExplorer; + params.value = javascript; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + typedef struct + { + char** result; + int32_t property; + const char* value; + } _OrthancPluginGlobalProperty; + + + /** + * @brief Get the value of a global property. + * + * Get the value of a global property that is stored in the Orthanc database. Global + * properties whose index is below 1024 are reserved by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param defaultValue The value to return, if the global property is unset. + * @return The value of the global property, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* defaultValue) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = property; + params.value = defaultValue; + + if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Set the value of a global property. + * + * Set the value of a global property into the Orthanc + * database. Setting a global property can be used by plugins to + * save their internal parameters. Plugins are only allowed to set + * properties whose index are above or equal to 1024 (properties + * below 1024 are read-only and reserved by Orthanc). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param value The value to be set in the global property. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* value) + { + _OrthancPluginGlobalProperty params; + params.result = NULL; + params.property = property; + params.value = value; + + return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, ¶ms); + } + + + + typedef struct + { + int32_t *resultInt32; + uint32_t *resultUint32; + int64_t *resultInt64; + uint64_t *resultUint64; + } _OrthancPluginReturnSingleValue; + + /** + * @brief Get the number of command-line arguments. + * + * Retrieve the number of command-line arguments that were used to launch Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of arguments. + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Get the value of a command-line argument. + * + * Get the value of one of the command-line arguments that were used + * to launch Orthanc. The number of available arguments can be + * retrieved by OrthancPluginGetCommandLineArgumentsCount(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param argument The index of the argument. + * @return The value of the argument, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument( + OrthancPluginContext* context, + uint32_t argument) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = (int32_t) argument; + params.value = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the expected version of the database schema. + * + * Retrieve the expected version of the database schema. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The version. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Return the content of the configuration file(s). + * + * This function returns the content of the configuration that is + * used by Orthanc, formatted as a JSON string. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the configuration. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* subType; + const char* contentType; + } _OrthancPluginStartMultipartAnswer; + + /** + * @brief Start an HTTP multipart answer. + * + * Initiates a HTTP multipart answer, as the result of a REST request. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param subType The sub-type of the multipart answer ("mixed" or "related"). + * @param contentType The MIME type of the items in the multipart answer. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* subType, + const char* contentType) + { + _OrthancPluginStartMultipartAnswer params; + params.output = output; + params.subType = subType; + params.contentType = contentType; + return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, ¶ms); + } + + + /** + * @brief Send an item as a part of some HTTP multipart answer. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const void* answer, + uint32_t answerSize) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = NULL; + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const void* source; + uint32_t size; + OrthancPluginCompressionType compression; + uint8_t uncompress; + } _OrthancPluginBufferCompression; + + + /** + * @brief Compress or decompress a buffer. + * + * This function compresses or decompresses a buffer, using the + * version of the zlib library that is used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param source The source buffer. + * @param size The size in bytes of the source buffer. + * @param compression The compression algorithm. + * @param uncompress If set to "0", the buffer must be compressed. + * If set to "1", the buffer must be uncompressed. + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const void* source, + uint32_t size, + OrthancPluginCompressionType compression, + uint8_t uncompress) + { + _OrthancPluginBufferCompression params; + params.target = target; + params.source = source; + params.size = size; + params.compression = compression; + params.uncompress = uncompress; + + return context->InvokeService(context, _OrthancPluginService_BufferCompression, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* path; + } _OrthancPluginReadFile; + + /** + * @brief Read a file. + * + * Read the content of a file on the filesystem, and returns it into + * a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param path The path of the file to be read. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReadFile( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* path) + { + _OrthancPluginReadFile params; + params.target = target; + params.path = path; + return context->InvokeService(context, _OrthancPluginService_ReadFile, ¶ms); + } + + + + typedef struct + { + const char* path; + const void* data; + uint32_t size; + } _OrthancPluginWriteFile; + + /** + * @brief Write a file. + * + * Write the content of a memory buffer to the filesystem. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param path The path of the file to be written. + * @param data The content of the memory buffer. + * @param size The size of the memory buffer. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWriteFile( + OrthancPluginContext* context, + const char* path, + const void* data, + uint32_t size) + { + _OrthancPluginWriteFile params; + params.path = path; + params.data = data; + params.size = size; + return context->InvokeService(context, _OrthancPluginService_WriteFile, ¶ms); + } + + + + typedef struct + { + const char** target; + OrthancPluginErrorCode error; + } _OrthancPluginGetErrorDescription; + + /** + * @brief Get the description of a given error code. + * + * This function returns the description of a given error code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param error The error code of interest. + * @return The error description. This is a statically-allocated + * string, do not free it. + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription( + OrthancPluginContext* context, + OrthancPluginErrorCode error) + { + const char* result = NULL; + + _OrthancPluginGetErrorDescription params; + params.target = &result; + params.error = error; + + if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, ¶ms) != OrthancPluginErrorCode_Success || + result == NULL) + { + return "Unknown error code"; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + const void* body; + uint32_t bodySize; + } _OrthancPluginSendHttpStatus; + + /** + * @brief Send a HTTP status, with a custom body. + * + * This function answers to a HTTP request by sending a HTTP status + * code (such as "400 - Bad Request"), together with a body + * describing the error. The body will only be returned if the + * configuration option "HttpDescribeErrors" of Orthanc is set to "true". + * + * Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @param body The body of the answer. + * @param bodySize The size of the body. + * @see OrthancPluginSendHttpStatusCode() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status, + const void* body, + uint32_t bodySize) + { + _OrthancPluginSendHttpStatus params; + params.output = output; + params.status = status; + params.body = body; + params.bodySize = bodySize; + context->InvokeService(context, _OrthancPluginService_SendHttpStatus, ¶ms); + } + + + + typedef struct + { + const OrthancPluginImage* image; + uint32_t* resultUint32; + OrthancPluginPixelFormat* resultPixelFormat; + void** resultBuffer; + } _OrthancPluginGetImageInfo; + + + /** + * @brief Return the pixel format of an image. + * + * This function returns the type of memory layout for the pixels of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pixel format. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat OrthancPluginGetImagePixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + OrthancPluginPixelFormat target; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultPixelFormat = ⌖ + + if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return OrthancPluginPixelFormat_Unknown; + } + else + { + return (OrthancPluginPixelFormat) target; + } + } + + + + /** + * @brief Return the width of an image. + * + * This function returns the width of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The width. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageWidth( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t width; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &width; + + if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return width; + } + } + + + + /** + * @brief Return the height of an image. + * + * This function returns the height of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The height. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageHeight( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t height; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &height; + + if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return height; + } + } + + + + /** + * @brief Return the pitch of an image. + * + * This function returns the pitch of the given image. The pitch is + * defined as the number of bytes between 2 successive lines of the + * image in the memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pitch. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImagePitch( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t pitch; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &pitch; + + if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return pitch; + } + } + + + + /** + * @brief Return a pointer to the content of an image. + * + * This function returns a pointer to the memory buffer that + * contains the pixels of the image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pointer. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void* OrthancPluginGetImageBuffer( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + void* target = NULL; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.resultBuffer = ⌖ + params.image = image; + + if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginImage** target; + const void* data; + uint32_t size; + OrthancPluginImageFormat format; + } _OrthancPluginUncompressImage; + + + /** + * @brief Decode a compressed image. + * + * This function decodes a compressed image from a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param data Pointer to a memory buffer containing the compressed image. + * @param size Size of the memory buffer containing the compressed image. + * @param format The file format of the compressed image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage( + OrthancPluginContext* context, + const void* data, + uint32_t size, + OrthancPluginImageFormat format) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginUncompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.data = data; + params.size = size; + params.format = format; + + if (context->InvokeService(context, _OrthancPluginService_UncompressImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + + typedef struct + { + OrthancPluginImage* image; + } _OrthancPluginFreeImage; + + /** + * @brief Free an image. + * + * This function frees an image that was decoded with OrthancPluginUncompressImage(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeImage( + OrthancPluginContext* context, + OrthancPluginImage* image) + { + _OrthancPluginFreeImage params; + params.image = image; + + context->InvokeService(context, _OrthancPluginService_FreeImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressImage; + + + /** + * @brief Encode a PNG image. + * + * This function compresses the given memory buffer containing an + * image using the PNG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginCompressAndAnswerPngImage() + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* Unused for PNG */ + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + /** + * @brief Encode a JPEG image. + * + * This function compresses the given memory buffer containing an + * image using the JPEG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + + /** + * @brief Answer to a REST request with a JPEG image. + * + * This function answers to a REST request with a JPEG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a JPEG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginHttpMethod method; + const char* url; + const char* username; + const char* password; + const void* body; + uint32_t bodySize; + } _OrthancPluginCallHttpClient; + + + /** + * @brief Issue a HTTP GET call. + * + * Make a HTTP GET call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiGet() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Get; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP POST call. + * + * Make a HTTP POST call to the given URL. The result to the query + * is stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPost() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const void* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Post; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP PUT call. + * + * Make a HTTP PUT call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPut() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const void* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Put; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP DELETE call. + * + * Make a HTTP DELETE call to the given URL. Favor + * OrthancPluginRestApiDelete() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpDelete( + OrthancPluginContext* context, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.method = OrthancPluginHttpMethod_Delete; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + const OrthancPluginImage* source; + OrthancPluginPixelFormat targetFormat; + } _OrthancPluginConvertPixelFormat; + + + /** + * @brief Change the pixel format of an image. + * + * This function creates a new image, changing the memory layout of the pixels. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param source The source image. + * @param targetFormat The target pixel format. + * @return The resulting image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* source, + OrthancPluginPixelFormat targetFormat) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginConvertPixelFormat params; + params.target = ⌖ + params.source = source; + params.targetFormat = targetFormat; + + if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Return the number of available fonts. + * + * This function returns the number of fonts that are built in the + * Orthanc core. These fonts can be used to draw texts on images + * through OrthancPluginDrawText(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of fonts. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + + typedef struct + { + uint32_t fontIndex; /* in */ + const char** name; /* out */ + uint32_t* size; /* out */ + } _OrthancPluginGetFontInfo; + + /** + * @brief Return the name of a font. + * + * This function returns the name of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font name. This is a statically-allocated string, do not free it. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName( + OrthancPluginContext* context, + uint32_t fontIndex) + { + const char* result = NULL; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.name = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the size of a font. + * + * This function returns the size of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font size. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize( + OrthancPluginContext* context, + uint32_t fontIndex) + { + uint32_t result; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.size = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginImage* image; + uint32_t fontIndex; + const char* utf8Text; + int32_t x; + int32_t y; + uint8_t r; + uint8_t g; + uint8_t b; + } _OrthancPluginDrawText; + + + /** + * @brief Draw text on an image. + * + * This function draws some text on some image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image upon which to draw the text. + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string. + * @param x The X position of the text over the image. + * @param y The Y position of the text over the image. + * @param r The value of the red color channel of the text. + * @param g The value of the green color channel of the text. + * @param b The value of the blue color channel of the text. + * @return 0 if success, other value if error. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDrawText( + OrthancPluginContext* context, + OrthancPluginImage* image, + uint32_t fontIndex, + const char* utf8Text, + int32_t x, + int32_t y, + uint8_t r, + uint8_t g, + uint8_t b) + { + _OrthancPluginDrawText params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.fontIndex = fontIndex; + params.utf8Text = utf8Text; + params.x = x; + params.y = y; + params.r = r; + params.g = g; + params.b = b; + + return context->InvokeService(context, _OrthancPluginService_DrawText, ¶ms); + } + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + const void* content; + uint64_t size; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaCreate; + + + /** + * @brief Create a file inside the storage area. + * + * This function creates a new file inside the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be created. + * @param content The content to store in the newly created file. + * @param size The size of the content. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiPut()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + const void* content, + uint64_t size, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaCreate params; + params.storageArea = storageArea; + params.uuid = uuid; + params.content = content; + params.size = size; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, ¶ms); + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRead; + + + /** + * @brief Read a file from the storage area. + * + * This function reads the content of a given file from the storage + * area that is currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be read. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiGet()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRead params; + params.target = target; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRemove; + + /** + * @brief Remove a file from the storage area. + * + * This function removes a given file from the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be removed. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiDelete()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRemove params; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, ¶ms); + } + + + + typedef struct + { + OrthancPluginErrorCode* target; + int32_t code; + uint16_t httpStatus; + const char* message; + } _OrthancPluginRegisterErrorCode; + + /** + * @brief Declare a custom error code for this plugin. + * + * This function declares a custom error code that can be generated + * by this plugin. This declaration is used to enrich the body of + * the HTTP answer in the case of an error, and to set the proper + * HTTP status code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param code The error code that is internal to this plugin. + * @param httpStatus The HTTP status corresponding to this error. + * @param message The description of the error. + * @return The error code that has been assigned inside the Orthanc core. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterErrorCode( + OrthancPluginContext* context, + int32_t code, + uint16_t httpStatus, + const char* message) + { + OrthancPluginErrorCode target; + + _OrthancPluginRegisterErrorCode params; + params.target = ⌖ + params.code = code; + params.httpStatus = httpStatus; + params.message = message; + + if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, ¶ms) == OrthancPluginErrorCode_Success) + { + return target; + } + else + { + /* There was an error while assigned the error. Use a generic code. */ + return OrthancPluginErrorCode_Plugin; + } + } + + + + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + } _OrthancPluginRegisterDictionaryTag; + + /** + * @brief Register a new tag into the DICOM dictionary. + * + * This function declares a new public tag in the dictionary of + * DICOM tags that are known to Orthanc. This function should be + * used in the OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("n"). + * @return 0 if success, other value if error. + * @see OrthancPluginRegisterPrivateDictionaryTag() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity) + { + _OrthancPluginRegisterDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + + return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, ¶ms); + } + + + + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + const char* privateCreator; + } _OrthancPluginRegisterPrivateDictionaryTag; + + /** + * @brief Register a new private tag into the DICOM dictionary. + * + * This function declares a new private tag in the dictionary of + * DICOM tags that are known to Orthanc. This function should be + * used in the OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("n"). + * @param privateCreator The private creator of this private tag. + * @return 0 if success, other value if error. + * @see OrthancPluginRegisterDictionaryTag() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterPrivateDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity, + const char* privateCreator) + { + _OrthancPluginRegisterPrivateDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + params.privateCreator = privateCreator; + + return context->InvokeService(context, _OrthancPluginService_RegisterPrivateDictionaryTag, ¶ms); + } + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + OrthancPluginResourceType level; + } _OrthancPluginReconstructMainDicomTags; + + /** + * @brief Reconstruct the main DICOM tags. + * + * This function requests the Orthanc core to reconstruct the main + * DICOM tags of all the resources of the given type. This function + * can only be used as a part of the upgrade of a custom database + * back-end. A database transaction will be automatically setup. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param level The type of the resources of interest. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReconstructMainDicomTags( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + OrthancPluginResourceType level) + { + _OrthancPluginReconstructMainDicomTags params; + params.level = level; + params.storageArea = storageArea; + + return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, ¶ms); + } + + + typedef struct + { + char** result; + const char* instanceId; + const void* buffer; + uint32_t size; + OrthancPluginDicomToJsonFormat format; + OrthancPluginDicomToJsonFlags flags; + uint32_t maxStringLength; + } _OrthancPluginDicomToJson; + + + /** + * @brief Format a DICOM memory buffer as a JSON string. + * + * This function takes as input a memory buffer containing a DICOM + * file, and outputs a JSON string representing the tags of this + * DICOM file. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer containing the DICOM file. + * @param size The size of the memory buffer. + * @param format The output format. + * @param flags Flags governing the output. + * @param maxStringLength The maximum length of a field. Too long fields will + * be output as "null". The 0 value means no maximum length. + * @return The NULL value if the case of an error, or the JSON + * string. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + * @see OrthancPluginDicomInstanceToJson + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson( + OrthancPluginContext* context, + const void* buffer, + uint32_t size, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + char* result; + + _OrthancPluginDicomToJson params; + memset(¶ms, 0, sizeof(params)); + params.result = &result; + params.buffer = buffer; + params.size = size; + params.format = format; + params.flags = flags; + params.maxStringLength = maxStringLength; + + if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Format a DICOM instance as a JSON string. + * + * This function formats a DICOM instance that is stored in Orthanc, + * and outputs a JSON string representing the tags of this DICOM + * instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instanceId The Orthanc identifier of the instance. + * @param format The output format. + * @param flags Flags governing the output. + * @param maxStringLength The maximum length of a field. Too long fields will + * be output as "null". The 0 value means no maximum length. + * @return The NULL value if the case of an error, or the JSON + * string. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + * @see OrthancPluginDicomInstanceToJson + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson( + OrthancPluginContext* context, + const char* instanceId, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + char* result; + + _OrthancPluginDicomToJson params; + memset(¶ms, 0, sizeof(params)); + params.result = &result; + params.instanceId = instanceId; + params.format = format; + params.flags = flags; + params.maxStringLength = maxStringLength; + + if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + int32_t afterPlugins; + } _OrthancPluginRestApiGet2; + + /** + * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers. + * + * Make a GET call to the Orthanc REST API with extended + * parameters. The result to the query is stored into a newly + * allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param afterPlugins If 0, the built-in API of Orthanc is used. + * If 1, the API is tainted by the plugins. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet2( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + int32_t afterPlugins) + { + _OrthancPluginRestApiGet2 params; + params.target = target; + params.uri = uri; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.afterPlugins = afterPlugins; + + return context->InvokeService(context, _OrthancPluginService_RestApiGet2, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistCallback callback; + } _OrthancPluginWorklistCallback; + + /** + * @brief Register a callback to handle modality worklists requests. + * + * This function registers a callback to handle C-Find SCP requests + * on modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback( + OrthancPluginContext* context, + OrthancPluginWorklistCallback callback) + { + _OrthancPluginWorklistCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistAnswers* answers; + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + } _OrthancPluginWorklistAnswersOperation; + + /** + * @brief Add one answer to some modality worklist request. + * + * This function adds one worklist (encoded as a DICOM file) to the + * set of answers corresponding to some C-Find SCP request against + * modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + * @see OrthancPluginCreateDicom() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddAnswer( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = query; + params.dicom = dicom; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, ¶ms); + } + + + /** + * @brief Mark the set of worklist answers as incomplete. + * + * This function marks as incomplete the set of answers + * corresponding to some C-Find SCP request against modality + * worklists. This must be used if canceling the handling of a + * request when too many answers are to be returned. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistMarkIncomplete( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = NULL; + params.dicom = NULL; + params.size = 0; + + return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, ¶ms); + } + + + typedef struct + { + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + int32_t* isMatch; + OrthancPluginMemoryBuffer* target; + } _OrthancPluginWorklistQueryOperation; + + /** + * @brief Test whether a worklist matches the query. + * + * This function checks whether one worklist (encoded as a DICOM + * file) matches the C-Find SCP query against modality + * worklists. This function must be called before adding the + * worklist as an answer through OrthancPluginWorklistAddAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 1 if the worklist matches the query, 0 otherwise. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginWorklistIsMatch( + OrthancPluginContext* context, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + int32_t isMatch = 0; + + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = dicom; + params.size = size; + params.isMatch = &isMatch; + params.target = NULL; + + if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, ¶ms) == OrthancPluginErrorCode_Success) + { + return isMatch; + } + else + { + /* Error: Assume non-match */ + return 0; + } + } + + + /** + * @brief Retrieve the worklist query as a DICOM file. + * + * This function retrieves the DICOM file that underlies a C-Find + * SCP query against modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param query The worklist query, as received by the callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistGetDicomQuery( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginWorklistQuery* query) + { + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = NULL; + params.size = 0; + params.isMatch = NULL; + params.target = target; + + return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, ¶ms); + } + + + /** + * @brief Get the origin of a DICOM file. + * + * This function returns the origin of a DICOM instance that has been received by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The origin of the instance. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + OrthancPluginInstanceOrigin origin; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultOrigin = &origin; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return OrthancPluginInstanceOrigin_Unknown; + } + else + { + return origin; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* json; + const OrthancPluginImage* pixelData; + OrthancPluginCreateDicomFlags flags; + } _OrthancPluginCreateDicom; + + /** + * @brief Create a DICOM instance from a JSON string and an image. + * + * This function takes as input a string containing a JSON file + * describing the content of a DICOM instance. As an output, it + * writes the corresponding DICOM instance to a newly allocated + * memory buffer. Additionally, an image to be encoded within the + * DICOM instance can also be provided. + * + * Private tags will be associated with the private creator whose + * value is specified in the "DefaultPrivateCreator" configuration + * option of Orthanc. The function OrthancPluginCreateDicom2() can + * be used if another private creator must be used to create this + * instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param json The input JSON file. + * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. + * @param flags Flags governing the output. + * @return 0 if success, other value if error. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom2 + * @see OrthancPluginDicomBufferToJson + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* json, + const OrthancPluginImage* pixelData, + OrthancPluginCreateDicomFlags flags) + { + _OrthancPluginCreateDicom params; + params.target = target; + params.json = json; + params.pixelData = pixelData; + params.flags = flags; + + return context->InvokeService(context, _OrthancPluginService_CreateDicom, ¶ms); + } + + + typedef struct + { + OrthancPluginDecodeImageCallback callback; + } _OrthancPluginDecodeImageCallback; + + /** + * @brief Register a callback to handle the decoding of DICOM images. + * + * This function registers a custom callback to decode DICOM images, + * extending the built-in decoder of Orthanc that uses + * DCMTK. Starting with Orthanc 1.7.0, the exact behavior is + * affected by the configuration option + * "BuiltinDecoderTranscoderOrder" of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback( + OrthancPluginContext* context, + OrthancPluginDecodeImageCallback callback) + { + _OrthancPluginDecodeImageCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + void* buffer; + const void* constBuffer; + uint32_t bufferSize; + uint32_t frameIndex; + } _OrthancPluginCreateImage; + + + /** + * @brief Create an image. + * + * This function creates an image of given size and format. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + + if (context->InvokeService(context, _OrthancPluginService_CreateImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Create an image pointing to a memory buffer. + * + * This function creates an image whose content points to a memory + * buffer managed by the plugin. Note that the buffer is directly + * accessed, no memory is allocated and no data is copied. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + + if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Decode one frame from a DICOM instance. + * + * This function decodes one frame of a DICOM image that is stored + * in a memory buffer. This function will give the same result as + * OrthancPluginUncompressImage() for single-frame DICOM images. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer Pointer to a memory buffer containing the DICOM image. + * @param bufferSize Size of the memory buffer containing the DICOM image. + * @param frameIndex The index of the frame of interest in a multi-frame image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + * @see OrthancPluginGetInstanceDecodedFrame() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage( + OrthancPluginContext* context, + const void* buffer, + uint32_t bufferSize, + uint32_t frameIndex) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.constBuffer = buffer; + params.bufferSize = bufferSize; + params.frameIndex = frameIndex; + + if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + char** result; + const void* buffer; + uint32_t size; + } _OrthancPluginComputeHash; + + /** + * @brief Compute an MD5 hash. + * + * This functions computes the MD5 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Compute a SHA-1 hash. + * + * This functions computes the SHA-1 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginDictionaryEntry* target; + const char* name; + } _OrthancPluginLookupDictionary; + + /** + * @brief Get information about the given DICOM tag. + * + * This functions makes a lookup in the dictionary of DICOM tags + * that are known to Orthanc, and returns information about this + * tag. The tag can be specified using its human-readable name + * (e.g. "PatientName") or a set of two hexadecimal numbers + * (e.g. "0010-0020"). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Where to store the information about the tag. + * @param name The name of the DICOM tag. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginLookupDictionary( + OrthancPluginContext* context, + OrthancPluginDictionaryEntry* target, + const char* name) + { + _OrthancPluginLookupDictionary params; + params.target = target; + params.name = name; + return context->InvokeService(context, _OrthancPluginService_LookupDictionary, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const void* answer; + uint32_t answerSize; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + } _OrthancPluginSendMultipartItem2; + + /** + * @brief Send an item as a part of some HTTP multipart answer, with custom headers. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to + * OrthancPluginSendMultipartItem(), this function will set HTTP header associated + * with the item. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers. + * @param headersValues Array containing the values of the HTTP headers. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const void* answer, + uint32_t answerSize, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues) + { + _OrthancPluginSendMultipartItem2 params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, ¶ms); + } + + + typedef struct + { + OrthancPluginIncomingHttpRequestFilter callback; + } _OrthancPluginIncomingHttpRequestFilter; + + /** + * @brief Register a callback to filter incoming HTTP requests. + * + * This function registers a custom callback to filter incoming HTTP/REST + * requests received by the HTTP server of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated Please instead use OrthancPluginRegisterIncomingHttpRequestFilter2() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter( + OrthancPluginContext* context, + OrthancPluginIncomingHttpRequestFilter callback) + { + _OrthancPluginIncomingHttpRequestFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + OrthancPluginHttpMethod method; + const char* url; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + const void* body; + uint32_t bodySize; + const char* username; + const char* password; + uint32_t timeout; + const char* certificateFile; + const char* certificateKeyFile; + const char* certificateKeyPassword; + uint8_t pkcs11; + } _OrthancPluginCallHttpClient2; + + + + /** + * @brief Issue a HTTP call with full flexibility. + * + * Make a HTTP call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. The HTTP request + * will be done accordingly to the global configuration of Orthanc + * (in particular, the options "HttpProxy", "HttpTimeout", + * "HttpsVerifyPeers", "HttpsCACertificates", and "Pkcs11" will be + * taken into account). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * The value of this argument is ignored if the HTTP method is DELETE. + * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param method HTTP method to be used. + * @param url The URL of interest. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param timeout Timeout in seconds (0 for default timeout). + * @param certificateFile Path to the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyPassword Password to unlock the key of the client certificate + * (can be NULL if no client certificate or if not using HTTPS). + * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginCallPeerApi() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpClient( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + OrthancPluginHttpMethod method, + const char* url, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + const void* body, + uint32_t bodySize, + const char* username, + const char* password, + uint32_t timeout, + const char* certificateFile, + const char* certificateKeyFile, + const char* certificateKeyPassword, + uint8_t pkcs11) + { + _OrthancPluginCallHttpClient2 params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.method = method; + params.url = url; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + params.timeout = timeout; + params.certificateFile = certificateFile; + params.certificateKeyFile = certificateKeyFile; + params.certificateKeyPassword = certificateKeyPassword; + params.pkcs11 = pkcs11; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient2, ¶ms); + } + + + /** + * @brief Generate an UUID. + * + * Generate a random GUID/UUID (globally unique identifier). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the UUID. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateUuid( + OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GenerateUuid, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + + typedef struct + { + OrthancPluginFindCallback callback; + } _OrthancPluginFindCallback; + + /** + * @brief Register a callback to handle C-Find requests. + * + * This function registers a callback to handle C-Find SCP requests + * that are not related to modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterFindCallback( + OrthancPluginContext* context, + OrthancPluginFindCallback callback) + { + _OrthancPluginFindCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterFindCallback, ¶ms); + } + + + typedef struct + { + OrthancPluginFindAnswers *answers; + const OrthancPluginFindQuery *query; + const void *dicom; + uint32_t size; + uint32_t index; + uint32_t *resultUint32; + uint16_t *resultGroup; + uint16_t *resultElement; + char **resultString; + } _OrthancPluginFindOperation; + + /** + * @brief Add one answer to some C-Find request. + * + * This function adds one answer (encoded as a DICOM file) to the + * set of answers corresponding to some C-Find SCP request that is + * not related to modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @param dicom The answer to be added, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + * @see OrthancPluginCreateDicom() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginFindAddAnswer( + OrthancPluginContext* context, + OrthancPluginFindAnswers* answers, + const void* dicom, + uint32_t size) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.answers = answers; + params.dicom = dicom; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_FindAddAnswer, ¶ms); + } + + + /** + * @brief Mark the set of C-Find answers as incomplete. + * + * This function marks as incomplete the set of answers + * corresponding to some C-Find SCP request that is not related to + * modality worklists. This must be used if canceling the handling + * of a request when too many answers are to be returned. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginFindMarkIncomplete( + OrthancPluginContext* context, + OrthancPluginFindAnswers* answers) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.answers = answers; + + return context->InvokeService(context, _OrthancPluginService_FindMarkIncomplete, ¶ms); + } + + + + /** + * @brief Get the number of tags in a C-Find query. + * + * This function returns the number of tags that are contained in + * the given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @return The number of tags. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFindQuerySize( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query) + { + uint32_t count = 0; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQuerySize, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + /** + * @brief Get one tag in a C-Find query. + * + * This function returns the group and the element of one DICOM tag + * in the given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag (output). + * @param element The element of the tag (output). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetFindQueryTag( + OrthancPluginContext* context, + uint16_t* group, + uint16_t* element, + const OrthancPluginFindQuery* query, + uint32_t index) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultGroup = group; + params.resultElement = element; + + return context->InvokeService(context, _OrthancPluginService_GetFindQueryTag, ¶ms); + } + + + /** + * @brief Get the symbolic name of one tag in a C-Find query. + * + * This function returns the symbolic name of one DICOM tag in the + * given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return The NULL value in case of error, or a string containing the name of the tag. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetFindQueryTagName( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query, + uint32_t index) + { + char* result; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultString = &result; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQueryTagName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the value associated with one tag in a C-Find query. + * + * This function returns the value associated with one tag in the + * given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return The NULL value in case of error, or a string containing the value of the tag. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetFindQueryValue( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query, + uint32_t index) + { + char* result; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultString = &result; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQueryValue, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + + typedef struct + { + OrthancPluginMoveCallback callback; + OrthancPluginGetMoveSize getMoveSize; + OrthancPluginApplyMove applyMove; + OrthancPluginFreeMove freeMove; + } _OrthancPluginMoveCallback; + + /** + * @brief Register a callback to handle C-Move requests. + * + * This function registers a callback to handle C-Move SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The main callback. + * @param getMoveSize Callback to read the number of C-Move suboperations. + * @param applyMove Callback to apply one C-Move suboperation. + * @param freeMove Callback to free the C-Move driver. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback( + OrthancPluginContext* context, + OrthancPluginMoveCallback callback, + OrthancPluginGetMoveSize getMoveSize, + OrthancPluginApplyMove applyMove, + OrthancPluginFreeMove freeMove) + { + _OrthancPluginMoveCallback params; + params.callback = callback; + params.getMoveSize = getMoveSize; + params.applyMove = applyMove; + params.freeMove = freeMove; + + return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginFindMatcher** target; + const void* query; + uint32_t size; + } _OrthancPluginCreateFindMatcher; + + + /** + * @brief Create a C-Find matcher. + * + * This function creates a "matcher" object that can be used to + * check whether a DICOM instance matches a C-Find query. The C-Find + * query must be expressed as a DICOM buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find DICOM query. + * @param size The size of the DICOM query. + * @return The newly allocated matcher. It must be freed with OrthancPluginFreeFindMatcher(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginFindMatcher* OrthancPluginCreateFindMatcher( + OrthancPluginContext* context, + const void* query, + uint32_t size) + { + OrthancPluginFindMatcher* target = NULL; + + _OrthancPluginCreateFindMatcher params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.query = query; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_CreateFindMatcher, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginFindMatcher* matcher; + } _OrthancPluginFreeFindMatcher; + + /** + * @brief Free a C-Find matcher. + * + * This function frees a matcher that was created using OrthancPluginCreateFindMatcher(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param matcher The matcher of interest. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeFindMatcher( + OrthancPluginContext* context, + OrthancPluginFindMatcher* matcher) + { + _OrthancPluginFreeFindMatcher params; + params.matcher = matcher; + + context->InvokeService(context, _OrthancPluginService_FreeFindMatcher, ¶ms); + } + + + typedef struct + { + const OrthancPluginFindMatcher* matcher; + const void* dicom; + uint32_t size; + int32_t* isMatch; + } _OrthancPluginFindMatcherIsMatch; + + /** + * @brief Test whether a DICOM instance matches a C-Find query. + * + * This function checks whether one DICOM instance matches C-Find + * matcher that was previously allocated using + * OrthancPluginCreateFindMatcher(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param matcher The matcher of interest. + * @param dicom The DICOM instance to be matched. + * @param size The size of the DICOM instance. + * @return 1 if the DICOM instance matches the query, 0 otherwise. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginFindMatcherIsMatch( + OrthancPluginContext* context, + const OrthancPluginFindMatcher* matcher, + const void* dicom, + uint32_t size) + { + int32_t isMatch = 0; + + _OrthancPluginFindMatcherIsMatch params; + params.matcher = matcher; + params.dicom = dicom; + params.size = size; + params.isMatch = &isMatch; + + if (context->InvokeService(context, _OrthancPluginService_FindMatcherIsMatch, ¶ms) == OrthancPluginErrorCode_Success) + { + return isMatch; + } + else + { + /* Error: Assume non-match */ + return 0; + } + } + + + typedef struct + { + OrthancPluginIncomingHttpRequestFilter2 callback; + } _OrthancPluginIncomingHttpRequestFilter2; + + /** + * @brief Register a callback to filter incoming HTTP requests. + * + * This function registers a custom callback to filter incoming HTTP/REST + * requests received by the HTTP server of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter2( + OrthancPluginContext* context, + OrthancPluginIncomingHttpRequestFilter2 callback) + { + _OrthancPluginIncomingHttpRequestFilter2 params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter2, ¶ms); + } + + + + typedef struct + { + OrthancPluginPeers** peers; + } _OrthancPluginGetPeers; + + /** + * @brief Return the list of available Orthanc peers. + * + * This function returns the parameters of the Orthanc peers that are known to + * the Orthanc server hosting the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL if error, or a newly allocated opaque data structure containing the peers. + * This structure must be freed with OrthancPluginFreePeers(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPeers* OrthancPluginGetPeers( + OrthancPluginContext* context) + { + OrthancPluginPeers* peers = NULL; + + _OrthancPluginGetPeers params; + memset(¶ms, 0, sizeof(params)); + params.peers = &peers; + + if (context->InvokeService(context, _OrthancPluginService_GetPeers, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return peers; + } + } + + + typedef struct + { + OrthancPluginPeers* peers; + } _OrthancPluginFreePeers; + + /** + * @brief Free the list of available Orthanc peers. + * + * This function frees the data structure returned by OrthancPluginGetPeers(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreePeers( + OrthancPluginContext* context, + OrthancPluginPeers* peers) + { + _OrthancPluginFreePeers params; + params.peers = peers; + + context->InvokeService(context, _OrthancPluginService_FreePeers, ¶ms); + } + + + typedef struct + { + uint32_t* target; + const OrthancPluginPeers* peers; + } _OrthancPluginGetPeersCount; + + /** + * @brief Get the number of Orthanc peers. + * + * This function returns the number of Orthanc peers. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @result The number of peers. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetPeersCount( + OrthancPluginContext* context, + const OrthancPluginPeers* peers) + { + uint32_t target = 0; + + _OrthancPluginGetPeersCount params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + + if (context->InvokeService(context, _OrthancPluginService_GetPeersCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return target; + } + } + + + typedef struct + { + const char** target; + const OrthancPluginPeers* peers; + uint32_t peerIndex; + const char* userProperty; + } _OrthancPluginGetPeerProperty; + + /** + * @brief Get the symbolic name of an Orthanc peer. + * + * This function returns the symbolic name of the Orthanc peer, + * which corresponds to the key of the "OrthancPeers" configuration + * option of Orthanc. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @result The symbolic name, or NULL in the case of an error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerName( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Get the base URL of an Orthanc peer. + * + * This function returns the base URL to the REST API of some Orthanc peer. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @result The URL, or NULL in the case of an error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUrl( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerUrl, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Get some user-defined property of an Orthanc peer. + * + * This function returns some user-defined property of some Orthanc + * peer. An user-defined property is a property that is associated + * with the peer in the Orthanc configuration file, but that is not + * recognized by the Orthanc core. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @param userProperty The user property of interest. + * @result The value of the user property, or NULL if it is not defined. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUserProperty( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex, + const char* userProperty) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = userProperty; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerUserProperty, ¶ms) != OrthancPluginErrorCode_Success) + { + /* No such user property */ + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + const OrthancPluginPeers* peers; + uint32_t peerIndex; + OrthancPluginHttpMethod method; + const char* uri; + uint32_t additionalHeadersCount; + const char* const* additionalHeadersKeys; + const char* const* additionalHeadersValues; + const void* body; + uint32_t bodySize; + uint32_t timeout; + } _OrthancPluginCallPeerApi; + + /** + * @brief Call the REST API of an Orthanc peer. + * + * Make a REST call to the given URI in the REST API of a remote + * Orthanc peer. The result to the query is stored into a newly + * allocated memory buffer. The HTTP request will be done according + * to the "OrthancPeers" configuration option of Orthanc. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * The value of this argument is ignored if the HTTP method is DELETE. + * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @param method HTTP method to be used. + * @param uri The URI of interest in the REST API. + * @param additionalHeadersCount The number of HTTP headers to be added to the + * HTTP headers provided in the global configuration of Orthanc. + * @param additionalHeadersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param additionalHeadersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param timeout Timeout in seconds (0 for default timeout). + * @return 0 if success, or the error code if failure. + * @see OrthancPluginHttpClient() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCallPeerApi( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + const OrthancPluginPeers* peers, + uint32_t peerIndex, + OrthancPluginHttpMethod method, + const char* uri, + uint32_t additionalHeadersCount, + const char* const* additionalHeadersKeys, + const char* const* additionalHeadersValues, + const void* body, + uint32_t bodySize, + uint32_t timeout) + { + _OrthancPluginCallPeerApi params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.peers = peers; + params.peerIndex = peerIndex; + params.method = method; + params.uri = uri; + params.additionalHeadersCount = additionalHeadersCount; + params.additionalHeadersKeys = additionalHeadersKeys; + params.additionalHeadersValues = additionalHeadersValues; + params.body = body; + params.bodySize = bodySize; + params.timeout = timeout; + + return context->InvokeService(context, _OrthancPluginService_CallPeerApi, ¶ms); + } + + + + + + typedef struct + { + OrthancPluginJob** target; + void *job; + OrthancPluginJobFinalize finalize; + const char *type; + OrthancPluginJobGetProgress getProgress; + OrthancPluginJobGetContent getContent; + OrthancPluginJobGetSerialized getSerialized; + OrthancPluginJobStep step; + OrthancPluginJobStop stop; + OrthancPluginJobReset reset; + } _OrthancPluginCreateJob; + + /** + * @brief Create a custom job. + * + * This function creates a custom job to be run by the jobs engine + * of Orthanc. + * + * Orthanc starts one dedicated thread per custom job that is + * running. It is guaranteed that all the callbacks will only be + * called from this single dedicated thread, in mutual exclusion: As + * a consequence, it is *not* mandatory to protect the various + * callbacks by mutexes. + * + * The custom job can nonetheless launch its own processing threads + * on the first call to the "step()" callback, and stop them once + * the "stop()" callback is called. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job to be executed. + * @param finalize The finalization callback. + * @param type The type of the job, provided to the job unserializer. + * See OrthancPluginRegisterJobsUnserializer(). + * @param getProgress The progress callback. + * @param getContent The content callback. + * @param getSerialized The serialization callback. + * @param step The callback to execute the individual steps of the job. + * @param stop The callback that is invoked once the job leaves the "running" state. + * @param reset The callback that is invoked if a stopped job is started again. + * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(), + * as long as it is not submitted with OrthancPluginSubmitJob(). + * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob( + OrthancPluginContext *context, + void *job, + OrthancPluginJobFinalize finalize, + const char *type, + OrthancPluginJobGetProgress getProgress, + OrthancPluginJobGetContent getContent, + OrthancPluginJobGetSerialized getSerialized, + OrthancPluginJobStep step, + OrthancPluginJobStop stop, + OrthancPluginJobReset reset) + { + OrthancPluginJob* target = NULL; + + _OrthancPluginCreateJob params; + memset(¶ms, 0, sizeof(params)); + + params.target = ⌖ + params.job = job; + params.finalize = finalize; + params.type = type; + params.getProgress = getProgress; + params.getContent = getContent; + params.getSerialized = getSerialized; + params.step = step; + params.stop = stop; + params.reset = reset; + + if (context->InvokeService(context, _OrthancPluginService_CreateJob, ¶ms) != OrthancPluginErrorCode_Success || + target == NULL) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginJob** target; + void *job; + OrthancPluginJobFinalize finalize; + const char *type; + OrthancPluginJobGetProgress getProgress; + OrthancPluginJobGetContent2 getContent; + OrthancPluginJobGetSerialized2 getSerialized; + OrthancPluginJobStep step; + OrthancPluginJobStop stop; + OrthancPluginJobReset reset; + } _OrthancPluginCreateJob2; + + /** + * @brief Create a custom job. + * + * This function creates a custom job to be run by the jobs engine + * of Orthanc. + * + * Orthanc starts one dedicated thread per custom job that is + * running. It is guaranteed that all the callbacks will only be + * called from this single dedicated thread, in mutual exclusion: As + * a consequence, it is *not* mandatory to protect the various + * callbacks by mutexes. + * + * The custom job can nonetheless launch its own processing threads + * on the first call to the "step()" callback, and stop them once + * the "stop()" callback is called. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job to be executed. + * @param finalize The finalization callback. + * @param type The type of the job, provided to the job unserializer. + * See OrthancPluginRegisterJobsUnserializer(). + * @param getProgress The progress callback. + * @param getContent The content callback. + * @param getSerialized The serialization callback. + * @param step The callback to execute the individual steps of the job. + * @param stop The callback that is invoked once the job leaves the "running" state. + * @param reset The callback that is invoked if a stopped job is started again. + * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(), + * as long as it is not submitted with OrthancPluginSubmitJob(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob2( + OrthancPluginContext *context, + void *job, + OrthancPluginJobFinalize finalize, + const char *type, + OrthancPluginJobGetProgress getProgress, + OrthancPluginJobGetContent2 getContent, + OrthancPluginJobGetSerialized2 getSerialized, + OrthancPluginJobStep step, + OrthancPluginJobStop stop, + OrthancPluginJobReset reset) + { + OrthancPluginJob* target = NULL; + + _OrthancPluginCreateJob2 params; + memset(¶ms, 0, sizeof(params)); + + params.target = ⌖ + params.job = job; + params.finalize = finalize; + params.type = type; + params.getProgress = getProgress; + params.getContent = getContent; + params.getSerialized = getSerialized; + params.step = step; + params.stop = stop; + params.reset = reset; + + if (context->InvokeService(context, _OrthancPluginService_CreateJob2, ¶ms) != OrthancPluginErrorCode_Success || + target == NULL) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginJob* job; + } _OrthancPluginFreeJob; + + /** + * @brief Free a custom job. + * + * This function frees an image that was created with OrthancPluginCreateJob(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeJob( + OrthancPluginContext* context, + OrthancPluginJob* job) + { + _OrthancPluginFreeJob params; + params.job = job; + + context->InvokeService(context, _OrthancPluginService_FreeJob, ¶ms); + } + + + + typedef struct + { + char** resultId; + OrthancPluginJob *job; + int priority; + } _OrthancPluginSubmitJob; + + /** + * @brief Submit a new job to the jobs engine of Orthanc. + * + * This function adds the given job to the pending jobs of + * Orthanc. Orthanc will take take of freeing it by invoking the + * finalization callback provided to OrthancPluginCreateJob(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job, as received by OrthancPluginCreateJob(). + * @param priority The priority of the job. + * @return ID of the newly-submitted job. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginSubmitJob( + OrthancPluginContext *context, + OrthancPluginJob *job, + int priority) + { + char* resultId = NULL; + + _OrthancPluginSubmitJob params; + memset(¶ms, 0, sizeof(params)); + + params.resultId = &resultId; + params.job = job; + params.priority = priority; + + if (context->InvokeService(context, _OrthancPluginService_SubmitJob, ¶ms) != OrthancPluginErrorCode_Success || + resultId == NULL) + { + /* Error */ + return NULL; + } + else + { + return resultId; + } + } + + + + typedef struct + { + OrthancPluginJobsUnserializer unserializer; + } _OrthancPluginJobsUnserializer; + + /** + * @brief Register an unserializer for custom jobs. + * + * This function registers an unserializer that decodes custom jobs + * from a JSON string. This callback is invoked when the jobs engine + * of Orthanc is started (on Orthanc initialization), for each job + * that is stored in the Orthanc database. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param unserializer The job unserializer. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterJobsUnserializer( + OrthancPluginContext* context, + OrthancPluginJobsUnserializer unserializer) + { + _OrthancPluginJobsUnserializer params; + params.unserializer = unserializer; + + context->InvokeService(context, _OrthancPluginService_RegisterJobsUnserializer, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* details; + uint8_t log; + } _OrthancPluginSetHttpErrorDetails; + + /** + * @brief Provide a detailed description for an HTTP error. + * + * This function sets the detailed description associated with an + * HTTP error. This description will be displayed in the "Details" + * field of the JSON body of the HTTP answer. It is only taken into + * consideration if the REST callback returns an error code that is + * different from "OrthancPluginErrorCode_Success", and if the + * "HttpDescribeErrors" configuration option of Orthanc is set to + * "true". + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param details The details of the error message. + * @param log Whether to also write the detailed error to the Orthanc logs. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpErrorDetails( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* details, + uint8_t log) + { + _OrthancPluginSetHttpErrorDetails params; + params.output = output; + params.details = details; + params.log = log; + context->InvokeService(context, _OrthancPluginService_SetHttpErrorDetails, ¶ms); + } + + + + typedef struct + { + const char** result; + const char* argument; + } _OrthancPluginRetrieveStaticString; + + /** + * @brief Detect the MIME type of a file. + * + * This function returns the MIME type of a file by inspecting its extension. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param path Path to the file. + * @return The MIME type. This is a statically-allocated + * string, do not free it. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginAutodetectMimeType( + OrthancPluginContext* context, + const char* path) + { + const char* result = NULL; + + _OrthancPluginRetrieveStaticString params; + params.result = &result; + params.argument = path; + + if (context->InvokeService(context, _OrthancPluginService_AutodetectMimeType, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + const char* name; + float value; + OrthancPluginMetricsType type; + } _OrthancPluginSetMetricsValue; + + /** + * @brief Set the value of a metrics. + * + * This function sets the value of a metrics to monitor the behavior + * of the plugin through tools such as Prometheus. The values of all + * the metrics are stored within the Orthanc context. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param name The name of the metrics to be set. + * @param value The value of the metrics. + * @param type The type of the metrics. This parameter is only taken into consideration + * the first time this metrics is set. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetMetricsValue( + OrthancPluginContext* context, + const char* name, + float value, + OrthancPluginMetricsType type) + { + _OrthancPluginSetMetricsValue params; + params.name = name; + params.value = value; + params.type = type; + context->InvokeService(context, _OrthancPluginService_SetMetricsValue, ¶ms); + } + + + + typedef struct + { + OrthancPluginRefreshMetricsCallback callback; + } _OrthancPluginRegisterRefreshMetricsCallback; + + /** + * @brief Register a callback to refresh the metrics. + * + * This function registers a callback to refresh the metrics. The + * callback must make calls to OrthancPluginSetMetricsValue(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function to handle the refresh. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRefreshMetricsCallback( + OrthancPluginContext* context, + OrthancPluginRefreshMetricsCallback callback) + { + _OrthancPluginRegisterRefreshMetricsCallback params; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRefreshMetricsCallback, ¶ms); + } + + + + + typedef struct + { + char** target; + const void* dicom; + uint32_t dicomSize; + OrthancPluginDicomWebBinaryCallback callback; + } _OrthancPluginEncodeDicomWeb; + + /** + * @brief Convert a DICOM instance to DICOMweb JSON. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb JSON representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @see OrthancPluginCreateDicom() + * @return The NULL value in case of error, or the JSON document. This string must + * be freed by OrthancPluginFreeString(). + * @deprecated OrthancPluginEncodeDicomWebJson2() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback callback) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb XML. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb XML representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @return The NULL value in case of error, or the XML document. This string must + * be freed by OrthancPluginFreeString(). + * @see OrthancPluginCreateDicom() + * @deprecated OrthancPluginEncodeDicomWebXml2() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback callback) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebXml, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + char** target; + const void* dicom; + uint32_t dicomSize; + OrthancPluginDicomWebBinaryCallback2 callback; + void* payload; + } _OrthancPluginEncodeDicomWeb2; + + /** + * @brief Convert a DICOM instance to DICOMweb JSON. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb JSON representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the JSON document. This string must + * be freed by OrthancPluginFreeString(). + * @see OrthancPluginCreateDicom() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson2( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb2 params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + params.payload = payload; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebJson2, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb XML. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb XML representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the XML document. This string must + * be freed by OrthancPluginFreeString(). + * @see OrthancPluginCreateDicom() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml2( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb2 params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + params.payload = payload; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebXml2, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Callback executed when a HTTP header is received during a chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, as soon as it + * receives one HTTP header from the answer of the remote HTTP + * server. + * + * @see OrthancPluginChunkedHttpClient() + * @param answer The user payload, as provided by the calling plugin. + * @param key The key of the HTTP header. + * @param value The value of the HTTP header. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddHeader) ( + void* answer, + const char* key, + const char* value); + + + /** + * @brief Callback executed when an answer chunk is received during a chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, as soon as it + * receives one data chunk from the answer of the remote HTTP + * server. + * + * @see OrthancPluginChunkedHttpClient() + * @param answer The user payload, as provided by the calling plugin. + * @param data The content of the data chunk. + * @param size The size of the data chunk. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddChunk) ( + void* answer, + const void* data, + uint32_t size); + + + /** + * @brief Callback to know whether the request body is entirely read during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. The plugin must answer "1" as + * soon as the body is entirely read: The "request" data structure + * must act as an iterator. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return "1" if the body is over, or "0" if there is still data to be read. + * @ingroup Toolbox + **/ + typedef uint8_t (*OrthancPluginChunkedClientRequestIsDone) (void* request); + + + /** + * @brief Callback to advance in the request body during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. This function asks the plugin + * to advance to the next chunk of data of the request body: The + * "request" data structure must act as an iterator. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientRequestNext) (void* request); + + + /** + * @brief Callback to read the current chunk of the request body during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. The plugin must provide the + * content of the current chunk of data of the request body. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return The content of the current request chunk. + * @ingroup Toolbox + **/ + typedef const void* (*OrthancPluginChunkedClientRequestGetChunkData) (void* request); + + + /** + * @brief Callback to read the size of the current request chunk during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. The plugin must provide the + * size of the current chunk of data of the request body. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return The size of the current request chunk. + * @ingroup Toolbox + **/ + typedef uint32_t (*OrthancPluginChunkedClientRequestGetChunkSize) (void* request); + + + typedef struct + { + void* answer; + OrthancPluginChunkedClientAnswerAddChunk answerAddChunk; + OrthancPluginChunkedClientAnswerAddHeader answerAddHeader; + uint16_t* httpStatus; + OrthancPluginHttpMethod method; + const char* url; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + void* request; + OrthancPluginChunkedClientRequestIsDone requestIsDone; + OrthancPluginChunkedClientRequestGetChunkData requestChunkData; + OrthancPluginChunkedClientRequestGetChunkSize requestChunkSize; + OrthancPluginChunkedClientRequestNext requestNext; + const char* username; + const char* password; + uint32_t timeout; + const char* certificateFile; + const char* certificateKeyFile; + const char* certificateKeyPassword; + uint8_t pkcs11; + } _OrthancPluginChunkedHttpClient; + + + /** + * @brief Issue a HTTP call, using chunked HTTP transfers. + * + * Make a HTTP call to the given URL using chunked HTTP + * transfers. The request body is provided as an iterator over data + * chunks. The answer is provided as a sequence of function calls + * with the individual HTTP headers and answer chunks. + * + * Contrarily to OrthancPluginHttpClient() that entirely stores the + * request body and the answer body in memory buffers, this function + * uses chunked HTTP transfers. This results in a lower memory + * consumption. Pay attention to the fact that Orthanc servers with + * version <= 1.5.6 do not support chunked transfers: You must use + * OrthancPluginHttpClient() if contacting such older servers. + * + * The HTTP request will be done accordingly to the global + * configuration of Orthanc (in particular, the options "HttpProxy", + * "HttpTimeout", "HttpsVerifyPeers", "HttpsCACertificates", and + * "Pkcs11" will be taken into account). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answer The user payload for the answer body. It will be provided to the callbacks for the answer. + * @param answerAddChunk Callback function to report a data chunk from the answer body. + * @param answerAddHeader Callback function to report an HTTP header sent by the remote server. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param method HTTP method to be used. + * @param url The URL of interest. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param request The user payload containing the request body, and acting as an iterator. + * It will be provided to the callbacks for the request. + * @param requestIsDone Callback function to tell whether the request body is entirely read. + * @param requestChunkData Callback function to get the content of the current data chunk of the request body. + * @param requestChunkSize Callback function to get the size of the current data chunk of the request body. + * @param requestNext Callback function to advance to the next data chunk of the request body. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @param timeout Timeout in seconds (0 for default timeout). + * @param certificateFile Path to the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyPassword Password to unlock the key of the client certificate + * (can be NULL if no client certificate or if not using HTTPS). + * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginHttpClient() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginChunkedHttpClient( + OrthancPluginContext* context, + void* answer, + OrthancPluginChunkedClientAnswerAddChunk answerAddChunk, + OrthancPluginChunkedClientAnswerAddHeader answerAddHeader, + uint16_t* httpStatus, + OrthancPluginHttpMethod method, + const char* url, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + void* request, + OrthancPluginChunkedClientRequestIsDone requestIsDone, + OrthancPluginChunkedClientRequestGetChunkData requestChunkData, + OrthancPluginChunkedClientRequestGetChunkSize requestChunkSize, + OrthancPluginChunkedClientRequestNext requestNext, + const char* username, + const char* password, + uint32_t timeout, + const char* certificateFile, + const char* certificateKeyFile, + const char* certificateKeyPassword, + uint8_t pkcs11) + { + _OrthancPluginChunkedHttpClient params; + memset(¶ms, 0, sizeof(params)); + + /* In common with OrthancPluginHttpClient() */ + params.httpStatus = httpStatus; + params.method = method; + params.url = url; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.username = username; + params.password = password; + params.timeout = timeout; + params.certificateFile = certificateFile; + params.certificateKeyFile = certificateKeyFile; + params.certificateKeyPassword = certificateKeyPassword; + params.pkcs11 = pkcs11; + + /* For chunked body/answer */ + params.answer = answer; + params.answerAddChunk = answerAddChunk; + params.answerAddHeader = answerAddHeader; + params.request = request; + params.requestIsDone = requestIsDone; + params.requestChunkData = requestChunkData; + params.requestChunkSize = requestChunkSize; + params.requestNext = requestNext; + + return context->InvokeService(context, _OrthancPluginService_ChunkedHttpClient, ¶ms); + } + + + + /** + * @brief Opaque structure that reads the content of a HTTP request body during a chunked HTTP transfer. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginServerChunkedRequestReader_t OrthancPluginServerChunkedRequestReader; + + + + /** + * @brief Callback to create a reader to handle incoming chunked HTTP transfers. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This + * callback is only invoked if the HTTP method is POST or PUT. The + * callback must create an user-specific "reader" object that will + * be fed with the body of the incoming body. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader Memory location that must be filled with the newly-created reader. + * @param url The URI that is accessed. + * @param request The body of the HTTP request. Note that "body" and "bodySize" are not used. + * @return 0 if success, or the error code if failure. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderFactory) ( + OrthancPluginServerChunkedRequestReader** reader, + const char* url, + const OrthancPluginHttpRequest* request); + + + /** + * @brief Callback invoked whenever a new data chunk is available during a chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This callback + * is invoked as soon as a new data chunk is available for the request body. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback. + * @param data The content of the data chunk. + * @param size The size of the data chunk. + * @return 0 if success, or the error code if failure. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderAddChunk) ( + OrthancPluginServerChunkedRequestReader* reader, + const void* data, + uint32_t size); + + + /** + * @brief Callback invoked whenever the request body is entirely received. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This + * callback is invoked as soon as the full body of the HTTP request + * is available. The plugin can then send its answer thanks to the + * provided "output" object. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback. + * @param output The HTTP connection to the client application. + * @return 0 if success, or the error code if failure. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderExecute) ( + OrthancPluginServerChunkedRequestReader* reader, + OrthancPluginRestOutput* output); + + + /** + * @brief Callback invoked to release the resources associated with an incoming HTTP chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This + * callback is invoked to release all the resources allocated by the + * given reader. Note that this function might be invoked even if + * the entire body was not read, to deal with client error or + * disconnection. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback. + **/ + typedef void (*OrthancPluginServerChunkedRequestReaderFinalize) ( + OrthancPluginServerChunkedRequestReader* reader); + + typedef struct + { + const char* pathRegularExpression; + OrthancPluginRestCallback getHandler; + OrthancPluginServerChunkedRequestReaderFactory postHandler; + OrthancPluginRestCallback deleteHandler; + OrthancPluginServerChunkedRequestReaderFactory putHandler; + OrthancPluginServerChunkedRequestReaderAddChunk addChunk; + OrthancPluginServerChunkedRequestReaderExecute execute; + OrthancPluginServerChunkedRequestReaderFinalize finalize; + } _OrthancPluginChunkedRestCallback; + + + /** + * @brief Register a REST callback to handle chunked HTTP transfers. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Contrarily to OrthancPluginRegisterRestCallback(), the callbacks + * will NOT be invoked in mutual exclusion, so it is up to the + * plugin to implement the required locking mechanisms. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param getHandler The callback function to handle REST calls using the GET HTTP method. + * @param postHandler The callback function to handle REST calls using the POST HTTP method. + * @param deleteHandler The callback function to handle REST calls using the DELETE HTTP method. + * @param putHandler The callback function to handle REST calls using the PUT HTTP method. + * @param addChunk The callback invoked when a new chunk is available for the request body of a POST or PUT call. + * @param execute The callback invoked once the entire body of a POST or PUT call is read. + * @param finalize The callback invoked to release the resources associated with a POST or PUT call. + * @see OrthancPluginRegisterRestCallbackNoLock() + * + * @note + * The regular expression is case sensitive and must follow the + * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html). + * + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterChunkedRestCallback( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback getHandler, + OrthancPluginServerChunkedRequestReaderFactory postHandler, + OrthancPluginRestCallback deleteHandler, + OrthancPluginServerChunkedRequestReaderFactory putHandler, + OrthancPluginServerChunkedRequestReaderAddChunk addChunk, + OrthancPluginServerChunkedRequestReaderExecute execute, + OrthancPluginServerChunkedRequestReaderFinalize finalize) + { + _OrthancPluginChunkedRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.getHandler = getHandler; + params.postHandler = postHandler; + params.deleteHandler = deleteHandler; + params.putHandler = putHandler; + params.addChunk = addChunk; + params.execute = execute; + params.finalize = finalize; + + context->InvokeService(context, _OrthancPluginService_RegisterChunkedRestCallback, ¶ms); + } + + + + + + typedef struct + { + char** result; + uint16_t group; + uint16_t element; + const char* privateCreator; + } _OrthancPluginGetTagName; + + /** + * @brief Returns the symbolic name of a DICOM tag. + * + * This function makes a lookup to the dictionary of DICOM tags that + * are known to Orthanc, and returns the symbolic name of a DICOM tag. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param privateCreator For private tags, the name of the private creator (can be NULL). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetTagName( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + const char* privateCreator) + { + char* result; + + _OrthancPluginGetTagName params; + params.result = &result; + params.group = group; + params.element = element; + params.privateCreator = privateCreator; + + if (context->InvokeService(context, _OrthancPluginService_GetTagName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + /** + * @brief Callback executed by the storage commitment SCP. + * + * Signature of a factory function that creates an object to handle + * one incoming storage commitment request. + * + * @remark The factory receives the list of the SOP class/instance + * UIDs of interest to the remote storage commitment SCU. This gives + * the factory the possibility to start some prefetch process + * upfront in the background, before the handler object is actually + * queried about the status of these DICOM instances. + * + * @param handler Output variable where the factory puts the handler object it created. + * @param jobId ID of the Orthanc job that is responsible for handling + * the storage commitment request. This job will successively look for the + * status of all the individual queried DICOM instances. + * @param transactionUid UID of the storage commitment transaction + * provided by the storage commitment SCU. It contains the value of the + * (0008,1195) DICOM tag. + * @param sopClassUids Array of the SOP class UIDs (0008,0016) that are queried by the SCU. + * @param sopInstanceUids Array of the SOP instance UIDs (0008,0018) that are queried by the SCU. + * @param countInstances Number of DICOM instances that are queried. This is the size + * of the `sopClassUids` and `sopInstanceUids` arrays. + * @param remoteAet The AET of the storage commitment SCU. + * @param calledAet The AET used by the SCU to contact the storage commitment SCP (i.e. Orthanc). + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentFactory) ( + void** handler /* out */, + const char* jobId, + const char* transactionUid, + const char* const* sopClassUids, + const char* const* sopInstanceUids, + uint32_t countInstances, + const char* remoteAet, + const char* calledAet); + + + /** + * @brief Callback to free one storage commitment SCP handler. + * + * Signature of a callback function that releases the resources + * allocated by the factory of the storage commitment SCP. The + * handler is the return value of a previous call to the + * OrthancPluginStorageCommitmentFactory() callback. + * + * @param handler The handler object to be destructed. + * @ingroup DicomCallbacks + **/ + typedef void (*OrthancPluginStorageCommitmentDestructor) (void* handler); + + + /** + * @brief Callback to get the status of one DICOM instance in the + * storage commitment SCP. + * + * Signature of a callback function that is successively invoked for + * each DICOM instance that is queried by the remote storage + * commitment SCU. The function must be tought of as a method of + * the handler object that was created by a previous call to the + * OrthancPluginStorageCommitmentFactory() callback. After each call + * to this method, the progress of the associated Orthanc job is + * updated. + * + * @param target Output variable where to put the status for the queried instance. + * @param handler The handler object associated with this storage commitment request. + * @param sopClassUid The SOP class UID (0008,0016) of interest. + * @param sopInstanceUid The SOP instance UID (0008,0018) of interest. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentLookup) ( + OrthancPluginStorageCommitmentFailureReason* target, + void* handler, + const char* sopClassUid, + const char* sopInstanceUid); + + + typedef struct + { + OrthancPluginStorageCommitmentFactory factory; + OrthancPluginStorageCommitmentDestructor destructor; + OrthancPluginStorageCommitmentLookup lookup; + } _OrthancPluginRegisterStorageCommitmentScpCallback; + + /** + * @brief Register a callback to handle incoming requests to the storage commitment SCP. + * + * This function registers a callback to handle storage commitment SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @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 function to get the status of one DICOM instance. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterStorageCommitmentScpCallback( + OrthancPluginContext* context, + OrthancPluginStorageCommitmentFactory factory, + OrthancPluginStorageCommitmentDestructor destructor, + OrthancPluginStorageCommitmentLookup lookup) + { + _OrthancPluginRegisterStorageCommitmentScpCallback params; + params.factory = factory; + params.destructor = destructor; + params.lookup = lookup; + return context->InvokeService(context, _OrthancPluginService_RegisterStorageCommitmentScpCallback, ¶ms); + } + + + + /** + * @brief Callback to filter incoming DICOM instances received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a new DICOM instance (e.g. through REST API or + * DICOM protocol), and that answers whether this DICOM instance + * should be accepted or discarded by Orthanc. + * + * Note that the metadata information is not available + * (i.e. GetInstanceMetadata() should not be used on "instance"). + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param instance The received DICOM instance. + * @return 0 to discard the instance, 1 to store the instance, -1 if error. + * @ingroup Callbacks + **/ + typedef int32_t (*OrthancPluginIncomingDicomInstanceFilter) ( + const OrthancPluginDicomInstance* instance); + + + typedef struct + { + OrthancPluginIncomingDicomInstanceFilter callback; + } _OrthancPluginIncomingDicomInstanceFilter; + + /** + * @brief Register a callback to filter incoming DICOM instances. + * + * This function registers a custom callback to filter incoming + * DICOM instances received by Orthanc (either through the REST API + * or through the DICOM protocol). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingDicomInstanceFilter( + OrthancPluginContext* context, + OrthancPluginIncomingDicomInstanceFilter callback) + { + _OrthancPluginIncomingDicomInstanceFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingDicomInstanceFilter, ¶ms); + } + + + /** + * @brief Callback to filter incoming DICOM instances received by + * Orthanc through C-STORE. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a new DICOM instance using DICOM C-STORE, and + * that answers whether this DICOM instance should be accepted or + * discarded by Orthanc. If the instance is discarded, the callback + * can specify the DIMSE error code answered by the Orthanc C-STORE + * SCP. + * + * Note that the metadata information is not available + * (i.e. GetInstanceMetadata() should not be used on "instance"). + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param dimseStatus If the DICOM instance is discarded, + * 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 Callbacks + **/ + typedef int32_t (*OrthancPluginIncomingCStoreInstanceFilter) ( + uint16_t* dimseStatus /* out */, + const OrthancPluginDicomInstance* instance); + + + typedef struct + { + OrthancPluginIncomingCStoreInstanceFilter callback; + } _OrthancPluginIncomingCStoreInstanceFilter; + + /** + * @brief Register a callback to filter incoming DICOM instances + * received by Orthanc through C-STORE. + * + * This function registers a custom callback to filter incoming + * DICOM instances received by Orthanc through the DICOM protocol. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingCStoreInstanceFilter( + OrthancPluginContext* context, + OrthancPluginIncomingCStoreInstanceFilter callback) + { + _OrthancPluginIncomingCStoreInstanceFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingCStoreInstanceFilter, ¶ms); + } + + /** + * @brief Callback to keep/discard/modify a DICOM instance received + * by Orthanc from any source (C-STORE or REST API) + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a new DICOM instance (through DICOM protocol or + * REST API), and that specifies an action to be applied to this + * newly received instance. The instance can be kept as it is, can + * be modified by the plugin, or can be discarded. + * + * This callback is invoked immediately after reception, i.e. before + * transcoding and before filtering + * (cf. OrthancPluginRegisterIncomingDicomInstanceFilter()). + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param modifiedDicomBuffer A buffer containing the modified DICOM (output). + * This buffer must be allocated using OrthancPluginCreateMemoryBuffer64() + * and will be freed by the Orthanc core. + * @param receivedDicomBuffer A buffer containing the received DICOM (input). + * @param receivedDicomBufferSize The size of the received DICOM (input). + * @param origin The origin of the DICOM instance (input). + * @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 Callbacks + **/ + typedef OrthancPluginReceivedInstanceAction (*OrthancPluginReceivedInstanceCallback) ( + OrthancPluginMemoryBuffer64* modifiedDicomBuffer, + const void* receivedDicomBuffer, + uint64_t receivedDicomBufferSize, + OrthancPluginInstanceOrigin origin); + + + typedef struct + { + OrthancPluginReceivedInstanceCallback callback; + } _OrthancPluginReceivedInstanceCallback; + + /** + * @brief Register a callback to keep/discard/modify a DICOM instance received + * by Orthanc from any source (C-STORE or REST API) + * + * This function registers a custom callback to keep/discard/modify + * incoming DICOM instances received by Orthanc from any source + * (C-STORE or REST API). + * + * @warning Contrarily to + * OrthancPluginRegisterIncomingCStoreInstanceFilter() and + * OrthancPluginRegisterIncomingDicomInstanceFilter() that can be + * called by multiple plugins, + * OrthancPluginRegisterReceivedInstanceCallback() can only be used + * by one single plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterReceivedInstanceCallback( + OrthancPluginContext* context, + OrthancPluginReceivedInstanceCallback callback) + { + _OrthancPluginReceivedInstanceCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterReceivedInstanceCallback, ¶ms); + } + + /** + * @brief Get the transfer syntax of a DICOM file. + * + * This function returns a pointer to a newly created string that + * contains the transfer syntax UID of the DICOM instance. The empty + * string might be returned if this information is unknown. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the + * transfer syntax UID. This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceTransferSyntaxUid( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceTransferSyntaxUid, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Check whether the DICOM file has pixel data. + * + * This function returns a Boolean value indicating whether the + * DICOM instance contains the pixel data (7FE0,0010) tag. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return "1" if the DICOM instance contains pixel data, or "0" if + * the tag is missing, or "-1" in the case of an error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginHasInstancePixelData( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + int64_t hasPixelData; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &hasPixelData; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_HasInstancePixelData, ¶ms) != OrthancPluginErrorCode_Success || + hasPixelData < 0 || + hasPixelData > 1) + { + /* Error */ + return -1; + } + else + { + return (hasPixelData != 0); + } + } + + + + + + + typedef struct + { + OrthancPluginDicomInstance** target; + const void* buffer; + uint32_t size; + const char* transferSyntax; + } _OrthancPluginCreateDicomInstance; + + /** + * @brief Parse a DICOM instance. + * + * This function parses a memory buffer that contains a DICOM + * file. The function returns a new pointer to a data structure that + * is managed by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer containing the DICOM instance. + * @param size The size of the memory buffer. + * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginCreateDicomInstance( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + OrthancPluginDicomInstance* target = NULL; + + _OrthancPluginCreateDicomInstance params; + params.target = ⌖ + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_CreateDicomInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + typedef struct + { + OrthancPluginDicomInstance* dicom; + } _OrthancPluginFreeDicomInstance; + + /** + * @brief Free a DICOM instance. + * + * This function frees a DICOM instance that was parsed using + * OrthancPluginCreateDicomInstance(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom The DICOM instance. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeDicomInstance( + OrthancPluginContext* context, + OrthancPluginDicomInstance* dicom) + { + _OrthancPluginFreeDicomInstance params; + params.dicom = dicom; + + context->InvokeService(context, _OrthancPluginService_FreeDicomInstance, ¶ms); + } + + + typedef struct + { + uint32_t* targetUint32; + OrthancPluginMemoryBuffer* targetBuffer; + OrthancPluginImage** targetImage; + char** targetStringToFree; + const OrthancPluginDicomInstance* instance; + uint32_t frameIndex; + OrthancPluginDicomToJsonFormat format; + OrthancPluginDicomToJsonFlags flags; + uint32_t maxStringLength; + OrthancPluginDicomWebBinaryCallback2 dicomWebCallback; + void* dicomWebPayload; + } _OrthancPluginAccessDicomInstance2; + + /** + * @brief Get the number of frames in a DICOM instance. + * + * This function returns the number of frames that are part of a + * DICOM image managed by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The number of frames (will be zero in the case of an error). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetInstanceFramesCount( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + uint32_t count; + + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetUint32 = &count; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceFramesCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + /** + * @brief Get the raw content of a frame in a DICOM instance. + * + * This function returns a memory buffer containing the raw content + * of a frame in a DICOM instance that is managed by the Orthanc + * core. This is notably useful for compressed transfer syntaxes, as + * it gives access to the embedded files (such as JPEG, JPEG-LS or + * JPEG2k). The Orthanc core transparently reassembles the fragments + * to extract the raw frame. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instance The instance of interest. + * @param frameIndex The index of the frame of interest. + * @return 0 if success, or the error code if failure. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetInstanceRawFrame( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginDicomInstance* instance, + uint32_t frameIndex) + { + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetBuffer = target; + params.instance = instance; + params.frameIndex = frameIndex; + + return context->InvokeService(context, _OrthancPluginService_GetInstanceRawFrame, ¶ms); + } + + + /** + * @brief Decode one frame from a DICOM instance. + * + * This function decodes one frame of a DICOM image that is managed + * by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param frameIndex The index of the frame of interest. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginGetInstanceDecodedFrame( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + uint32_t frameIndex) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetImage = ⌖ + params.instance = instance; + params.frameIndex = frameIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceDecodedFrame, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Parse and transcode a DICOM instance. + * + * This function parses a memory buffer that contains a DICOM file, + * then transcodes it to the given transfer syntax. The function + * returns a new pointer to a data structure that is managed by the + * Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer containing the DICOM instance. + * @param size The size of the memory buffer. + * @param transferSyntax The transfer syntax UID for the transcoding. + * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginTranscodeDicomInstance( + OrthancPluginContext* context, + const void* buffer, + uint32_t size, + const char* transferSyntax) + { + OrthancPluginDicomInstance* target = NULL; + + _OrthancPluginCreateDicomInstance params; + params.target = ⌖ + params.buffer = buffer; + params.size = size; + params.transferSyntax = transferSyntax; + + if (context->InvokeService(context, _OrthancPluginService_TranscodeDicomInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + /** + * @brief Writes a DICOM instance to a memory buffer. + * + * This function returns a memory buffer containing the + * serialization of a DICOM instance that is managed by the Orthanc + * core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instance The instance of interest. + * @return 0 if success, or the error code if failure. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSerializeDicomInstance( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginDicomInstance* instance) + { + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetBuffer = target; + params.instance = instance; + + return context->InvokeService(context, _OrthancPluginService_SerializeDicomInstance, ¶ms); + } + + + /** + * @brief Format a DICOM memory buffer as a JSON string. + * + * This function takes as DICOM instance managed by the Orthanc + * core, and outputs a JSON string representing the tags of this + * DICOM file. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The DICOM instance of interest. + * @param format The output format. + * @param flags Flags governing the output. + * @param maxStringLength The maximum length of a field. Too long fields will + * be output as "null". The 0 value means no maximum length. + * @return The NULL value if the case of an error, or the JSON + * string. This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + * @see OrthancPluginDicomBufferToJson + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceAdvancedJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + char* result = NULL; + + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetStringToFree = &result; + params.instance = instance; + params.format = format; + params.flags = flags; + params.maxStringLength = maxStringLength; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceAdvancedJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb JSON. + * + * This function converts a DICOM instance that is managed by the + * Orthanc core, into its DICOMweb JSON representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The DICOM instance of interest. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the JSON document. This string must + * be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceDicomWebJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginAccessDicomInstance2 params; + params.targetStringToFree = ⌖ + params.instance = instance; + params.dicomWebCallback = callback; + params.dicomWebPayload = payload; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceDicomWebJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb XML. + * + * This function converts a DICOM instance that is managed by the + * Orthanc core, into its DICOMweb XML representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The DICOM instance of interest. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the XML document. This string must + * be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceDicomWebXml( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginAccessDicomInstance2 params; + params.targetStringToFree = ⌖ + params.instance = instance; + params.dicomWebCallback = callback; + params.dicomWebPayload = payload; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceDicomWebXml, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Signature of a callback function to transcode a DICOM instance. + * @param transcoded Target memory buffer. It must be allocated by the + * plugin using OrthancPluginCreateMemoryBuffer(). + * @param buffer Memory buffer containing the source DICOM instance. + * @param size Size of the source memory buffer. + * @param allowedSyntaxes A C array of possible transfer syntaxes UIDs for the + * result of the transcoding. The plugin must choose by itself the + * transfer syntax that will be used for the resulting DICOM image. + * @param countSyntaxes The number of transfer syntaxes that are contained + * in the "allowedSyntaxes" array. + * @param allowNewSopInstanceUid Whether the transcoding plugin can select + * a transfer syntax that will change the SOP instance UID (or, in other + * terms, whether the plugin can transcode using lossy compression). + * @return 0 if success (i.e. image successfully transcoded and stored into + * "transcoded"), or the error code if failure. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginTranscoderCallback) ( + OrthancPluginMemoryBuffer* transcoded /* out */, + const void* buffer, + uint64_t size, + const char* const* allowedSyntaxes, + uint32_t countSyntaxes, + uint8_t allowNewSopInstanceUid); + + + typedef struct + { + OrthancPluginTranscoderCallback callback; + } _OrthancPluginTranscoderCallback; + + /** + * @brief Register a callback to handle the transcoding of DICOM images. + * + * This function registers a custom callback to transcode DICOM + * images, extending the built-in transcoder of Orthanc that uses + * DCMTK. The exact behavior is affected by the configuration option + * "BuiltinDecoderTranscoderOrder" of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterTranscoderCallback( + OrthancPluginContext* context, + OrthancPluginTranscoderCallback callback) + { + _OrthancPluginTranscoderCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterTranscoderCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + uint32_t size; + } _OrthancPluginCreateMemoryBuffer; + + /** + * @brief Create a 32-bit memory buffer. + * + * This function creates a memory buffer that is managed by the + * Orthanc core. The main use case of this function is for plugins + * that act as DICOM transcoders. + * + * Your plugin should never call "free()" on the resulting memory + * buffer, as the C library that is used by the plugin is in general + * not the same as the one used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param size Size of the memory buffer to be created. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateMemoryBuffer( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + uint32_t size) + { + _OrthancPluginCreateMemoryBuffer params; + params.target = target; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_CreateMemoryBuffer, ¶ms); + } + + + /** + * @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 + * of Orthanc using an external HTTP client. Using this function + * avoids the need of adding a separate user in the + * "RegisteredUsers" configuration of Orthanc, which eases + * deployments. + * + * This feature is notably useful in multiprocess scenarios, where a + * subprocess created by a plugin has no access to the + * "OrthancPluginContext", and thus cannot call + * "OrthancPluginRestApi[Get|Post|Put|Delete]()". + * + * This situation is frequently encountered in Python plugins, where + * the "multiprocessing" package can be used to bypass the Global + * Interpreter Lock (GIL) and thus to improve performance and + * concurrency. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The authorization token, or NULL value in the case of an error. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateRestApiAuthorizationToken( + OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GenerateRestApiAuthorizationToken, + ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginMemoryBuffer64* target; + uint64_t size; + } _OrthancPluginCreateMemoryBuffer64; + + /** + * @brief Create a 64-bit memory buffer. + * + * This function creates a 64-bit memory buffer that is managed by + * the Orthanc core. The main use case of this function is for + * plugins that read files from the storage area. + * + * Your plugin should never call "free()" on the resulting memory + * buffer, as the C library that is used by the plugin is in general + * not the same as the one used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param size Size of the memory buffer to be created. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateMemoryBuffer64( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer64* target, + uint64_t size) + { + _OrthancPluginCreateMemoryBuffer64 params; + params.target = target; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_CreateMemoryBuffer64, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageCreate create; + OrthancPluginStorageReadWhole readWhole; + OrthancPluginStorageReadRange readRange; + OrthancPluginStorageRemove remove; + } _OrthancPluginRegisterStorageArea2; + + /** + * @brief Register a custom storage area, with support for range request. + * + * This function registers a custom storage area, to replace the + * built-in way Orthanc stores its files on the filesystem. 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 create The callback function to store a file on the custom storage area. + * @param readWhole The callback function to read a whole file from the custom storage area. + * @param readRange The callback function to read some range of a file from the custom storage area. + * If this feature is not supported by the plugin, this value can be set to NULL. + * @param remove The callback function to remove a file from the custom storage area. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea2( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageReadWhole readWhole, + OrthancPluginStorageReadRange readRange, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea2 params; + params.create = create; + params.readWhole = readWhole; + params.readRange = readRange; + params.remove = remove; + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea2, ¶ms); + } + + + + typedef struct + { + _OrthancPluginCreateDicom createDicom; + const char* privateCreator; + } _OrthancPluginCreateDicom2; + + /** + * @brief Create a DICOM instance from a JSON string and an image, with a private creator. + * + * This function takes as input a string containing a JSON file + * describing the content of a DICOM instance. As an output, it + * writes the corresponding DICOM instance to a newly allocated + * memory buffer. Additionally, an image to be encoded within the + * DICOM instance can also be provided. + * + * Contrarily to the function OrthancPluginCreateDicom(), this + * function can be explicitly provided with a private creator. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param json The input JSON file. + * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. + * @param flags Flags governing the output. + * @param privateCreator The private creator to be used for the private DICOM tags. + * Check out the global configuration option "Dictionary" of Orthanc. + * @return 0 if success, other value if error. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom + * @see OrthancPluginDicomBufferToJson + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom2( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* json, + const OrthancPluginImage* pixelData, + OrthancPluginCreateDicomFlags flags, + const char* privateCreator) + { + _OrthancPluginCreateDicom2 params; + params.createDicom.target = target; + params.createDicom.json = json; + params.createDicom.pixelData = pixelData; + params.createDicom.flags = flags; + params.privateCreator = privateCreator; + + return context->InvokeService(context, _OrthancPluginService_CreateDicom2, ¶ms); + } + + + + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + OrthancPluginHttpMethod method; + const char* uri; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + const void* body; + uint32_t bodySize; + uint8_t afterPlugins; + } _OrthancPluginCallRestApi; + + /** + * @brief Call the REST API of Orthanc with full flexibility. + * + * Make a call to the given URI in the REST API of Orthanc. The + * result to the query is stored into a newly allocated memory + * buffer. This function is always granted full access to the REST + * API (no credentials, nor security token is needed). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * The value of this argument is ignored if the HTTP method is DELETE. + * @param answerHeaders The target memory buffer for the HTTP headers in the answer (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the answer HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param method HTTP method to be used. + * @param uri The URI of interest. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param afterPlugins If 0, the built-in API of Orthanc is used. + * If 1, the API is tainted by the plugins. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet2, OrthancPluginRestApiPost, OrthancPluginRestApiPut, OrthancPluginRestApiDelete + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCallRestApi( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + OrthancPluginHttpMethod method, + const char* uri, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + const void* body, + uint32_t bodySize, + uint8_t afterPlugins) + { + _OrthancPluginCallRestApi params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.method = method; + params.uri = uri; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.body = body; + params.bodySize = bodySize; + params.afterPlugins = afterPlugins; + + return context->InvokeService(context, _OrthancPluginService_CallRestApi, ¶ms); + } + + + + /** + * @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); + } + + + /** + * @brief Gets the DatabaseServerIdentifier. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return the database server identifier. This is a statically-allocated + * string, do not free it. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetDatabaseServerIdentifier( + OrthancPluginContext* context) + { + const char* result; + + _OrthancPluginRetrieveStaticString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetDatabaseServerIdentifier, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Signature of a callback function that is triggered when + * the Orthanc core requests an operation from the database plugin. + * Both request and response are encoded as protobuf buffers. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginCallDatabaseBackendV4) ( + OrthancPluginMemoryBuffer64* response, + void* backend, + const void* request, + uint64_t requestSize); + + /** + * @brief Signature of a callback function that is triggered when + * the database plugin must be finalized. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginFinalizeDatabaseBackendV4) (void* backend); + + typedef struct + { + void* backend; + uint32_t maxDatabaseRetries; + OrthancPluginCallDatabaseBackendV4 operations; + OrthancPluginFinalizeDatabaseBackendV4 finalize; + } _OrthancPluginRegisterDatabaseBackendV4; + + /** + * Register a custom database back-end. + * + * This function was added in Orthanc SDK 1.12.0. It uses Google + * Protocol Buffers for the communications between the Orthanc core + * and database plugins. Check out "OrthancDatabasePlugin.proto" for + * the definition of the protobuf messages. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param backend Pointer to the custom database backend. + * @param maxDatabaseRetries Maximum number of retries if transaction doesn't succeed. + * If no retry is successful, OrthancPluginErrorCode_DatabaseCannotSerialize is generated. + * @param operations Access to the operations of the custom database backend. + * @param finalize Callback to deallocate the custom database backend. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDatabaseBackendV4( + OrthancPluginContext* context, + void* backend, + uint32_t maxDatabaseRetries, + OrthancPluginCallDatabaseBackendV4 operations, + OrthancPluginFinalizeDatabaseBackendV4 finalize) + { + _OrthancPluginRegisterDatabaseBackendV4 params; + params.backend = backend; + params.maxDatabaseRetries = maxDatabaseRetries; + params.operations = operations; + params.finalize = finalize; + + return context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV4, ¶ms); + } + +#ifdef __cplusplus +} +#endif + + +/** @} */ + diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Sdk-1.12.0/orthanc/OrthancDatabasePlugin.proto --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Orthanc/Sdk-1.12.0/orthanc/OrthancDatabasePlugin.proto Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,904 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2021-2023 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 . + **/ + + +/** + * This Protocol Buffers prototype describes the exchanges between the + * Orthanc core and its database plugins. The various calls correspond + * to the "IDatabaseWrapper" interface in the source code of Orthanc. + * + * WARNING: *NEVER* modify or remove existing entries. It is only + * allowed to *add* new stuff. + **/ + +syntax = "proto3"; + +/** + * Turn off protobuf reflection to avoid clashes between the Orthanc + * core and the database plugin, otherwise both will try to register + * the same messages in the process-wide descriptor pool, which would + * result in protobuf error "File already exists in database". + **/ +option optimize_for = LITE_RUNTIME; + +package Orthanc.DatabasePluginMessages; + + +/** + * Data structures that are common with the Orthanc core. + **/ + +message FileInfo { + string uuid = 1; + int32 content_type = 2; // opaque "FileContentType" in Orthanc + uint64 uncompressed_size = 3; + string uncompressed_hash = 4; + int32 compression_type = 5; // opaque "CompressionType" in Orthanc + uint64 compressed_size = 6; + string compressed_hash = 7; +} + +enum ResourceType { + RESOURCE_PATIENT = 0; + RESOURCE_STUDY = 1; + RESOURCE_SERIES = 2; + RESOURCE_INSTANCE = 3; +} + +enum ConstraintType { + CONSTRAINT_EQUAL = 0; + CONSTRAINT_SMALLER_OR_EQUAL = 1; + CONSTRAINT_GREATER_OR_EQUAL = 2; + CONSTRAINT_WILDCARD = 3; + CONSTRAINT_LIST = 4; +} + +enum LabelsConstraintType { + LABELS_CONSTRAINT_ALL = 0; + LABELS_CONSTRAINT_ANY = 1; + LABELS_CONSTRAINT_NONE = 2; +} + +message ServerIndexChange { + int64 seq = 1; + int32 change_type = 2; // opaque "ChangeType" in Orthanc + ResourceType resource_type = 3; + string public_id = 4; + string date = 5; +} + +message ExportedResource { + int64 seq = 1; + ResourceType resource_type = 2; + string public_id = 3; + string modality = 4; + string date = 5; + string patient_id = 6; + string study_instance_uid = 7; + string series_instance_uid = 8; + string sop_instance_uid = 9; +} + +message DatabaseConstraint { + ResourceType level = 1; + uint32 tag_group = 2; + uint32 tag_element = 3; + bool is_identifier_tag = 4; + bool is_case_sensitive = 5; + bool is_mandatory = 6; + ConstraintType type = 7; + repeated string values = 8; +} + + +/** + * Database-level operations. + **/ + +enum DatabaseOperation { + OPERATION_GET_SYSTEM_INFORMATION = 0; + OPERATION_OPEN = 1; + OPERATION_CLOSE = 2; + OPERATION_FLUSH_TO_DISK = 3; + OPERATION_START_TRANSACTION = 4; + OPERATION_UPGRADE = 5; + OPERATION_FINALIZE_TRANSACTION = 6; +} + +enum TransactionType { + TRANSACTION_READ_ONLY = 0; + TRANSACTION_READ_WRITE = 1; +} + +message GetSystemInformation { + message Request { + } + message Response { + uint32 database_version = 1; + bool supports_flush_to_disk = 2; + bool supports_revisions = 3; + bool supports_labels = 4; + } +} + +message Open { + message Request { + message IdentifierTag { + ResourceType level = 1; + uint32 group = 2; + uint32 element = 3; + string name = 4; + } + repeated IdentifierTag identifier_tags = 1; + } + message Response { + } +} + +message Close { + message Request { + } + message Response { + } +} + +message FlushToDisk { + message Request { + } + message Response { + } +} + +message StartTransaction { + message Request { + TransactionType type = 1; + } + message Response { + sfixed64 transaction = 1; + } +} + +message Upgrade { + /** + * It is guaranteed that a read-write transaction is created by the + * Orthanc core before executing this operation. + **/ + message Request { + uint32 target_version = 1; + sfixed64 storage_area = 2; + sfixed64 transaction = 3; + } + message Response { + } +} + +message FinalizeTransaction { + message Request { + sfixed64 transaction = 1; + } + message Response { + } +} + +message DatabaseRequest { + sfixed64 database = 1; + DatabaseOperation operation = 2; + + GetSystemInformation.Request get_system_information = 100; + Open.Request open = 101; + Close.Request close = 102; + FlushToDisk.Request flush_to_disk = 103; + StartTransaction.Request start_transaction = 104; + Upgrade.Request upgrade = 105; + FinalizeTransaction.Request finalize_transaction = 106; +} + +message DatabaseResponse { + GetSystemInformation.Response get_system_information = 100; + Open.Response open = 101; + Close.Response close = 102; + FlushToDisk.Response flush_to_disk = 103; + StartTransaction.Response start_transaction = 104; + Upgrade.Response upgrade = 105; + FinalizeTransaction.Response finalize_transaction = 106; +} + + +/** + * Transaction-level operations. + **/ + +enum TransactionOperation { + OPERATION_ROLLBACK = 0; + OPERATION_COMMIT = 1; + OPERATION_ADD_ATTACHMENT = 2; + OPERATION_CLEAR_CHANGES = 3; + OPERATION_CLEAR_EXPORTED_RESOURCES = 4; + OPERATION_DELETE_ATTACHMENT = 5; + OPERATION_DELETE_METADATA = 6; + OPERATION_DELETE_RESOURCE = 7; + OPERATION_GET_ALL_METADATA = 8; + OPERATION_GET_ALL_PUBLIC_IDS = 9; + OPERATION_GET_ALL_PUBLIC_IDS_WITH_LIMITS = 10; + OPERATION_GET_CHANGES = 11; + OPERATION_GET_CHILDREN_INTERNAL_ID = 12; + OPERATION_GET_CHILDREN_PUBLIC_ID = 13; + OPERATION_GET_EXPORTED_RESOURCES = 14; + OPERATION_GET_LAST_CHANGE = 15; + OPERATION_GET_LAST_EXPORTED_RESOURCE = 16; + OPERATION_GET_MAIN_DICOM_TAGS = 17; + OPERATION_GET_PUBLIC_ID = 18; + OPERATION_GET_RESOURCES_COUNT = 19; + OPERATION_GET_RESOURCE_TYPE = 20; + OPERATION_GET_TOTAL_COMPRESSED_SIZE = 21; + OPERATION_GET_TOTAL_UNCOMPRESSED_SIZE = 22; + OPERATION_IS_PROTECTED_PATIENT = 23; + OPERATION_LIST_AVAILABLE_ATTACHMENTS = 24; + OPERATION_LOG_CHANGE = 25; + OPERATION_LOG_EXPORTED_RESOURCE = 26; + OPERATION_LOOKUP_ATTACHMENT = 27; + OPERATION_LOOKUP_GLOBAL_PROPERTY = 28; + OPERATION_LOOKUP_METADATA = 29; + OPERATION_LOOKUP_PARENT = 30; + OPERATION_LOOKUP_RESOURCE = 31; + OPERATION_SELECT_PATIENT_TO_RECYCLE = 32; + OPERATION_SELECT_PATIENT_TO_RECYCLE_WITH_AVOID = 33; + OPERATION_SET_GLOBAL_PROPERTY = 34; + OPERATION_CLEAR_MAIN_DICOM_TAGS = 35; + OPERATION_SET_METADATA = 36; + OPERATION_SET_PROTECTED_PATIENT = 37; + OPERATION_IS_DISK_SIZE_ABOVE = 38; + OPERATION_LOOKUP_RESOURCES = 39; + OPERATION_CREATE_INSTANCE = 40; + OPERATION_SET_RESOURCES_CONTENT = 41; + OPERATION_GET_CHILDREN_METADATA = 42; + OPERATION_GET_LAST_CHANGE_INDEX = 43; + OPERATION_LOOKUP_RESOURCE_AND_PARENT = 44; + OPERATION_ADD_LABEL = 45; // New in Orthanc 1.12.0 + OPERATION_REMOVE_LABEL = 46; // New in Orthanc 1.12.0 + OPERATION_LIST_LABELS = 47; // New in Orthanc 1.12.0 +} + +message Rollback { + message Request { + } + message Response { + } +} + +message Commit { + message Request { + int64 file_size_delta = 1; + } + message Response { + } +} + +message AddAttachment { + message Request { + int64 id = 1; + FileInfo attachment = 2; + int64 revision = 3; + } + message Response { + } +} + +message ClearChanges { + message Request { + } + message Response { + } +} + +message ClearExportedResources { + message Request { + } + message Response { + } +} + +message DeleteAttachment { + message Request { + int64 id = 1; + int32 type = 2; + } + message Response { + FileInfo deleted_attachment = 1; + } +} + +message DeleteMetadata { + message Request { + int64 id = 1; + int32 type = 2; + } + message Response { + } +} + +message DeleteResource { + message Request { + int64 id = 1; + } + message Response { + message Resource { + ResourceType level = 1; + string public_id = 2; + } + repeated FileInfo deleted_attachments = 1; + repeated Resource deleted_resources = 2; + bool is_remaining_ancestor = 3; + Resource remaining_ancestor = 4; + } +} + +message GetAllMetadata { + message Request { + int64 id = 1; + } + message Response { + message Metadata { + int32 type = 1; + string value = 2; + } + repeated Metadata metadata = 1; + } +} + +message GetAllPublicIds { + message Request { + ResourceType resource_type = 1; + } + message Response { + repeated string ids = 1; + } +} + +message GetAllPublicIdsWithLimits { + message Request { + ResourceType resource_type = 1; + int64 since = 2; + uint32 limit = 3; + } + message Response { + repeated string ids = 1; + } +} + +message GetChanges { + message Request { + int64 since = 1; + uint32 limit = 2; + } + message Response { + repeated ServerIndexChange changes = 1; + bool done = 2; + } +} + +message GetChildrenInternalId { + message Request { + int64 id = 1; + } + message Response { + repeated int64 ids = 1; + } +} + +message GetChildrenPublicId { + message Request { + int64 id = 1; + } + message Response { + repeated string ids = 1; + } +} + +message GetExportedResources { + message Request { + int64 since = 1; + uint32 limit = 2; + } + message Response { + repeated ExportedResource resources = 1; + bool done = 2; + } +} + +message GetLastChange { + message Request { + } + message Response { + bool found = 1; + ServerIndexChange change = 2; + } +} + +message GetLastExportedResource { + message Request { + } + message Response { + bool found = 1; + ExportedResource resource = 2; + } +} + +message GetMainDicomTags { + message Request { + int64 id = 1; + } + message Response { + message Tag { + uint32 group = 1; + uint32 element = 2; + string value = 3; + } + repeated Tag tags = 1; + } +} + +message GetPublicId { + message Request { + int64 id = 1; + } + message Response { + string id = 1; + } +} + +message GetResourcesCount { + message Request { + ResourceType type = 1; + } + message Response { + uint64 count = 1; + } +} + +message GetResourceType { + message Request { + int64 id = 1; + } + message Response { + ResourceType type = 1; + } +} + +message GetTotalCompressedSize { + message Request { + } + message Response { + uint64 size = 1; + } +} + +message GetTotalUncompressedSize { + message Request { + } + message Response { + uint64 size = 1; + } +} + +message IsProtectedPatient { + message Request { + int64 patient_id = 1; + } + message Response { + bool protected_patient = 1; + } +} + +message ListAvailableAttachments { + message Request { + int64 id = 1; + } + message Response { + repeated int32 attachments = 1; + } +} + +message LogChange { + message Request { + int32 change_type = 1; + ResourceType resource_type = 2; + int64 resource_id = 3; + string date = 4; + } + message Response { + } +} + +message LogExportedResource { + message Request { + ResourceType resource_type = 1; + string public_id = 2; + string modality = 3; + string date = 4; + string patient_id = 5; + string study_instance_uid = 6; + string series_instance_uid = 7; + string sop_instance_uid = 8; + } + message Response { + } +} + +message LookupAttachment { + message Request { + int64 id = 1; + int32 content_type = 2; + } + message Response { + bool found = 1; + FileInfo attachment = 2; + int64 revision = 3; + } +} + +message LookupGlobalProperty { + message Request { + string server_id = 1; + int32 property = 2; + } + message Response { + bool found = 1; + string value = 2; + } +} + +message LookupMetadata { + message Request { + int64 id = 1; + int32 metadata_type = 2; + } + message Response { + bool found = 1; + string value = 2; + int64 revision = 3; + } +} + +message LookupParent { + message Request { + int64 id = 1; + } + message Response { + bool found = 1; + int64 parent = 2; + } +} + +message LookupResource { + message Request { + string public_id = 1; + } + message Response { + bool found = 1; + int64 internal_id = 2; + ResourceType type = 3; + } +} + +message SelectPatientToRecycle { + message Request { + } + message Response { + bool found = 1; + int64 patient_id = 2; + } +} + +message SelectPatientToRecycleWithAvoid { + message Request { + int64 patient_id_to_avoid = 1; + } + message Response { + bool found = 1; + int64 patient_id = 2; + } +} + +message SetGlobalProperty { + message Request { + string server_id = 1; + int32 property = 2; + string value = 3; + } + message Response { + } +} + +message ClearMainDicomTags { + message Request { + int64 id = 1; + } + message Response { + } +} + +message SetMetadata { + message Request { + int64 id = 1; + int32 metadata_type = 2; + string value = 3; + int64 revision = 4; + } + message Response { + } +} + +message SetProtectedPatient { + message Request { + int64 patient_id = 1; + bool protected_patient = 2; + } + message Response { + } +} + +message IsDiskSizeAbove { + message Request { + uint64 threshold = 1; + } + message Response { + bool result = 1; + } +} + +message LookupResources { + message Request { + repeated DatabaseConstraint lookup = 1; + ResourceType query_level = 2; + uint32 limit = 3; + bool retrieve_instances_ids = 4; + repeated string labels = 5; // New in Orthanc 1.12.0 + LabelsConstraintType labels_constraint = 6; // New in Orthanc 1.12.0 + } + message Response { + repeated string resources_ids = 1; + repeated string instances_ids = 2; // Only filled if "retrieve_instances" is true + } +} + +message CreateInstance { + message Request { + string patient = 1; + string study = 2; + string series = 3; + string instance = 4; + } + message Response { + bool is_new_instance = 1; + int64 instance_id = 2; + + // The fields below are only set if "is_new_instance" is true + bool is_new_patient = 3; + bool is_new_study = 4; + bool is_new_series = 5; + int64 patient_id = 6; + int64 study_id = 7; + int64 series_id = 8; + } +} + +message SetResourcesContent { + message Request { + message Tag { + int64 resource_id = 1; + bool is_identifier = 2; + uint32 group = 3; + uint32 element = 4; + string value = 5; + } + + message Metadata { + int64 resource_id = 1; + int32 metadata = 2; + string value = 3; + } + + repeated Tag tags = 1; + repeated Metadata metadata = 2; + } + message Response { + } +} + +message GetChildrenMetadata { + message Request { + int64 id = 1; + int32 metadata = 2; + } + message Response { + repeated string values = 1; + } +} + +message GetLastChangeIndex { + message Request { + } + message Response { + int64 result = 1; + } +} + +message LookupResourceAndParent { + message Request { + string public_id = 1; + } + message Response { + bool found = 1; + int64 id = 2; + ResourceType type = 3; + string parent_public_id = 4; // Only for study, series, or instance + } +} + +message AddLabel { + message Request { + int64 id = 1; + string label = 2; + } + message Response { + } +} + +message RemoveLabel { + message Request { + int64 id = 1; + string label = 2; + } + message Response { + } +} + +message ListLabels { + message Request { + bool single_resource = 1; + int64 id = 2; // Only if "single_resource" is "true" + } + message Response { + repeated string labels = 1; + } +} + +message TransactionRequest { + sfixed64 transaction = 1; + TransactionOperation operation = 2; + + Rollback.Request rollback = 100; + Commit.Request commit = 101; + AddAttachment.Request add_attachment = 102; + ClearChanges.Request clear_changes = 103; + ClearExportedResources.Request clear_exported_resources = 104; + DeleteAttachment.Request delete_attachment = 105; + DeleteMetadata.Request delete_metadata = 106; + DeleteResource.Request delete_resource = 107; + GetAllMetadata.Request get_all_metadata = 108; + GetAllPublicIds.Request get_all_public_ids = 109; + GetAllPublicIdsWithLimits.Request get_all_public_ids_with_limits = 110; + GetChanges.Request get_changes = 111; + GetChildrenInternalId.Request get_children_internal_id = 112; + GetChildrenPublicId.Request get_children_public_id = 113; + GetExportedResources.Request get_exported_resources = 114; + GetLastChange.Request get_last_change = 115; + GetLastExportedResource.Request get_last_exported_resource = 116; + GetMainDicomTags.Request get_main_dicom_tags = 117; + GetPublicId.Request get_public_id = 118; + GetResourcesCount.Request get_resources_count = 119; + GetResourceType.Request get_resource_type = 120; + GetTotalCompressedSize.Request get_total_compressed_size = 121; + GetTotalUncompressedSize.Request get_total_uncompressed_size = 122; + IsProtectedPatient.Request is_protected_patient = 123; + ListAvailableAttachments.Request list_available_attachments = 124; + LogChange.Request log_change = 125; + LogExportedResource.Request log_exported_resource = 126; + LookupAttachment.Request lookup_attachment = 127; + LookupGlobalProperty.Request lookup_global_property = 128; + LookupMetadata.Request lookup_metadata = 129; + LookupParent.Request lookup_parent = 130; + LookupResource.Request lookup_resource = 131; + SelectPatientToRecycle.Request select_patient_to_recycle = 132; + SelectPatientToRecycleWithAvoid.Request select_patient_to_recycle_with_avoid = 133; + SetGlobalProperty.Request set_global_property = 134; + ClearMainDicomTags.Request clear_main_dicom_tags = 135; + SetMetadata.Request set_metadata = 136; + SetProtectedPatient.Request set_protected_patient = 137; + IsDiskSizeAbove.Request is_disk_size_above = 138; + LookupResources.Request lookup_resources = 139; + CreateInstance.Request create_instance = 140; + SetResourcesContent.Request set_resources_content = 141; + GetChildrenMetadata.Request get_children_metadata = 142; + GetLastChangeIndex.Request get_last_change_index = 143; + LookupResourceAndParent.Request lookup_resource_and_parent = 144; + AddLabel.Request add_label = 145; + RemoveLabel.Request remove_label = 146; + ListLabels.Request list_labels = 147; +} + +message TransactionResponse { + Rollback.Response rollback = 100; + Commit.Response commit = 101; + AddAttachment.Response add_attachment = 102; + ClearChanges.Response clear_changes = 103; + ClearExportedResources.Response clear_exported_resources = 104; + DeleteAttachment.Response delete_attachment = 105; + DeleteMetadata.Response delete_metadata = 106; + DeleteResource.Response delete_resource = 107; + GetAllMetadata.Response get_all_metadata = 108; + GetAllPublicIds.Response get_all_public_ids = 109; + GetAllPublicIdsWithLimits.Response get_all_public_ids_with_limits = 110; + GetChanges.Response get_changes = 111; + GetChildrenInternalId.Response get_children_internal_id = 112; + GetChildrenPublicId.Response get_children_public_id = 113; + GetExportedResources.Response get_exported_resources = 114; + GetLastChange.Response get_last_change = 115; + GetLastExportedResource.Response get_last_exported_resource = 116; + GetMainDicomTags.Response get_main_dicom_tags = 117; + GetPublicId.Response get_public_id = 118; + GetResourcesCount.Response get_resources_count = 119; + GetResourceType.Response get_resource_type = 120; + GetTotalCompressedSize.Response get_total_compressed_size = 121; + GetTotalUncompressedSize.Response get_total_uncompressed_size = 122; + IsProtectedPatient.Response is_protected_patient = 123; + ListAvailableAttachments.Response list_available_attachments = 124; + LogChange.Response log_change = 125; + LogExportedResource.Response log_exported_resource = 126; + LookupAttachment.Response lookup_attachment = 127; + LookupGlobalProperty.Response lookup_global_property = 128; + LookupMetadata.Response lookup_metadata = 129; + LookupParent.Response lookup_parent = 130; + LookupResource.Response lookup_resource = 131; + SelectPatientToRecycle.Response select_patient_to_recycle = 132; + SelectPatientToRecycleWithAvoid.Response select_patient_to_recycle_with_avoid = 133; + SetGlobalProperty.Response set_global_property = 134; + ClearMainDicomTags.Response clear_main_dicom_tags = 135; + SetMetadata.Response set_metadata = 136; + SetProtectedPatient.Response set_protected_patient = 137; + IsDiskSizeAbove.Response is_disk_size_above = 138; + LookupResources.Response lookup_resources = 139; + CreateInstance.Response create_instance = 140; + SetResourcesContent.Response set_resources_content = 141; + GetChildrenMetadata.Response get_children_metadata = 142; + GetLastChangeIndex.Response get_last_change_index = 143; + LookupResourceAndParent.Response lookup_resource_and_parent = 144; + AddLabel.Response add_label = 145; + RemoveLabel.Response remove_label = 146; + ListLabels.Response list_labels = 147; +} + +enum RequestType { + REQUEST_DATABASE = 0; + REQUEST_TRANSACTION = 1; +} + +message Request { + RequestType type = 1; + DatabaseRequest database_request = 2; + TransactionRequest transaction_request = 3; +} + +message Response { + DatabaseResponse database_response = 2; + TransactionResponse transaction_response = 3; +} diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Sdk-1.12.3/orthanc/OrthancCDatabasePlugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Orthanc/Sdk-1.12.3/orthanc/OrthancCDatabasePlugin.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,1370 @@ +/** + * @ingroup CInterface + **/ + +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2024 Osimis S.A., Belgium + * Copyright (C) 2021-2024 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 . + **/ + + + +#pragma once + +#include "OrthancCPlugin.h" + + +/** @{ */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + + /** + * Opaque structure that represents the context of a custom database engine. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginDatabaseContext_t OrthancPluginDatabaseContext; + + + /** + * Opaque structure that represents a transaction of a custom database engine. + * New in Orthanc 1.9.2. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginDatabaseTransaction_t OrthancPluginDatabaseTransaction; + + +/*InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChange( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginChange* change) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Change; + params.valueUint32 = 0; + params.valueGeneric = change; + + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChangesDone( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Change; + params.valueUint32 = 1; + params.valueGeneric = NULL; + + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt32( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int32_t value) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Int32; + params.valueInt32 = value; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt64( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int64_t value) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Int64; + params.valueInt64 = value; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginExportedResource* exported) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_ExportedResource; + params.valueUint32 = 0; + params.valueGeneric = exported; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResourcesDone( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_ExportedResource; + params.valueUint32 = 1; + params.valueGeneric = NULL; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerDicomTag( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginDicomTag* tag) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DicomTag; + params.valueGeneric = tag; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerAttachment( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginAttachment* attachment) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Attachment; + params.valueGeneric = attachment; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int64_t id, + OrthancPluginResourceType resourceType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Resource; + params.valueInt64 = id; + params.valueInt32 = (int32_t) resourceType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerMatchingResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginMatchingResource* match) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_MatchingResource; + params.valueGeneric = match; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerMetadata( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int64_t resourceId, + int32_t type, + const char* value) + { + OrthancPluginResourcesContentMetadata metadata; + _OrthancPluginDatabaseAnswer params; + metadata.resource = resourceId; + metadata.metadata = type; + metadata.value = value; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Metadata; + params.valueGeneric = &metadata; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedAttachment( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginAttachment* attachment) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DeletedAttachment; + params.valueGeneric = attachment; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const char* publicId, + OrthancPluginResourceType resourceType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DeletedResource; + params.valueString = publicId; + params.valueInt32 = (int32_t) resourceType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalRemainingAncestor( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const char* ancestorId, + OrthancPluginResourceType ancestorType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_RemainingAncestor; + params.valueString = ancestorId; + params.valueInt32 = (int32_t) ancestorType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + + + + + typedef struct + { + OrthancPluginErrorCode (*addAttachment) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginAttachment* attachment); + + OrthancPluginErrorCode (*attachChild) ( + /* inputs */ + void* payload, + int64_t parent, + int64_t child); + + OrthancPluginErrorCode (*clearChanges) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*clearExportedResources) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*createResource) ( + /* outputs */ + int64_t* id, + /* inputs */ + void* payload, + const char* publicId, + OrthancPluginResourceType resourceType); + + OrthancPluginErrorCode (*deleteAttachment) ( + /* inputs */ + void* payload, + int64_t id, + int32_t contentType); + + OrthancPluginErrorCode (*deleteMetadata) ( + /* inputs */ + void* payload, + int64_t id, + int32_t metadataType); + + OrthancPluginErrorCode (*deleteResource) ( + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getAllPublicIds) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType); + + /* Output: Use OrthancPluginDatabaseAnswerChange() and + * OrthancPluginDatabaseAnswerChangesDone() */ + OrthancPluginErrorCode (*getChanges) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t since, + uint32_t maxResult); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*getChildrenInternalId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getChildrenPublicId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerExportedResource() and + * OrthancPluginDatabaseAnswerExportedResourcesDone() */ + OrthancPluginErrorCode (*getExportedResources) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t since, + uint32_t maxResult); + + /* Output: Use OrthancPluginDatabaseAnswerChange() */ + OrthancPluginErrorCode (*getLastChange) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerExportedResource() */ + OrthancPluginErrorCode (*getLastExportedResource) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerDicomTag() */ + OrthancPluginErrorCode (*getMainDicomTags) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getPublicId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*getResourceCount) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType); + + OrthancPluginErrorCode (*getResourceType) ( + /* outputs */ + OrthancPluginResourceType* resourceType, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*getTotalCompressedSize) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*getTotalUncompressedSize) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*isExistingResource) ( + /* outputs */ + int32_t* existing, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*isProtectedPatient) ( + /* outputs */ + int32_t* isProtected, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerInt32() */ + OrthancPluginErrorCode (*listAvailableMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerInt32() */ + OrthancPluginErrorCode (*listAvailableAttachments) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*logChange) ( + /* inputs */ + void* payload, + const OrthancPluginChange* change); + + OrthancPluginErrorCode (*logExportedResource) ( + /* inputs */ + void* payload, + const OrthancPluginExportedResource* exported); + + /* Output: Use OrthancPluginDatabaseAnswerAttachment() */ + OrthancPluginErrorCode (*lookupAttachment) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id, + int32_t contentType); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*lookupGlobalProperty) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int32_t property); + + /* Use "OrthancPluginDatabaseExtensions::lookupIdentifier3" + instead of this function as of Orthanc 0.9.5 (db v6), can be set to NULL. + Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifier) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const OrthancPluginDicomTag* tag); + + /* Unused starting with Orthanc 0.9.5 (db v6), can be set to NULL. + Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifier2) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const char* value); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*lookupMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id, + int32_t metadata); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupParent) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerResource() */ + OrthancPluginErrorCode (*lookupResource) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const char* publicId); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*selectPatientToRecycle) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*selectPatientToRecycle2) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t patientIdToAvoid); + + OrthancPluginErrorCode (*setGlobalProperty) ( + /* inputs */ + void* payload, + int32_t property, + const char* value); + + OrthancPluginErrorCode (*setMainDicomTag) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginDicomTag* tag); + + OrthancPluginErrorCode (*setIdentifierTag) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginDicomTag* tag); + + OrthancPluginErrorCode (*setMetadata) ( + /* inputs */ + void* payload, + int64_t id, + int32_t metadata, + const char* value); + + OrthancPluginErrorCode (*setProtectedPatient) ( + /* inputs */ + void* payload, + int64_t id, + int32_t isProtected); + + OrthancPluginErrorCode (*startTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*rollbackTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*commitTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*open) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*close) ( + /* inputs */ + void* payload); + + } OrthancPluginDatabaseBackend; + + + typedef struct + { + /** + * Base extensions since Orthanc 1.0.0 + **/ + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getAllPublicIdsWithLimit) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + uint64_t since, + uint64_t limit); + + OrthancPluginErrorCode (*getDatabaseVersion) ( + /* outputs */ + uint32_t* version, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*upgradeDatabase) ( + /* inputs */ + void* payload, + uint32_t targetVersion, + OrthancPluginStorageArea* storageArea); + + OrthancPluginErrorCode (*clearMainDicomTags) ( + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*getAllInternalIds) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifier3) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + const OrthancPluginDicomTag* tag, + OrthancPluginIdentifierConstraint constraint); + + + /** + * Extensions since Orthanc 1.4.0 + **/ + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifierRange) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + uint16_t group, + uint16_t element, + const char* start, + const char* end); + + + /** + * Extensions since Orthanc 1.5.2 + **/ + + /* Ouput: Use OrthancPluginDatabaseAnswerMatchingResource */ + OrthancPluginErrorCode (*lookupResources) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + uint32_t constraintsCount, + const OrthancPluginDatabaseConstraint* constraints, + OrthancPluginResourceType queryLevel, + uint32_t limit, + uint8_t requestSomeInstance); + + OrthancPluginErrorCode (*createInstance) ( + /* output */ + OrthancPluginCreateInstanceResult* output, + /* inputs */ + void* payload, + const char* hashPatient, + const char* hashStudy, + const char* hashSeries, + const char* hashInstance); + + OrthancPluginErrorCode (*setResourcesContent) ( + /* inputs */ + void* payload, + uint32_t countIdentifierTags, + const OrthancPluginResourcesContentTags* identifierTags, + uint32_t countMainDicomTags, + const OrthancPluginResourcesContentTags* mainDicomTags, + uint32_t countMetadata, + const OrthancPluginResourcesContentMetadata* metadata); + + /* Ouput: Use OrthancPluginDatabaseAnswerString */ + OrthancPluginErrorCode (*getChildrenMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t resourceId, + int32_t metadata); + + OrthancPluginErrorCode (*getLastChangeIndex) ( + /* outputs */ + int64_t* target, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*tagMostRecentPatient) ( + /* inputs */ + void* payload, + int64_t patientId); + + + /** + * Extensions since Orthanc 1.5.4 + **/ + + /* Ouput: Use OrthancPluginDatabaseAnswerMetadata */ + OrthancPluginErrorCode (*getAllMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t resourceId); + + /* Ouput: Use OrthancPluginDatabaseAnswerString to send + the public ID of the parent (if the resource is not a patient) */ + OrthancPluginErrorCode (*lookupResourceAndParent) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + uint8_t* isExisting, + int64_t* id, + OrthancPluginResourceType* type, + + /* inputs */ + void* payload, + const char* publicId); + + } OrthancPluginDatabaseExtensions; + +/*InvokeService(context, _OrthancPluginService_RegisterDatabaseBackend, ¶ms) || + result == NULL) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + typedef struct + { + OrthancPluginDatabaseContext** result; + const OrthancPluginDatabaseBackend* backend; + void* payload; + const OrthancPluginDatabaseExtensions* extensions; + uint32_t extensionsSize; + } _OrthancPluginRegisterDatabaseBackendV2; + + + /** + * Register a custom database back-end. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param backend The callbacks of the custom database engine. + * @param payload Pointer containing private information for the database engine. + * @param extensions Extensions to the base database SDK that was shipped until Orthanc 0.9.3. + * @return The context of the database engine (it must not be manually freed). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDatabaseContext* OrthancPluginRegisterDatabaseBackendV2( + OrthancPluginContext* context, + const OrthancPluginDatabaseBackend* backend, + const OrthancPluginDatabaseExtensions* extensions, + void* payload) + { + OrthancPluginDatabaseContext* result = NULL; + _OrthancPluginRegisterDatabaseBackendV2 params; + + if (sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType)) + { + return NULL; + } + + memset(¶ms, 0, sizeof(params)); + params.backend = backend; + params.result = &result; + params.payload = payload; + params.extensions = extensions; + params.extensionsSize = sizeof(OrthancPluginDatabaseExtensions); + + if (context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV2, ¶ms) || + result == NULL) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + /** + * New interface starting with Orthanc 1.9.2 + **/ + +/*InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV3, ¶ms); + } + +#ifdef __cplusplus +} +#endif + + +/** @} */ + diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Sdk-1.12.3/orthanc/OrthancCPlugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Orthanc/Sdk-1.12.3/orthanc/OrthancCPlugin.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,9388 @@ +/** + * \mainpage + * + * This C/C++ SDK allows external developers to create plugins that + * can be loaded into Orthanc to extend its functionality. Each + * Orthanc plugin must expose 4 public functions with the following + * signatures: + * + * -# int32_t OrthancPluginInitialize(const OrthancPluginContext* context): + * This function is invoked by Orthanc when it loads the plugin on startup. + * The plugin must: + * - Check its compatibility with the Orthanc version using + * ::OrthancPluginCheckVersion(). + * - Store the context pointer so that it can use the plugin + * services of Orthanc. + * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). + * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). + * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). + * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea2(). + * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV4(). + * - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback(). + * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). + * - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback(). + * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). + * - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter2(). + * - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer(). + * - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback(). + * - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback(). + * - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback(). + * - 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. + * -# const char* OrthancPluginGetName(): + * The plugin must return a short string to identify itself. + * -# const char* OrthancPluginGetVersion(): + * The plugin must return a string containing its version number. + * + * The name and the version of a plugin is only used to prevent it + * from being loaded twice. Note that, in C++, it is mandatory to + * declare these functions within an extern "C" section. + * + * To ensure multi-threading safety, the various REST callbacks are + * guaranteed to be executed in mutual exclusion since Orthanc + * 0.8.5. If this feature is undesired (notably when developing + * high-performance plugins handling simultaneous requests), use + * ::OrthancPluginRegisterRestCallbackNoLock(). + **/ + + + +/** + * @defgroup Images Images and compression + * @brief Functions to deal with images and compressed buffers. + * + * @defgroup REST REST + * @brief Functions to answer REST requests in a callback. + * + * @defgroup Callbacks Callbacks + * @brief Functions to register and manage callbacks by the plugins. + * + * @defgroup DicomCallbacks DicomCallbacks + * @brief Functions to register and manage DICOM callbacks (worklists, C-FIND, C-MOVE, storage commitment). + * + * @defgroup Orthanc Orthanc + * @brief Functions to access the content of the Orthanc server. + * + * @defgroup DicomInstance DicomInstance + * @brief Functions to access DICOM images that are managed by the Orthanc core. + **/ + + + +/** + * @defgroup Toolbox Toolbox + * @brief Generic functions to help with the creation of plugins. + **/ + + + +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2024 Osimis S.A., Belgium + * Copyright (C) 2021-2024 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 . + **/ + + + +#pragma once + + +#include +#include + +#ifdef WIN32 +# define ORTHANC_PLUGINS_API __declspec(dllexport) +#elif __GNUC__ >= 4 +# define ORTHANC_PLUGINS_API __attribute__ ((visibility ("default"))) +#else +# define ORTHANC_PLUGINS_API +#endif + +#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 +#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 12 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 3 + + +#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) +#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \ + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) +#endif + + + +/******************************************************************** + ** Check that function inlining is properly supported. The use of + ** inlining is required, to avoid the duplication of object code + ** between two compilation modules that would use the Orthanc Plugin + ** API. + ********************************************************************/ + +/* If the auto-detection of the "inline" keyword below does not work + automatically and that your compiler is known to properly support + inlining, uncomment the following #define and adapt the definition + of "static inline". */ + +/* #define ORTHANC_PLUGIN_INLINE static inline */ + +#ifndef ORTHANC_PLUGIN_INLINE +# if __STDC_VERSION__ >= 199901L +/* This is C99 or above: http://predef.sourceforge.net/prestd.html */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__cplusplus) +/* This is C++ */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__GNUC__) +/* This is GCC running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# elif defined(_MSC_VER) +/* This is Visual Studio running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# else +# error Your compiler is not known to support the "inline" keyword +# endif +#endif + + + +/******************************************************************** + ** Inclusion of standard libraries. + ********************************************************************/ + +/** + * For Microsoft Visual Studio, a compatibility "stdint.h" can be + * downloaded at the following URL: + * https://orthanc.uclouvain.be/hg/orthanc/raw-file/default/OrthancFramework/Resources/ThirdParty/VisualStudio/stdint.h + **/ +#include + +#include + + + +/******************************************************************** + ** Definition of the Orthanc Plugin API. + ********************************************************************/ + +/** @{ */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * The various error codes that can be returned by the Orthanc core. + **/ + typedef enum + { + OrthancPluginErrorCode_InternalError = -1 /*!< Internal error */, + OrthancPluginErrorCode_Success = 0 /*!< Success */, + OrthancPluginErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, + OrthancPluginErrorCode_NotImplemented = 2 /*!< Not implemented yet */, + OrthancPluginErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, + OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< The server hosting Orthanc is running out of memory */, + OrthancPluginErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, + OrthancPluginErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, + OrthancPluginErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, + OrthancPluginErrorCode_BadRequest = 8 /*!< Bad request */, + OrthancPluginErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */, + OrthancPluginErrorCode_SystemCommand = 10 /*!< Error while calling a system command */, + OrthancPluginErrorCode_Database = 11 /*!< Error with the database engine */, + OrthancPluginErrorCode_UriSyntax = 12 /*!< Badly formatted URI */, + OrthancPluginErrorCode_InexistentFile = 13 /*!< Inexistent file */, + OrthancPluginErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */, + OrthancPluginErrorCode_BadFileFormat = 15 /*!< Bad file format */, + OrthancPluginErrorCode_Timeout = 16 /*!< Timeout */, + OrthancPluginErrorCode_UnknownResource = 17 /*!< Unknown resource */, + OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */, + OrthancPluginErrorCode_FullStorage = 19 /*!< The file storage is full */, + OrthancPluginErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */, + OrthancPluginErrorCode_InexistentTag = 21 /*!< Inexistent tag */, + OrthancPluginErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */, + OrthancPluginErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */, + OrthancPluginErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */, + OrthancPluginErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */, + OrthancPluginErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */, + OrthancPluginErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */, + OrthancPluginErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */, + OrthancPluginErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */, + OrthancPluginErrorCode_BadFont = 30 /*!< Badly formatted font file */, + OrthancPluginErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, + OrthancPluginErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, + OrthancPluginErrorCode_EmptyRequest = 33 /*!< The request is empty */, + OrthancPluginErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, + OrthancPluginErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */, + OrthancPluginErrorCode_DatabaseUnavailable = 36 /*!< The database is currently not available (probably a transient situation) */, + OrthancPluginErrorCode_CanceledJob = 37 /*!< This job was canceled */, + OrthancPluginErrorCode_BadGeometry = 38 /*!< Geometry error encountered in Stone */, + OrthancPluginErrorCode_SslInitialization = 39 /*!< Cannot initialize SSL encryption, check out your certificates */, + OrthancPluginErrorCode_DiscontinuedAbi = 40 /*!< Calling a function that has been removed from the Orthanc Framework */, + OrthancPluginErrorCode_BadRange = 41 /*!< Incorrect range request */, + OrthancPluginErrorCode_DatabaseCannotSerialize = 42 /*!< Database could not serialize access due to concurrent update, the transaction should be retried */, + OrthancPluginErrorCode_Revision = 43 /*!< A bad revision number was provided, which might indicate conflict between multiple writers */, + OrthancPluginErrorCode_MainDicomTagsMultiplyDefined = 44 /*!< A main DICOM Tag has been defined multiple times for the same resource level */, + OrthancPluginErrorCode_ForbiddenAccess = 45 /*!< Access to a resource is forbidden */, + OrthancPluginErrorCode_DuplicateResource = 46 /*!< Duplicate resource */, + OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, + OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, + OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, + OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */, + OrthancPluginErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */, + OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */, + OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */, + OrthancPluginErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */, + OrthancPluginErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, + OrthancPluginErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, + OrthancPluginErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, + OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, + OrthancPluginErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, + OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, + OrthancPluginErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, + OrthancPluginErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */, + OrthancPluginErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, + OrthancPluginErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, + OrthancPluginErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, + OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is privileged or already in use */, + OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is privileged or already in use */, + OrthancPluginErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, + OrthancPluginErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, + OrthancPluginErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, + OrthancPluginErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */, + OrthancPluginErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */, + OrthancPluginErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */, + OrthancPluginErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */, + OrthancPluginErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */, + OrthancPluginErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */, + OrthancPluginErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */, + OrthancPluginErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */, + OrthancPluginErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */, + OrthancPluginErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */, + OrthancPluginErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */, + OrthancPluginErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */, + OrthancPluginErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */, + OrthancPluginErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */, + OrthancPluginErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */, + OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */, + OrthancPluginErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */, + OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */, + OrthancPluginErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */, + OrthancPluginErrorCode_UnknownModality = 2027 /*!< Unknown modality */, + OrthancPluginErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */, + OrthancPluginErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */, + OrthancPluginErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */, + OrthancPluginErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */, + OrthancPluginErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */, + OrthancPluginErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */, + OrthancPluginErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */, + OrthancPluginErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */, + OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, + OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, + OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, + OrthancPluginErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, + OrthancPluginErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, + OrthancPluginErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */, + OrthancPluginErrorCode_AlreadyExistingTag = 2042 /*!< Cannot override the value of a tag that already exists */, + OrthancPluginErrorCode_NoStorageCommitmentHandler = 2043 /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */, + OrthancPluginErrorCode_NoCGetHandler = 2044 /*!< No request handler factory for DICOM C-GET SCP */, + OrthancPluginErrorCode_UnsupportedMediaType = 3000 /*!< Unsupported media type */, + + _OrthancPluginErrorCode_INTERNAL = 0x7fffffff + } OrthancPluginErrorCode; + + + /** + * Forward declaration of one of the mandatory functions for Orthanc + * plugins. + **/ + ORTHANC_PLUGINS_API const char* OrthancPluginGetName(); + + + /** + * The various HTTP methods for a REST call. + **/ + typedef enum + { + OrthancPluginHttpMethod_Get = 1, /*!< GET request */ + OrthancPluginHttpMethod_Post = 2, /*!< POST request */ + OrthancPluginHttpMethod_Put = 3, /*!< PUT request */ + OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */ + + _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff + } OrthancPluginHttpMethod; + + + /** + * @brief The parameters of a REST request. + * @ingroup Callbacks + **/ + typedef struct + { + /** + * @brief The HTTP method. + **/ + OrthancPluginHttpMethod method; + + /** + * @brief The number of groups of the regular expression. + **/ + uint32_t groupsCount; + + /** + * @brief The matched values for the groups of the regular expression. + **/ + const char* const* groups; + + /** + * @brief For a GET request, the number of GET parameters. + **/ + uint32_t getCount; + + /** + * @brief For a GET request, the keys of the GET parameters. + **/ + const char* const* getKeys; + + /** + * @brief For a GET request, the values of the GET parameters. + **/ + const char* const* getValues; + + /** + * @brief For a PUT or POST request, the content of the body. + **/ + const void* body; + + /** + * @brief For a PUT or POST request, the number of bytes of the body. + **/ + uint32_t bodySize; + + + /* -------------------------------------------------- + New in version 0.8.1 + -------------------------------------------------- */ + + /** + * @brief The number of HTTP headers. + **/ + uint32_t headersCount; + + /** + * @brief The keys of the HTTP headers (always converted to low-case). + **/ + const char* const* headersKeys; + + /** + * @brief The values of the HTTP headers. + **/ + const char* const* headersValues; + + } OrthancPluginHttpRequest; + + + typedef enum + { + /* Generic services */ + _OrthancPluginService_LogInfo = 1, + _OrthancPluginService_LogWarning = 2, + _OrthancPluginService_LogError = 3, + _OrthancPluginService_GetOrthancPath = 4, + _OrthancPluginService_GetOrthancDirectory = 5, + _OrthancPluginService_GetConfigurationPath = 6, + _OrthancPluginService_SetPluginProperty = 7, + _OrthancPluginService_GetGlobalProperty = 8, + _OrthancPluginService_SetGlobalProperty = 9, + _OrthancPluginService_GetCommandLineArgumentsCount = 10, + _OrthancPluginService_GetCommandLineArgument = 11, + _OrthancPluginService_GetExpectedDatabaseVersion = 12, + _OrthancPluginService_GetConfiguration = 13, + _OrthancPluginService_BufferCompression = 14, + _OrthancPluginService_ReadFile = 15, + _OrthancPluginService_WriteFile = 16, + _OrthancPluginService_GetErrorDescription = 17, + _OrthancPluginService_CallHttpClient = 18, + _OrthancPluginService_RegisterErrorCode = 19, + _OrthancPluginService_RegisterDictionaryTag = 20, + _OrthancPluginService_DicomBufferToJson = 21, + _OrthancPluginService_DicomInstanceToJson = 22, + _OrthancPluginService_CreateDicom = 23, + _OrthancPluginService_ComputeMd5 = 24, + _OrthancPluginService_ComputeSha1 = 25, + _OrthancPluginService_LookupDictionary = 26, + _OrthancPluginService_CallHttpClient2 = 27, + _OrthancPluginService_GenerateUuid = 28, + _OrthancPluginService_RegisterPrivateDictionaryTag = 29, + _OrthancPluginService_AutodetectMimeType = 30, + _OrthancPluginService_SetMetricsValue = 31, + _OrthancPluginService_EncodeDicomWebJson = 32, + _OrthancPluginService_EncodeDicomWebXml = 33, + _OrthancPluginService_ChunkedHttpClient = 34, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_GetTagName = 35, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_EncodeDicomWebJson2 = 36, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_EncodeDicomWebXml2 = 37, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_CreateMemoryBuffer = 38, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GenerateRestApiAuthorizationToken = 39, /* New in Orthanc 1.8.1 */ + _OrthancPluginService_CreateMemoryBuffer64 = 40, /* New in Orthanc 1.9.0 */ + _OrthancPluginService_CreateDicom2 = 41, /* New in Orthanc 1.9.0 */ + _OrthancPluginService_GetDatabaseServerIdentifier = 42, /* New in Orthanc 1.11.1 */ + _OrthancPluginService_SetMetricsIntegerValue = 43, /* New in Orthanc 1.12.1 */ + _OrthancPluginService_SetCurrentThreadName = 44, /* New in Orthanc 1.12.2 */ + + + /* Registration of callbacks */ + _OrthancPluginService_RegisterRestCallback = 1000, + _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, + _OrthancPluginService_RegisterStorageArea = 1002, + _OrthancPluginService_RegisterOnChangeCallback = 1003, + _OrthancPluginService_RegisterRestCallbackNoLock = 1004, + _OrthancPluginService_RegisterWorklistCallback = 1005, + _OrthancPluginService_RegisterDecodeImageCallback = 1006, + _OrthancPluginService_RegisterIncomingHttpRequestFilter = 1007, + _OrthancPluginService_RegisterFindCallback = 1008, + _OrthancPluginService_RegisterMoveCallback = 1009, + _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010, + _OrthancPluginService_RegisterRefreshMetricsCallback = 1011, + _OrthancPluginService_RegisterChunkedRestCallback = 1012, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_RegisterStorageCommitmentScpCallback = 1013, + _OrthancPluginService_RegisterIncomingDicomInstanceFilter = 1014, + _OrthancPluginService_RegisterTranscoderCallback = 1015, /* New in Orthanc 1.7.0 */ + _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, + _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */ + _OrthancPluginService_Redirect = 2002, + _OrthancPluginService_SendHttpStatusCode = 2003, + _OrthancPluginService_SendUnauthorized = 2004, + _OrthancPluginService_SendMethodNotAllowed = 2005, + _OrthancPluginService_SetCookie = 2006, + _OrthancPluginService_SetHttpHeader = 2007, + _OrthancPluginService_StartMultipartAnswer = 2008, + _OrthancPluginService_SendMultipartItem = 2009, + _OrthancPluginService_SendHttpStatus = 2010, + _OrthancPluginService_CompressAndAnswerImage = 2011, + _OrthancPluginService_SendMultipartItem2 = 2012, + _OrthancPluginService_SetHttpErrorDetails = 2013, + + /* Access to the Orthanc database and API */ + _OrthancPluginService_GetDicomForInstance = 3000, + _OrthancPluginService_RestApiGet = 3001, + _OrthancPluginService_RestApiPost = 3002, + _OrthancPluginService_RestApiDelete = 3003, + _OrthancPluginService_RestApiPut = 3004, + _OrthancPluginService_LookupPatient = 3005, + _OrthancPluginService_LookupStudy = 3006, + _OrthancPluginService_LookupSeries = 3007, + _OrthancPluginService_LookupInstance = 3008, + _OrthancPluginService_LookupStudyWithAccessionNumber = 3009, + _OrthancPluginService_RestApiGetAfterPlugins = 3010, + _OrthancPluginService_RestApiPostAfterPlugins = 3011, + _OrthancPluginService_RestApiDeleteAfterPlugins = 3012, + _OrthancPluginService_RestApiPutAfterPlugins = 3013, + _OrthancPluginService_ReconstructMainDicomTags = 3014, + _OrthancPluginService_RestApiGet2 = 3015, + _OrthancPluginService_CallRestApi = 3016, /* New in Orthanc 1.9.2 */ + + /* Access to DICOM instances */ + _OrthancPluginService_GetInstanceRemoteAet = 4000, + _OrthancPluginService_GetInstanceSize = 4001, + _OrthancPluginService_GetInstanceData = 4002, + _OrthancPluginService_GetInstanceJson = 4003, + _OrthancPluginService_GetInstanceSimplifiedJson = 4004, + _OrthancPluginService_HasInstanceMetadata = 4005, + _OrthancPluginService_GetInstanceMetadata = 4006, + _OrthancPluginService_GetInstanceOrigin = 4007, + _OrthancPluginService_GetInstanceTransferSyntaxUid = 4008, + _OrthancPluginService_HasInstancePixelData = 4009, + _OrthancPluginService_CreateDicomInstance = 4010, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_FreeDicomInstance = 4011, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceFramesCount = 4012, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceRawFrame = 4013, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceDecodedFrame = 4014, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_TranscodeDicomInstance = 4015, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_SerializeDicomInstance = 4016, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceAdvancedJson = 4017, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceDicomWebJson = 4018, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceDicomWebXml = 4019, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_LoadDicomInstance = 4020, /* New in Orthanc 1.12.1 */ + + /* Services for plugins implementing a database back-end */ + _OrthancPluginService_RegisterDatabaseBackend = 5000, /* New in Orthanc 0.8.6 */ + _OrthancPluginService_DatabaseAnswer = 5001, + _OrthancPluginService_RegisterDatabaseBackendV2 = 5002, /* New in Orthanc 0.9.4 */ + _OrthancPluginService_StorageAreaCreate = 5003, + _OrthancPluginService_StorageAreaRead = 5004, + _OrthancPluginService_StorageAreaRemove = 5005, + _OrthancPluginService_RegisterDatabaseBackendV3 = 5006, /* New in Orthanc 1.9.2 */ + _OrthancPluginService_RegisterDatabaseBackendV4 = 5007, /* New in Orthanc 1.12.0 */ + + /* Primitives for handling images */ + _OrthancPluginService_GetImagePixelFormat = 6000, + _OrthancPluginService_GetImageWidth = 6001, + _OrthancPluginService_GetImageHeight = 6002, + _OrthancPluginService_GetImagePitch = 6003, + _OrthancPluginService_GetImageBuffer = 6004, + _OrthancPluginService_UncompressImage = 6005, + _OrthancPluginService_FreeImage = 6006, + _OrthancPluginService_CompressImage = 6007, + _OrthancPluginService_ConvertPixelFormat = 6008, + _OrthancPluginService_GetFontsCount = 6009, + _OrthancPluginService_GetFontInfo = 6010, + _OrthancPluginService_DrawText = 6011, + _OrthancPluginService_CreateImage = 6012, + _OrthancPluginService_CreateImageAccessor = 6013, + _OrthancPluginService_DecodeDicomImage = 6014, + + /* Primitives for handling C-Find, C-Move and worklists */ + _OrthancPluginService_WorklistAddAnswer = 7000, + _OrthancPluginService_WorklistMarkIncomplete = 7001, + _OrthancPluginService_WorklistIsMatch = 7002, + _OrthancPluginService_WorklistGetDicomQuery = 7003, + _OrthancPluginService_FindAddAnswer = 7004, + _OrthancPluginService_FindMarkIncomplete = 7005, + _OrthancPluginService_GetFindQuerySize = 7006, + _OrthancPluginService_GetFindQueryTag = 7007, + _OrthancPluginService_GetFindQueryTagName = 7008, + _OrthancPluginService_GetFindQueryValue = 7009, + _OrthancPluginService_CreateFindMatcher = 7010, + _OrthancPluginService_FreeFindMatcher = 7011, + _OrthancPluginService_FindMatcherIsMatch = 7012, + + /* Primitives for accessing Orthanc Peers (new in 1.4.2) */ + _OrthancPluginService_GetPeers = 8000, + _OrthancPluginService_FreePeers = 8001, + _OrthancPluginService_GetPeersCount = 8003, + _OrthancPluginService_GetPeerName = 8004, + _OrthancPluginService_GetPeerUrl = 8005, + _OrthancPluginService_CallPeerApi = 8006, + _OrthancPluginService_GetPeerUserProperty = 8007, + + /* Primitives for handling jobs (new in 1.4.2) */ + _OrthancPluginService_CreateJob = 9000, /* Deprecated since SDK 1.11.3 */ + _OrthancPluginService_FreeJob = 9001, + _OrthancPluginService_SubmitJob = 9002, + _OrthancPluginService_RegisterJobsUnserializer = 9003, + _OrthancPluginService_CreateJob2 = 9004, /* New in SDK 1.11.3 */ + + _OrthancPluginService_INTERNAL = 0x7fffffff + } _OrthancPluginService; + + + typedef enum + { + _OrthancPluginProperty_Description = 1, + _OrthancPluginProperty_RootUri = 2, + _OrthancPluginProperty_OrthancExplorer = 3, + + _OrthancPluginProperty_INTERNAL = 0x7fffffff + } _OrthancPluginProperty; + + + + /** + * The memory layout of the pixels of an image. + * @ingroup Images + **/ + typedef enum + { + /** + * @brief Graylevel 8bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * one byte. + **/ + OrthancPluginPixelFormat_Grayscale8 = 1, + + /** + * @brief Graylevel, unsigned 16bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * two bytes. + **/ + OrthancPluginPixelFormat_Grayscale16 = 2, + + /** + * @brief Graylevel, signed 16bpp image. + * + * The image is graylevel. Each pixel is signed and stored in two + * bytes. + **/ + OrthancPluginPixelFormat_SignedGrayscale16 = 3, + + /** + * @brief Color image in RGB24 format. + * + * This format describes a color image. The pixels are stored in 3 + * consecutive bytes. The memory layout is RGB. + **/ + OrthancPluginPixelFormat_RGB24 = 4, + + /** + * @brief Color image in RGBA32 format. + * + * This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is RGBA. + **/ + OrthancPluginPixelFormat_RGBA32 = 5, + + OrthancPluginPixelFormat_Unknown = 6, /*!< Unknown pixel format */ + + /** + * @brief Color image in RGB48 format. + * + * This format describes a color image. The pixels are stored in 6 + * consecutive bytes. The memory layout is RRGGBB. + **/ + OrthancPluginPixelFormat_RGB48 = 7, + + /** + * @brief Graylevel, unsigned 32bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * four bytes. + **/ + OrthancPluginPixelFormat_Grayscale32 = 8, + + /** + * @brief Graylevel, floating-point 32bpp image. + * + * The image is graylevel. Each pixel is floating-point and stored + * in four bytes. + **/ + OrthancPluginPixelFormat_Float32 = 9, + + /** + * @brief Color image in BGRA32 format. + * + * This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is BGRA. + **/ + OrthancPluginPixelFormat_BGRA32 = 10, + + /** + * @brief Graylevel, unsigned 64bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * eight bytes. + **/ + OrthancPluginPixelFormat_Grayscale64 = 11, + + _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff + } OrthancPluginPixelFormat; + + + + /** + * The content types that are supported by Orthanc plugins. + **/ + typedef enum + { + OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */ + OrthancPluginContentType_Dicom = 1, /*!< DICOM */ + OrthancPluginContentType_DicomAsJson = 2, /*!< JSON summary of a DICOM file */ + OrthancPluginContentType_DicomUntilPixelData = 3, /*!< DICOM Header till pixel data */ + + _OrthancPluginContentType_INTERNAL = 0x7fffffff + } OrthancPluginContentType; + + + + /** + * The supported types of DICOM resources. + **/ + typedef enum + { + OrthancPluginResourceType_Patient = 0, /*!< Patient */ + OrthancPluginResourceType_Study = 1, /*!< Study */ + OrthancPluginResourceType_Series = 2, /*!< Series */ + OrthancPluginResourceType_Instance = 3, /*!< Instance */ + OrthancPluginResourceType_None = 4, /*!< Unavailable resource type */ + + _OrthancPluginResourceType_INTERNAL = 0x7fffffff + } OrthancPluginResourceType; + + + + /** + * The supported types of changes that can be signaled to the change callback. + * @ingroup Callbacks + **/ + typedef enum + { + OrthancPluginChangeType_CompletedSeries = 0, /*!< Series is now complete */ + OrthancPluginChangeType_Deleted = 1, /*!< Deleted resource */ + OrthancPluginChangeType_NewChildInstance = 2, /*!< A new instance was added to this resource */ + OrthancPluginChangeType_NewInstance = 3, /*!< New instance received */ + OrthancPluginChangeType_NewPatient = 4, /*!< New patient created */ + OrthancPluginChangeType_NewSeries = 5, /*!< New series created */ + OrthancPluginChangeType_NewStudy = 6, /*!< New study created */ + OrthancPluginChangeType_StablePatient = 7, /*!< Timeout: No new instance in this patient */ + OrthancPluginChangeType_StableSeries = 8, /*!< Timeout: No new instance in this series */ + OrthancPluginChangeType_StableStudy = 9, /*!< Timeout: No new instance in this study */ + OrthancPluginChangeType_OrthancStarted = 10, /*!< Orthanc has started */ + OrthancPluginChangeType_OrthancStopped = 11, /*!< Orthanc is stopping */ + OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */ + OrthancPluginChangeType_UpdatedMetadata = 13, /*!< Some user-defined metadata has changed for this resource */ + OrthancPluginChangeType_UpdatedPeers = 14, /*!< The list of Orthanc peers has changed */ + OrthancPluginChangeType_UpdatedModalities = 15, /*!< The list of DICOM modalities has changed */ + OrthancPluginChangeType_JobSubmitted = 16, /*!< New Job submitted */ + OrthancPluginChangeType_JobSuccess = 17, /*!< A Job has completed successfully */ + OrthancPluginChangeType_JobFailure = 18, /*!< A Job has failed */ + + _OrthancPluginChangeType_INTERNAL = 0x7fffffff + } OrthancPluginChangeType; + + + /** + * The compression algorithms that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginCompressionType_Zlib = 0, /*!< Standard zlib compression */ + OrthancPluginCompressionType_ZlibWithSize = 1, /*!< zlib, prefixed with uncompressed size (uint64_t) */ + OrthancPluginCompressionType_Gzip = 2, /*!< Standard gzip compression */ + OrthancPluginCompressionType_GzipWithSize = 3, /*!< gzip, prefixed with uncompressed size (uint64_t) */ + + _OrthancPluginCompressionType_INTERNAL = 0x7fffffff + } OrthancPluginCompressionType; + + + /** + * The image formats that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */ + OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */ + OrthancPluginImageFormat_Dicom = 2, /*!< Image compressed using DICOM */ + + _OrthancPluginImageFormat_INTERNAL = 0x7fffffff + } OrthancPluginImageFormat; + + + /** + * The value representations present in the DICOM standard (version 2013). + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginValueRepresentation_AE = 1, /*!< Application Entity */ + OrthancPluginValueRepresentation_AS = 2, /*!< Age String */ + OrthancPluginValueRepresentation_AT = 3, /*!< Attribute Tag */ + OrthancPluginValueRepresentation_CS = 4, /*!< Code String */ + OrthancPluginValueRepresentation_DA = 5, /*!< Date */ + OrthancPluginValueRepresentation_DS = 6, /*!< Decimal String */ + OrthancPluginValueRepresentation_DT = 7, /*!< Date Time */ + OrthancPluginValueRepresentation_FD = 8, /*!< Floating Point Double */ + OrthancPluginValueRepresentation_FL = 9, /*!< Floating Point Single */ + OrthancPluginValueRepresentation_IS = 10, /*!< Integer String */ + OrthancPluginValueRepresentation_LO = 11, /*!< Long String */ + OrthancPluginValueRepresentation_LT = 12, /*!< Long Text */ + OrthancPluginValueRepresentation_OB = 13, /*!< Other Byte String */ + OrthancPluginValueRepresentation_OF = 14, /*!< Other Float String */ + OrthancPluginValueRepresentation_OW = 15, /*!< Other Word String */ + OrthancPluginValueRepresentation_PN = 16, /*!< Person Name */ + OrthancPluginValueRepresentation_SH = 17, /*!< Short String */ + OrthancPluginValueRepresentation_SL = 18, /*!< Signed Long */ + OrthancPluginValueRepresentation_SQ = 19, /*!< Sequence of Items */ + OrthancPluginValueRepresentation_SS = 20, /*!< Signed Short */ + OrthancPluginValueRepresentation_ST = 21, /*!< Short Text */ + OrthancPluginValueRepresentation_TM = 22, /*!< Time */ + OrthancPluginValueRepresentation_UI = 23, /*!< Unique Identifier (UID) */ + OrthancPluginValueRepresentation_UL = 24, /*!< Unsigned Long */ + OrthancPluginValueRepresentation_UN = 25, /*!< Unknown */ + OrthancPluginValueRepresentation_US = 26, /*!< Unsigned Short */ + OrthancPluginValueRepresentation_UT = 27, /*!< Unlimited Text */ + + _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff + } OrthancPluginValueRepresentation; + + + /** + * The possible output formats for a DICOM-to-JSON conversion. + * @ingroup Toolbox + * @see OrthancPluginDicomToJson() + **/ + typedef enum + { + OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */ + OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */ + OrthancPluginDicomToJsonFormat_Human = 3, /*!< Human-readable JSON */ + + _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff + } OrthancPluginDicomToJsonFormat; + + + /** + * Flags to customize a DICOM-to-JSON conversion. By default, binary + * tags are formatted using Data URI scheme. + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginDicomToJsonFlags_None = 0, /*!< Default formatting */ + OrthancPluginDicomToJsonFlags_IncludeBinary = (1 << 0), /*!< Include the binary tags */ + OrthancPluginDicomToJsonFlags_IncludePrivateTags = (1 << 1), /*!< Include the private tags */ + OrthancPluginDicomToJsonFlags_IncludeUnknownTags = (1 << 2), /*!< Include the tags unknown by the dictionary */ + OrthancPluginDicomToJsonFlags_IncludePixelData = (1 << 3), /*!< Include the pixel data */ + OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii = (1 << 4), /*!< Output binary tags as-is, dropping non-ASCII */ + OrthancPluginDicomToJsonFlags_ConvertBinaryToNull = (1 << 5), /*!< Signal binary tags as null values */ + OrthancPluginDicomToJsonFlags_StopAfterPixelData = (1 << 6), /*!< Stop processing after pixel data (new in 1.9.1) */ + OrthancPluginDicomToJsonFlags_SkipGroupLengths = (1 << 7), /*!< Skip tags whose element is zero (new in 1.9.1) */ + + _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff + } OrthancPluginDicomToJsonFlags; + + + /** + * Flags for the creation of a DICOM file. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom() + **/ + typedef enum + { + OrthancPluginCreateDicomFlags_None = 0, /*!< Default mode */ + OrthancPluginCreateDicomFlags_DecodeDataUriScheme = (1 << 0), /*!< Decode fields encoded using data URI scheme */ + OrthancPluginCreateDicomFlags_GenerateIdentifiers = (1 << 1), /*!< Automatically generate DICOM identifiers */ + + _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff + } OrthancPluginCreateDicomFlags; + + + /** + * The constraints on the DICOM identifiers that must be supported + * by the database plugins. + * @deprecated Plugins using OrthancPluginConstraintType will be faster + **/ + typedef enum + { + OrthancPluginIdentifierConstraint_Equal = 1, /*!< Equal */ + OrthancPluginIdentifierConstraint_SmallerOrEqual = 2, /*!< Less or equal */ + OrthancPluginIdentifierConstraint_GreaterOrEqual = 3, /*!< More or equal */ + OrthancPluginIdentifierConstraint_Wildcard = 4, /*!< Case-sensitive wildcard matching (with * and ?) */ + + _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff + } OrthancPluginIdentifierConstraint; + + + /** + * The constraints on the tags (main DICOM tags and identifier tags) + * that must be supported by the database plugins. + **/ + typedef enum + { + OrthancPluginConstraintType_Equal = 1, /*!< Equal */ + OrthancPluginConstraintType_SmallerOrEqual = 2, /*!< Less or equal */ + OrthancPluginConstraintType_GreaterOrEqual = 3, /*!< More or equal */ + OrthancPluginConstraintType_Wildcard = 4, /*!< Wildcard matching */ + OrthancPluginConstraintType_List = 5, /*!< List of values */ + + _OrthancPluginConstraintType_INTERNAL = 0x7fffffff + } OrthancPluginConstraintType; + + + /** + * The origin of a DICOM instance that has been received by Orthanc. + **/ + typedef enum + { + OrthancPluginInstanceOrigin_Unknown = 1, /*!< Unknown origin */ + OrthancPluginInstanceOrigin_DicomProtocol = 2, /*!< Instance received through DICOM protocol */ + OrthancPluginInstanceOrigin_RestApi = 3, /*!< Instance received through REST API of Orthanc */ + OrthancPluginInstanceOrigin_Plugin = 4, /*!< Instance added to Orthanc by a plugin */ + OrthancPluginInstanceOrigin_Lua = 5, /*!< Instance added to Orthanc by a Lua script */ + OrthancPluginInstanceOrigin_WebDav = 6, /*!< Instance received through WebDAV (new in 1.8.0) */ + + _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff + } OrthancPluginInstanceOrigin; + + + /** + * The possible status for one single step of a job. + **/ + typedef enum + { + OrthancPluginJobStepStatus_Success = 1, /*!< The job has successfully executed all its steps */ + OrthancPluginJobStepStatus_Failure = 2, /*!< The job has failed while executing this step */ + OrthancPluginJobStepStatus_Continue = 3 /*!< The job has still data to process after this step */ + } OrthancPluginJobStepStatus; + + + /** + * Explains why the job should stop and release the resources it has + * allocated. This is especially important to disambiguate between + * the "paused" condition and the "final" conditions (success, + * failure, or canceled). + **/ + typedef enum + { + OrthancPluginJobStopReason_Success = 1, /*!< The job has succeeded */ + OrthancPluginJobStopReason_Paused = 2, /*!< The job was paused, and will be resumed later */ + OrthancPluginJobStopReason_Failure = 3, /*!< The job has failed, and might be resubmitted later */ + OrthancPluginJobStopReason_Canceled = 4 /*!< The job was canceled, and might be resubmitted later */ + } OrthancPluginJobStopReason; + + + /** + * The available types of metrics. + **/ + typedef enum + { + OrthancPluginMetricsType_Default = 0, /*!< Default metrics */ + + /** + * This metrics represents a time duration. Orthanc will keep the + * maximum value of the metrics over a sliding window of ten + * seconds, which is useful if the metrics is sampled frequently. + **/ + OrthancPluginMetricsType_Timer = 1 + } OrthancPluginMetricsType; + + + /** + * The available modes to export a binary DICOM tag into a DICOMweb + * JSON or XML document. + **/ + typedef enum + { + OrthancPluginDicomWebBinaryMode_Ignore = 0, /*!< Don't include binary tags */ + OrthancPluginDicomWebBinaryMode_InlineBinary = 1, /*!< Inline encoding using Base64 */ + OrthancPluginDicomWebBinaryMode_BulkDataUri = 2 /*!< Use a bulk data URI field */ + } OrthancPluginDicomWebBinaryMode; + + + /** + * The available values for the Failure Reason (0008,1197) during + * storage commitment. + * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 + **/ + typedef enum + { + /** + * Success: The DICOM instance is properly stored in the SCP + **/ + OrthancPluginStorageCommitmentFailureReason_Success = 0, + + /** + * 0110H: A general failure in processing the operation was encountered + **/ + OrthancPluginStorageCommitmentFailureReason_ProcessingFailure = 1, + + /** + * 0112H: One or more of the elements in the Referenced SOP + * Instance Sequence was not available + **/ + OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance = 2, + + /** + * 0213H: The SCP does not currently have enough resources to + * store the requested SOP Instance(s) + **/ + OrthancPluginStorageCommitmentFailureReason_ResourceLimitation = 3, + + /** + * 0122H: Storage Commitment has been requested for a SOP Instance + * with a SOP Class that is not supported by the SCP + **/ + OrthancPluginStorageCommitmentFailureReason_ReferencedSOPClassNotSupported = 4, + + /** + * 0119H: The SOP Class of an element in the Referenced SOP + * Instance Sequence did not correspond to the SOP class + * registered for this SOP Instance at the SCP + **/ + OrthancPluginStorageCommitmentFailureReason_ClassInstanceConflict = 5, + + /** + * 0131H: The Transaction UID of the Storage Commitment Request is + * already in use + **/ + OrthancPluginStorageCommitmentFailureReason_DuplicateTransactionUID = 6 + } OrthancPluginStorageCommitmentFailureReason; + + + /** + * The action to be taken after ReceivedInstanceCallback is triggered + **/ + typedef enum + { + OrthancPluginReceivedInstanceAction_KeepAsIs = 1, /*!< Keep the instance as is */ + OrthancPluginReceivedInstanceAction_Modify = 2, /*!< Modify the instance */ + OrthancPluginReceivedInstanceAction_Discard = 3, /*!< Discard the instance */ + + _OrthancPluginReceivedInstanceAction_INTERNAL = 0x7fffffff + } OrthancPluginReceivedInstanceAction; + + + /** + * Mode specifying how to load a DICOM instance. + * @see OrthancPluginLoadDicomInstance() + **/ + typedef enum + { + /** + * Load the whole DICOM file, including pixel data + **/ + OrthancPluginLoadDicomInstanceMode_WholeDicom = 1, + + /** + * Load the whole DICOM file until pixel data, which speeds up the + * loading + **/ + OrthancPluginLoadDicomInstanceMode_UntilPixelData = 2, + + /** + * Load the whole DICOM file until pixel data, and replace pixel + * data by an empty tag whose VR (value representation) is the + * same as those of the original DICOM file + **/ + OrthancPluginLoadDicomInstanceMode_EmptyPixelData = 3, + + _OrthancPluginLoadDicomInstanceMode_INTERNAL = 0x7fffffff + } OrthancPluginLoadDicomInstanceMode; + + + /** + * @brief A 32-bit memory buffer allocated by the core system of Orthanc. + * + * A memory buffer allocated by the core system of Orthanc. When the + * content of the buffer is not useful anymore, it must be free by a + * call to ::OrthancPluginFreeMemoryBuffer(). + **/ + typedef struct + { + /** + * @brief The content of the buffer. + **/ + void* data; + + /** + * @brief The number of bytes in the buffer. + **/ + uint32_t size; + } OrthancPluginMemoryBuffer; + + + + /** + * @brief A 64-bit memory buffer allocated by the core system of Orthanc. + * + * A memory buffer allocated by the core system of Orthanc. When the + * content of the buffer is not useful anymore, it must be free by a + * call to ::OrthancPluginFreeMemoryBuffer64(). + **/ + typedef struct + { + /** + * @brief The content of the buffer. + **/ + void* data; + + /** + * @brief The number of bytes in the buffer. + **/ + uint64_t size; + } OrthancPluginMemoryBuffer64; + + + + + /** + * @brief Opaque structure that represents the HTTP connection to the client application. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput; + + + + /** + * @brief Opaque structure that represents a DICOM instance that is managed by the Orthanc core. + * @ingroup DicomInstance + **/ + typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance; + + + + /** + * @brief Opaque structure that represents an image that is uncompressed in memory. + * @ingroup Images + **/ + typedef struct _OrthancPluginImage_t OrthancPluginImage; + + + + /** + * @brief Opaque structure that represents the storage area that is actually used by Orthanc. + * @ingroup Images + **/ + typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea; + + + + /** + * @brief Opaque structure to an object that represents a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery; + + + + /** + * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers; + + + + /** + * @brief Opaque structure to an object that represents a C-Find query. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginFindQuery_t OrthancPluginFindQuery; + + + + /** + * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindAnswers; + + + + /** + * @brief Opaque structure to an object that can be used to check whether a DICOM instance matches a C-Find query. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginFindMatcher_t OrthancPluginFindMatcher; + + + + /** + * @brief Opaque structure to the set of remote Orthanc Peers that are known to the local Orthanc server. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginPeers_t OrthancPluginPeers; + + + + /** + * @brief Opaque structure to a job to be executed by Orthanc. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginJob_t OrthancPluginJob; + + + + /** + * @brief Opaque structure that represents a node in a JSON or XML + * document used in DICOMweb. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginDicomWebNode_t OrthancPluginDicomWebNode; + + + + /** + * @brief Signature of a callback function that answers to a REST request. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) ( + OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + + + /** + * @brief Signature of a callback function that is triggered when Orthanc stores a new DICOM instance. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) ( + const OrthancPluginDicomInstance* instance, + const char* instanceId); + + + + /** + * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) ( + OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId); + + + + /** + * @brief Signature of a callback function to decode a DICOM instance as an image. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) ( + OrthancPluginImage** target, + const void* dicom, + const uint32_t size, + uint32_t frameIndex); + + + + /** + * @brief Signature of a function to free dynamic memory. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginFree) (void* buffer); + + + + /** + * @brief Signature of a function to set the content of a node + * encoding a binary DICOM tag, into a JSON or XML document + * generated for DICOMweb. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginDicomWebSetBinaryNode) ( + OrthancPluginDicomWebNode* node, + OrthancPluginDicomWebBinaryMode mode, + const char* bulkDataUri); + + + + /** + * @brief Callback for writing to the storage area. + * + * Signature of a callback function that is triggered when Orthanc writes a file to the storage area. + * + * @param uuid The UUID of the file. + * @param content The content of the file. + * @param size The size of the file. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) ( + const char* uuid, + const void* content, + int64_t size, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading from the storage area. + * + * Signature of a callback function that is triggered when Orthanc reads a file from the storage area. + * + * @param content The content of the file (output). + * @param size The size of the file (output). + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated New plugins should use OrthancPluginStorageRead2 + * + * @warning The "content" buffer *must* have been allocated using + * the "malloc()" function of your C standard library (i.e. nor + * "new[]", neither a pointer to a buffer). The "free()" function of + * your C standard library will automatically be invoked on the + * "content" pointer. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) ( + void** content, + int64_t* size, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading a whole file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc + * reads a whole file from the storage area. + * + * @param target Memory buffer where to store the content of the file. It must be allocated by the + * plugin using OrthancPluginCreateMemoryBuffer64(). The core of Orthanc will free it. + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageReadWhole) ( + OrthancPluginMemoryBuffer64* target, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading a range of a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc + * reads a portion of a file from the storage area. Orthanc + * indicates the start position and the length of the range. + * + * @param target Memory buffer where to store the content of the range. + * The memory buffer is allocated and freed by Orthanc. The length of the range + * of interest corresponds to the size of this buffer. + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @param rangeStart Start position of the requested range in the file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageReadRange) ( + OrthancPluginMemoryBuffer64* target, + const char* uuid, + OrthancPluginContentType type, + uint64_t rangeStart); + + + + /** + * @brief Callback for removing a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area. + * + * @param uuid The UUID of the file to be removed. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) ( + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback to handle the C-Find SCP requests for worklists. + * + * Signature of a callback function that is triggered when Orthanc + * receives a C-Find SCP request against modality worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) ( + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const char* issuerAet, + const char* calledAet); + + + + /** + * @brief Callback to filter incoming HTTP requests received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives an HTTP/REST request, and that answers whether + * this request should be allowed. If the callback returns "0" + * ("false"), the server answers with HTTP status code 403 + * (Forbidden). + * + * Pay attention to the fact that this function may be invoked + * concurrently by different threads of the Web server of + * Orthanc. You must implement proper locking if applicable. + * + * @param method The HTTP method used by the request. + * @param uri The URI of interest. + * @param ip The IP address of the HTTP client. + * @param headersCount The number of HTTP headers. + * @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 Callbacks + * @deprecated Please instead use OrthancPluginIncomingHttpRequestFilter2() + **/ + typedef int32_t (*OrthancPluginIncomingHttpRequestFilter) ( + OrthancPluginHttpMethod method, + const char* uri, + const char* ip, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues); + + + + /** + * @brief Callback to filter incoming HTTP requests received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives an HTTP/REST request, and that answers whether + * this request should be allowed. If the callback returns "0" + * ("false"), the server answers with HTTP status code 403 + * (Forbidden). + * + * Pay attention to the fact that this function may be invoked + * concurrently by different threads of the Web server of + * Orthanc. You must implement proper locking if applicable. + * + * @param method The HTTP method used by the request. + * @param uri The URI of interest. + * @param ip The IP address of the HTTP client. + * @param headersCount The number of HTTP headers. + * @param headersKeys The keys of the HTTP headers (always converted to low-case). + * @param headersValues The values of the HTTP headers. + * @param getArgumentsCount The number of GET arguments (only for the GET HTTP method). + * @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 Callbacks + **/ + typedef int32_t (*OrthancPluginIncomingHttpRequestFilter2) ( + OrthancPluginHttpMethod method, + const char* uri, + const char* ip, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + uint32_t getArgumentsCount, + const char* const* getArgumentsKeys, + const char* const* getArgumentsValues); + + + + /** + * @brief Callback to handle incoming C-Find SCP requests. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Find SCP request not concerning modality + * worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) ( + OrthancPluginFindAnswers* answers, + const OrthancPluginFindQuery* query, + const char* issuerAet, + const char* calledAet); + + + + /** + * @brief Callback to handle incoming C-Move SCP requests. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Move SCP request. The callback receives the + * type of the resource of interest (study, series, instance...) + * together with the DICOM tags containing its identifiers. In turn, + * the plugin must create a driver object that will be responsible + * for driving the successive move suboperations. + * + * @param resourceType The type of the resource of interest. Note + * that this might be set to ResourceType_None if the + * QueryRetrieveLevel (0008,0052) tag was not provided by the + * issuer (i.e. the originator modality). + * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL. + * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL. + * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL. + * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL. + * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL. + * @param originatorAet The Application Entity Title (AET) of the + * modality from which the request originates. + * @param sourceAet The Application Entity Title (AET) of the + * modality that should send its DICOM files to another modality. + * @param targetAet The Application Entity Title (AET) of the + * modality that should receive the DICOM files. + * @param originatorId The Message ID issued by the originator modality, + * as found in tag (0000,0110) of the DICOM query emitted by the issuer. + * + * @return The NULL value if the plugin cannot deal with this query, + * or a pointer to the driver object that is responsible for + * handling the successive move suboperations. + * + * @note If targetAet equals sourceAet, this is actually a query/retrieve operation. + * @ingroup DicomCallbacks + **/ + typedef void* (*OrthancPluginMoveCallback) ( + OrthancPluginResourceType resourceType, + const char* patientId, + const char* accessionNumber, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid, + const char* originatorAet, + const char* sourceAet, + const char* targetAet, + uint16_t originatorId); + + + /** + * @brief Callback to read the size of a C-Move driver. + * + * Signature of a callback function that returns the number of + * C-Move suboperations that are to be achieved by the given C-Move + * driver. This driver is the return value of a previous call to the + * OrthancPluginMoveCallback() callback. + * + * @param moveDriver The C-Move driver of interest. + * @return The number of suboperations. + * @ingroup DicomCallbacks + **/ + typedef uint32_t (*OrthancPluginGetMoveSize) (void* moveDriver); + + + /** + * @brief Callback to apply one C-Move suboperation. + * + * Signature of a callback function that applies the next C-Move + * suboperation that os to be achieved by the given C-Move + * driver. This driver is the return value of a previous call to the + * OrthancPluginMoveCallback() callback. + * + * @param moveDriver The C-Move driver of interest. + * @return 0 if success, or the error code if failure. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginApplyMove) (void* moveDriver); + + + /** + * @brief Callback to free one C-Move driver. + * + * Signature of a callback function that releases the resources + * allocated by the given C-Move driver. This driver is the return + * value of a previous call to the OrthancPluginMoveCallback() + * callback. + * + * @param moveDriver The C-Move driver of interest. + * @ingroup DicomCallbacks + **/ + typedef void (*OrthancPluginFreeMove) (void* moveDriver); + + + /** + * @brief Callback to finalize one custom job. + * + * Signature of a callback function that releases all the resources + * allocated by the given job. This job is the argument provided to + * OrthancPluginCreateJob(). + * + * @param job The job of interest. + * @ingroup Toolbox + **/ + typedef void (*OrthancPluginJobFinalize) (void* job); + + + /** + * @brief Callback to check the progress of one custom job. + * + * Signature of a callback function that returns the progress of the + * job. + * + * @param job The job of interest. + * @return The progress, as a floating-point number ranging from 0 to 1. + * @ingroup Toolbox + **/ + typedef float (*OrthancPluginJobGetProgress) (void* job); + + + /** + * @brief Callback to retrieve the content of one custom job. + * + * Signature of a callback function that returns human-readable + * statistics about the job. This statistics must be formatted as a + * JSON object. This information is notably displayed in the "Jobs" + * tab of "Orthanc Explorer". + * + * @param job The job of interest. + * @return The statistics, as a JSON object encoded as a string. + * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. + **/ + typedef const char* (*OrthancPluginJobGetContent) (void* job); + + + /** + * @brief Callback to retrieve the content of one custom job. + * + * Signature of a callback function that returns human-readable + * statistics about the job. This statistics must be formatted as a + * JSON object. This information is notably displayed in the "Jobs" + * tab of "Orthanc Explorer". + * + * @param target The target memory buffer where to store the JSON string. + * This buffer must be allocated using OrthancPluginCreateMemoryBuffer() + * and will be freed by the Orthanc core. + * @param job The job of interest. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobGetContent2) (OrthancPluginMemoryBuffer* target, + void* job); + + + /** + * @brief Callback to serialize one custom job. + * + * Signature of a callback function that returns a serialized + * version of the job, formatted as a JSON object. This + * serialization is stored in the Orthanc database, and is used to + * reload the job on the restart of Orthanc. The "unserialization" + * callback (with OrthancPluginJobsUnserializer signature) will + * receive this serialized object. + * + * @param job The job of interest. + * @return The serialized job, as a JSON object encoded as a string. + * @see OrthancPluginRegisterJobsUnserializer() + * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. + **/ + typedef const char* (*OrthancPluginJobGetSerialized) (void* job); + + + /** + * @brief Callback to serialize one custom job. + * + * Signature of a callback function that returns a serialized + * version of the job, formatted as a JSON object. This + * serialization is stored in the Orthanc database, and is used to + * reload the job on the restart of Orthanc. The "unserialization" + * callback (with OrthancPluginJobsUnserializer signature) will + * receive this serialized object. + * + * @param target The target memory buffer where to store the JSON string. + * This buffer must be allocated using OrthancPluginCreateMemoryBuffer() + * and will be freed by the Orthanc core. + * @param job The job of interest. + * @return 1 if the serialization has succeeded, 0 if serialization is + * not implemented for this type of job, or -1 in case of error. + **/ + typedef int32_t (*OrthancPluginJobGetSerialized2) (OrthancPluginMemoryBuffer* target, + void* job); + + + /** + * @brief Callback to execute one step of a custom job. + * + * Signature of a callback function that executes one step in the + * job. The jobs engine of Orthanc will make successive calls to + * this method, as long as it returns + * OrthancPluginJobStepStatus_Continue. + * + * @param job The job of interest. + * @return The status of execution. + * @ingroup Toolbox + **/ + typedef OrthancPluginJobStepStatus (*OrthancPluginJobStep) (void* job); + + + /** + * @brief Callback executed once one custom job leaves the "running" state. + * + * Signature of a callback function that is invoked once a job + * leaves the "running" state. This can happen if the previous call + * to OrthancPluginJobStep has failed/succeeded, if the host Orthanc + * server is being stopped, or if the user manually tags the job as + * paused/canceled. This callback allows the plugin to free + * resources allocated for running this custom job (e.g. to stop + * threads, or to remove temporary files). + * + * Note that handling pauses might involves a specific treatment + * (such a stopping threads, but keeping temporary files on the + * disk). This "paused" situation can be checked by looking at the + * "reason" parameter. + * + * @param job The job of interest. + * @param reason The reason for leaving the "running" state. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobStop) (void* job, + OrthancPluginJobStopReason reason); + + + /** + * @brief Callback executed once one stopped custom job is started again. + * + * Signature of a callback function that is invoked once a job + * leaves the "failure/canceled" state, to be started again. This + * function will typically reset the progress to zero. Note that + * before being actually executed, the job would first be tagged as + * "pending" in the Orthanc jobs engine. + * + * @param job The job of interest. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobReset) (void* job); + + + /** + * @brief Callback executed to unserialize a custom job. + * + * Signature of a callback function that unserializes a job that was + * saved in the Orthanc database. + * + * @param jobType The type of the job, as provided to OrthancPluginCreateJob(). + * @param serialized The serialization of the job, as provided by OrthancPluginJobGetSerialized. + * @return The unserialized job (as created by OrthancPluginCreateJob()), or NULL + * if this unserializer cannot handle this job type. + * @see OrthancPluginRegisterJobsUnserializer() + * @ingroup Callbacks + **/ + typedef OrthancPluginJob* (*OrthancPluginJobsUnserializer) (const char* jobType, + const char* serialized); + + + + /** + * @brief Callback executed to update the metrics of the plugin. + * + * Signature of a callback function that is called by Orthanc + * whenever a monitoring tool (such as Prometheus) asks the current + * values of the metrics. This callback gives the plugin a chance to + * update its metrics, by calling OrthancPluginSetMetricsValue() or + * OrthancPluginSetMetricsIntegerValue(). + * This is typically useful for metrics that are expensive to + * acquire. + * + * @see OrthancPluginRegisterRefreshMetrics() + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginRefreshMetricsCallback) (); + + + + /** + * @brief Callback executed to encode a binary tag in DICOMweb. + * + * Signature of a callback function that is called by Orthanc + * whenever a DICOM tag that contains a binary value must be written + * to a JSON or XML node, while a DICOMweb document is being + * generated. The value representation (VR) of the DICOM tag can be + * OB, OD, OF, OL, OW, or UN. + * + * @see OrthancPluginEncodeDicomWebJson() and OrthancPluginEncodeDicomWebXml() + * @param node The node being generated, as provided by Orthanc. + * @param setter The setter to be used to encode the content of the node. If + * the setter is not called, the binary tag is not written to the output document. + * @param levelDepth The depth of the node in the DICOM hierarchy of sequences. + * This parameter gives the number of elements in the "levelTagGroup", + * "levelTagElement", and "levelIndex" arrays. + * @param levelTagGroup The group of the parent DICOM tags in the hierarchy. + * @param levelTagElement The element of the parent DICOM tags in the hierarchy. + * @param levelIndex The index of the node in the parent sequences of the hierarchy. + * @param tagGroup The group of the DICOM tag of interest. + * @param tagElement The element of the DICOM tag of interest. + * @param vr The value representation of the binary DICOM node. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginDicomWebBinaryCallback) ( + OrthancPluginDicomWebNode* node, + OrthancPluginDicomWebSetBinaryNode setter, + uint32_t levelDepth, + const uint16_t* levelTagGroup, + const uint16_t* levelTagElement, + const uint32_t* levelIndex, + uint16_t tagGroup, + uint16_t tagElement, + OrthancPluginValueRepresentation vr); + + + + /** + * @brief Callback executed to encode a binary tag in DICOMweb. + * + * Signature of a callback function that is called by Orthanc + * whenever a DICOM tag that contains a binary value must be written + * to a JSON or XML node, while a DICOMweb document is being + * generated. The value representation (VR) of the DICOM tag can be + * OB, OD, OF, OL, OW, or UN. + * + * @see OrthancPluginEncodeDicomWebJson() and OrthancPluginEncodeDicomWebXml() + * @param node The node being generated, as provided by Orthanc. + * @param setter The setter to be used to encode the content of the node. If + * the setter is not called, the binary tag is not written to the output document. + * @param levelDepth The depth of the node in the DICOM hierarchy of sequences. + * This parameter gives the number of elements in the "levelTagGroup", + * "levelTagElement", and "levelIndex" arrays. + * @param levelTagGroup The group of the parent DICOM tags in the hierarchy. + * @param levelTagElement The element of the parent DICOM tags in the hierarchy. + * @param levelIndex The index of the node in the parent sequences of the hierarchy. + * @param tagGroup The group of the DICOM tag of interest. + * @param tagElement The element of the DICOM tag of interest. + * @param vr The value representation of the binary DICOM node. + * @param payload The user payload. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginDicomWebBinaryCallback2) ( + OrthancPluginDicomWebNode* node, + OrthancPluginDicomWebSetBinaryNode setter, + uint32_t levelDepth, + const uint16_t* levelTagGroup, + const uint16_t* levelTagElement, + const uint32_t* levelIndex, + uint16_t tagGroup, + uint16_t tagElement, + OrthancPluginValueRepresentation vr, + void* payload); + + + + /** + * @brief Data structure that contains information about the Orthanc core. + **/ + typedef struct _OrthancPluginContext_t + { + void* pluginsManager; + const char* orthancVersion; + OrthancPluginFree Free; + OrthancPluginErrorCode (*InvokeService) (struct _OrthancPluginContext_t* context, + _OrthancPluginService service, + const void* params); + } OrthancPluginContext; + + + + /** + * @brief An entry in the dictionary of DICOM tags. + **/ + typedef struct + { + uint16_t group; /*!< The group of the tag */ + uint16_t element; /*!< The element of the tag */ + OrthancPluginValueRepresentation vr; /*!< The value representation of the tag */ + uint32_t minMultiplicity; /*!< The minimum multiplicity of the tag */ + uint32_t maxMultiplicity; /*!< The maximum multiplicity of the tag (0 means arbitrary) */ + } OrthancPluginDictionaryEntry; + + + + /** + * @brief Free a string. + * + * Free a string that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param str The string to be freed. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeString( + OrthancPluginContext* context, + char* str) + { + if (str != NULL) + { + context->Free(str); + } + } + + + /** + * @brief Check that the version of the hosting Orthanc is above a given version. + * + * This function checks whether the version of the Orthanc server + * running this plugin, is above the given version. Contrarily to + * OrthancPluginCheckVersion(), it is up to the developer of the + * plugin to make sure that all the Orthanc SDK services called by + * the plugin are actually implemented in the given version of + * Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param expectedMajor Expected major version. + * @param expectedMinor Expected minor version. + * @param expectedRevision Expected revision. + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @see OrthancPluginCheckVersion() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginCheckVersionAdvanced( + OrthancPluginContext* context, + int32_t expectedMajor, + int32_t expectedMinor, + int32_t expectedRevision) + { + int32_t major, minor, revision; + + if (sizeof(int) != sizeof(int32_t) || /* Ensure binary compatibility with Orthanc SDK <= 1.12.1 */ + sizeof(int32_t) != sizeof(OrthancPluginErrorCode) || + sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) || + sizeof(int32_t) != sizeof(_OrthancPluginService) || + sizeof(int32_t) != sizeof(_OrthancPluginProperty) || + sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) || + sizeof(int32_t) != sizeof(OrthancPluginContentType) || + sizeof(int32_t) != sizeof(OrthancPluginResourceType) || + sizeof(int32_t) != sizeof(OrthancPluginChangeType) || + sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || + sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || + sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) || + sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) || + sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) || + sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) || + sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) || + sizeof(int32_t) != sizeof(OrthancPluginConstraintType) || + sizeof(int32_t) != sizeof(OrthancPluginMetricsType) || + sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) || + sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) || + sizeof(int32_t) != sizeof(OrthancPluginLoadDicomInstanceMode)) + { + /* Mismatch in the size of the enumerations */ + return 0; + } + + /* Assume compatibility with the mainline */ + if (!strcmp(context->orthancVersion, "mainline")) + { + return 1; + } + + /* Parse the version of the Orthanc core */ + if ( +#ifdef _MSC_VER + sscanf_s +#else + sscanf +#endif + (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3) + { + return 0; + } + + /* Check the major number of the version */ + + if (major > expectedMajor) + { + return 1; + } + + if (major < expectedMajor) + { + return 0; + } + + /* Check the minor number of the version */ + + if (minor > expectedMinor) + { + return 1; + } + + if (minor < expectedMinor) + { + return 0; + } + + /* Check the revision number of the version */ + + if (revision >= expectedRevision) + { + return 1; + } + else + { + return 0; + } + } + + + /** + * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. + * + * This function checks whether the version of the Orthanc server + * running this plugin, is above the version of the current Orthanc + * SDK header. This guarantees that the plugin is compatible with + * the hosting Orthanc (i.e. it will not call unavailable services). + * The result of this function should always be checked in the + * OrthancPluginInitialize() entry point of the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @see OrthancPluginCheckVersionAdvanced() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginCheckVersion( + OrthancPluginContext* context) + { + return OrthancPluginCheckVersionAdvanced( + context, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + } + + + /** + * @brief Free a memory buffer. + * + * Free a memory buffer that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer to release. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* buffer) + { + context->Free(buffer->data); + } + + + /** + * @brief Free a memory buffer. + * + * Free a memory buffer that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer to release. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer64( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer64* buffer) + { + context->Free(buffer->data); + } + + + /** + * @brief Log an error. + * + * Log an error message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogError( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogError, message); + } + + + /** + * @brief Log a warning. + * + * Log a warning message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogWarning, message); + } + + + /** + * @brief Log an information. + * + * Log an information message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogInfo, message); + } + + + + typedef struct + { + const char* pathRegularExpression; + OrthancPluginRestCallback callback; + } _OrthancPluginRestCallback; + + /** + * @brief Register a REST callback. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Each REST callback is guaranteed to run in mutual exclusion. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallbackNoLock() + * + * @note + * The regular expression is case sensitive and must follow the + * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html). + * + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, ¶ms); + } + + + + /** + * @brief Register a REST callback, without locking. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Contrarily to OrthancPluginRegisterRestCallback(), the callback + * will NOT be invoked in mutual exclusion. This can be useful for + * high-performance plugins that must handle concurrent requests + * (Orthanc uses a pool of threads, one thread being assigned to + * each incoming HTTP request). Of course, if using this function, + * it is up to the plugin to implement the required locking + * mechanisms. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallback() + * + * @note + * The regular expression is case sensitive and must follow the + * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html). + * + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, ¶ms); + } + + + + typedef struct + { + OrthancPluginOnStoredInstanceCallback callback; + } _OrthancPluginOnStoredInstanceCallback; + + /** + * @brief Register a callback for received instances. + * + * This function registers a callback function that is called + * whenever a new DICOM instance is stored into the Orthanc core. + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback( + OrthancPluginContext* context, + OrthancPluginOnStoredInstanceCallback callback) + { + _OrthancPluginOnStoredInstanceCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const void* answer; + uint32_t answerSize; + const char* mimeType; + } _OrthancPluginAnswerBuffer; + + /** + * @brief Answer to a REST request. + * + * This function answers to a REST request with the content of a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the answer. + * @param answerSize Number of bytes of the answer. + * @param mimeType The MIME type of the answer. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const void* answer, + uint32_t answerSize, + const char* mimeType) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = mimeType; + context->InvokeService(context, _OrthancPluginService_AnswerBuffer, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + } _OrthancPluginCompressAndAnswerPngImage; + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressAndAnswerImage; + + + /** + * @brief Answer to a REST request with a PNG image. + * + * This function answers to a REST request with a PNG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a PNG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* No quality for PNG */ + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* instanceId; + } _OrthancPluginGetDicomForInstance; + + /** + * @brief Retrieve a DICOM instance using its Orthanc identifier. + * + * Retrieve a DICOM instance using its Orthanc identifier. The DICOM + * file is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instanceId The Orthanc identifier of the DICOM instance of interest. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetDicomForInstance( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* instanceId) + { + _OrthancPluginGetDicomForInstance params; + params.target = target; + params.instanceId = instanceId; + return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + } _OrthancPluginRestApiGet; + + /** + * @brief Make a GET call to the built-in Orthanc REST API. + * + * Make a GET call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiGetAfterPlugins() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGet, ¶ms); + } + + + + /** + * @brief Make a GET call to the REST API, as tainted by the plugins. + * + * Make a GET call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiGet() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGetAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + const void* body; + uint32_t bodySize; + } _OrthancPluginRestApiPostPut; + + /** + * @brief Make a POST call to the built-in Orthanc REST API. + * + * Make a POST call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPostAfterPlugins() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPost, ¶ms); + } + + + /** + * @brief Make a POST call to the REST API, as tainted by the plugins. + * + * Make a POST call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPost() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPostAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, ¶ms); + } + + + + /** + * @brief Make a DELETE call to the built-in Orthanc REST API. + * + * Make a DELETE call to the built-in Orthanc REST API. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiDeleteAfterPlugins() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDelete( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri); + } + + + /** + * @brief Make a DELETE call to the REST API, as tainted by the plugins. + * + * Make a DELETE call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiDelete() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDeleteAfterPlugins( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri); + } + + + + /** + * @brief Make a PUT call to the built-in Orthanc REST API. + * + * Make a PUT call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPutAfterPlugins() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPut, ¶ms); + } + + + + /** + * @brief Make a PUT call to the REST API, as tainted by the plugins. + * + * Make a PUT call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPut() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPutAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* argument; + } _OrthancPluginOutputPlusArgument; + + /** + * @brief Redirect a REST request. + * + * This function answers to a REST request by redirecting the user + * to another URI using HTTP status 301. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param redirection Where to redirect. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* redirection) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = redirection; + context->InvokeService(context, _OrthancPluginService_Redirect, ¶ms); + } + + + + typedef struct + { + char** result; + const char* argument; + } _OrthancPluginRetrieveDynamicString; + + /** + * @brief Look for a patient. + * + * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored patients). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param patientID The Patient ID of interest. + * @return The NULL value if the patient is non-existent, or a string containing the + * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient( + OrthancPluginContext* context, + const char* patientID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = patientID; + + if (context->InvokeService(context, _OrthancPluginService_LookupPatient, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study. + * + * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param studyUID The Study Instance UID of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy( + OrthancPluginContext* context, + const char* studyUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = studyUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudy, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study, using the accession number. + * + * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param accessionNumber The Accession Number of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber( + OrthancPluginContext* context, + const char* accessionNumber) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = accessionNumber; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a series. + * + * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored series). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param seriesUID The Series Instance UID of interest. + * @return The NULL value if the series is non-existent, or a string containing the + * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries( + OrthancPluginContext* context, + const char* seriesUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = seriesUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupSeries, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for an instance. + * + * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored instances). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param sopInstanceUID The SOP Instance UID of interest. + * @return The NULL value if the instance is non-existent, or a string containing the + * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance( + OrthancPluginContext* context, + const char* sopInstanceUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = sopInstanceUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + } _OrthancPluginSendHttpStatusCode; + + /** + * @brief Send a HTTP status code. + * + * This function answers to a REST request by sending a HTTP status + * code (such as "400 - Bad Request"). Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @ingroup REST + * @see OrthancPluginSendHttpStatus() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status) + { + _OrthancPluginSendHttpStatusCode params; + params.output = output; + params.status = status; + context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, ¶ms); + } + + + /** + * @brief Signal that a REST request is not authorized. + * + * This function answers to a REST request by signaling that it is + * not authorized. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param realm The realm for the authorization process. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* realm) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = realm; + context->InvokeService(context, _OrthancPluginService_SendUnauthorized, ¶ms); + } + + + /** + * @brief Signal that this URI does not support this HTTP method. + * + * This function answers to a REST request by signaling that the + * queried URI does not support this method. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* allowedMethods) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = allowedMethods; + context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* key; + const char* value; + } _OrthancPluginSetHttpHeader; + + /** + * @brief Set a cookie. + * + * This function sets a cookie in the HTTP client. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param cookie The cookie to be set. + * @param value The value of the cookie. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* cookie, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = cookie; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetCookie, ¶ms); + } + + + /** + * @brief Set some HTTP header. + * + * This function sets a HTTP header in the HTTP answer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param key The HTTP header to be set. + * @param value The value of the HTTP header. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* key, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = key; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetHttpHeader, ¶ms); + } + + + typedef struct + { + char** resultStringToFree; + const char** resultString; + int64_t* resultInt64; + const char* key; + const OrthancPluginDicomInstance* instance; + OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */ + } _OrthancPluginAccessDicomInstance; + + + /** + * @brief Get the AET of a DICOM instance. + * + * This function returns the Application Entity Title (AET) of the + * DICOM modality from which a DICOM instance originates. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The AET if success, NULL if error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the size of a DICOM file. + * + * This function returns the number of bytes of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The size of the file, -1 in case of error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + int64_t size; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &size; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return size; + } + } + + + /** + * @brief Get the data of a DICOM file. + * + * This function returns a pointer to the content of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The pointer to the DICOM data, NULL in case of error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE const void* OrthancPluginGetInstanceData( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file. + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file (with simplification). + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. In contrast with + * ::OrthancPluginGetInstanceJson(), the returned JSON file is in + * its simplified version. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Check whether a DICOM instance is associated with some metadata. + * + * This function checks whether the DICOM instance of interest is + * associated with some metadata. As of Orthanc 0.8.1, in the + * callbacks registered by + * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only + * possibly available metadata are "ReceptionDate", "RemoteAET" and + * "IndexInSeries". + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginHasInstanceMetadata( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + const char* metadata) + { + int64_t result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return (result != 0); + } + } + + + /** + * @brief Get the value of some metadata associated with a given DICOM instance. + * + * This functions returns the value of some metadata that is associated with the DICOM instance of interest. + * Before calling this function, the existence of the metadata must have been checked with + * ::OrthancPluginHasInstanceMetadata(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return The metadata value if success, NULL if error. Please note that the + * returned string belongs to the instance object and must NOT be + * deallocated. Please make a copy of the string if you wish to access + * it later. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + const char* metadata) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginStorageCreate create; + OrthancPluginStorageRead read; + OrthancPluginStorageRemove remove; + OrthancPluginFree free; + } _OrthancPluginRegisterStorageArea; + + /** + * @brief Register a custom storage area. + * + * This function registers a custom storage area, to replace the + * built-in way Orthanc stores its files on the filesystem. 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 create The callback function to store a file on the custom storage area. + * @param read The callback function to read a file from the custom storage area. + * @param remove The callback function to remove a file from the custom storage area. + * @ingroup Callbacks + * @deprecated Please use OrthancPluginRegisterStorageArea2() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageRead read, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea params; + params.create = create; + params.read = read; + params.remove = remove; + +#ifdef __cplusplus + params.free = ::free; +#else + params.free = free; +#endif + + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, ¶ms); + } + + + + /** + * @brief Return the path to the Orthanc executable. + * + * This function returns the path to the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the directory containing the Orthanc. + * + * This function returns the path to the directory containing the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the path to the configuration file(s). + * + * This function returns the path to the configuration file(s) that + * was specified when starting Orthanc. Since version 0.9.1, this + * path can refer to a folder that stores a set of configuration + * files. This function is deprecated in favor of + * OrthancPluginGetConfiguration(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + * @see OrthancPluginGetConfiguration() + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginOnChangeCallback callback; + } _OrthancPluginOnChangeCallback; + + /** + * @brief Register a callback to monitor changes. + * + * This function registers a callback function that is called + * whenever a change happens to some DICOM resource. + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( + OrthancPluginContext* context, + OrthancPluginOnChangeCallback callback) + { + _OrthancPluginOnChangeCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); + } + + + + typedef struct + { + const char* plugin; + _OrthancPluginProperty property; + const char* value; + } _OrthancPluginSetPluginProperty; + + + /** + * @brief Set the URI where the plugin provides its Web interface. + * + * For plugins that come with a Web interface, this function + * declares the entry path where to find this interface. This + * information is notably used in the "Plugins" page of Orthanc + * Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The root URI for this plugin. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri( + OrthancPluginContext* context, + const char* uri) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_RootUri; + params.value = uri; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Set a description for this plugin. + * + * Set a description for this plugin. It is displayed in the + * "Plugins" page of Orthanc Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param description The description. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription( + OrthancPluginContext* context, + const char* description) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_Description; + params.value = description; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Extend the JavaScript code of Orthanc Explorer. + * + * Add JavaScript code to customize the default behavior of Orthanc + * Explorer. This can for instance be used to add new buttons. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param javascript The custom JavaScript code. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer( + OrthancPluginContext* context, + const char* javascript) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_OrthancExplorer; + params.value = javascript; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + typedef struct + { + char** result; + int32_t property; + const char* value; + } _OrthancPluginGlobalProperty; + + + /** + * @brief Get the value of a global property. + * + * Get the value of a global property that is stored in the Orthanc database. Global + * properties whose index is below 1024 are reserved by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param defaultValue The value to return, if the global property is unset. + * @return The value of the global property, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* defaultValue) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = property; + params.value = defaultValue; + + if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Set the value of a global property. + * + * Set the value of a global property into the Orthanc + * database. Setting a global property can be used by plugins to + * save their internal parameters. Plugins are only allowed to set + * properties whose index are above or equal to 1024 (properties + * below 1024 are read-only and reserved by Orthanc). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param value The value to be set in the global property. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* value) + { + _OrthancPluginGlobalProperty params; + params.result = NULL; + params.property = property; + params.value = value; + + return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, ¶ms); + } + + + + typedef struct + { + int32_t *resultInt32; + uint32_t *resultUint32; + int64_t *resultInt64; + uint64_t *resultUint64; + } _OrthancPluginReturnSingleValue; + + /** + * @brief Get the number of command-line arguments. + * + * Retrieve the number of command-line arguments that were used to launch Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of arguments. + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Get the value of a command-line argument. + * + * Get the value of one of the command-line arguments that were used + * to launch Orthanc. The number of available arguments can be + * retrieved by OrthancPluginGetCommandLineArgumentsCount(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param argument The index of the argument. + * @return The value of the argument, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument( + OrthancPluginContext* context, + uint32_t argument) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = (int32_t) argument; + params.value = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the expected version of the database schema. + * + * Retrieve the expected version of the database schema. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The version. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Return the content of the configuration file(s). + * + * This function returns the content of the configuration that is + * used by Orthanc, formatted as a JSON string. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the configuration. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* subType; + const char* contentType; + } _OrthancPluginStartMultipartAnswer; + + /** + * @brief Start an HTTP multipart answer. + * + * Initiates a HTTP multipart answer, as the result of a REST request. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param subType The sub-type of the multipart answer ("mixed" or "related"). + * @param contentType The MIME type of the items in the multipart answer. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* subType, + const char* contentType) + { + _OrthancPluginStartMultipartAnswer params; + params.output = output; + params.subType = subType; + params.contentType = contentType; + return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, ¶ms); + } + + + /** + * @brief Send an item as a part of some HTTP multipart answer. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const void* answer, + uint32_t answerSize) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = NULL; + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const void* source; + uint32_t size; + OrthancPluginCompressionType compression; + uint8_t uncompress; + } _OrthancPluginBufferCompression; + + + /** + * @brief Compress or decompress a buffer. + * + * This function compresses or decompresses a buffer, using the + * version of the zlib library that is used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param source The source buffer. + * @param size The size in bytes of the source buffer. + * @param compression The compression algorithm. + * @param uncompress If set to "0", the buffer must be compressed. + * If set to "1", the buffer must be uncompressed. + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const void* source, + uint32_t size, + OrthancPluginCompressionType compression, + uint8_t uncompress) + { + _OrthancPluginBufferCompression params; + params.target = target; + params.source = source; + params.size = size; + params.compression = compression; + params.uncompress = uncompress; + + return context->InvokeService(context, _OrthancPluginService_BufferCompression, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* path; + } _OrthancPluginReadFile; + + /** + * @brief Read a file. + * + * Read the content of a file on the filesystem, and returns it into + * a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param path The path of the file to be read. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReadFile( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* path) + { + _OrthancPluginReadFile params; + params.target = target; + params.path = path; + return context->InvokeService(context, _OrthancPluginService_ReadFile, ¶ms); + } + + + + typedef struct + { + const char* path; + const void* data; + uint32_t size; + } _OrthancPluginWriteFile; + + /** + * @brief Write a file. + * + * Write the content of a memory buffer to the filesystem. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param path The path of the file to be written. + * @param data The content of the memory buffer. + * @param size The size of the memory buffer. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWriteFile( + OrthancPluginContext* context, + const char* path, + const void* data, + uint32_t size) + { + _OrthancPluginWriteFile params; + params.path = path; + params.data = data; + params.size = size; + return context->InvokeService(context, _OrthancPluginService_WriteFile, ¶ms); + } + + + + typedef struct + { + const char** target; + OrthancPluginErrorCode error; + } _OrthancPluginGetErrorDescription; + + /** + * @brief Get the description of a given error code. + * + * This function returns the description of a given error code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param error The error code of interest. + * @return The error description. This is a statically-allocated + * string, do not free it. + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription( + OrthancPluginContext* context, + OrthancPluginErrorCode error) + { + const char* result = NULL; + + _OrthancPluginGetErrorDescription params; + params.target = &result; + params.error = error; + + if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, ¶ms) != OrthancPluginErrorCode_Success || + result == NULL) + { + return "Unknown error code"; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + const void* body; + uint32_t bodySize; + } _OrthancPluginSendHttpStatus; + + /** + * @brief Send a HTTP status, with a custom body. + * + * This function answers to a HTTP request by sending a HTTP status + * code (such as "400 - Bad Request"), together with a body + * describing the error. The body will only be returned if the + * configuration option "HttpDescribeErrors" of Orthanc is set to "true". + * + * Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @param body The body of the answer. + * @param bodySize The size of the body. + * @see OrthancPluginSendHttpStatusCode() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status, + const void* body, + uint32_t bodySize) + { + _OrthancPluginSendHttpStatus params; + params.output = output; + params.status = status; + params.body = body; + params.bodySize = bodySize; + context->InvokeService(context, _OrthancPluginService_SendHttpStatus, ¶ms); + } + + + + typedef struct + { + const OrthancPluginImage* image; + uint32_t* resultUint32; + OrthancPluginPixelFormat* resultPixelFormat; + void** resultBuffer; + } _OrthancPluginGetImageInfo; + + + /** + * @brief Return the pixel format of an image. + * + * This function returns the type of memory layout for the pixels of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pixel format. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat OrthancPluginGetImagePixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + OrthancPluginPixelFormat target; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultPixelFormat = ⌖ + + if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return OrthancPluginPixelFormat_Unknown; + } + else + { + return (OrthancPluginPixelFormat) target; + } + } + + + + /** + * @brief Return the width of an image. + * + * This function returns the width of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The width. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageWidth( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t width; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &width; + + if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return width; + } + } + + + + /** + * @brief Return the height of an image. + * + * This function returns the height of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The height. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageHeight( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t height; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &height; + + if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return height; + } + } + + + + /** + * @brief Return the pitch of an image. + * + * This function returns the pitch of the given image. The pitch is + * defined as the number of bytes between 2 successive lines of the + * image in the memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pitch. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImagePitch( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t pitch; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &pitch; + + if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return pitch; + } + } + + + + /** + * @brief Return a pointer to the content of an image. + * + * This function returns a pointer to the memory buffer that + * contains the pixels of the image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pointer. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void* OrthancPluginGetImageBuffer( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + void* target = NULL; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.resultBuffer = ⌖ + params.image = image; + + if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginImage** target; + const void* data; + uint32_t size; + OrthancPluginImageFormat format; + } _OrthancPluginUncompressImage; + + + /** + * @brief Decode a compressed image. + * + * This function decodes a compressed image from a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param data Pointer to a memory buffer containing the compressed image. + * @param size Size of the memory buffer containing the compressed image. + * @param format The file format of the compressed image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage( + OrthancPluginContext* context, + const void* data, + uint32_t size, + OrthancPluginImageFormat format) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginUncompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.data = data; + params.size = size; + params.format = format; + + if (context->InvokeService(context, _OrthancPluginService_UncompressImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + + typedef struct + { + OrthancPluginImage* image; + } _OrthancPluginFreeImage; + + /** + * @brief Free an image. + * + * This function frees an image that was decoded with OrthancPluginUncompressImage(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeImage( + OrthancPluginContext* context, + OrthancPluginImage* image) + { + _OrthancPluginFreeImage params; + params.image = image; + + context->InvokeService(context, _OrthancPluginService_FreeImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressImage; + + + /** + * @brief Encode a PNG image. + * + * This function compresses the given memory buffer containing an + * image using the PNG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginCompressAndAnswerPngImage() + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* Unused for PNG */ + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + /** + * @brief Encode a JPEG image. + * + * This function compresses the given memory buffer containing an + * image using the JPEG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + + /** + * @brief Answer to a REST request with a JPEG image. + * + * This function answers to a REST request with a JPEG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a JPEG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginHttpMethod method; + const char* url; + const char* username; + const char* password; + const void* body; + uint32_t bodySize; + } _OrthancPluginCallHttpClient; + + + /** + * @brief Issue a HTTP GET call. + * + * Make a HTTP GET call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiGet() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Get; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP POST call. + * + * Make a HTTP POST call to the given URL. The result to the query + * is stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPost() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const void* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Post; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP PUT call. + * + * Make a HTTP PUT call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPut() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const void* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Put; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP DELETE call. + * + * Make a HTTP DELETE call to the given URL. Favor + * OrthancPluginRestApiDelete() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpDelete( + OrthancPluginContext* context, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.method = OrthancPluginHttpMethod_Delete; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + const OrthancPluginImage* source; + OrthancPluginPixelFormat targetFormat; + } _OrthancPluginConvertPixelFormat; + + + /** + * @brief Change the pixel format of an image. + * + * This function creates a new image, changing the memory layout of the pixels. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param source The source image. + * @param targetFormat The target pixel format. + * @return The resulting image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* source, + OrthancPluginPixelFormat targetFormat) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginConvertPixelFormat params; + params.target = ⌖ + params.source = source; + params.targetFormat = targetFormat; + + if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Return the number of available fonts. + * + * This function returns the number of fonts that are built in the + * Orthanc core. These fonts can be used to draw texts on images + * through OrthancPluginDrawText(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of fonts. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + + typedef struct + { + uint32_t fontIndex; /* in */ + const char** name; /* out */ + uint32_t* size; /* out */ + } _OrthancPluginGetFontInfo; + + /** + * @brief Return the name of a font. + * + * This function returns the name of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font name. This is a statically-allocated string, do not free it. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName( + OrthancPluginContext* context, + uint32_t fontIndex) + { + const char* result = NULL; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.name = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the size of a font. + * + * This function returns the size of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font size. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize( + OrthancPluginContext* context, + uint32_t fontIndex) + { + uint32_t result; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.size = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginImage* image; + uint32_t fontIndex; + const char* utf8Text; + int32_t x; + int32_t y; + uint8_t r; + uint8_t g; + uint8_t b; + } _OrthancPluginDrawText; + + + /** + * @brief Draw text on an image. + * + * This function draws some text on some image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image upon which to draw the text. + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string. + * @param x The X position of the text over the image. + * @param y The Y position of the text over the image. + * @param r The value of the red color channel of the text. + * @param g The value of the green color channel of the text. + * @param b The value of the blue color channel of the text. + * @return 0 if success, other value if error. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDrawText( + OrthancPluginContext* context, + OrthancPluginImage* image, + uint32_t fontIndex, + const char* utf8Text, + int32_t x, + int32_t y, + uint8_t r, + uint8_t g, + uint8_t b) + { + _OrthancPluginDrawText params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.fontIndex = fontIndex; + params.utf8Text = utf8Text; + params.x = x; + params.y = y; + params.r = r; + params.g = g; + params.b = b; + + return context->InvokeService(context, _OrthancPluginService_DrawText, ¶ms); + } + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + const void* content; + uint64_t size; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaCreate; + + + /** + * @brief Create a file inside the storage area. + * + * This function creates a new file inside the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be created. + * @param content The content to store in the newly created file. + * @param size The size of the content. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiPut()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + const void* content, + uint64_t size, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaCreate params; + params.storageArea = storageArea; + params.uuid = uuid; + params.content = content; + params.size = size; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, ¶ms); + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRead; + + + /** + * @brief Read a file from the storage area. + * + * This function reads the content of a given file from the storage + * area that is currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be read. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiGet()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRead params; + params.target = target; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRemove; + + /** + * @brief Remove a file from the storage area. + * + * This function removes a given file from the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be removed. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiDelete()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRemove params; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, ¶ms); + } + + + + typedef struct + { + OrthancPluginErrorCode* target; + int32_t code; + uint16_t httpStatus; + const char* message; + } _OrthancPluginRegisterErrorCode; + + /** + * @brief Declare a custom error code for this plugin. + * + * This function declares a custom error code that can be generated + * by this plugin. This declaration is used to enrich the body of + * the HTTP answer in the case of an error, and to set the proper + * HTTP status code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param code The error code that is internal to this plugin. + * @param httpStatus The HTTP status corresponding to this error. + * @param message The description of the error. + * @return The error code that has been assigned inside the Orthanc core. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterErrorCode( + OrthancPluginContext* context, + int32_t code, + uint16_t httpStatus, + const char* message) + { + OrthancPluginErrorCode target; + + _OrthancPluginRegisterErrorCode params; + params.target = ⌖ + params.code = code; + params.httpStatus = httpStatus; + params.message = message; + + if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, ¶ms) == OrthancPluginErrorCode_Success) + { + return target; + } + else + { + /* There was an error while assigned the error. Use a generic code. */ + return OrthancPluginErrorCode_Plugin; + } + } + + + + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + } _OrthancPluginRegisterDictionaryTag; + + /** + * @brief Register a new tag into the DICOM dictionary. + * + * This function declares a new public tag in the dictionary of + * DICOM tags that are known to Orthanc. This function should be + * used in the OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("n"). + * @return 0 if success, other value if error. + * @see OrthancPluginRegisterPrivateDictionaryTag() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity) + { + _OrthancPluginRegisterDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + + return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, ¶ms); + } + + + + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + const char* privateCreator; + } _OrthancPluginRegisterPrivateDictionaryTag; + + /** + * @brief Register a new private tag into the DICOM dictionary. + * + * This function declares a new private tag in the dictionary of + * DICOM tags that are known to Orthanc. This function should be + * used in the OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("n"). + * @param privateCreator The private creator of this private tag. + * @return 0 if success, other value if error. + * @see OrthancPluginRegisterDictionaryTag() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterPrivateDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity, + const char* privateCreator) + { + _OrthancPluginRegisterPrivateDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + params.privateCreator = privateCreator; + + return context->InvokeService(context, _OrthancPluginService_RegisterPrivateDictionaryTag, ¶ms); + } + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + OrthancPluginResourceType level; + } _OrthancPluginReconstructMainDicomTags; + + /** + * @brief Reconstruct the main DICOM tags. + * + * This function requests the Orthanc core to reconstruct the main + * DICOM tags of all the resources of the given type. This function + * can only be used as a part of the upgrade of a custom database + * back-end. A database transaction will be automatically setup. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param level The type of the resources of interest. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReconstructMainDicomTags( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + OrthancPluginResourceType level) + { + _OrthancPluginReconstructMainDicomTags params; + params.level = level; + params.storageArea = storageArea; + + return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, ¶ms); + } + + + typedef struct + { + char** result; + const char* instanceId; + const void* buffer; + uint32_t size; + OrthancPluginDicomToJsonFormat format; + OrthancPluginDicomToJsonFlags flags; + uint32_t maxStringLength; + } _OrthancPluginDicomToJson; + + + /** + * @brief Format a DICOM memory buffer as a JSON string. + * + * This function takes as input a memory buffer containing a DICOM + * file, and outputs a JSON string representing the tags of this + * DICOM file. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer containing the DICOM file. + * @param size The size of the memory buffer. + * @param format The output format. + * @param flags Flags governing the output. + * @param maxStringLength The maximum length of a field. Too long fields will + * be output as "null". The 0 value means no maximum length. + * @return The NULL value if the case of an error, or the JSON + * string. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + * @see OrthancPluginDicomInstanceToJson() + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson( + OrthancPluginContext* context, + const void* buffer, + uint32_t size, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + char* result; + + _OrthancPluginDicomToJson params; + memset(¶ms, 0, sizeof(params)); + params.result = &result; + params.buffer = buffer; + params.size = size; + params.format = format; + params.flags = flags; + params.maxStringLength = maxStringLength; + + if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Format a DICOM instance as a JSON string. + * + * This function formats a DICOM instance that is stored in Orthanc, + * and outputs a JSON string representing the tags of this DICOM + * instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instanceId The Orthanc identifier of the instance. + * @param format The output format. + * @param flags Flags governing the output. + * @param maxStringLength The maximum length of a field. Too long fields will + * be output as "null". The 0 value means no maximum length. + * @return The NULL value if the case of an error, or the JSON + * string. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + * @see OrthancPluginDicomInstanceToJson() + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson( + OrthancPluginContext* context, + const char* instanceId, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + char* result; + + _OrthancPluginDicomToJson params; + memset(¶ms, 0, sizeof(params)); + params.result = &result; + params.instanceId = instanceId; + params.format = format; + params.flags = flags; + params.maxStringLength = maxStringLength; + + if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + int32_t afterPlugins; + } _OrthancPluginRestApiGet2; + + /** + * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers. + * + * Make a GET call to the Orthanc REST API with extended + * parameters. The result to the query is stored into a newly + * allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param afterPlugins If 0, the built-in API of Orthanc is used. + * If 1, the API is tainted by the plugins. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet(), OrthancPluginRestApiGetAfterPlugins() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet2( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + int32_t afterPlugins) + { + _OrthancPluginRestApiGet2 params; + params.target = target; + params.uri = uri; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.afterPlugins = afterPlugins; + + return context->InvokeService(context, _OrthancPluginService_RestApiGet2, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistCallback callback; + } _OrthancPluginWorklistCallback; + + /** + * @brief Register a callback to handle modality worklists requests. + * + * This function registers a callback to handle C-Find SCP requests + * on modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback( + OrthancPluginContext* context, + OrthancPluginWorklistCallback callback) + { + _OrthancPluginWorklistCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistAnswers* answers; + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + } _OrthancPluginWorklistAnswersOperation; + + /** + * @brief Add one answer to some modality worklist request. + * + * This function adds one worklist (encoded as a DICOM file) to the + * set of answers corresponding to some C-Find SCP request against + * modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + * @see OrthancPluginCreateDicom() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddAnswer( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = query; + params.dicom = dicom; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, ¶ms); + } + + + /** + * @brief Mark the set of worklist answers as incomplete. + * + * This function marks as incomplete the set of answers + * corresponding to some C-Find SCP request against modality + * worklists. This must be used if canceling the handling of a + * request when too many answers are to be returned. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistMarkIncomplete( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = NULL; + params.dicom = NULL; + params.size = 0; + + return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, ¶ms); + } + + + typedef struct + { + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + int32_t* isMatch; + OrthancPluginMemoryBuffer* target; + } _OrthancPluginWorklistQueryOperation; + + /** + * @brief Test whether a worklist matches the query. + * + * This function checks whether one worklist (encoded as a DICOM + * file) matches the C-Find SCP query against modality + * worklists. This function must be called before adding the + * worklist as an answer through OrthancPluginWorklistAddAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 1 if the worklist matches the query, 0 otherwise. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginWorklistIsMatch( + OrthancPluginContext* context, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + int32_t isMatch = 0; + + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = dicom; + params.size = size; + params.isMatch = &isMatch; + params.target = NULL; + + if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, ¶ms) == OrthancPluginErrorCode_Success) + { + return isMatch; + } + else + { + /* Error: Assume non-match */ + return 0; + } + } + + + /** + * @brief Retrieve the worklist query as a DICOM file. + * + * This function retrieves the DICOM file that underlies a C-Find + * SCP query against modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param query The worklist query, as received by the callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistGetDicomQuery( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginWorklistQuery* query) + { + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = NULL; + params.size = 0; + params.isMatch = NULL; + params.target = target; + + return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, ¶ms); + } + + + /** + * @brief Get the origin of a DICOM file. + * + * This function returns the origin of a DICOM instance that has been received by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The origin of the instance. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + OrthancPluginInstanceOrigin origin; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultOrigin = &origin; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return OrthancPluginInstanceOrigin_Unknown; + } + else + { + return origin; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* json; + const OrthancPluginImage* pixelData; + OrthancPluginCreateDicomFlags flags; + } _OrthancPluginCreateDicom; + + /** + * @brief Create a DICOM instance from a JSON string and an image. + * + * This function takes as input a string containing a JSON file + * describing the content of a DICOM instance. As an output, it + * writes the corresponding DICOM instance to a newly allocated + * memory buffer. Additionally, an image to be encoded within the + * DICOM instance can also be provided. + * + * Private tags will be associated with the private creator whose + * value is specified in the "DefaultPrivateCreator" configuration + * option of Orthanc. The function OrthancPluginCreateDicom2() can + * be used if another private creator must be used to create this + * instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param json The input JSON file. + * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. + * @param flags Flags governing the output. + * @return 0 if success, other value if error. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom2() + * @see OrthancPluginDicomBufferToJson() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* json, + const OrthancPluginImage* pixelData, + OrthancPluginCreateDicomFlags flags) + { + _OrthancPluginCreateDicom params; + params.target = target; + params.json = json; + params.pixelData = pixelData; + params.flags = flags; + + return context->InvokeService(context, _OrthancPluginService_CreateDicom, ¶ms); + } + + + typedef struct + { + OrthancPluginDecodeImageCallback callback; + } _OrthancPluginDecodeImageCallback; + + /** + * @brief Register a callback to handle the decoding of DICOM images. + * + * This function registers a custom callback to decode DICOM images, + * extending the built-in decoder of Orthanc that uses + * DCMTK. Starting with Orthanc 1.7.0, the exact behavior is + * affected by the configuration option + * "BuiltinDecoderTranscoderOrder" of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback( + OrthancPluginContext* context, + OrthancPluginDecodeImageCallback callback) + { + _OrthancPluginDecodeImageCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + void* buffer; + const void* constBuffer; + uint32_t bufferSize; + uint32_t frameIndex; + } _OrthancPluginCreateImage; + + + /** + * @brief Create an image. + * + * This function creates an image of given size and format. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + + if (context->InvokeService(context, _OrthancPluginService_CreateImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Create an image pointing to a memory buffer. + * + * This function creates an image whose content points to a memory + * buffer managed by the plugin. Note that the buffer is directly + * accessed, no memory is allocated and no data is copied. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + + if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Decode one frame from a DICOM instance. + * + * This function decodes one frame of a DICOM image that is stored + * in a memory buffer. This function will give the same result as + * OrthancPluginUncompressImage() for single-frame DICOM images. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer Pointer to a memory buffer containing the DICOM image. + * @param bufferSize Size of the memory buffer containing the DICOM image. + * @param frameIndex The index of the frame of interest in a multi-frame image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + * @see OrthancPluginGetInstanceDecodedFrame() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage( + OrthancPluginContext* context, + const void* buffer, + uint32_t bufferSize, + uint32_t frameIndex) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.constBuffer = buffer; + params.bufferSize = bufferSize; + params.frameIndex = frameIndex; + + if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + char** result; + const void* buffer; + uint32_t size; + } _OrthancPluginComputeHash; + + /** + * @brief Compute an MD5 hash. + * + * This functions computes the MD5 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Compute a SHA-1 hash. + * + * This functions computes the SHA-1 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginDictionaryEntry* target; + const char* name; + } _OrthancPluginLookupDictionary; + + /** + * @brief Get information about the given DICOM tag. + * + * This functions makes a lookup in the dictionary of DICOM tags + * that are known to Orthanc, and returns information about this + * tag. The tag can be specified using its human-readable name + * (e.g. "PatientName") or a set of two hexadecimal numbers + * (e.g. "0010-0020"). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Where to store the information about the tag. + * @param name The name of the DICOM tag. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginLookupDictionary( + OrthancPluginContext* context, + OrthancPluginDictionaryEntry* target, + const char* name) + { + _OrthancPluginLookupDictionary params; + params.target = target; + params.name = name; + return context->InvokeService(context, _OrthancPluginService_LookupDictionary, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const void* answer; + uint32_t answerSize; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + } _OrthancPluginSendMultipartItem2; + + /** + * @brief Send an item as a part of some HTTP multipart answer, with custom headers. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to + * OrthancPluginSendMultipartItem(), this function will set HTTP header associated + * with the item. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers. + * @param headersValues Array containing the values of the HTTP headers. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const void* answer, + uint32_t answerSize, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues) + { + _OrthancPluginSendMultipartItem2 params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, ¶ms); + } + + + typedef struct + { + OrthancPluginIncomingHttpRequestFilter callback; + } _OrthancPluginIncomingHttpRequestFilter; + + /** + * @brief Register a callback to filter incoming HTTP requests. + * + * This function registers a custom callback to filter incoming HTTP/REST + * requests received by the HTTP server of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated Please instead use OrthancPluginRegisterIncomingHttpRequestFilter2() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter( + OrthancPluginContext* context, + OrthancPluginIncomingHttpRequestFilter callback) + { + _OrthancPluginIncomingHttpRequestFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + OrthancPluginHttpMethod method; + const char* url; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + const void* body; + uint32_t bodySize; + const char* username; + const char* password; + uint32_t timeout; + const char* certificateFile; + const char* certificateKeyFile; + const char* certificateKeyPassword; + uint8_t pkcs11; + } _OrthancPluginCallHttpClient2; + + + + /** + * @brief Issue a HTTP call with full flexibility. + * + * Make a HTTP call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. The HTTP request + * will be done accordingly to the global configuration of Orthanc + * (in particular, the options "HttpProxy", "HttpTimeout", + * "HttpsVerifyPeers", "HttpsCACertificates", and "Pkcs11" will be + * taken into account). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * The value of this argument is ignored if the HTTP method is DELETE. + * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param method HTTP method to be used. + * @param url The URL of interest. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param timeout Timeout in seconds (0 for default timeout). + * @param certificateFile Path to the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyPassword Password to unlock the key of the client certificate + * (can be NULL if no client certificate or if not using HTTPS). + * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginCallPeerApi() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpClient( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + OrthancPluginHttpMethod method, + const char* url, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + const void* body, + uint32_t bodySize, + const char* username, + const char* password, + uint32_t timeout, + const char* certificateFile, + const char* certificateKeyFile, + const char* certificateKeyPassword, + uint8_t pkcs11) + { + _OrthancPluginCallHttpClient2 params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.method = method; + params.url = url; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + params.timeout = timeout; + params.certificateFile = certificateFile; + params.certificateKeyFile = certificateKeyFile; + params.certificateKeyPassword = certificateKeyPassword; + params.pkcs11 = pkcs11; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient2, ¶ms); + } + + + /** + * @brief Generate an UUID. + * + * Generate a random GUID/UUID (globally unique identifier). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the UUID. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateUuid( + OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GenerateUuid, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + + typedef struct + { + OrthancPluginFindCallback callback; + } _OrthancPluginFindCallback; + + /** + * @brief Register a callback to handle C-Find requests. + * + * This function registers a callback to handle C-Find SCP requests + * that are not related to modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterFindCallback( + OrthancPluginContext* context, + OrthancPluginFindCallback callback) + { + _OrthancPluginFindCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterFindCallback, ¶ms); + } + + + typedef struct + { + OrthancPluginFindAnswers *answers; + const OrthancPluginFindQuery *query; + const void *dicom; + uint32_t size; + uint32_t index; + uint32_t *resultUint32; + uint16_t *resultGroup; + uint16_t *resultElement; + char **resultString; + } _OrthancPluginFindOperation; + + /** + * @brief Add one answer to some C-Find request. + * + * This function adds one answer (encoded as a DICOM file) to the + * set of answers corresponding to some C-Find SCP request that is + * not related to modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @param dicom The answer to be added, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + * @see OrthancPluginCreateDicom() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginFindAddAnswer( + OrthancPluginContext* context, + OrthancPluginFindAnswers* answers, + const void* dicom, + uint32_t size) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.answers = answers; + params.dicom = dicom; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_FindAddAnswer, ¶ms); + } + + + /** + * @brief Mark the set of C-Find answers as incomplete. + * + * This function marks as incomplete the set of answers + * corresponding to some C-Find SCP request that is not related to + * modality worklists. This must be used if canceling the handling + * of a request when too many answers are to be returned. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginFindMarkIncomplete( + OrthancPluginContext* context, + OrthancPluginFindAnswers* answers) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.answers = answers; + + return context->InvokeService(context, _OrthancPluginService_FindMarkIncomplete, ¶ms); + } + + + + /** + * @brief Get the number of tags in a C-Find query. + * + * This function returns the number of tags that are contained in + * the given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @return The number of tags. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFindQuerySize( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query) + { + uint32_t count = 0; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQuerySize, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + /** + * @brief Get one tag in a C-Find query. + * + * This function returns the group and the element of one DICOM tag + * in the given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag (output). + * @param element The element of the tag (output). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetFindQueryTag( + OrthancPluginContext* context, + uint16_t* group, + uint16_t* element, + const OrthancPluginFindQuery* query, + uint32_t index) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultGroup = group; + params.resultElement = element; + + return context->InvokeService(context, _OrthancPluginService_GetFindQueryTag, ¶ms); + } + + + /** + * @brief Get the symbolic name of one tag in a C-Find query. + * + * This function returns the symbolic name of one DICOM tag in the + * given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return The NULL value in case of error, or a string containing the name of the tag. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetFindQueryTagName( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query, + uint32_t index) + { + char* result; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultString = &result; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQueryTagName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the value associated with one tag in a C-Find query. + * + * This function returns the value associated with one tag in the + * given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return The NULL value in case of error, or a string containing the value of the tag. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetFindQueryValue( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query, + uint32_t index) + { + char* result; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultString = &result; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQueryValue, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + + typedef struct + { + OrthancPluginMoveCallback callback; + OrthancPluginGetMoveSize getMoveSize; + OrthancPluginApplyMove applyMove; + OrthancPluginFreeMove freeMove; + } _OrthancPluginMoveCallback; + + /** + * @brief Register a callback to handle C-Move requests. + * + * This function registers a callback to handle C-Move SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The main callback. + * @param getMoveSize Callback to read the number of C-Move suboperations. + * @param applyMove Callback to apply one C-Move suboperation. + * @param freeMove Callback to free the C-Move driver. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback( + OrthancPluginContext* context, + OrthancPluginMoveCallback callback, + OrthancPluginGetMoveSize getMoveSize, + OrthancPluginApplyMove applyMove, + OrthancPluginFreeMove freeMove) + { + _OrthancPluginMoveCallback params; + params.callback = callback; + params.getMoveSize = getMoveSize; + params.applyMove = applyMove; + params.freeMove = freeMove; + + return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginFindMatcher** target; + const void* query; + uint32_t size; + } _OrthancPluginCreateFindMatcher; + + + /** + * @brief Create a C-Find matcher. + * + * This function creates a "matcher" object that can be used to + * check whether a DICOM instance matches a C-Find query. The C-Find + * query must be expressed as a DICOM buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find DICOM query. + * @param size The size of the DICOM query. + * @return The newly allocated matcher. It must be freed with OrthancPluginFreeFindMatcher(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginFindMatcher* OrthancPluginCreateFindMatcher( + OrthancPluginContext* context, + const void* query, + uint32_t size) + { + OrthancPluginFindMatcher* target = NULL; + + _OrthancPluginCreateFindMatcher params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.query = query; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_CreateFindMatcher, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginFindMatcher* matcher; + } _OrthancPluginFreeFindMatcher; + + /** + * @brief Free a C-Find matcher. + * + * This function frees a matcher that was created using OrthancPluginCreateFindMatcher(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param matcher The matcher of interest. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeFindMatcher( + OrthancPluginContext* context, + OrthancPluginFindMatcher* matcher) + { + _OrthancPluginFreeFindMatcher params; + params.matcher = matcher; + + context->InvokeService(context, _OrthancPluginService_FreeFindMatcher, ¶ms); + } + + + typedef struct + { + const OrthancPluginFindMatcher* matcher; + const void* dicom; + uint32_t size; + int32_t* isMatch; + } _OrthancPluginFindMatcherIsMatch; + + /** + * @brief Test whether a DICOM instance matches a C-Find query. + * + * This function checks whether one DICOM instance matches C-Find + * matcher that was previously allocated using + * OrthancPluginCreateFindMatcher(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param matcher The matcher of interest. + * @param dicom The DICOM instance to be matched. + * @param size The size of the DICOM instance. + * @return 1 if the DICOM instance matches the query, 0 otherwise. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginFindMatcherIsMatch( + OrthancPluginContext* context, + const OrthancPluginFindMatcher* matcher, + const void* dicom, + uint32_t size) + { + int32_t isMatch = 0; + + _OrthancPluginFindMatcherIsMatch params; + params.matcher = matcher; + params.dicom = dicom; + params.size = size; + params.isMatch = &isMatch; + + if (context->InvokeService(context, _OrthancPluginService_FindMatcherIsMatch, ¶ms) == OrthancPluginErrorCode_Success) + { + return isMatch; + } + else + { + /* Error: Assume non-match */ + return 0; + } + } + + + typedef struct + { + OrthancPluginIncomingHttpRequestFilter2 callback; + } _OrthancPluginIncomingHttpRequestFilter2; + + /** + * @brief Register a callback to filter incoming HTTP requests. + * + * This function registers a custom callback to filter incoming HTTP/REST + * requests received by the HTTP server of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter2( + OrthancPluginContext* context, + OrthancPluginIncomingHttpRequestFilter2 callback) + { + _OrthancPluginIncomingHttpRequestFilter2 params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter2, ¶ms); + } + + + + typedef struct + { + OrthancPluginPeers** peers; + } _OrthancPluginGetPeers; + + /** + * @brief Return the list of available Orthanc peers. + * + * This function returns the parameters of the Orthanc peers that are known to + * the Orthanc server hosting the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL if error, or a newly allocated opaque data structure containing the peers. + * This structure must be freed with OrthancPluginFreePeers(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPeers* OrthancPluginGetPeers( + OrthancPluginContext* context) + { + OrthancPluginPeers* peers = NULL; + + _OrthancPluginGetPeers params; + memset(¶ms, 0, sizeof(params)); + params.peers = &peers; + + if (context->InvokeService(context, _OrthancPluginService_GetPeers, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return peers; + } + } + + + typedef struct + { + OrthancPluginPeers* peers; + } _OrthancPluginFreePeers; + + /** + * @brief Free the list of available Orthanc peers. + * + * This function frees the data structure returned by OrthancPluginGetPeers(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreePeers( + OrthancPluginContext* context, + OrthancPluginPeers* peers) + { + _OrthancPluginFreePeers params; + params.peers = peers; + + context->InvokeService(context, _OrthancPluginService_FreePeers, ¶ms); + } + + + typedef struct + { + uint32_t* target; + const OrthancPluginPeers* peers; + } _OrthancPluginGetPeersCount; + + /** + * @brief Get the number of Orthanc peers. + * + * This function returns the number of Orthanc peers. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @result The number of peers. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetPeersCount( + OrthancPluginContext* context, + const OrthancPluginPeers* peers) + { + uint32_t target = 0; + + _OrthancPluginGetPeersCount params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + + if (context->InvokeService(context, _OrthancPluginService_GetPeersCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return target; + } + } + + + typedef struct + { + const char** target; + const OrthancPluginPeers* peers; + uint32_t peerIndex; + const char* userProperty; + } _OrthancPluginGetPeerProperty; + + /** + * @brief Get the symbolic name of an Orthanc peer. + * + * This function returns the symbolic name of the Orthanc peer, + * which corresponds to the key of the "OrthancPeers" configuration + * option of Orthanc. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @result The symbolic name, or NULL in the case of an error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerName( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Get the base URL of an Orthanc peer. + * + * This function returns the base URL to the REST API of some Orthanc peer. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @result The URL, or NULL in the case of an error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUrl( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerUrl, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Get some user-defined property of an Orthanc peer. + * + * This function returns some user-defined property of some Orthanc + * peer. An user-defined property is a property that is associated + * with the peer in the Orthanc configuration file, but that is not + * recognized by the Orthanc core. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @param userProperty The user property of interest. + * @result The value of the user property, or NULL if it is not defined. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUserProperty( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex, + const char* userProperty) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = userProperty; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerUserProperty, ¶ms) != OrthancPluginErrorCode_Success) + { + /* No such user property */ + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + const OrthancPluginPeers* peers; + uint32_t peerIndex; + OrthancPluginHttpMethod method; + const char* uri; + uint32_t additionalHeadersCount; + const char* const* additionalHeadersKeys; + const char* const* additionalHeadersValues; + const void* body; + uint32_t bodySize; + uint32_t timeout; + } _OrthancPluginCallPeerApi; + + /** + * @brief Call the REST API of an Orthanc peer. + * + * Make a REST call to the given URI in the REST API of a remote + * Orthanc peer. The result to the query is stored into a newly + * allocated memory buffer. The HTTP request will be done according + * to the "OrthancPeers" configuration option of Orthanc. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * The value of this argument is ignored if the HTTP method is DELETE. + * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @param method HTTP method to be used. + * @param uri The URI of interest in the REST API. + * @param additionalHeadersCount The number of HTTP headers to be added to the + * HTTP headers provided in the global configuration of Orthanc. + * @param additionalHeadersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param additionalHeadersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param timeout Timeout in seconds (0 for default timeout). + * @return 0 if success, or the error code if failure. + * @see OrthancPluginHttpClient() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCallPeerApi( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + const OrthancPluginPeers* peers, + uint32_t peerIndex, + OrthancPluginHttpMethod method, + const char* uri, + uint32_t additionalHeadersCount, + const char* const* additionalHeadersKeys, + const char* const* additionalHeadersValues, + const void* body, + uint32_t bodySize, + uint32_t timeout) + { + _OrthancPluginCallPeerApi params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.peers = peers; + params.peerIndex = peerIndex; + params.method = method; + params.uri = uri; + params.additionalHeadersCount = additionalHeadersCount; + params.additionalHeadersKeys = additionalHeadersKeys; + params.additionalHeadersValues = additionalHeadersValues; + params.body = body; + params.bodySize = bodySize; + params.timeout = timeout; + + return context->InvokeService(context, _OrthancPluginService_CallPeerApi, ¶ms); + } + + + + + + typedef struct + { + OrthancPluginJob** target; + void *job; + OrthancPluginJobFinalize finalize; + const char *type; + OrthancPluginJobGetProgress getProgress; + OrthancPluginJobGetContent getContent; + OrthancPluginJobGetSerialized getSerialized; + OrthancPluginJobStep step; + OrthancPluginJobStop stop; + OrthancPluginJobReset reset; + } _OrthancPluginCreateJob; + + /** + * @brief Create a custom job. + * + * This function creates a custom job to be run by the jobs engine + * of Orthanc. + * + * Orthanc starts one dedicated thread per custom job that is + * running. It is guaranteed that all the callbacks will only be + * called from this single dedicated thread, in mutual exclusion: As + * a consequence, it is *not* mandatory to protect the various + * callbacks by mutexes. + * + * The custom job can nonetheless launch its own processing threads + * on the first call to the "step()" callback, and stop them once + * the "stop()" callback is called. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job to be executed. + * @param finalize The finalization callback. + * @param type The type of the job, provided to the job unserializer. + * See OrthancPluginRegisterJobsUnserializer(). + * @param getProgress The progress callback. + * @param getContent The content callback. + * @param getSerialized The serialization callback. + * @param step The callback to execute the individual steps of the job. + * @param stop The callback that is invoked once the job leaves the "running" state. + * @param reset The callback that is invoked if a stopped job is started again. + * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(), + * as long as it is not submitted with OrthancPluginSubmitJob(). + * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob( + OrthancPluginContext *context, + void *job, + OrthancPluginJobFinalize finalize, + const char *type, + OrthancPluginJobGetProgress getProgress, + OrthancPluginJobGetContent getContent, + OrthancPluginJobGetSerialized getSerialized, + OrthancPluginJobStep step, + OrthancPluginJobStop stop, + OrthancPluginJobReset reset) + { + OrthancPluginJob* target = NULL; + + _OrthancPluginCreateJob params; + memset(¶ms, 0, sizeof(params)); + + params.target = ⌖ + params.job = job; + params.finalize = finalize; + params.type = type; + params.getProgress = getProgress; + params.getContent = getContent; + params.getSerialized = getSerialized; + params.step = step; + params.stop = stop; + params.reset = reset; + + if (context->InvokeService(context, _OrthancPluginService_CreateJob, ¶ms) != OrthancPluginErrorCode_Success || + target == NULL) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginJob** target; + void *job; + OrthancPluginJobFinalize finalize; + const char *type; + OrthancPluginJobGetProgress getProgress; + OrthancPluginJobGetContent2 getContent; + OrthancPluginJobGetSerialized2 getSerialized; + OrthancPluginJobStep step; + OrthancPluginJobStop stop; + OrthancPluginJobReset reset; + } _OrthancPluginCreateJob2; + + /** + * @brief Create a custom job. + * + * This function creates a custom job to be run by the jobs engine + * of Orthanc. + * + * Orthanc starts one dedicated thread per custom job that is + * running. It is guaranteed that all the callbacks will only be + * called from this single dedicated thread, in mutual exclusion: As + * a consequence, it is *not* mandatory to protect the various + * callbacks by mutexes. + * + * The custom job can nonetheless launch its own processing threads + * on the first call to the "step()" callback, and stop them once + * the "stop()" callback is called. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job to be executed. + * @param finalize The finalization callback. + * @param type The type of the job, provided to the job unserializer. + * See OrthancPluginRegisterJobsUnserializer(). + * @param getProgress The progress callback. + * @param getContent The content callback. + * @param getSerialized The serialization callback. + * @param step The callback to execute the individual steps of the job. + * @param stop The callback that is invoked once the job leaves the "running" state. + * @param reset The callback that is invoked if a stopped job is started again. + * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(), + * as long as it is not submitted with OrthancPluginSubmitJob(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob2( + OrthancPluginContext *context, + void *job, + OrthancPluginJobFinalize finalize, + const char *type, + OrthancPluginJobGetProgress getProgress, + OrthancPluginJobGetContent2 getContent, + OrthancPluginJobGetSerialized2 getSerialized, + OrthancPluginJobStep step, + OrthancPluginJobStop stop, + OrthancPluginJobReset reset) + { + OrthancPluginJob* target = NULL; + + _OrthancPluginCreateJob2 params; + memset(¶ms, 0, sizeof(params)); + + params.target = ⌖ + params.job = job; + params.finalize = finalize; + params.type = type; + params.getProgress = getProgress; + params.getContent = getContent; + params.getSerialized = getSerialized; + params.step = step; + params.stop = stop; + params.reset = reset; + + if (context->InvokeService(context, _OrthancPluginService_CreateJob2, ¶ms) != OrthancPluginErrorCode_Success || + target == NULL) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginJob* job; + } _OrthancPluginFreeJob; + + /** + * @brief Free a custom job. + * + * This function frees an image that was created with OrthancPluginCreateJob(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeJob( + OrthancPluginContext* context, + OrthancPluginJob* job) + { + _OrthancPluginFreeJob params; + params.job = job; + + context->InvokeService(context, _OrthancPluginService_FreeJob, ¶ms); + } + + + + typedef struct + { + char** resultId; + OrthancPluginJob *job; + int32_t priority; + } _OrthancPluginSubmitJob; + + /** + * @brief Submit a new job to the jobs engine of Orthanc. + * + * This function adds the given job to the pending jobs of + * Orthanc. Orthanc will take take of freeing it by invoking the + * finalization callback provided to OrthancPluginCreateJob(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job, as received by OrthancPluginCreateJob(). + * @param priority The priority of the job. + * @return ID of the newly-submitted job. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginSubmitJob( + OrthancPluginContext *context, + OrthancPluginJob *job, + int32_t priority) + { + char* resultId = NULL; + + _OrthancPluginSubmitJob params; + memset(¶ms, 0, sizeof(params)); + + params.resultId = &resultId; + params.job = job; + params.priority = priority; + + if (context->InvokeService(context, _OrthancPluginService_SubmitJob, ¶ms) != OrthancPluginErrorCode_Success || + resultId == NULL) + { + /* Error */ + return NULL; + } + else + { + return resultId; + } + } + + + + typedef struct + { + OrthancPluginJobsUnserializer unserializer; + } _OrthancPluginJobsUnserializer; + + /** + * @brief Register an unserializer for custom jobs. + * + * This function registers an unserializer that decodes custom jobs + * from a JSON string. This callback is invoked when the jobs engine + * of Orthanc is started (on Orthanc initialization), for each job + * that is stored in the Orthanc database. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param unserializer The job unserializer. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterJobsUnserializer( + OrthancPluginContext* context, + OrthancPluginJobsUnserializer unserializer) + { + _OrthancPluginJobsUnserializer params; + params.unserializer = unserializer; + + context->InvokeService(context, _OrthancPluginService_RegisterJobsUnserializer, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* details; + uint8_t log; + } _OrthancPluginSetHttpErrorDetails; + + /** + * @brief Provide a detailed description for an HTTP error. + * + * This function sets the detailed description associated with an + * HTTP error. This description will be displayed in the "Details" + * field of the JSON body of the HTTP answer. It is only taken into + * consideration if the REST callback returns an error code that is + * different from "OrthancPluginErrorCode_Success", and if the + * "HttpDescribeErrors" configuration option of Orthanc is set to + * "true". + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param details The details of the error message. + * @param log Whether to also write the detailed error to the Orthanc logs. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpErrorDetails( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* details, + uint8_t log) + { + _OrthancPluginSetHttpErrorDetails params; + params.output = output; + params.details = details; + params.log = log; + context->InvokeService(context, _OrthancPluginService_SetHttpErrorDetails, ¶ms); + } + + + + typedef struct + { + const char** result; + const char* argument; + } _OrthancPluginRetrieveStaticString; + + /** + * @brief Detect the MIME type of a file. + * + * This function returns the MIME type of a file by inspecting its extension. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param path Path to the file. + * @return The MIME type. This is a statically-allocated + * string, do not free it. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginAutodetectMimeType( + OrthancPluginContext* context, + const char* path) + { + const char* result = NULL; + + _OrthancPluginRetrieveStaticString params; + params.result = &result; + params.argument = path; + + if (context->InvokeService(context, _OrthancPluginService_AutodetectMimeType, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + const char* name; + float value; + OrthancPluginMetricsType type; + } _OrthancPluginSetMetricsValue; + + /** + * @brief Set the value of a floating-point metrics. + * + * This function sets the value of a floating-point metrics to + * monitor the behavior of the plugin through tools such as + * Prometheus. The values of all the metrics are stored within the + * Orthanc context. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param name The name of the metrics to be set. + * @param value The value of the metrics. + * @param type The type of the metrics. This parameter is only taken into consideration + * the first time this metrics is set. + * @ingroup Toolbox + * @see OrthancPluginSetMetricsIntegerValue() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetMetricsValue( + OrthancPluginContext* context, + const char* name, + float value, + OrthancPluginMetricsType type) + { + _OrthancPluginSetMetricsValue params; + params.name = name; + params.value = value; + params.type = type; + context->InvokeService(context, _OrthancPluginService_SetMetricsValue, ¶ms); + } + + + + typedef struct + { + OrthancPluginRefreshMetricsCallback callback; + } _OrthancPluginRegisterRefreshMetricsCallback; + + /** + * @brief Register a callback to refresh the metrics. + * + * This function registers a callback to refresh the metrics. The + * callback must make calls to OrthancPluginSetMetricsValue() or + * OrthancPluginSetMetricsIntegerValue(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function to handle the refresh. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRefreshMetricsCallback( + OrthancPluginContext* context, + OrthancPluginRefreshMetricsCallback callback) + { + _OrthancPluginRegisterRefreshMetricsCallback params; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRefreshMetricsCallback, ¶ms); + } + + + + + typedef struct + { + char** target; + const void* dicom; + uint32_t dicomSize; + OrthancPluginDicomWebBinaryCallback callback; + } _OrthancPluginEncodeDicomWeb; + + /** + * @brief Convert a DICOM instance to DICOMweb JSON. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb JSON representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @see OrthancPluginCreateDicom() + * @return The NULL value in case of error, or the JSON document. This string must + * be freed by OrthancPluginFreeString(). + * @deprecated OrthancPluginEncodeDicomWebJson2() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback callback) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb XML. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb XML representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @return The NULL value in case of error, or the XML document. This string must + * be freed by OrthancPluginFreeString(). + * @see OrthancPluginCreateDicom() + * @deprecated OrthancPluginEncodeDicomWebXml2() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback callback) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebXml, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + char** target; + const void* dicom; + uint32_t dicomSize; + OrthancPluginDicomWebBinaryCallback2 callback; + void* payload; + } _OrthancPluginEncodeDicomWeb2; + + /** + * @brief Convert a DICOM instance to DICOMweb JSON. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb JSON representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the JSON document. This string must + * be freed by OrthancPluginFreeString(). + * @see OrthancPluginCreateDicom() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson2( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb2 params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + params.payload = payload; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebJson2, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb XML. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb XML representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the XML document. This string must + * be freed by OrthancPluginFreeString(). + * @see OrthancPluginCreateDicom() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml2( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb2 params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + params.payload = payload; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebXml2, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Callback executed when a HTTP header is received during a chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, as soon as it + * receives one HTTP header from the answer of the remote HTTP + * server. + * + * @see OrthancPluginChunkedHttpClient() + * @param answer The user payload, as provided by the calling plugin. + * @param key The key of the HTTP header. + * @param value The value of the HTTP header. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddHeader) ( + void* answer, + const char* key, + const char* value); + + + /** + * @brief Callback executed when an answer chunk is received during a chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, as soon as it + * receives one data chunk from the answer of the remote HTTP + * server. + * + * @see OrthancPluginChunkedHttpClient() + * @param answer The user payload, as provided by the calling plugin. + * @param data The content of the data chunk. + * @param size The size of the data chunk. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddChunk) ( + void* answer, + const void* data, + uint32_t size); + + + /** + * @brief Callback to know whether the request body is entirely read during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. The plugin must answer "1" as + * soon as the body is entirely read: The "request" data structure + * must act as an iterator. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return "1" if the body is over, or "0" if there is still data to be read. + * @ingroup Toolbox + **/ + typedef uint8_t (*OrthancPluginChunkedClientRequestIsDone) (void* request); + + + /** + * @brief Callback to advance in the request body during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. This function asks the plugin + * to advance to the next chunk of data of the request body: The + * "request" data structure must act as an iterator. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientRequestNext) (void* request); + + + /** + * @brief Callback to read the current chunk of the request body during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. The plugin must provide the + * content of the current chunk of data of the request body. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return The content of the current request chunk. + * @ingroup Toolbox + **/ + typedef const void* (*OrthancPluginChunkedClientRequestGetChunkData) (void* request); + + + /** + * @brief Callback to read the size of the current request chunk during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. The plugin must provide the + * size of the current chunk of data of the request body. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return The size of the current request chunk. + * @ingroup Toolbox + **/ + typedef uint32_t (*OrthancPluginChunkedClientRequestGetChunkSize) (void* request); + + + typedef struct + { + void* answer; + OrthancPluginChunkedClientAnswerAddChunk answerAddChunk; + OrthancPluginChunkedClientAnswerAddHeader answerAddHeader; + uint16_t* httpStatus; + OrthancPluginHttpMethod method; + const char* url; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + void* request; + OrthancPluginChunkedClientRequestIsDone requestIsDone; + OrthancPluginChunkedClientRequestGetChunkData requestChunkData; + OrthancPluginChunkedClientRequestGetChunkSize requestChunkSize; + OrthancPluginChunkedClientRequestNext requestNext; + const char* username; + const char* password; + uint32_t timeout; + const char* certificateFile; + const char* certificateKeyFile; + const char* certificateKeyPassword; + uint8_t pkcs11; + } _OrthancPluginChunkedHttpClient; + + + /** + * @brief Issue a HTTP call, using chunked HTTP transfers. + * + * Make a HTTP call to the given URL using chunked HTTP + * transfers. The request body is provided as an iterator over data + * chunks. The answer is provided as a sequence of function calls + * with the individual HTTP headers and answer chunks. + * + * Contrarily to OrthancPluginHttpClient() that entirely stores the + * request body and the answer body in memory buffers, this function + * uses chunked HTTP transfers. This results in a lower memory + * consumption. Pay attention to the fact that Orthanc servers with + * version <= 1.5.6 do not support chunked transfers: You must use + * OrthancPluginHttpClient() if contacting such older servers. + * + * The HTTP request will be done accordingly to the global + * configuration of Orthanc (in particular, the options "HttpProxy", + * "HttpTimeout", "HttpsVerifyPeers", "HttpsCACertificates", and + * "Pkcs11" will be taken into account). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answer The user payload for the answer body. It will be provided to the callbacks for the answer. + * @param answerAddChunk Callback function to report a data chunk from the answer body. + * @param answerAddHeader Callback function to report an HTTP header sent by the remote server. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param method HTTP method to be used. + * @param url The URL of interest. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param request The user payload containing the request body, and acting as an iterator. + * It will be provided to the callbacks for the request. + * @param requestIsDone Callback function to tell whether the request body is entirely read. + * @param requestChunkData Callback function to get the content of the current data chunk of the request body. + * @param requestChunkSize Callback function to get the size of the current data chunk of the request body. + * @param requestNext Callback function to advance to the next data chunk of the request body. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @param timeout Timeout in seconds (0 for default timeout). + * @param certificateFile Path to the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyPassword Password to unlock the key of the client certificate + * (can be NULL if no client certificate or if not using HTTPS). + * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginHttpClient() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginChunkedHttpClient( + OrthancPluginContext* context, + void* answer, + OrthancPluginChunkedClientAnswerAddChunk answerAddChunk, + OrthancPluginChunkedClientAnswerAddHeader answerAddHeader, + uint16_t* httpStatus, + OrthancPluginHttpMethod method, + const char* url, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + void* request, + OrthancPluginChunkedClientRequestIsDone requestIsDone, + OrthancPluginChunkedClientRequestGetChunkData requestChunkData, + OrthancPluginChunkedClientRequestGetChunkSize requestChunkSize, + OrthancPluginChunkedClientRequestNext requestNext, + const char* username, + const char* password, + uint32_t timeout, + const char* certificateFile, + const char* certificateKeyFile, + const char* certificateKeyPassword, + uint8_t pkcs11) + { + _OrthancPluginChunkedHttpClient params; + memset(¶ms, 0, sizeof(params)); + + /* In common with OrthancPluginHttpClient() */ + params.httpStatus = httpStatus; + params.method = method; + params.url = url; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.username = username; + params.password = password; + params.timeout = timeout; + params.certificateFile = certificateFile; + params.certificateKeyFile = certificateKeyFile; + params.certificateKeyPassword = certificateKeyPassword; + params.pkcs11 = pkcs11; + + /* For chunked body/answer */ + params.answer = answer; + params.answerAddChunk = answerAddChunk; + params.answerAddHeader = answerAddHeader; + params.request = request; + params.requestIsDone = requestIsDone; + params.requestChunkData = requestChunkData; + params.requestChunkSize = requestChunkSize; + params.requestNext = requestNext; + + return context->InvokeService(context, _OrthancPluginService_ChunkedHttpClient, ¶ms); + } + + + + /** + * @brief Opaque structure that reads the content of a HTTP request body during a chunked HTTP transfer. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginServerChunkedRequestReader_t OrthancPluginServerChunkedRequestReader; + + + + /** + * @brief Callback to create a reader to handle incoming chunked HTTP transfers. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This + * callback is only invoked if the HTTP method is POST or PUT. The + * callback must create an user-specific "reader" object that will + * be fed with the body of the incoming body. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader Memory location that must be filled with the newly-created reader. + * @param url The URI that is accessed. + * @param request The body of the HTTP request. Note that "body" and "bodySize" are not used. + * @return 0 if success, or the error code if failure. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderFactory) ( + OrthancPluginServerChunkedRequestReader** reader, + const char* url, + const OrthancPluginHttpRequest* request); + + + /** + * @brief Callback invoked whenever a new data chunk is available during a chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This callback + * is invoked as soon as a new data chunk is available for the request body. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback. + * @param data The content of the data chunk. + * @param size The size of the data chunk. + * @return 0 if success, or the error code if failure. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderAddChunk) ( + OrthancPluginServerChunkedRequestReader* reader, + const void* data, + uint32_t size); + + + /** + * @brief Callback invoked whenever the request body is entirely received. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This + * callback is invoked as soon as the full body of the HTTP request + * is available. The plugin can then send its answer thanks to the + * provided "output" object. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback. + * @param output The HTTP connection to the client application. + * @return 0 if success, or the error code if failure. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderExecute) ( + OrthancPluginServerChunkedRequestReader* reader, + OrthancPluginRestOutput* output); + + + /** + * @brief Callback invoked to release the resources associated with an incoming HTTP chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This + * callback is invoked to release all the resources allocated by the + * given reader. Note that this function might be invoked even if + * the entire body was not read, to deal with client error or + * disconnection. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback. + **/ + typedef void (*OrthancPluginServerChunkedRequestReaderFinalize) ( + OrthancPluginServerChunkedRequestReader* reader); + + typedef struct + { + const char* pathRegularExpression; + OrthancPluginRestCallback getHandler; + OrthancPluginServerChunkedRequestReaderFactory postHandler; + OrthancPluginRestCallback deleteHandler; + OrthancPluginServerChunkedRequestReaderFactory putHandler; + OrthancPluginServerChunkedRequestReaderAddChunk addChunk; + OrthancPluginServerChunkedRequestReaderExecute execute; + OrthancPluginServerChunkedRequestReaderFinalize finalize; + } _OrthancPluginChunkedRestCallback; + + + /** + * @brief Register a REST callback to handle chunked HTTP transfers. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Contrarily to OrthancPluginRegisterRestCallback(), the callbacks + * will NOT be invoked in mutual exclusion, so it is up to the + * plugin to implement the required locking mechanisms. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param getHandler The callback function to handle REST calls using the GET HTTP method. + * @param postHandler The callback function to handle REST calls using the POST HTTP method. + * @param deleteHandler The callback function to handle REST calls using the DELETE HTTP method. + * @param putHandler The callback function to handle REST calls using the PUT HTTP method. + * @param addChunk The callback invoked when a new chunk is available for the request body of a POST or PUT call. + * @param execute The callback invoked once the entire body of a POST or PUT call is read. + * @param finalize The callback invoked to release the resources associated with a POST or PUT call. + * @see OrthancPluginRegisterRestCallbackNoLock() + * + * @note + * The regular expression is case sensitive and must follow the + * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html). + * + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterChunkedRestCallback( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback getHandler, + OrthancPluginServerChunkedRequestReaderFactory postHandler, + OrthancPluginRestCallback deleteHandler, + OrthancPluginServerChunkedRequestReaderFactory putHandler, + OrthancPluginServerChunkedRequestReaderAddChunk addChunk, + OrthancPluginServerChunkedRequestReaderExecute execute, + OrthancPluginServerChunkedRequestReaderFinalize finalize) + { + _OrthancPluginChunkedRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.getHandler = getHandler; + params.postHandler = postHandler; + params.deleteHandler = deleteHandler; + params.putHandler = putHandler; + params.addChunk = addChunk; + params.execute = execute; + params.finalize = finalize; + + context->InvokeService(context, _OrthancPluginService_RegisterChunkedRestCallback, ¶ms); + } + + + + + + typedef struct + { + char** result; + uint16_t group; + uint16_t element; + const char* privateCreator; + } _OrthancPluginGetTagName; + + /** + * @brief Returns the symbolic name of a DICOM tag. + * + * This function makes a lookup to the dictionary of DICOM tags that + * are known to Orthanc, and returns the symbolic name of a DICOM tag. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param privateCreator For private tags, the name of the private creator (can be NULL). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetTagName( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + const char* privateCreator) + { + char* result; + + _OrthancPluginGetTagName params; + params.result = &result; + params.group = group; + params.element = element; + params.privateCreator = privateCreator; + + if (context->InvokeService(context, _OrthancPluginService_GetTagName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + /** + * @brief Callback executed by the storage commitment SCP. + * + * Signature of a factory function that creates an object to handle + * one incoming storage commitment request. + * + * @remark The factory receives the list of the SOP class/instance + * UIDs of interest to the remote storage commitment SCU. This gives + * the factory the possibility to start some prefetch process + * upfront in the background, before the handler object is actually + * queried about the status of these DICOM instances. + * + * @param handler Output variable where the factory puts the handler object it created. + * @param jobId ID of the Orthanc job that is responsible for handling + * the storage commitment request. This job will successively look for the + * status of all the individual queried DICOM instances. + * @param transactionUid UID of the storage commitment transaction + * provided by the storage commitment SCU. It contains the value of the + * (0008,1195) DICOM tag. + * @param sopClassUids Array of the SOP class UIDs (0008,0016) that are queried by the SCU. + * @param sopInstanceUids Array of the SOP instance UIDs (0008,0018) that are queried by the SCU. + * @param countInstances Number of DICOM instances that are queried. This is the size + * of the `sopClassUids` and `sopInstanceUids` arrays. + * @param remoteAet The AET of the storage commitment SCU. + * @param calledAet The AET used by the SCU to contact the storage commitment SCP (i.e. Orthanc). + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentFactory) ( + void** handler /* out */, + const char* jobId, + const char* transactionUid, + const char* const* sopClassUids, + const char* const* sopInstanceUids, + uint32_t countInstances, + const char* remoteAet, + const char* calledAet); + + + /** + * @brief Callback to free one storage commitment SCP handler. + * + * Signature of a callback function that releases the resources + * allocated by the factory of the storage commitment SCP. The + * handler is the return value of a previous call to the + * OrthancPluginStorageCommitmentFactory() callback. + * + * @param handler The handler object to be destructed. + * @ingroup DicomCallbacks + **/ + typedef void (*OrthancPluginStorageCommitmentDestructor) (void* handler); + + + /** + * @brief Callback to get the status of one DICOM instance in the + * storage commitment SCP. + * + * Signature of a callback function that is successively invoked for + * each DICOM instance that is queried by the remote storage + * commitment SCU. The function must be tought of as a method of + * the handler object that was created by a previous call to the + * OrthancPluginStorageCommitmentFactory() callback. After each call + * to this method, the progress of the associated Orthanc job is + * updated. + * + * @param target Output variable where to put the status for the queried instance. + * @param handler The handler object associated with this storage commitment request. + * @param sopClassUid The SOP class UID (0008,0016) of interest. + * @param sopInstanceUid The SOP instance UID (0008,0018) of interest. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentLookup) ( + OrthancPluginStorageCommitmentFailureReason* target, + void* handler, + const char* sopClassUid, + const char* sopInstanceUid); + + + typedef struct + { + OrthancPluginStorageCommitmentFactory factory; + OrthancPluginStorageCommitmentDestructor destructor; + OrthancPluginStorageCommitmentLookup lookup; + } _OrthancPluginRegisterStorageCommitmentScpCallback; + + /** + * @brief Register a callback to handle incoming requests to the storage commitment SCP. + * + * This function registers a callback to handle storage commitment SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @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 function to get the status of one DICOM instance. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterStorageCommitmentScpCallback( + OrthancPluginContext* context, + OrthancPluginStorageCommitmentFactory factory, + OrthancPluginStorageCommitmentDestructor destructor, + OrthancPluginStorageCommitmentLookup lookup) + { + _OrthancPluginRegisterStorageCommitmentScpCallback params; + params.factory = factory; + params.destructor = destructor; + params.lookup = lookup; + return context->InvokeService(context, _OrthancPluginService_RegisterStorageCommitmentScpCallback, ¶ms); + } + + + + /** + * @brief Callback to filter incoming DICOM instances received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a new DICOM instance (e.g. through REST API or + * DICOM protocol), and that answers whether this DICOM instance + * should be accepted or discarded by Orthanc. + * + * Note that the metadata information is not available + * (i.e. GetInstanceMetadata() should not be used on "instance"). + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param instance The received DICOM instance. + * @return 0 to discard the instance, 1 to store the instance, -1 if error. + * @ingroup Callbacks + **/ + typedef int32_t (*OrthancPluginIncomingDicomInstanceFilter) ( + const OrthancPluginDicomInstance* instance); + + + typedef struct + { + OrthancPluginIncomingDicomInstanceFilter callback; + } _OrthancPluginIncomingDicomInstanceFilter; + + /** + * @brief Register a callback to filter incoming DICOM instances. + * + * This function registers a custom callback to filter incoming + * DICOM instances received by Orthanc (either through the REST API + * or through the DICOM protocol). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingDicomInstanceFilter( + OrthancPluginContext* context, + OrthancPluginIncomingDicomInstanceFilter callback) + { + _OrthancPluginIncomingDicomInstanceFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingDicomInstanceFilter, ¶ms); + } + + + /** + * @brief Callback to filter incoming DICOM instances received by + * Orthanc through C-STORE. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a new DICOM instance using DICOM C-STORE, and + * that answers whether this DICOM instance should be accepted or + * discarded by Orthanc. If the instance is discarded, the callback + * can specify the DIMSE error code answered by the Orthanc C-STORE + * SCP. + * + * Note that the metadata information is not available + * (i.e. GetInstanceMetadata() should not be used on "instance"). + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param dimseStatus If the DICOM instance is discarded, + * 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 Callbacks + **/ + typedef int32_t (*OrthancPluginIncomingCStoreInstanceFilter) ( + uint16_t* dimseStatus /* out */, + const OrthancPluginDicomInstance* instance); + + + typedef struct + { + OrthancPluginIncomingCStoreInstanceFilter callback; + } _OrthancPluginIncomingCStoreInstanceFilter; + + /** + * @brief Register a callback to filter incoming DICOM instances + * received by Orthanc through C-STORE. + * + * This function registers a custom callback to filter incoming + * DICOM instances received by Orthanc through the DICOM protocol. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingCStoreInstanceFilter( + OrthancPluginContext* context, + OrthancPluginIncomingCStoreInstanceFilter callback) + { + _OrthancPluginIncomingCStoreInstanceFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingCStoreInstanceFilter, ¶ms); + } + + /** + * @brief Callback to keep/discard/modify a DICOM instance received + * by Orthanc from any source (C-STORE or REST API) + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a new DICOM instance (through DICOM protocol or + * REST API), and that specifies an action to be applied to this + * newly received instance. The instance can be kept as it is, can + * be modified by the plugin, or can be discarded. + * + * This callback is invoked immediately after reception, i.e. before + * transcoding and before filtering + * (cf. OrthancPluginRegisterIncomingDicomInstanceFilter()). + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param modifiedDicomBuffer A buffer containing the modified DICOM (output). + * This buffer must be allocated using OrthancPluginCreateMemoryBuffer64() + * and will be freed by the Orthanc core. + * @param receivedDicomBuffer A buffer containing the received DICOM (input). + * @param receivedDicomBufferSize The size of the received DICOM (input). + * @param origin The origin of the DICOM instance (input). + * @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 Callbacks + **/ + typedef OrthancPluginReceivedInstanceAction (*OrthancPluginReceivedInstanceCallback) ( + OrthancPluginMemoryBuffer64* modifiedDicomBuffer, + const void* receivedDicomBuffer, + uint64_t receivedDicomBufferSize, + OrthancPluginInstanceOrigin origin); + + + typedef struct + { + OrthancPluginReceivedInstanceCallback callback; + } _OrthancPluginReceivedInstanceCallback; + + /** + * @brief Register a callback to keep/discard/modify a DICOM instance received + * by Orthanc from any source (C-STORE or REST API) + * + * This function registers a custom callback to keep/discard/modify + * incoming DICOM instances received by Orthanc from any source + * (C-STORE or REST API). + * + * @warning Contrarily to + * OrthancPluginRegisterIncomingCStoreInstanceFilter() and + * OrthancPluginRegisterIncomingDicomInstanceFilter() that can be + * called by multiple plugins, + * OrthancPluginRegisterReceivedInstanceCallback() can only be used + * by one single plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterReceivedInstanceCallback( + OrthancPluginContext* context, + OrthancPluginReceivedInstanceCallback callback) + { + _OrthancPluginReceivedInstanceCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterReceivedInstanceCallback, ¶ms); + } + + /** + * @brief Get the transfer syntax of a DICOM file. + * + * This function returns a pointer to a newly created string that + * contains the transfer syntax UID of the DICOM instance. The empty + * string might be returned if this information is unknown. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the + * transfer syntax UID. This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceTransferSyntaxUid( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceTransferSyntaxUid, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Check whether the DICOM file has pixel data. + * + * This function returns a Boolean value indicating whether the + * DICOM instance contains the pixel data (7FE0,0010) tag. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return "1" if the DICOM instance contains pixel data, or "0" if + * the tag is missing, or "-1" in the case of an error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginHasInstancePixelData( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + int64_t hasPixelData; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &hasPixelData; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_HasInstancePixelData, ¶ms) != OrthancPluginErrorCode_Success || + hasPixelData < 0 || + hasPixelData > 1) + { + /* Error */ + return -1; + } + else + { + return (hasPixelData != 0); + } + } + + + + + + + typedef struct + { + OrthancPluginDicomInstance** target; + const void* buffer; + uint32_t size; + const char* transferSyntax; + } _OrthancPluginCreateDicomInstance; + + /** + * @brief Parse a DICOM instance. + * + * This function parses a memory buffer that contains a DICOM + * file. The function returns a new pointer to a data structure that + * is managed by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer containing the DICOM instance. + * @param size The size of the memory buffer. + * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginCreateDicomInstance( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + OrthancPluginDicomInstance* target = NULL; + + _OrthancPluginCreateDicomInstance params; + params.target = ⌖ + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_CreateDicomInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + typedef struct + { + OrthancPluginDicomInstance* dicom; + } _OrthancPluginFreeDicomInstance; + + /** + * @brief Free a DICOM instance. + * + * This function frees a DICOM instance that was parsed using + * OrthancPluginCreateDicomInstance(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom The DICOM instance. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeDicomInstance( + OrthancPluginContext* context, + OrthancPluginDicomInstance* dicom) + { + _OrthancPluginFreeDicomInstance params; + params.dicom = dicom; + + context->InvokeService(context, _OrthancPluginService_FreeDicomInstance, ¶ms); + } + + + typedef struct + { + uint32_t* targetUint32; + OrthancPluginMemoryBuffer* targetBuffer; + OrthancPluginImage** targetImage; + char** targetStringToFree; + const OrthancPluginDicomInstance* instance; + uint32_t frameIndex; + OrthancPluginDicomToJsonFormat format; + OrthancPluginDicomToJsonFlags flags; + uint32_t maxStringLength; + OrthancPluginDicomWebBinaryCallback2 dicomWebCallback; + void* dicomWebPayload; + } _OrthancPluginAccessDicomInstance2; + + /** + * @brief Get the number of frames in a DICOM instance. + * + * This function returns the number of frames that are part of a + * DICOM image managed by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The number of frames (will be zero in the case of an error). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetInstanceFramesCount( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + uint32_t count; + + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetUint32 = &count; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceFramesCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + /** + * @brief Get the raw content of a frame in a DICOM instance. + * + * This function returns a memory buffer containing the raw content + * of a frame in a DICOM instance that is managed by the Orthanc + * core. This is notably useful for compressed transfer syntaxes, as + * it gives access to the embedded files (such as JPEG, JPEG-LS or + * JPEG2k). The Orthanc core transparently reassembles the fragments + * to extract the raw frame. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instance The instance of interest. + * @param frameIndex The index of the frame of interest. + * @return 0 if success, or the error code if failure. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetInstanceRawFrame( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginDicomInstance* instance, + uint32_t frameIndex) + { + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetBuffer = target; + params.instance = instance; + params.frameIndex = frameIndex; + + return context->InvokeService(context, _OrthancPluginService_GetInstanceRawFrame, ¶ms); + } + + + /** + * @brief Decode one frame from a DICOM instance. + * + * This function decodes one frame of a DICOM image that is managed + * by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param frameIndex The index of the frame of interest. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginGetInstanceDecodedFrame( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + uint32_t frameIndex) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetImage = ⌖ + params.instance = instance; + params.frameIndex = frameIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceDecodedFrame, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Parse and transcode a DICOM instance. + * + * This function parses a memory buffer that contains a DICOM file, + * then transcodes it to the given transfer syntax. The function + * returns a new pointer to a data structure that is managed by the + * Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer containing the DICOM instance. + * @param size The size of the memory buffer. + * @param transferSyntax The transfer syntax UID for the transcoding. + * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginTranscodeDicomInstance( + OrthancPluginContext* context, + const void* buffer, + uint32_t size, + const char* transferSyntax) + { + OrthancPluginDicomInstance* target = NULL; + + _OrthancPluginCreateDicomInstance params; + params.target = ⌖ + params.buffer = buffer; + params.size = size; + params.transferSyntax = transferSyntax; + + if (context->InvokeService(context, _OrthancPluginService_TranscodeDicomInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + /** + * @brief Writes a DICOM instance to a memory buffer. + * + * This function returns a memory buffer containing the + * serialization of a DICOM instance that is managed by the Orthanc + * core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instance The instance of interest. + * @return 0 if success, or the error code if failure. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSerializeDicomInstance( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginDicomInstance* instance) + { + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetBuffer = target; + params.instance = instance; + + return context->InvokeService(context, _OrthancPluginService_SerializeDicomInstance, ¶ms); + } + + + /** + * @brief Format a DICOM memory buffer as a JSON string. + * + * This function takes as DICOM instance managed by the Orthanc + * core, and outputs a JSON string representing the tags of this + * DICOM file. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The DICOM instance of interest. + * @param format The output format. + * @param flags Flags governing the output. + * @param maxStringLength The maximum length of a field. Too long fields will + * be output as "null". The 0 value means no maximum length. + * @return The NULL value if the case of an error, or the JSON + * string. This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + * @see OrthancPluginDicomBufferToJson() + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceAdvancedJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + char* result = NULL; + + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetStringToFree = &result; + params.instance = instance; + params.format = format; + params.flags = flags; + params.maxStringLength = maxStringLength; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceAdvancedJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb JSON. + * + * This function converts a DICOM instance that is managed by the + * Orthanc core, into its DICOMweb JSON representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The DICOM instance of interest. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the JSON document. This string must + * be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceDicomWebJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginAccessDicomInstance2 params; + params.targetStringToFree = ⌖ + params.instance = instance; + params.dicomWebCallback = callback; + params.dicomWebPayload = payload; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceDicomWebJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb XML. + * + * This function converts a DICOM instance that is managed by the + * Orthanc core, into its DICOMweb XML representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The DICOM instance of interest. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the XML document. This string must + * be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceDicomWebXml( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginAccessDicomInstance2 params; + params.targetStringToFree = ⌖ + params.instance = instance; + params.dicomWebCallback = callback; + params.dicomWebPayload = payload; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceDicomWebXml, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Signature of a callback function to transcode a DICOM instance. + * @param transcoded Target memory buffer. It must be allocated by the + * plugin using OrthancPluginCreateMemoryBuffer(). + * @param buffer Memory buffer containing the source DICOM instance. + * @param size Size of the source memory buffer. + * @param allowedSyntaxes A C array of possible transfer syntaxes UIDs for the + * result of the transcoding. The plugin must choose by itself the + * transfer syntax that will be used for the resulting DICOM image. + * @param countSyntaxes The number of transfer syntaxes that are contained + * in the "allowedSyntaxes" array. + * @param allowNewSopInstanceUid Whether the transcoding plugin can select + * a transfer syntax that will change the SOP instance UID (or, in other + * terms, whether the plugin can transcode using lossy compression). + * @return 0 if success (i.e. image successfully transcoded and stored into + * "transcoded"), or the error code if failure. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginTranscoderCallback) ( + OrthancPluginMemoryBuffer* transcoded /* out */, + const void* buffer, + uint64_t size, + const char* const* allowedSyntaxes, + uint32_t countSyntaxes, + uint8_t allowNewSopInstanceUid); + + + typedef struct + { + OrthancPluginTranscoderCallback callback; + } _OrthancPluginTranscoderCallback; + + /** + * @brief Register a callback to handle the transcoding of DICOM images. + * + * This function registers a custom callback to transcode DICOM + * images, extending the built-in transcoder of Orthanc that uses + * DCMTK. The exact behavior is affected by the configuration option + * "BuiltinDecoderTranscoderOrder" of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterTranscoderCallback( + OrthancPluginContext* context, + OrthancPluginTranscoderCallback callback) + { + _OrthancPluginTranscoderCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterTranscoderCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + uint32_t size; + } _OrthancPluginCreateMemoryBuffer; + + /** + * @brief Create a 32-bit memory buffer. + * + * This function creates a memory buffer that is managed by the + * Orthanc core. The main use case of this function is for plugins + * that act as DICOM transcoders. + * + * Your plugin should never call "free()" on the resulting memory + * buffer, as the C library that is used by the plugin is in general + * not the same as the one used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param size Size of the memory buffer to be created. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateMemoryBuffer( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + uint32_t size) + { + _OrthancPluginCreateMemoryBuffer params; + params.target = target; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_CreateMemoryBuffer, ¶ms); + } + + + /** + * @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 + * of Orthanc using an external HTTP client. Using this function + * avoids the need of adding a separate user in the + * "RegisteredUsers" configuration of Orthanc, which eases + * deployments. + * + * This feature is notably useful in multiprocess scenarios, where a + * subprocess created by a plugin has no access to the + * "OrthancPluginContext", and thus cannot call + * "OrthancPluginRestApi[Get|Post|Put|Delete]()". + * + * This situation is frequently encountered in Python plugins, where + * the "multiprocessing" package can be used to bypass the Global + * Interpreter Lock (GIL) and thus to improve performance and + * concurrency. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The authorization token, or NULL value in the case of an error. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateRestApiAuthorizationToken( + OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GenerateRestApiAuthorizationToken, + ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginMemoryBuffer64* target; + uint64_t size; + } _OrthancPluginCreateMemoryBuffer64; + + /** + * @brief Create a 64-bit memory buffer. + * + * This function creates a 64-bit memory buffer that is managed by + * the Orthanc core. The main use case of this function is for + * plugins that read files from the storage area. + * + * Your plugin should never call "free()" on the resulting memory + * buffer, as the C library that is used by the plugin is in general + * not the same as the one used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param size Size of the memory buffer to be created. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateMemoryBuffer64( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer64* target, + uint64_t size) + { + _OrthancPluginCreateMemoryBuffer64 params; + params.target = target; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_CreateMemoryBuffer64, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageCreate create; + OrthancPluginStorageReadWhole readWhole; + OrthancPluginStorageReadRange readRange; + OrthancPluginStorageRemove remove; + } _OrthancPluginRegisterStorageArea2; + + /** + * @brief Register a custom storage area, with support for range request. + * + * This function registers a custom storage area, to replace the + * built-in way Orthanc stores its files on the filesystem. 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 create The callback function to store a file on the custom storage area. + * @param readWhole The callback function to read a whole file from the custom storage area. + * @param readRange The callback function to read some range of a file from the custom storage area. + * If this feature is not supported by the plugin, this value can be set to NULL. + * @param remove The callback function to remove a file from the custom storage area. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea2( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageReadWhole readWhole, + OrthancPluginStorageReadRange readRange, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea2 params; + params.create = create; + params.readWhole = readWhole; + params.readRange = readRange; + params.remove = remove; + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea2, ¶ms); + } + + + + typedef struct + { + _OrthancPluginCreateDicom createDicom; + const char* privateCreator; + } _OrthancPluginCreateDicom2; + + /** + * @brief Create a DICOM instance from a JSON string and an image, with a private creator. + * + * This function takes as input a string containing a JSON file + * describing the content of a DICOM instance. As an output, it + * writes the corresponding DICOM instance to a newly allocated + * memory buffer. Additionally, an image to be encoded within the + * DICOM instance can also be provided. + * + * Contrarily to the function OrthancPluginCreateDicom(), this + * function can be explicitly provided with a private creator. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param json The input JSON file. + * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. + * @param flags Flags governing the output. + * @param privateCreator The private creator to be used for the private DICOM tags. + * Check out the global configuration option "Dictionary" of Orthanc. + * @return 0 if success, other value if error. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom() + * @see OrthancPluginDicomBufferToJson() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom2( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* json, + const OrthancPluginImage* pixelData, + OrthancPluginCreateDicomFlags flags, + const char* privateCreator) + { + _OrthancPluginCreateDicom2 params; + params.createDicom.target = target; + params.createDicom.json = json; + params.createDicom.pixelData = pixelData; + params.createDicom.flags = flags; + params.privateCreator = privateCreator; + + return context->InvokeService(context, _OrthancPluginService_CreateDicom2, ¶ms); + } + + + + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + OrthancPluginHttpMethod method; + const char* uri; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + const void* body; + uint32_t bodySize; + uint8_t afterPlugins; + } _OrthancPluginCallRestApi; + + /** + * @brief Call the REST API of Orthanc with full flexibility. + * + * Make a call to the given URI in the REST API of Orthanc. The + * result to the query is stored into a newly allocated memory + * buffer. This function is always granted full access to the REST + * API (no credentials, nor security token is needed). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * The value of this argument is ignored if the HTTP method is DELETE. + * @param answerHeaders The target memory buffer for the HTTP headers in the answer (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the answer HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param method HTTP method to be used. + * @param uri The URI of interest. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param afterPlugins If 0, the built-in API of Orthanc is used. + * If 1, the API is tainted by the plugins. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet2(), OrthancPluginRestApiPost(), OrthancPluginRestApiPut(), OrthancPluginRestApiDelete() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCallRestApi( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + OrthancPluginHttpMethod method, + const char* uri, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + const void* body, + uint32_t bodySize, + uint8_t afterPlugins) + { + _OrthancPluginCallRestApi params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.method = method; + params.uri = uri; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.body = body; + params.bodySize = bodySize; + params.afterPlugins = afterPlugins; + + return context->InvokeService(context, _OrthancPluginService_CallRestApi, ¶ms); + } + + + + /** + * @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); + } + + + /** + * @brief Gets the DatabaseServerIdentifier. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return the database server identifier. This is a statically-allocated + * string, do not free it. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetDatabaseServerIdentifier( + OrthancPluginContext* context) + { + const char* result; + + _OrthancPluginRetrieveStaticString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetDatabaseServerIdentifier, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Signature of a callback function that is triggered when + * the Orthanc core requests an operation from the database plugin. + * Both request and response are encoded as protobuf buffers. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginCallDatabaseBackendV4) ( + OrthancPluginMemoryBuffer64* response, + void* backend, + const void* request, + uint64_t requestSize); + + /** + * @brief Signature of a callback function that is triggered when + * the database plugin must be finalized. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginFinalizeDatabaseBackendV4) (void* backend); + + typedef struct + { + void* backend; + uint32_t maxDatabaseRetries; + OrthancPluginCallDatabaseBackendV4 operations; + OrthancPluginFinalizeDatabaseBackendV4 finalize; + } _OrthancPluginRegisterDatabaseBackendV4; + + /** + * @brief Register a custom database back-end. + * + * This function was added in Orthanc SDK 1.12.0. It uses Google + * Protocol Buffers for the communications between the Orthanc core + * and database plugins. Check out "OrthancDatabasePlugin.proto" for + * the definition of the protobuf messages. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param backend Pointer to the custom database backend. + * @param maxDatabaseRetries Maximum number of retries if transaction doesn't succeed. + * If no retry is successful, OrthancPluginErrorCode_DatabaseCannotSerialize is generated. + * @param operations Access to the operations of the custom database backend. + * @param finalize Callback to deallocate the custom database backend. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDatabaseBackendV4( + OrthancPluginContext* context, + void* backend, + uint32_t maxDatabaseRetries, + OrthancPluginCallDatabaseBackendV4 operations, + OrthancPluginFinalizeDatabaseBackendV4 finalize) + { + _OrthancPluginRegisterDatabaseBackendV4 params; + params.backend = backend; + params.maxDatabaseRetries = maxDatabaseRetries; + params.operations = operations; + params.finalize = finalize; + + return context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV4, ¶ms); + } + + + typedef struct + { + OrthancPluginDicomInstance** target; + const char* instanceId; + OrthancPluginLoadDicomInstanceMode mode; + } _OrthancPluginLoadDicomInstance; + + /** + * @brief Load a DICOM instance from the Orthanc server. + * + * This function loads a DICOM instance from the content of the + * Orthanc database. The function returns a new pointer to a data + * structure that is managed by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instanceId The Orthanc identifier of the DICOM instance of interest. + * @param mode Flag specifying how to deal with pixel data. + * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginLoadDicomInstance( + OrthancPluginContext* context, + const char* instanceId, + OrthancPluginLoadDicomInstanceMode mode) + { + OrthancPluginDicomInstance* target = NULL; + + _OrthancPluginLoadDicomInstance params; + params.target = ⌖ + params.instanceId = instanceId; + params.mode = mode; + + if (context->InvokeService(context, _OrthancPluginService_LoadDicomInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + const char* name; + int64_t value; + OrthancPluginMetricsType type; + } _OrthancPluginSetMetricsIntegerValue; + + /** + * @brief Set the value of an integer metrics. + * + * This function sets the value of an integer metrics to monitor the + * behavior of the plugin through tools such as Prometheus. The + * values of all the metrics are stored within the Orthanc context. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param name The name of the metrics to be set. + * @param value The value of the metrics. + * @param type The type of the metrics. This parameter is only taken into consideration + * the first time this metrics is set. + * @ingroup Toolbox + * @see OrthancPluginSetMetricsValue() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetMetricsIntegerValue( + OrthancPluginContext* context, + const char* name, + int64_t value, + OrthancPluginMetricsType type) + { + _OrthancPluginSetMetricsIntegerValue params; + params.name = name; + params.value = value; + params.type = type; + context->InvokeService(context, _OrthancPluginService_SetMetricsIntegerValue, ¶ms); + } + + + /** + * @brief Set the name of the current thread. + * + * This function gives a name to the thread that is calling this + * function. This name is used in the Orthanc logs. This function + * must only be called from threads that the plugin has created + * itself. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param threadName The name of the current thread. A thread name cannot be longer than 16 characters. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetCurrentThreadName( + OrthancPluginContext* context, + const char* threadName) + { + return context->InvokeService(context, _OrthancPluginService_SetCurrentThreadName, threadName); + } + +#ifdef __cplusplus +} +#endif + + +/** @} */ + diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Sdk-1.12.3/orthanc/OrthancDatabasePlugin.proto --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Orthanc/Sdk-1.12.3/orthanc/OrthancDatabasePlugin.proto Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,949 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2024 Osimis S.A., Belgium + * Copyright (C) 2021-2024 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 . + **/ + + +/** + * This Protocol Buffers prototype describes the exchanges between the + * Orthanc core and its database plugins. The various calls correspond + * to the "IDatabaseWrapper" interface in the source code of Orthanc. + * + * WARNING: *NEVER* modify or remove existing entries. It is only + * allowed to *add* new stuff. + **/ + +syntax = "proto3"; + +/** + * Turn off protobuf reflection to avoid clashes between the Orthanc + * core and the database plugin, otherwise both will try to register + * the same messages in the process-wide descriptor pool, which would + * result in protobuf error "File already exists in database". + **/ +option optimize_for = LITE_RUNTIME; + +package Orthanc.DatabasePluginMessages; + + +/** + * Data structures that are common with the Orthanc core. + **/ + +message FileInfo { + string uuid = 1; + int32 content_type = 2; // opaque "FileContentType" in Orthanc + uint64 uncompressed_size = 3; + string uncompressed_hash = 4; + int32 compression_type = 5; // opaque "CompressionType" in Orthanc + uint64 compressed_size = 6; + string compressed_hash = 7; +} + +enum ResourceType { + RESOURCE_PATIENT = 0; + RESOURCE_STUDY = 1; + RESOURCE_SERIES = 2; + RESOURCE_INSTANCE = 3; +} + +enum ConstraintType { + CONSTRAINT_EQUAL = 0; + CONSTRAINT_SMALLER_OR_EQUAL = 1; + CONSTRAINT_GREATER_OR_EQUAL = 2; + CONSTRAINT_WILDCARD = 3; + CONSTRAINT_LIST = 4; +} + +enum LabelsConstraintType { + LABELS_CONSTRAINT_ALL = 0; + LABELS_CONSTRAINT_ANY = 1; + LABELS_CONSTRAINT_NONE = 2; +} + +message ServerIndexChange { + int64 seq = 1; + int32 change_type = 2; // opaque "ChangeType" in Orthanc + ResourceType resource_type = 3; + string public_id = 4; + string date = 5; +} + +message ExportedResource { + int64 seq = 1; + ResourceType resource_type = 2; + string public_id = 3; + string modality = 4; + string date = 5; + string patient_id = 6; + string study_instance_uid = 7; + string series_instance_uid = 8; + string sop_instance_uid = 9; +} + +message DatabaseConstraint { + ResourceType level = 1; + uint32 tag_group = 2; + uint32 tag_element = 3; + bool is_identifier_tag = 4; + bool is_case_sensitive = 5; + bool is_mandatory = 6; + ConstraintType type = 7; + repeated string values = 8; +} + + +/** + * Database-level operations. + **/ + +enum DatabaseOperation { + OPERATION_GET_SYSTEM_INFORMATION = 0; + OPERATION_OPEN = 1; + OPERATION_CLOSE = 2; + OPERATION_FLUSH_TO_DISK = 3; + OPERATION_START_TRANSACTION = 4; + OPERATION_UPGRADE = 5; + OPERATION_FINALIZE_TRANSACTION = 6; + OPERATION_MEASURE_LATENCY = 7; // New in Orthanc 1.12.X +} + +enum TransactionType { + TRANSACTION_READ_ONLY = 0; + TRANSACTION_READ_WRITE = 1; +} + +message GetSystemInformation { + message Request { + } + message Response { + uint32 database_version = 1; + bool supports_flush_to_disk = 2; + bool supports_revisions = 3; + bool supports_labels = 4; + bool supports_increment_global_property = 5; + bool has_update_and_get_statistics = 6; + bool has_measure_latency = 7; + } +} + +message Open { + message Request { + message IdentifierTag { + ResourceType level = 1; + uint32 group = 2; + uint32 element = 3; + string name = 4; + } + repeated IdentifierTag identifier_tags = 1; + } + message Response { + } +} + +message Close { + message Request { + } + message Response { + } +} + +message FlushToDisk { + message Request { + } + message Response { + } +} + +message StartTransaction { + message Request { + TransactionType type = 1; + } + message Response { + sfixed64 transaction = 1; + } +} + +message Upgrade { + /** + * It is guaranteed that a read-write transaction is created by the + * Orthanc core before executing this operation. + **/ + message Request { + uint32 target_version = 1; + sfixed64 storage_area = 2; + sfixed64 transaction = 3; + } + message Response { + } +} + +message FinalizeTransaction { + message Request { + sfixed64 transaction = 1; + } + message Response { + } +} + +message MeasureLatency { + message Request { + } + message Response { + int64 latency_us = 1; + } +} + + +message DatabaseRequest { + sfixed64 database = 1; + DatabaseOperation operation = 2; + + GetSystemInformation.Request get_system_information = 100; + Open.Request open = 101; + Close.Request close = 102; + FlushToDisk.Request flush_to_disk = 103; + StartTransaction.Request start_transaction = 104; + Upgrade.Request upgrade = 105; + FinalizeTransaction.Request finalize_transaction = 106; + MeasureLatency.Request measure_latency = 107; +} + +message DatabaseResponse { + GetSystemInformation.Response get_system_information = 100; + Open.Response open = 101; + Close.Response close = 102; + FlushToDisk.Response flush_to_disk = 103; + StartTransaction.Response start_transaction = 104; + Upgrade.Response upgrade = 105; + FinalizeTransaction.Response finalize_transaction = 106; + MeasureLatency.Response measure_latency = 107; +} + + +/** + * Transaction-level operations. + **/ + +enum TransactionOperation { + OPERATION_ROLLBACK = 0; + OPERATION_COMMIT = 1; + OPERATION_ADD_ATTACHMENT = 2; + OPERATION_CLEAR_CHANGES = 3; + OPERATION_CLEAR_EXPORTED_RESOURCES = 4; + OPERATION_DELETE_ATTACHMENT = 5; + OPERATION_DELETE_METADATA = 6; + OPERATION_DELETE_RESOURCE = 7; + OPERATION_GET_ALL_METADATA = 8; + OPERATION_GET_ALL_PUBLIC_IDS = 9; + OPERATION_GET_ALL_PUBLIC_IDS_WITH_LIMITS = 10; + OPERATION_GET_CHANGES = 11; + OPERATION_GET_CHILDREN_INTERNAL_ID = 12; + OPERATION_GET_CHILDREN_PUBLIC_ID = 13; + OPERATION_GET_EXPORTED_RESOURCES = 14; + OPERATION_GET_LAST_CHANGE = 15; + OPERATION_GET_LAST_EXPORTED_RESOURCE = 16; + OPERATION_GET_MAIN_DICOM_TAGS = 17; + OPERATION_GET_PUBLIC_ID = 18; + OPERATION_GET_RESOURCES_COUNT = 19; + OPERATION_GET_RESOURCE_TYPE = 20; + OPERATION_GET_TOTAL_COMPRESSED_SIZE = 21; + OPERATION_GET_TOTAL_UNCOMPRESSED_SIZE = 22; + OPERATION_IS_PROTECTED_PATIENT = 23; + OPERATION_LIST_AVAILABLE_ATTACHMENTS = 24; + OPERATION_LOG_CHANGE = 25; + OPERATION_LOG_EXPORTED_RESOURCE = 26; + OPERATION_LOOKUP_ATTACHMENT = 27; + OPERATION_LOOKUP_GLOBAL_PROPERTY = 28; + OPERATION_LOOKUP_METADATA = 29; + OPERATION_LOOKUP_PARENT = 30; + OPERATION_LOOKUP_RESOURCE = 31; + OPERATION_SELECT_PATIENT_TO_RECYCLE = 32; + OPERATION_SELECT_PATIENT_TO_RECYCLE_WITH_AVOID = 33; + OPERATION_SET_GLOBAL_PROPERTY = 34; + OPERATION_CLEAR_MAIN_DICOM_TAGS = 35; + OPERATION_SET_METADATA = 36; + OPERATION_SET_PROTECTED_PATIENT = 37; + OPERATION_IS_DISK_SIZE_ABOVE = 38; + OPERATION_LOOKUP_RESOURCES = 39; + OPERATION_CREATE_INSTANCE = 40; + OPERATION_SET_RESOURCES_CONTENT = 41; + OPERATION_GET_CHILDREN_METADATA = 42; + OPERATION_GET_LAST_CHANGE_INDEX = 43; + OPERATION_LOOKUP_RESOURCE_AND_PARENT = 44; + OPERATION_ADD_LABEL = 45; // New in Orthanc 1.12.0 + OPERATION_REMOVE_LABEL = 46; // New in Orthanc 1.12.0 + OPERATION_LIST_LABELS = 47; // New in Orthanc 1.12.0 + OPERATION_INCREMENT_GLOBAL_PROPERTY = 48; // New in Orthanc 1.12.X + OPERATION_UPDATE_AND_GET_STATISTICS = 49; // New in Orthanc 1.12.X +} + +message Rollback { + message Request { + } + message Response { + } +} + +message Commit { + message Request { + int64 file_size_delta = 1; + } + message Response { + } +} + +message AddAttachment { + message Request { + int64 id = 1; + FileInfo attachment = 2; + int64 revision = 3; + } + message Response { + } +} + +message ClearChanges { + message Request { + } + message Response { + } +} + +message ClearExportedResources { + message Request { + } + message Response { + } +} + +message DeleteAttachment { + message Request { + int64 id = 1; + int32 type = 2; + } + message Response { + FileInfo deleted_attachment = 1; + } +} + +message DeleteMetadata { + message Request { + int64 id = 1; + int32 type = 2; + } + message Response { + } +} + +message DeleteResource { + message Request { + int64 id = 1; + } + message Response { + message Resource { + ResourceType level = 1; + string public_id = 2; + } + repeated FileInfo deleted_attachments = 1; + repeated Resource deleted_resources = 2; + bool is_remaining_ancestor = 3; + Resource remaining_ancestor = 4; + } +} + +message GetAllMetadata { + message Request { + int64 id = 1; + } + message Response { + message Metadata { + int32 type = 1; + string value = 2; + } + repeated Metadata metadata = 1; + } +} + +message GetAllPublicIds { + message Request { + ResourceType resource_type = 1; + } + message Response { + repeated string ids = 1; + } +} + +message GetAllPublicIdsWithLimits { + message Request { + ResourceType resource_type = 1; + int64 since = 2; + uint32 limit = 3; + } + message Response { + repeated string ids = 1; + } +} + +message GetChanges { + message Request { + int64 since = 1; + uint32 limit = 2; + } + message Response { + repeated ServerIndexChange changes = 1; + bool done = 2; + } +} + +message GetChildrenInternalId { + message Request { + int64 id = 1; + } + message Response { + repeated int64 ids = 1; + } +} + +message GetChildrenPublicId { + message Request { + int64 id = 1; + } + message Response { + repeated string ids = 1; + } +} + +message GetExportedResources { + message Request { + int64 since = 1; + uint32 limit = 2; + } + message Response { + repeated ExportedResource resources = 1; + bool done = 2; + } +} + +message GetLastChange { + message Request { + } + message Response { + bool found = 1; + ServerIndexChange change = 2; + } +} + +message GetLastExportedResource { + message Request { + } + message Response { + bool found = 1; + ExportedResource resource = 2; + } +} + +message GetMainDicomTags { + message Request { + int64 id = 1; + } + message Response { + message Tag { + uint32 group = 1; + uint32 element = 2; + string value = 3; + } + repeated Tag tags = 1; + } +} + +message GetPublicId { + message Request { + int64 id = 1; + } + message Response { + string id = 1; + } +} + +message GetResourcesCount { + message Request { + ResourceType type = 1; + } + message Response { + uint64 count = 1; + } +} + +message GetResourceType { + message Request { + int64 id = 1; + } + message Response { + ResourceType type = 1; + } +} + +message GetTotalCompressedSize { + message Request { + } + message Response { + uint64 size = 1; + } +} + +message GetTotalUncompressedSize { + message Request { + } + message Response { + uint64 size = 1; + } +} + +message IsProtectedPatient { + message Request { + int64 patient_id = 1; + } + message Response { + bool protected_patient = 1; + } +} + +message ListAvailableAttachments { + message Request { + int64 id = 1; + } + message Response { + repeated int32 attachments = 1; + } +} + +message LogChange { + message Request { + int32 change_type = 1; + ResourceType resource_type = 2; + int64 resource_id = 3; + string date = 4; + } + message Response { + } +} + +message LogExportedResource { + message Request { + ResourceType resource_type = 1; + string public_id = 2; + string modality = 3; + string date = 4; + string patient_id = 5; + string study_instance_uid = 6; + string series_instance_uid = 7; + string sop_instance_uid = 8; + } + message Response { + } +} + +message LookupAttachment { + message Request { + int64 id = 1; + int32 content_type = 2; + } + message Response { + bool found = 1; + FileInfo attachment = 2; + int64 revision = 3; + } +} + +message LookupGlobalProperty { + message Request { + string server_id = 1; + int32 property = 2; + } + message Response { + bool found = 1; + string value = 2; + } +} + +message LookupMetadata { + message Request { + int64 id = 1; + int32 metadata_type = 2; + } + message Response { + bool found = 1; + string value = 2; + int64 revision = 3; + } +} + +message LookupParent { + message Request { + int64 id = 1; + } + message Response { + bool found = 1; + int64 parent = 2; + } +} + +message LookupResource { + message Request { + string public_id = 1; + } + message Response { + bool found = 1; + int64 internal_id = 2; + ResourceType type = 3; + } +} + +message SelectPatientToRecycle { + message Request { + } + message Response { + bool found = 1; + int64 patient_id = 2; + } +} + +message SelectPatientToRecycleWithAvoid { + message Request { + int64 patient_id_to_avoid = 1; + } + message Response { + bool found = 1; + int64 patient_id = 2; + } +} + +message SetGlobalProperty { + message Request { + string server_id = 1; + int32 property = 2; + string value = 3; + } + message Response { + } +} + +message IncrementGlobalProperty { + message Request { + string server_id = 1; + int32 property = 2; + int64 increment = 3; + } + message Response { + int64 new_value = 1; + } +} + +message UpdateAndGetStatistics { + message Request { + } + message Response { + int64 patients_count = 1; + int64 studies_count = 2; + int64 series_count = 3; + int64 instances_count = 4; + int64 total_compressed_size = 5; + int64 total_uncompressed_size = 6; + } +} + +message ClearMainDicomTags { + message Request { + int64 id = 1; + } + message Response { + } +} + +message SetMetadata { + message Request { + int64 id = 1; + int32 metadata_type = 2; + string value = 3; + int64 revision = 4; + } + message Response { + } +} + +message SetProtectedPatient { + message Request { + int64 patient_id = 1; + bool protected_patient = 2; + } + message Response { + } +} + +message IsDiskSizeAbove { + message Request { + uint64 threshold = 1; + } + message Response { + bool result = 1; + } +} + +message LookupResources { + message Request { + repeated DatabaseConstraint lookup = 1; + ResourceType query_level = 2; + uint32 limit = 3; + bool retrieve_instances_ids = 4; + repeated string labels = 5; // New in Orthanc 1.12.0 + LabelsConstraintType labels_constraint = 6; // New in Orthanc 1.12.0 + } + message Response { + repeated string resources_ids = 1; + repeated string instances_ids = 2; // Only filled if "retrieve_instances" is true + } +} + +message CreateInstance { + message Request { + string patient = 1; + string study = 2; + string series = 3; + string instance = 4; + } + message Response { + bool is_new_instance = 1; + int64 instance_id = 2; + + // The fields below are only set if "is_new_instance" is true + bool is_new_patient = 3; + bool is_new_study = 4; + bool is_new_series = 5; + int64 patient_id = 6; + int64 study_id = 7; + int64 series_id = 8; + } +} + +message SetResourcesContent { + message Request { + message Tag { + int64 resource_id = 1; + bool is_identifier = 2; + uint32 group = 3; + uint32 element = 4; + string value = 5; + } + + message Metadata { + int64 resource_id = 1; + int32 metadata = 2; + string value = 3; + } + + repeated Tag tags = 1; + repeated Metadata metadata = 2; + } + message Response { + } +} + +message GetChildrenMetadata { + message Request { + int64 id = 1; + int32 metadata = 2; + } + message Response { + repeated string values = 1; + } +} + +message GetLastChangeIndex { + message Request { + } + message Response { + int64 result = 1; + } +} + +message LookupResourceAndParent { + message Request { + string public_id = 1; + } + message Response { + bool found = 1; + int64 id = 2; + ResourceType type = 3; + string parent_public_id = 4; // Only for study, series, or instance + } +} + +message AddLabel { + message Request { + int64 id = 1; + string label = 2; + } + message Response { + } +} + +message RemoveLabel { + message Request { + int64 id = 1; + string label = 2; + } + message Response { + } +} + +message ListLabels { + message Request { + bool single_resource = 1; + int64 id = 2; // Only if "single_resource" is "true" + } + message Response { + repeated string labels = 1; + } +} + +message TransactionRequest { + sfixed64 transaction = 1; + TransactionOperation operation = 2; + + Rollback.Request rollback = 100; + Commit.Request commit = 101; + AddAttachment.Request add_attachment = 102; + ClearChanges.Request clear_changes = 103; + ClearExportedResources.Request clear_exported_resources = 104; + DeleteAttachment.Request delete_attachment = 105; + DeleteMetadata.Request delete_metadata = 106; + DeleteResource.Request delete_resource = 107; + GetAllMetadata.Request get_all_metadata = 108; + GetAllPublicIds.Request get_all_public_ids = 109; + GetAllPublicIdsWithLimits.Request get_all_public_ids_with_limits = 110; + GetChanges.Request get_changes = 111; + GetChildrenInternalId.Request get_children_internal_id = 112; + GetChildrenPublicId.Request get_children_public_id = 113; + GetExportedResources.Request get_exported_resources = 114; + GetLastChange.Request get_last_change = 115; + GetLastExportedResource.Request get_last_exported_resource = 116; + GetMainDicomTags.Request get_main_dicom_tags = 117; + GetPublicId.Request get_public_id = 118; + GetResourcesCount.Request get_resources_count = 119; + GetResourceType.Request get_resource_type = 120; + GetTotalCompressedSize.Request get_total_compressed_size = 121; + GetTotalUncompressedSize.Request get_total_uncompressed_size = 122; + IsProtectedPatient.Request is_protected_patient = 123; + ListAvailableAttachments.Request list_available_attachments = 124; + LogChange.Request log_change = 125; + LogExportedResource.Request log_exported_resource = 126; + LookupAttachment.Request lookup_attachment = 127; + LookupGlobalProperty.Request lookup_global_property = 128; + LookupMetadata.Request lookup_metadata = 129; + LookupParent.Request lookup_parent = 130; + LookupResource.Request lookup_resource = 131; + SelectPatientToRecycle.Request select_patient_to_recycle = 132; + SelectPatientToRecycleWithAvoid.Request select_patient_to_recycle_with_avoid = 133; + SetGlobalProperty.Request set_global_property = 134; + ClearMainDicomTags.Request clear_main_dicom_tags = 135; + SetMetadata.Request set_metadata = 136; + SetProtectedPatient.Request set_protected_patient = 137; + IsDiskSizeAbove.Request is_disk_size_above = 138; + LookupResources.Request lookup_resources = 139; + CreateInstance.Request create_instance = 140; + SetResourcesContent.Request set_resources_content = 141; + GetChildrenMetadata.Request get_children_metadata = 142; + GetLastChangeIndex.Request get_last_change_index = 143; + LookupResourceAndParent.Request lookup_resource_and_parent = 144; + AddLabel.Request add_label = 145; + RemoveLabel.Request remove_label = 146; + ListLabels.Request list_labels = 147; + IncrementGlobalProperty.Request increment_global_property = 148; + UpdateAndGetStatistics.Request update_and_get_statistics = 149; +} + +message TransactionResponse { + Rollback.Response rollback = 100; + Commit.Response commit = 101; + AddAttachment.Response add_attachment = 102; + ClearChanges.Response clear_changes = 103; + ClearExportedResources.Response clear_exported_resources = 104; + DeleteAttachment.Response delete_attachment = 105; + DeleteMetadata.Response delete_metadata = 106; + DeleteResource.Response delete_resource = 107; + GetAllMetadata.Response get_all_metadata = 108; + GetAllPublicIds.Response get_all_public_ids = 109; + GetAllPublicIdsWithLimits.Response get_all_public_ids_with_limits = 110; + GetChanges.Response get_changes = 111; + GetChildrenInternalId.Response get_children_internal_id = 112; + GetChildrenPublicId.Response get_children_public_id = 113; + GetExportedResources.Response get_exported_resources = 114; + GetLastChange.Response get_last_change = 115; + GetLastExportedResource.Response get_last_exported_resource = 116; + GetMainDicomTags.Response get_main_dicom_tags = 117; + GetPublicId.Response get_public_id = 118; + GetResourcesCount.Response get_resources_count = 119; + GetResourceType.Response get_resource_type = 120; + GetTotalCompressedSize.Response get_total_compressed_size = 121; + GetTotalUncompressedSize.Response get_total_uncompressed_size = 122; + IsProtectedPatient.Response is_protected_patient = 123; + ListAvailableAttachments.Response list_available_attachments = 124; + LogChange.Response log_change = 125; + LogExportedResource.Response log_exported_resource = 126; + LookupAttachment.Response lookup_attachment = 127; + LookupGlobalProperty.Response lookup_global_property = 128; + LookupMetadata.Response lookup_metadata = 129; + LookupParent.Response lookup_parent = 130; + LookupResource.Response lookup_resource = 131; + SelectPatientToRecycle.Response select_patient_to_recycle = 132; + SelectPatientToRecycleWithAvoid.Response select_patient_to_recycle_with_avoid = 133; + SetGlobalProperty.Response set_global_property = 134; + ClearMainDicomTags.Response clear_main_dicom_tags = 135; + SetMetadata.Response set_metadata = 136; + SetProtectedPatient.Response set_protected_patient = 137; + IsDiskSizeAbove.Response is_disk_size_above = 138; + LookupResources.Response lookup_resources = 139; + CreateInstance.Response create_instance = 140; + SetResourcesContent.Response set_resources_content = 141; + GetChildrenMetadata.Response get_children_metadata = 142; + GetLastChangeIndex.Response get_last_change_index = 143; + LookupResourceAndParent.Response lookup_resource_and_parent = 144; + AddLabel.Response add_label = 145; + RemoveLabel.Response remove_label = 146; + ListLabels.Response list_labels = 147; + IncrementGlobalProperty.Response increment_global_property = 148; + UpdateAndGetStatistics.Response update_and_get_statistics = 149; +} + +enum RequestType { + REQUEST_DATABASE = 0; + REQUEST_TRANSACTION = 1; +} + +message Request { + RequestType type = 1; + DatabaseRequest database_request = 2; + TransactionRequest transaction_request = 3; +} + +message Response { + DatabaseResponse database_response = 2; + TransactionResponse transaction_response = 3; +} diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Sdk-1.12.4/orthanc/OrthancCDatabasePlugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Orthanc/Sdk-1.12.4/orthanc/OrthancCDatabasePlugin.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,1371 @@ +/** + * @ingroup CInterface + **/ + +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 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 . + **/ + + + +#pragma once + +#include "OrthancCPlugin.h" + + +/** @{ */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + + /** + * Opaque structure that represents the context of a custom database engine. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginDatabaseContext_t OrthancPluginDatabaseContext; + + + /** + * Opaque structure that represents a transaction of a custom database engine. + * New in Orthanc 1.9.2. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginDatabaseTransaction_t OrthancPluginDatabaseTransaction; + + +/*InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChange( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginChange* change) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Change; + params.valueUint32 = 0; + params.valueGeneric = change; + + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChangesDone( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Change; + params.valueUint32 = 1; + params.valueGeneric = NULL; + + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt32( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int32_t value) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Int32; + params.valueInt32 = value; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt64( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int64_t value) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Int64; + params.valueInt64 = value; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginExportedResource* exported) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_ExportedResource; + params.valueUint32 = 0; + params.valueGeneric = exported; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResourcesDone( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_ExportedResource; + params.valueUint32 = 1; + params.valueGeneric = NULL; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerDicomTag( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginDicomTag* tag) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DicomTag; + params.valueGeneric = tag; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerAttachment( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginAttachment* attachment) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Attachment; + params.valueGeneric = attachment; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int64_t id, + OrthancPluginResourceType resourceType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Resource; + params.valueInt64 = id; + params.valueInt32 = (int32_t) resourceType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerMatchingResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginMatchingResource* match) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_MatchingResource; + params.valueGeneric = match; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerMetadata( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int64_t resourceId, + int32_t type, + const char* value) + { + OrthancPluginResourcesContentMetadata metadata; + _OrthancPluginDatabaseAnswer params; + metadata.resource = resourceId; + metadata.metadata = type; + metadata.value = value; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Metadata; + params.valueGeneric = &metadata; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedAttachment( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginAttachment* attachment) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DeletedAttachment; + params.valueGeneric = attachment; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const char* publicId, + OrthancPluginResourceType resourceType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DeletedResource; + params.valueString = publicId; + params.valueInt32 = (int32_t) resourceType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalRemainingAncestor( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const char* ancestorId, + OrthancPluginResourceType ancestorType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_RemainingAncestor; + params.valueString = ancestorId; + params.valueInt32 = (int32_t) ancestorType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + + + + + typedef struct + { + OrthancPluginErrorCode (*addAttachment) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginAttachment* attachment); + + OrthancPluginErrorCode (*attachChild) ( + /* inputs */ + void* payload, + int64_t parent, + int64_t child); + + OrthancPluginErrorCode (*clearChanges) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*clearExportedResources) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*createResource) ( + /* outputs */ + int64_t* id, + /* inputs */ + void* payload, + const char* publicId, + OrthancPluginResourceType resourceType); + + OrthancPluginErrorCode (*deleteAttachment) ( + /* inputs */ + void* payload, + int64_t id, + int32_t contentType); + + OrthancPluginErrorCode (*deleteMetadata) ( + /* inputs */ + void* payload, + int64_t id, + int32_t metadataType); + + OrthancPluginErrorCode (*deleteResource) ( + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getAllPublicIds) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType); + + /* Output: Use OrthancPluginDatabaseAnswerChange() and + * OrthancPluginDatabaseAnswerChangesDone() */ + OrthancPluginErrorCode (*getChanges) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t since, + uint32_t maxResult); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*getChildrenInternalId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getChildrenPublicId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerExportedResource() and + * OrthancPluginDatabaseAnswerExportedResourcesDone() */ + OrthancPluginErrorCode (*getExportedResources) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t since, + uint32_t maxResult); + + /* Output: Use OrthancPluginDatabaseAnswerChange() */ + OrthancPluginErrorCode (*getLastChange) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerExportedResource() */ + OrthancPluginErrorCode (*getLastExportedResource) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerDicomTag() */ + OrthancPluginErrorCode (*getMainDicomTags) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getPublicId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*getResourceCount) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType); + + OrthancPluginErrorCode (*getResourceType) ( + /* outputs */ + OrthancPluginResourceType* resourceType, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*getTotalCompressedSize) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*getTotalUncompressedSize) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*isExistingResource) ( + /* outputs */ + int32_t* existing, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*isProtectedPatient) ( + /* outputs */ + int32_t* isProtected, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerInt32() */ + OrthancPluginErrorCode (*listAvailableMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerInt32() */ + OrthancPluginErrorCode (*listAvailableAttachments) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*logChange) ( + /* inputs */ + void* payload, + const OrthancPluginChange* change); + + OrthancPluginErrorCode (*logExportedResource) ( + /* inputs */ + void* payload, + const OrthancPluginExportedResource* exported); + + /* Output: Use OrthancPluginDatabaseAnswerAttachment() */ + OrthancPluginErrorCode (*lookupAttachment) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id, + int32_t contentType); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*lookupGlobalProperty) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int32_t property); + + /* Use "OrthancPluginDatabaseExtensions::lookupIdentifier3" + instead of this function as of Orthanc 0.9.5 (db v6), can be set to NULL. + Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifier) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const OrthancPluginDicomTag* tag); + + /* Unused starting with Orthanc 0.9.5 (db v6), can be set to NULL. + Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifier2) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const char* value); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*lookupMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id, + int32_t metadata); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupParent) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerResource() */ + OrthancPluginErrorCode (*lookupResource) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const char* publicId); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*selectPatientToRecycle) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*selectPatientToRecycle2) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t patientIdToAvoid); + + OrthancPluginErrorCode (*setGlobalProperty) ( + /* inputs */ + void* payload, + int32_t property, + const char* value); + + OrthancPluginErrorCode (*setMainDicomTag) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginDicomTag* tag); + + OrthancPluginErrorCode (*setIdentifierTag) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginDicomTag* tag); + + OrthancPluginErrorCode (*setMetadata) ( + /* inputs */ + void* payload, + int64_t id, + int32_t metadata, + const char* value); + + OrthancPluginErrorCode (*setProtectedPatient) ( + /* inputs */ + void* payload, + int64_t id, + int32_t isProtected); + + OrthancPluginErrorCode (*startTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*rollbackTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*commitTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*open) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*close) ( + /* inputs */ + void* payload); + + } OrthancPluginDatabaseBackend; + + + typedef struct + { + /** + * Base extensions since Orthanc 1.0.0 + **/ + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getAllPublicIdsWithLimit) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + uint64_t since, + uint64_t limit); + + OrthancPluginErrorCode (*getDatabaseVersion) ( + /* outputs */ + uint32_t* version, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*upgradeDatabase) ( + /* inputs */ + void* payload, + uint32_t targetVersion, + OrthancPluginStorageArea* storageArea); + + OrthancPluginErrorCode (*clearMainDicomTags) ( + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*getAllInternalIds) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifier3) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + const OrthancPluginDicomTag* tag, + OrthancPluginIdentifierConstraint constraint); + + + /** + * Extensions since Orthanc 1.4.0 + **/ + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifierRange) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + uint16_t group, + uint16_t element, + const char* start, + const char* end); + + + /** + * Extensions since Orthanc 1.5.2 + **/ + + /* Ouput: Use OrthancPluginDatabaseAnswerMatchingResource */ + OrthancPluginErrorCode (*lookupResources) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + uint32_t constraintsCount, + const OrthancPluginDatabaseConstraint* constraints, + OrthancPluginResourceType queryLevel, + uint32_t limit, + uint8_t requestSomeInstance); + + OrthancPluginErrorCode (*createInstance) ( + /* output */ + OrthancPluginCreateInstanceResult* output, + /* inputs */ + void* payload, + const char* hashPatient, + const char* hashStudy, + const char* hashSeries, + const char* hashInstance); + + OrthancPluginErrorCode (*setResourcesContent) ( + /* inputs */ + void* payload, + uint32_t countIdentifierTags, + const OrthancPluginResourcesContentTags* identifierTags, + uint32_t countMainDicomTags, + const OrthancPluginResourcesContentTags* mainDicomTags, + uint32_t countMetadata, + const OrthancPluginResourcesContentMetadata* metadata); + + /* Ouput: Use OrthancPluginDatabaseAnswerString */ + OrthancPluginErrorCode (*getChildrenMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t resourceId, + int32_t metadata); + + OrthancPluginErrorCode (*getLastChangeIndex) ( + /* outputs */ + int64_t* target, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*tagMostRecentPatient) ( + /* inputs */ + void* payload, + int64_t patientId); + + + /** + * Extensions since Orthanc 1.5.4 + **/ + + /* Ouput: Use OrthancPluginDatabaseAnswerMetadata */ + OrthancPluginErrorCode (*getAllMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t resourceId); + + /* Ouput: Use OrthancPluginDatabaseAnswerString to send + the public ID of the parent (if the resource is not a patient) */ + OrthancPluginErrorCode (*lookupResourceAndParent) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + uint8_t* isExisting, + int64_t* id, + OrthancPluginResourceType* type, + + /* inputs */ + void* payload, + const char* publicId); + + } OrthancPluginDatabaseExtensions; + +/*InvokeService(context, _OrthancPluginService_RegisterDatabaseBackend, ¶ms) || + result == NULL) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + typedef struct + { + OrthancPluginDatabaseContext** result; + const OrthancPluginDatabaseBackend* backend; + void* payload; + const OrthancPluginDatabaseExtensions* extensions; + uint32_t extensionsSize; + } _OrthancPluginRegisterDatabaseBackendV2; + + + /** + * Register a custom database back-end. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param backend The callbacks of the custom database engine. + * @param payload Pointer containing private information for the database engine. + * @param extensions Extensions to the base database SDK that was shipped until Orthanc 0.9.3. + * @return The context of the database engine (it must not be manually freed). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDatabaseContext* OrthancPluginRegisterDatabaseBackendV2( + OrthancPluginContext* context, + const OrthancPluginDatabaseBackend* backend, + const OrthancPluginDatabaseExtensions* extensions, + void* payload) + { + OrthancPluginDatabaseContext* result = NULL; + _OrthancPluginRegisterDatabaseBackendV2 params; + + if (sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType)) + { + return NULL; + } + + memset(¶ms, 0, sizeof(params)); + params.backend = backend; + params.result = &result; + params.payload = payload; + params.extensions = extensions; + params.extensionsSize = sizeof(OrthancPluginDatabaseExtensions); + + if (context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV2, ¶ms) || + result == NULL) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + /** + * New interface starting with Orthanc 1.9.2 + **/ + +/*InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV3, ¶ms); + } + +#ifdef __cplusplus +} +#endif + + +/** @} */ + diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Sdk-1.12.4/orthanc/OrthancCPlugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Orthanc/Sdk-1.12.4/orthanc/OrthancCPlugin.h Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,9575 @@ +/** + * \mainpage + * + * This C/C++ SDK allows external developers to create plugins that + * can be loaded into Orthanc to extend its functionality. Each + * Orthanc plugin must expose 4 public functions with the following + * signatures: + * + * -# int32_t OrthancPluginInitialize(const OrthancPluginContext* context): + * This function is invoked by Orthanc when it loads the plugin on startup. + * The plugin must: + * - Check its compatibility with the Orthanc version using + * ::OrthancPluginCheckVersion(). + * - Store the context pointer so that it can use the plugin + * services of Orthanc. + * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). + * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). + * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). + * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea2(). + * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV4(). + * - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback(). + * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). + * - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback(). + * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). + * - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter2(). + * - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer(). + * - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback(). + * - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback(). + * - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback(). + * - 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. + * -# const char* OrthancPluginGetName(): + * The plugin must return a short string to identify itself. + * -# const char* OrthancPluginGetVersion(): + * The plugin must return a string containing its version number. + * + * The name and the version of a plugin is only used to prevent it + * from being loaded twice. Note that, in C++, it is mandatory to + * declare these functions within an extern "C" section. + * + * To ensure multi-threading safety, the various REST callbacks are + * guaranteed to be executed in mutual exclusion since Orthanc + * 0.8.5. If this feature is undesired (notably when developing + * high-performance plugins handling simultaneous requests), use + * ::OrthancPluginRegisterRestCallbackNoLock(). + **/ + + + +/** + * @defgroup Images Images and compression + * @brief Functions to deal with images and compressed buffers. + * + * @defgroup REST REST + * @brief Functions to answer REST requests in a callback. + * + * @defgroup Callbacks Callbacks + * @brief Functions to register and manage callbacks by the plugins. + * + * @defgroup DicomCallbacks DicomCallbacks + * @brief Functions to register and manage DICOM callbacks (worklists, C-FIND, C-MOVE, storage commitment). + * + * @defgroup Orthanc Orthanc + * @brief Functions to access the content of the Orthanc server. + * + * @defgroup DicomInstance DicomInstance + * @brief Functions to access DICOM images that are managed by the Orthanc core. + **/ + + + +/** + * @defgroup Toolbox Toolbox + * @brief Generic functions to help with the creation of plugins. + **/ + + + +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 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 . + **/ + + + +#pragma once + + +#include +#include + +#ifdef WIN32 +# define ORTHANC_PLUGINS_API __declspec(dllexport) +#elif __GNUC__ >= 4 +# define ORTHANC_PLUGINS_API __attribute__ ((visibility ("default"))) +#else +# define ORTHANC_PLUGINS_API +#endif + +#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 +#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 12 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 4 + + +#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) +#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \ + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) +#endif + + + +/******************************************************************** + ** Check that function inlining is properly supported. The use of + ** inlining is required, to avoid the duplication of object code + ** between two compilation modules that would use the Orthanc Plugin + ** API. + ********************************************************************/ + +/* If the auto-detection of the "inline" keyword below does not work + automatically and that your compiler is known to properly support + inlining, uncomment the following #define and adapt the definition + of "static inline". */ + +/* #define ORTHANC_PLUGIN_INLINE static inline */ + +#ifndef ORTHANC_PLUGIN_INLINE +# if __STDC_VERSION__ >= 199901L +/* This is C99 or above: http://predef.sourceforge.net/prestd.html */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__cplusplus) +/* This is C++ */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__GNUC__) +/* This is GCC running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# elif defined(_MSC_VER) +/* This is Visual Studio running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# else +# error Your compiler is not known to support the "inline" keyword +# endif +#endif + + +#ifndef ORTHANC_PLUGIN_DEPRECATED +# if defined(_MSC_VER) +# define ORTHANC_PLUGIN_DEPRECATED __declspec(deprecated) +# elif __GNUC__ >= 4 +# define ORTHANC_PLUGIN_DEPRECATED __attribute__ ((deprecated)) +# elif defined(__clang__) +# define ORTHANC_PLUGIN_DEPRECATED __attribute__ ((deprecated)) +# else +# pragma message("WARNING: You need to implement ORTHANC_PLUGINS_DEPRECATED for this compiler") +# define ORTHANC_PLUGIN_DEPRECATED +# endif +#endif + + + +/******************************************************************** + ** Inclusion of standard libraries. + ********************************************************************/ + +/** + * For Microsoft Visual Studio, a compatibility "stdint.h" can be + * downloaded at the following URL: + * https://orthanc.uclouvain.be/hg/orthanc/raw-file/default/OrthancFramework/Resources/ThirdParty/VisualStudio/stdint.h + **/ +#include + +#include + + + +/******************************************************************** + ** Definition of the Orthanc Plugin API. + ********************************************************************/ + +/** @{ */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * The various error codes that can be returned by the Orthanc core. + **/ + typedef enum + { + OrthancPluginErrorCode_InternalError = -1 /*!< Internal error */, + OrthancPluginErrorCode_Success = 0 /*!< Success */, + OrthancPluginErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, + OrthancPluginErrorCode_NotImplemented = 2 /*!< Not implemented yet */, + OrthancPluginErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, + OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< The server hosting Orthanc is running out of memory */, + OrthancPluginErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, + OrthancPluginErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, + OrthancPluginErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, + OrthancPluginErrorCode_BadRequest = 8 /*!< Bad request */, + OrthancPluginErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */, + OrthancPluginErrorCode_SystemCommand = 10 /*!< Error while calling a system command */, + OrthancPluginErrorCode_Database = 11 /*!< Error with the database engine */, + OrthancPluginErrorCode_UriSyntax = 12 /*!< Badly formatted URI */, + OrthancPluginErrorCode_InexistentFile = 13 /*!< Inexistent file */, + OrthancPluginErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */, + OrthancPluginErrorCode_BadFileFormat = 15 /*!< Bad file format */, + OrthancPluginErrorCode_Timeout = 16 /*!< Timeout */, + OrthancPluginErrorCode_UnknownResource = 17 /*!< Unknown resource */, + OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */, + OrthancPluginErrorCode_FullStorage = 19 /*!< The file storage is full */, + OrthancPluginErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */, + OrthancPluginErrorCode_InexistentTag = 21 /*!< Inexistent tag */, + OrthancPluginErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */, + OrthancPluginErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */, + OrthancPluginErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */, + OrthancPluginErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */, + OrthancPluginErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */, + OrthancPluginErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */, + OrthancPluginErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */, + OrthancPluginErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */, + OrthancPluginErrorCode_BadFont = 30 /*!< Badly formatted font file */, + OrthancPluginErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, + OrthancPluginErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, + OrthancPluginErrorCode_EmptyRequest = 33 /*!< The request is empty */, + OrthancPluginErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, + OrthancPluginErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */, + OrthancPluginErrorCode_DatabaseUnavailable = 36 /*!< The database is currently not available (probably a transient situation) */, + OrthancPluginErrorCode_CanceledJob = 37 /*!< This job was canceled */, + OrthancPluginErrorCode_BadGeometry = 38 /*!< Geometry error encountered in Stone */, + OrthancPluginErrorCode_SslInitialization = 39 /*!< Cannot initialize SSL encryption, check out your certificates */, + OrthancPluginErrorCode_DiscontinuedAbi = 40 /*!< Calling a function that has been removed from the Orthanc Framework */, + OrthancPluginErrorCode_BadRange = 41 /*!< Incorrect range request */, + OrthancPluginErrorCode_DatabaseCannotSerialize = 42 /*!< Database could not serialize access due to concurrent update, the transaction should be retried */, + OrthancPluginErrorCode_Revision = 43 /*!< A bad revision number was provided, which might indicate conflict between multiple writers */, + OrthancPluginErrorCode_MainDicomTagsMultiplyDefined = 44 /*!< A main DICOM Tag has been defined multiple times for the same resource level */, + OrthancPluginErrorCode_ForbiddenAccess = 45 /*!< Access to a resource is forbidden */, + OrthancPluginErrorCode_DuplicateResource = 46 /*!< Duplicate resource */, + OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, + OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, + OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, + OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */, + OrthancPluginErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */, + OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */, + OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */, + OrthancPluginErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */, + OrthancPluginErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, + OrthancPluginErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, + OrthancPluginErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, + OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, + OrthancPluginErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, + OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, + OrthancPluginErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, + OrthancPluginErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */, + OrthancPluginErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, + OrthancPluginErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, + OrthancPluginErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, + OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is privileged or already in use */, + OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is privileged or already in use */, + OrthancPluginErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, + OrthancPluginErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, + OrthancPluginErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, + OrthancPluginErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */, + OrthancPluginErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */, + OrthancPluginErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */, + OrthancPluginErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */, + OrthancPluginErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */, + OrthancPluginErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */, + OrthancPluginErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */, + OrthancPluginErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */, + OrthancPluginErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */, + OrthancPluginErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */, + OrthancPluginErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */, + OrthancPluginErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */, + OrthancPluginErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */, + OrthancPluginErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */, + OrthancPluginErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */, + OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */, + OrthancPluginErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */, + OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */, + OrthancPluginErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */, + OrthancPluginErrorCode_UnknownModality = 2027 /*!< Unknown modality */, + OrthancPluginErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */, + OrthancPluginErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */, + OrthancPluginErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */, + OrthancPluginErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */, + OrthancPluginErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */, + OrthancPluginErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */, + OrthancPluginErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */, + OrthancPluginErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */, + OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, + OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, + OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, + OrthancPluginErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, + OrthancPluginErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, + OrthancPluginErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */, + OrthancPluginErrorCode_AlreadyExistingTag = 2042 /*!< Cannot override the value of a tag that already exists */, + OrthancPluginErrorCode_NoStorageCommitmentHandler = 2043 /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */, + OrthancPluginErrorCode_NoCGetHandler = 2044 /*!< No request handler factory for DICOM C-GET SCP */, + OrthancPluginErrorCode_UnsupportedMediaType = 3000 /*!< Unsupported media type */, + + _OrthancPluginErrorCode_INTERNAL = 0x7fffffff + } OrthancPluginErrorCode; + + + /** + * Forward declaration of one of the mandatory functions for Orthanc + * plugins. + **/ + ORTHANC_PLUGINS_API const char* OrthancPluginGetName(); + + + /** + * The various HTTP methods for a REST call. + **/ + typedef enum + { + OrthancPluginHttpMethod_Get = 1, /*!< GET request */ + OrthancPluginHttpMethod_Post = 2, /*!< POST request */ + OrthancPluginHttpMethod_Put = 3, /*!< PUT request */ + OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */ + + _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff + } OrthancPluginHttpMethod; + + + /** + * @brief The parameters of a REST request. + * @ingroup Callbacks + **/ + typedef struct + { + /** + * @brief The HTTP method. + **/ + OrthancPluginHttpMethod method; + + /** + * @brief The number of groups of the regular expression. + **/ + uint32_t groupsCount; + + /** + * @brief The matched values for the groups of the regular expression. + **/ + const char* const* groups; + + /** + * @brief For a GET request, the number of GET parameters. + **/ + uint32_t getCount; + + /** + * @brief For a GET request, the keys of the GET parameters. + **/ + const char* const* getKeys; + + /** + * @brief For a GET request, the values of the GET parameters. + **/ + const char* const* getValues; + + /** + * @brief For a PUT or POST request, the content of the body. + **/ + const void* body; + + /** + * @brief For a PUT or POST request, the number of bytes of the body. + **/ + uint32_t bodySize; + + + /* -------------------------------------------------- + New in version 0.8.1 + -------------------------------------------------- */ + + /** + * @brief The number of HTTP headers. + **/ + uint32_t headersCount; + + /** + * @brief The keys of the HTTP headers (always converted to low-case). + **/ + const char* const* headersKeys; + + /** + * @brief The values of the HTTP headers. + **/ + const char* const* headersValues; + + } OrthancPluginHttpRequest; + + + typedef enum + { + /* Generic services */ + _OrthancPluginService_LogInfo = 1, + _OrthancPluginService_LogWarning = 2, + _OrthancPluginService_LogError = 3, + _OrthancPluginService_GetOrthancPath = 4, + _OrthancPluginService_GetOrthancDirectory = 5, + _OrthancPluginService_GetConfigurationPath = 6, + _OrthancPluginService_SetPluginProperty = 7, + _OrthancPluginService_GetGlobalProperty = 8, + _OrthancPluginService_SetGlobalProperty = 9, + _OrthancPluginService_GetCommandLineArgumentsCount = 10, + _OrthancPluginService_GetCommandLineArgument = 11, + _OrthancPluginService_GetExpectedDatabaseVersion = 12, + _OrthancPluginService_GetConfiguration = 13, + _OrthancPluginService_BufferCompression = 14, + _OrthancPluginService_ReadFile = 15, + _OrthancPluginService_WriteFile = 16, + _OrthancPluginService_GetErrorDescription = 17, + _OrthancPluginService_CallHttpClient = 18, + _OrthancPluginService_RegisterErrorCode = 19, + _OrthancPluginService_RegisterDictionaryTag = 20, + _OrthancPluginService_DicomBufferToJson = 21, + _OrthancPluginService_DicomInstanceToJson = 22, + _OrthancPluginService_CreateDicom = 23, + _OrthancPluginService_ComputeMd5 = 24, + _OrthancPluginService_ComputeSha1 = 25, + _OrthancPluginService_LookupDictionary = 26, + _OrthancPluginService_CallHttpClient2 = 27, + _OrthancPluginService_GenerateUuid = 28, + _OrthancPluginService_RegisterPrivateDictionaryTag = 29, + _OrthancPluginService_AutodetectMimeType = 30, + _OrthancPluginService_SetMetricsValue = 31, + _OrthancPluginService_EncodeDicomWebJson = 32, + _OrthancPluginService_EncodeDicomWebXml = 33, + _OrthancPluginService_ChunkedHttpClient = 34, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_GetTagName = 35, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_EncodeDicomWebJson2 = 36, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_EncodeDicomWebXml2 = 37, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_CreateMemoryBuffer = 38, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GenerateRestApiAuthorizationToken = 39, /* New in Orthanc 1.8.1 */ + _OrthancPluginService_CreateMemoryBuffer64 = 40, /* New in Orthanc 1.9.0 */ + _OrthancPluginService_CreateDicom2 = 41, /* New in Orthanc 1.9.0 */ + _OrthancPluginService_GetDatabaseServerIdentifier = 42, /* New in Orthanc 1.11.1 */ + _OrthancPluginService_SetMetricsIntegerValue = 43, /* New in Orthanc 1.12.1 */ + _OrthancPluginService_SetCurrentThreadName = 44, /* New in Orthanc 1.12.2 */ + _OrthancPluginService_LogMessage = 45, /* New in Orthanc 1.12.4 */ + + + /* Registration of callbacks */ + _OrthancPluginService_RegisterRestCallback = 1000, + _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, + _OrthancPluginService_RegisterStorageArea = 1002, + _OrthancPluginService_RegisterOnChangeCallback = 1003, + _OrthancPluginService_RegisterRestCallbackNoLock = 1004, + _OrthancPluginService_RegisterWorklistCallback = 1005, + _OrthancPluginService_RegisterDecodeImageCallback = 1006, + _OrthancPluginService_RegisterIncomingHttpRequestFilter = 1007, + _OrthancPluginService_RegisterFindCallback = 1008, + _OrthancPluginService_RegisterMoveCallback = 1009, + _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010, + _OrthancPluginService_RegisterRefreshMetricsCallback = 1011, + _OrthancPluginService_RegisterChunkedRestCallback = 1012, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_RegisterStorageCommitmentScpCallback = 1013, + _OrthancPluginService_RegisterIncomingDicomInstanceFilter = 1014, + _OrthancPluginService_RegisterTranscoderCallback = 1015, /* New in Orthanc 1.7.0 */ + _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, + _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */ + _OrthancPluginService_Redirect = 2002, + _OrthancPluginService_SendHttpStatusCode = 2003, + _OrthancPluginService_SendUnauthorized = 2004, + _OrthancPluginService_SendMethodNotAllowed = 2005, + _OrthancPluginService_SetCookie = 2006, + _OrthancPluginService_SetHttpHeader = 2007, + _OrthancPluginService_StartMultipartAnswer = 2008, + _OrthancPluginService_SendMultipartItem = 2009, + _OrthancPluginService_SendHttpStatus = 2010, + _OrthancPluginService_CompressAndAnswerImage = 2011, + _OrthancPluginService_SendMultipartItem2 = 2012, + _OrthancPluginService_SetHttpErrorDetails = 2013, + + /* Access to the Orthanc database and API */ + _OrthancPluginService_GetDicomForInstance = 3000, + _OrthancPluginService_RestApiGet = 3001, + _OrthancPluginService_RestApiPost = 3002, + _OrthancPluginService_RestApiDelete = 3003, + _OrthancPluginService_RestApiPut = 3004, + _OrthancPluginService_LookupPatient = 3005, + _OrthancPluginService_LookupStudy = 3006, + _OrthancPluginService_LookupSeries = 3007, + _OrthancPluginService_LookupInstance = 3008, + _OrthancPluginService_LookupStudyWithAccessionNumber = 3009, + _OrthancPluginService_RestApiGetAfterPlugins = 3010, + _OrthancPluginService_RestApiPostAfterPlugins = 3011, + _OrthancPluginService_RestApiDeleteAfterPlugins = 3012, + _OrthancPluginService_RestApiPutAfterPlugins = 3013, + _OrthancPluginService_ReconstructMainDicomTags = 3014, + _OrthancPluginService_RestApiGet2 = 3015, + _OrthancPluginService_CallRestApi = 3016, /* New in Orthanc 1.9.2 */ + + /* Access to DICOM instances */ + _OrthancPluginService_GetInstanceRemoteAet = 4000, + _OrthancPluginService_GetInstanceSize = 4001, + _OrthancPluginService_GetInstanceData = 4002, + _OrthancPluginService_GetInstanceJson = 4003, + _OrthancPluginService_GetInstanceSimplifiedJson = 4004, + _OrthancPluginService_HasInstanceMetadata = 4005, + _OrthancPluginService_GetInstanceMetadata = 4006, + _OrthancPluginService_GetInstanceOrigin = 4007, + _OrthancPluginService_GetInstanceTransferSyntaxUid = 4008, + _OrthancPluginService_HasInstancePixelData = 4009, + _OrthancPluginService_CreateDicomInstance = 4010, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_FreeDicomInstance = 4011, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceFramesCount = 4012, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceRawFrame = 4013, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceDecodedFrame = 4014, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_TranscodeDicomInstance = 4015, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_SerializeDicomInstance = 4016, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceAdvancedJson = 4017, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceDicomWebJson = 4018, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_GetInstanceDicomWebXml = 4019, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_LoadDicomInstance = 4020, /* New in Orthanc 1.12.1 */ + + /* Services for plugins implementing a database back-end */ + _OrthancPluginService_RegisterDatabaseBackend = 5000, /* New in Orthanc 0.8.6 */ + _OrthancPluginService_DatabaseAnswer = 5001, + _OrthancPluginService_RegisterDatabaseBackendV2 = 5002, /* New in Orthanc 0.9.4 */ + _OrthancPluginService_StorageAreaCreate = 5003, + _OrthancPluginService_StorageAreaRead = 5004, + _OrthancPluginService_StorageAreaRemove = 5005, + _OrthancPluginService_RegisterDatabaseBackendV3 = 5006, /* New in Orthanc 1.9.2 */ + _OrthancPluginService_RegisterDatabaseBackendV4 = 5007, /* New in Orthanc 1.12.0 */ + + /* Primitives for handling images */ + _OrthancPluginService_GetImagePixelFormat = 6000, + _OrthancPluginService_GetImageWidth = 6001, + _OrthancPluginService_GetImageHeight = 6002, + _OrthancPluginService_GetImagePitch = 6003, + _OrthancPluginService_GetImageBuffer = 6004, + _OrthancPluginService_UncompressImage = 6005, + _OrthancPluginService_FreeImage = 6006, + _OrthancPluginService_CompressImage = 6007, + _OrthancPluginService_ConvertPixelFormat = 6008, + _OrthancPluginService_GetFontsCount = 6009, + _OrthancPluginService_GetFontInfo = 6010, + _OrthancPluginService_DrawText = 6011, + _OrthancPluginService_CreateImage = 6012, + _OrthancPluginService_CreateImageAccessor = 6013, + _OrthancPluginService_DecodeDicomImage = 6014, + + /* Primitives for handling C-Find, C-Move and worklists */ + _OrthancPluginService_WorklistAddAnswer = 7000, + _OrthancPluginService_WorklistMarkIncomplete = 7001, + _OrthancPluginService_WorklistIsMatch = 7002, + _OrthancPluginService_WorklistGetDicomQuery = 7003, + _OrthancPluginService_FindAddAnswer = 7004, + _OrthancPluginService_FindMarkIncomplete = 7005, + _OrthancPluginService_GetFindQuerySize = 7006, + _OrthancPluginService_GetFindQueryTag = 7007, + _OrthancPluginService_GetFindQueryTagName = 7008, + _OrthancPluginService_GetFindQueryValue = 7009, + _OrthancPluginService_CreateFindMatcher = 7010, + _OrthancPluginService_FreeFindMatcher = 7011, + _OrthancPluginService_FindMatcherIsMatch = 7012, + + /* Primitives for accessing Orthanc Peers (new in 1.4.2) */ + _OrthancPluginService_GetPeers = 8000, + _OrthancPluginService_FreePeers = 8001, + _OrthancPluginService_GetPeersCount = 8003, + _OrthancPluginService_GetPeerName = 8004, + _OrthancPluginService_GetPeerUrl = 8005, + _OrthancPluginService_CallPeerApi = 8006, + _OrthancPluginService_GetPeerUserProperty = 8007, + + /* Primitives for handling jobs (new in 1.4.2) */ + _OrthancPluginService_CreateJob = 9000, /* Deprecated since SDK 1.11.3 */ + _OrthancPluginService_FreeJob = 9001, + _OrthancPluginService_SubmitJob = 9002, + _OrthancPluginService_RegisterJobsUnserializer = 9003, + _OrthancPluginService_CreateJob2 = 9004, /* New in SDK 1.11.3 */ + + _OrthancPluginService_INTERNAL = 0x7fffffff + } _OrthancPluginService; + + + typedef enum + { + _OrthancPluginProperty_Description = 1, + _OrthancPluginProperty_RootUri = 2, + _OrthancPluginProperty_OrthancExplorer = 3, + + _OrthancPluginProperty_INTERNAL = 0x7fffffff + } _OrthancPluginProperty; + + + + /** + * The memory layout of the pixels of an image. + * @ingroup Images + **/ + typedef enum + { + /** + * @brief Graylevel 8bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * one byte. + **/ + OrthancPluginPixelFormat_Grayscale8 = 1, + + /** + * @brief Graylevel, unsigned 16bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * two bytes. + **/ + OrthancPluginPixelFormat_Grayscale16 = 2, + + /** + * @brief Graylevel, signed 16bpp image. + * + * The image is graylevel. Each pixel is signed and stored in two + * bytes. + **/ + OrthancPluginPixelFormat_SignedGrayscale16 = 3, + + /** + * @brief Color image in RGB24 format. + * + * This format describes a color image. The pixels are stored in 3 + * consecutive bytes. The memory layout is RGB. + **/ + OrthancPluginPixelFormat_RGB24 = 4, + + /** + * @brief Color image in RGBA32 format. + * + * This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is RGBA. + **/ + OrthancPluginPixelFormat_RGBA32 = 5, + + OrthancPluginPixelFormat_Unknown = 6, /*!< Unknown pixel format */ + + /** + * @brief Color image in RGB48 format. + * + * This format describes a color image. The pixels are stored in 6 + * consecutive bytes. The memory layout is RRGGBB. + **/ + OrthancPluginPixelFormat_RGB48 = 7, + + /** + * @brief Graylevel, unsigned 32bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * four bytes. + **/ + OrthancPluginPixelFormat_Grayscale32 = 8, + + /** + * @brief Graylevel, floating-point 32bpp image. + * + * The image is graylevel. Each pixel is floating-point and stored + * in four bytes. + **/ + OrthancPluginPixelFormat_Float32 = 9, + + /** + * @brief Color image in BGRA32 format. + * + * This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is BGRA. + **/ + OrthancPluginPixelFormat_BGRA32 = 10, + + /** + * @brief Graylevel, unsigned 64bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * eight bytes. + **/ + OrthancPluginPixelFormat_Grayscale64 = 11, + + _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff + } OrthancPluginPixelFormat; + + + + /** + * The content types that are supported by Orthanc plugins. + **/ + typedef enum + { + OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */ + OrthancPluginContentType_Dicom = 1, /*!< DICOM */ + OrthancPluginContentType_DicomAsJson = 2, /*!< JSON summary of a DICOM file */ + OrthancPluginContentType_DicomUntilPixelData = 3, /*!< DICOM Header till pixel data */ + + _OrthancPluginContentType_INTERNAL = 0x7fffffff + } OrthancPluginContentType; + + + + /** + * The supported types of DICOM resources. + **/ + typedef enum + { + OrthancPluginResourceType_Patient = 0, /*!< Patient */ + OrthancPluginResourceType_Study = 1, /*!< Study */ + OrthancPluginResourceType_Series = 2, /*!< Series */ + OrthancPluginResourceType_Instance = 3, /*!< Instance */ + OrthancPluginResourceType_None = 4, /*!< Unavailable resource type */ + + _OrthancPluginResourceType_INTERNAL = 0x7fffffff + } OrthancPluginResourceType; + + + + /** + * The supported types of changes that can be signaled to the change callback. + * @ingroup Callbacks + **/ + typedef enum + { + OrthancPluginChangeType_CompletedSeries = 0, /*!< Series is now complete */ + OrthancPluginChangeType_Deleted = 1, /*!< Deleted resource */ + OrthancPluginChangeType_NewChildInstance = 2, /*!< A new instance was added to this resource */ + OrthancPluginChangeType_NewInstance = 3, /*!< New instance received */ + OrthancPluginChangeType_NewPatient = 4, /*!< New patient created */ + OrthancPluginChangeType_NewSeries = 5, /*!< New series created */ + OrthancPluginChangeType_NewStudy = 6, /*!< New study created */ + OrthancPluginChangeType_StablePatient = 7, /*!< Timeout: No new instance in this patient */ + OrthancPluginChangeType_StableSeries = 8, /*!< Timeout: No new instance in this series */ + OrthancPluginChangeType_StableStudy = 9, /*!< Timeout: No new instance in this study */ + OrthancPluginChangeType_OrthancStarted = 10, /*!< Orthanc has started */ + OrthancPluginChangeType_OrthancStopped = 11, /*!< Orthanc is stopping */ + OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */ + OrthancPluginChangeType_UpdatedMetadata = 13, /*!< Some user-defined metadata has changed for this resource */ + OrthancPluginChangeType_UpdatedPeers = 14, /*!< The list of Orthanc peers has changed */ + OrthancPluginChangeType_UpdatedModalities = 15, /*!< The list of DICOM modalities has changed */ + OrthancPluginChangeType_JobSubmitted = 16, /*!< New Job submitted */ + OrthancPluginChangeType_JobSuccess = 17, /*!< A Job has completed successfully */ + OrthancPluginChangeType_JobFailure = 18, /*!< A Job has failed */ + + _OrthancPluginChangeType_INTERNAL = 0x7fffffff + } OrthancPluginChangeType; + + + /** + * The compression algorithms that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginCompressionType_Zlib = 0, /*!< Standard zlib compression */ + OrthancPluginCompressionType_ZlibWithSize = 1, /*!< zlib, prefixed with uncompressed size (uint64_t) */ + OrthancPluginCompressionType_Gzip = 2, /*!< Standard gzip compression */ + OrthancPluginCompressionType_GzipWithSize = 3, /*!< gzip, prefixed with uncompressed size (uint64_t) */ + + _OrthancPluginCompressionType_INTERNAL = 0x7fffffff + } OrthancPluginCompressionType; + + + /** + * The image formats that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */ + OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */ + OrthancPluginImageFormat_Dicom = 2, /*!< Image compressed using DICOM */ + + _OrthancPluginImageFormat_INTERNAL = 0x7fffffff + } OrthancPluginImageFormat; + + + /** + * The value representations present in the DICOM standard (version 2013). + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginValueRepresentation_AE = 1, /*!< Application Entity */ + OrthancPluginValueRepresentation_AS = 2, /*!< Age String */ + OrthancPluginValueRepresentation_AT = 3, /*!< Attribute Tag */ + OrthancPluginValueRepresentation_CS = 4, /*!< Code String */ + OrthancPluginValueRepresentation_DA = 5, /*!< Date */ + OrthancPluginValueRepresentation_DS = 6, /*!< Decimal String */ + OrthancPluginValueRepresentation_DT = 7, /*!< Date Time */ + OrthancPluginValueRepresentation_FD = 8, /*!< Floating Point Double */ + OrthancPluginValueRepresentation_FL = 9, /*!< Floating Point Single */ + OrthancPluginValueRepresentation_IS = 10, /*!< Integer String */ + OrthancPluginValueRepresentation_LO = 11, /*!< Long String */ + OrthancPluginValueRepresentation_LT = 12, /*!< Long Text */ + OrthancPluginValueRepresentation_OB = 13, /*!< Other Byte String */ + OrthancPluginValueRepresentation_OF = 14, /*!< Other Float String */ + OrthancPluginValueRepresentation_OW = 15, /*!< Other Word String */ + OrthancPluginValueRepresentation_PN = 16, /*!< Person Name */ + OrthancPluginValueRepresentation_SH = 17, /*!< Short String */ + OrthancPluginValueRepresentation_SL = 18, /*!< Signed Long */ + OrthancPluginValueRepresentation_SQ = 19, /*!< Sequence of Items */ + OrthancPluginValueRepresentation_SS = 20, /*!< Signed Short */ + OrthancPluginValueRepresentation_ST = 21, /*!< Short Text */ + OrthancPluginValueRepresentation_TM = 22, /*!< Time */ + OrthancPluginValueRepresentation_UI = 23, /*!< Unique Identifier (UID) */ + OrthancPluginValueRepresentation_UL = 24, /*!< Unsigned Long */ + OrthancPluginValueRepresentation_UN = 25, /*!< Unknown */ + OrthancPluginValueRepresentation_US = 26, /*!< Unsigned Short */ + OrthancPluginValueRepresentation_UT = 27, /*!< Unlimited Text */ + + _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff + } OrthancPluginValueRepresentation; + + + /** + * The possible output formats for a DICOM-to-JSON conversion. + * @ingroup Toolbox + * @see OrthancPluginDicomToJson() + **/ + typedef enum + { + OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */ + OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */ + OrthancPluginDicomToJsonFormat_Human = 3, /*!< Human-readable JSON */ + + _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff + } OrthancPluginDicomToJsonFormat; + + + /** + * Flags to customize a DICOM-to-JSON conversion. By default, binary + * tags are formatted using Data URI scheme. + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginDicomToJsonFlags_None = 0, /*!< Default formatting */ + OrthancPluginDicomToJsonFlags_IncludeBinary = (1 << 0), /*!< Include the binary tags */ + OrthancPluginDicomToJsonFlags_IncludePrivateTags = (1 << 1), /*!< Include the private tags */ + OrthancPluginDicomToJsonFlags_IncludeUnknownTags = (1 << 2), /*!< Include the tags unknown by the dictionary */ + OrthancPluginDicomToJsonFlags_IncludePixelData = (1 << 3), /*!< Include the pixel data */ + OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii = (1 << 4), /*!< Output binary tags as-is, dropping non-ASCII */ + OrthancPluginDicomToJsonFlags_ConvertBinaryToNull = (1 << 5), /*!< Signal binary tags as null values */ + OrthancPluginDicomToJsonFlags_StopAfterPixelData = (1 << 6), /*!< Stop processing after pixel data (new in 1.9.1) */ + OrthancPluginDicomToJsonFlags_SkipGroupLengths = (1 << 7), /*!< Skip tags whose element is zero (new in 1.9.1) */ + + _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff + } OrthancPluginDicomToJsonFlags; + + + /** + * Flags for the creation of a DICOM file. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom() + **/ + typedef enum + { + OrthancPluginCreateDicomFlags_None = 0, /*!< Default mode */ + OrthancPluginCreateDicomFlags_DecodeDataUriScheme = (1 << 0), /*!< Decode fields encoded using data URI scheme */ + OrthancPluginCreateDicomFlags_GenerateIdentifiers = (1 << 1), /*!< Automatically generate DICOM identifiers */ + + _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff + } OrthancPluginCreateDicomFlags; + + + /** + * The constraints on the DICOM identifiers that must be supported + * by the database plugins. + * @deprecated Plugins using OrthancPluginConstraintType will be faster + **/ + typedef enum + { + OrthancPluginIdentifierConstraint_Equal = 1, /*!< Equal */ + OrthancPluginIdentifierConstraint_SmallerOrEqual = 2, /*!< Less or equal */ + OrthancPluginIdentifierConstraint_GreaterOrEqual = 3, /*!< More or equal */ + OrthancPluginIdentifierConstraint_Wildcard = 4, /*!< Case-sensitive wildcard matching (with * and ?) */ + + _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff + } OrthancPluginIdentifierConstraint; + + + /** + * The constraints on the tags (main DICOM tags and identifier tags) + * that must be supported by the database plugins. + **/ + typedef enum + { + OrthancPluginConstraintType_Equal = 1, /*!< Equal */ + OrthancPluginConstraintType_SmallerOrEqual = 2, /*!< Less or equal */ + OrthancPluginConstraintType_GreaterOrEqual = 3, /*!< More or equal */ + OrthancPluginConstraintType_Wildcard = 4, /*!< Wildcard matching */ + OrthancPluginConstraintType_List = 5, /*!< List of values */ + + _OrthancPluginConstraintType_INTERNAL = 0x7fffffff + } OrthancPluginConstraintType; + + + /** + * The origin of a DICOM instance that has been received by Orthanc. + **/ + typedef enum + { + OrthancPluginInstanceOrigin_Unknown = 1, /*!< Unknown origin */ + OrthancPluginInstanceOrigin_DicomProtocol = 2, /*!< Instance received through DICOM protocol */ + OrthancPluginInstanceOrigin_RestApi = 3, /*!< Instance received through REST API of Orthanc */ + OrthancPluginInstanceOrigin_Plugin = 4, /*!< Instance added to Orthanc by a plugin */ + OrthancPluginInstanceOrigin_Lua = 5, /*!< Instance added to Orthanc by a Lua script */ + OrthancPluginInstanceOrigin_WebDav = 6, /*!< Instance received through WebDAV (new in 1.8.0) */ + + _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff + } OrthancPluginInstanceOrigin; + + + /** + * The possible status for one single step of a job. + **/ + typedef enum + { + OrthancPluginJobStepStatus_Success = 1, /*!< The job has successfully executed all its steps */ + OrthancPluginJobStepStatus_Failure = 2, /*!< The job has failed while executing this step */ + OrthancPluginJobStepStatus_Continue = 3 /*!< The job has still data to process after this step */ + } OrthancPluginJobStepStatus; + + + /** + * Explains why the job should stop and release the resources it has + * allocated. This is especially important to disambiguate between + * the "paused" condition and the "final" conditions (success, + * failure, or canceled). + **/ + typedef enum + { + OrthancPluginJobStopReason_Success = 1, /*!< The job has succeeded */ + OrthancPluginJobStopReason_Paused = 2, /*!< The job was paused, and will be resumed later */ + OrthancPluginJobStopReason_Failure = 3, /*!< The job has failed, and might be resubmitted later */ + OrthancPluginJobStopReason_Canceled = 4 /*!< The job was canceled, and might be resubmitted later */ + } OrthancPluginJobStopReason; + + + /** + * The available types of metrics. + **/ + typedef enum + { + OrthancPluginMetricsType_Default = 0, /*!< Default metrics */ + + /** + * This metrics represents a time duration. Orthanc will keep the + * maximum value of the metrics over a sliding window of ten + * seconds, which is useful if the metrics is sampled frequently. + **/ + OrthancPluginMetricsType_Timer = 1 + } OrthancPluginMetricsType; + + + /** + * The available modes to export a binary DICOM tag into a DICOMweb + * JSON or XML document. + **/ + typedef enum + { + OrthancPluginDicomWebBinaryMode_Ignore = 0, /*!< Don't include binary tags */ + OrthancPluginDicomWebBinaryMode_InlineBinary = 1, /*!< Inline encoding using Base64 */ + OrthancPluginDicomWebBinaryMode_BulkDataUri = 2 /*!< Use a bulk data URI field */ + } OrthancPluginDicomWebBinaryMode; + + + /** + * The available values for the Failure Reason (0008,1197) during + * storage commitment. + * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 + **/ + typedef enum + { + /** + * Success: The DICOM instance is properly stored in the SCP + **/ + OrthancPluginStorageCommitmentFailureReason_Success = 0, + + /** + * 0110H: A general failure in processing the operation was encountered + **/ + OrthancPluginStorageCommitmentFailureReason_ProcessingFailure = 1, + + /** + * 0112H: One or more of the elements in the Referenced SOP + * Instance Sequence was not available + **/ + OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance = 2, + + /** + * 0213H: The SCP does not currently have enough resources to + * store the requested SOP Instance(s) + **/ + OrthancPluginStorageCommitmentFailureReason_ResourceLimitation = 3, + + /** + * 0122H: Storage Commitment has been requested for a SOP Instance + * with a SOP Class that is not supported by the SCP + **/ + OrthancPluginStorageCommitmentFailureReason_ReferencedSOPClassNotSupported = 4, + + /** + * 0119H: The SOP Class of an element in the Referenced SOP + * Instance Sequence did not correspond to the SOP class + * registered for this SOP Instance at the SCP + **/ + OrthancPluginStorageCommitmentFailureReason_ClassInstanceConflict = 5, + + /** + * 0131H: The Transaction UID of the Storage Commitment Request is + * already in use + **/ + OrthancPluginStorageCommitmentFailureReason_DuplicateTransactionUID = 6 + } OrthancPluginStorageCommitmentFailureReason; + + + /** + * The action to be taken after ReceivedInstanceCallback is triggered + **/ + typedef enum + { + OrthancPluginReceivedInstanceAction_KeepAsIs = 1, /*!< Keep the instance as is */ + OrthancPluginReceivedInstanceAction_Modify = 2, /*!< Modify the instance */ + OrthancPluginReceivedInstanceAction_Discard = 3, /*!< Discard the instance */ + + _OrthancPluginReceivedInstanceAction_INTERNAL = 0x7fffffff + } OrthancPluginReceivedInstanceAction; + + + /** + * Mode specifying how to load a DICOM instance. + * @see OrthancPluginLoadDicomInstance() + **/ + typedef enum + { + /** + * Load the whole DICOM file, including pixel data + **/ + OrthancPluginLoadDicomInstanceMode_WholeDicom = 1, + + /** + * Load the whole DICOM file until pixel data, which speeds up the + * loading + **/ + OrthancPluginLoadDicomInstanceMode_UntilPixelData = 2, + + /** + * Load the whole DICOM file until pixel data, and replace pixel + * data by an empty tag whose VR (value representation) is the + * same as those of the original DICOM file + **/ + OrthancPluginLoadDicomInstanceMode_EmptyPixelData = 3, + + _OrthancPluginLoadDicomInstanceMode_INTERNAL = 0x7fffffff + } OrthancPluginLoadDicomInstanceMode; + + + /** + * The log levels supported by Orthanc. + * + * These values must match those of enumeration "LogLevel" in the + * Orthanc Core. + **/ + typedef enum + { + OrthancPluginLogLevel_Error = 0, /*!< Error log level */ + OrthancPluginLogLevel_Warning = 1, /*!< Warning log level */ + OrthancPluginLogLevel_Info = 2, /*!< Info log level */ + OrthancPluginLogLevel_Trace = 3, /*!< Trace log level */ + + _OrthancPluginLogLevel_INTERNAL = 0x7fffffff + } OrthancPluginLogLevel; + + + /** + * The log categories supported by Orthanc. + * + * These values must match those of enumeration "LogCategory" in the + * Orthanc Core. + **/ + typedef enum + { + OrthancPluginLogCategory_Generic = (1 << 0), /*!< Generic (default) category */ + OrthancPluginLogCategory_Plugins = (1 << 1), /*!< Plugin engine related logs (shall not be used by plugins) */ + OrthancPluginLogCategory_Http = (1 << 2), /*!< HTTP related logs */ + OrthancPluginLogCategory_Sqlite = (1 << 3), /*!< SQLite related logs (shall not be used by plugins) */ + OrthancPluginLogCategory_Dicom = (1 << 4), /*!< DICOM related logs */ + OrthancPluginLogCategory_Jobs = (1 << 5), /*!< jobs related logs */ + OrthancPluginLogCategory_Lua = (1 << 6), /*!< Lua related logs (shall not be used by plugins) */ + + _OrthancPluginLogCategory_INTERNAL = 0x7fffffff + } OrthancPluginLogCategory; + + + /** + * @brief A 32-bit memory buffer allocated by the core system of Orthanc. + * + * A memory buffer allocated by the core system of Orthanc. When the + * content of the buffer is not useful anymore, it must be free by a + * call to ::OrthancPluginFreeMemoryBuffer(). + **/ + typedef struct + { + /** + * @brief The content of the buffer. + **/ + void* data; + + /** + * @brief The number of bytes in the buffer. + **/ + uint32_t size; + } OrthancPluginMemoryBuffer; + + + + /** + * @brief A 64-bit memory buffer allocated by the core system of Orthanc. + * + * A memory buffer allocated by the core system of Orthanc. When the + * content of the buffer is not useful anymore, it must be free by a + * call to ::OrthancPluginFreeMemoryBuffer64(). + **/ + typedef struct + { + /** + * @brief The content of the buffer. + **/ + void* data; + + /** + * @brief The number of bytes in the buffer. + **/ + uint64_t size; + } OrthancPluginMemoryBuffer64; + + + + + /** + * @brief Opaque structure that represents the HTTP connection to the client application. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput; + + + + /** + * @brief Opaque structure that represents a DICOM instance that is managed by the Orthanc core. + * @ingroup DicomInstance + **/ + typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance; + + + + /** + * @brief Opaque structure that represents an image that is uncompressed in memory. + * @ingroup Images + **/ + typedef struct _OrthancPluginImage_t OrthancPluginImage; + + + + /** + * @brief Opaque structure that represents the storage area that is actually used by Orthanc. + * @ingroup Images + **/ + typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea; + + + + /** + * @brief Opaque structure to an object that represents a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery; + + + + /** + * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers; + + + + /** + * @brief Opaque structure to an object that represents a C-Find query. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginFindQuery_t OrthancPluginFindQuery; + + + + /** + * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindAnswers; + + + + /** + * @brief Opaque structure to an object that can be used to check whether a DICOM instance matches a C-Find query. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginFindMatcher_t OrthancPluginFindMatcher; + + + + /** + * @brief Opaque structure to the set of remote Orthanc Peers that are known to the local Orthanc server. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginPeers_t OrthancPluginPeers; + + + + /** + * @brief Opaque structure to a job to be executed by Orthanc. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginJob_t OrthancPluginJob; + + + + /** + * @brief Opaque structure that represents a node in a JSON or XML + * document used in DICOMweb. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginDicomWebNode_t OrthancPluginDicomWebNode; + + + + /** + * @brief Signature of a callback function that answers to a REST request. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) ( + OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + + + /** + * @brief Signature of a callback function that is triggered when Orthanc stores a new DICOM instance. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) ( + const OrthancPluginDicomInstance* instance, + const char* instanceId); + + + + /** + * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) ( + OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId); + + + + /** + * @brief Signature of a callback function to decode a DICOM instance as an image. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) ( + OrthancPluginImage** target, + const void* dicom, + const uint32_t size, + uint32_t frameIndex); + + + + /** + * @brief Signature of a function to free dynamic memory. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginFree) (void* buffer); + + + + /** + * @brief Signature of a function to set the content of a node + * encoding a binary DICOM tag, into a JSON or XML document + * generated for DICOMweb. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginDicomWebSetBinaryNode) ( + OrthancPluginDicomWebNode* node, + OrthancPluginDicomWebBinaryMode mode, + const char* bulkDataUri); + + + + /** + * @brief Callback for writing to the storage area. + * + * Signature of a callback function that is triggered when Orthanc writes a file to the storage area. + * + * @param uuid The UUID of the file. + * @param content The content of the file. + * @param size The size of the file. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) ( + const char* uuid, + const void* content, + int64_t size, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading from the storage area. + * + * Signature of a callback function that is triggered when Orthanc reads a file from the storage area. + * + * @param content The content of the file (output). + * @param size The size of the file (output). + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated New plugins should use OrthancPluginStorageRead2 + * + * @warning The "content" buffer *must* have been allocated using + * the "malloc()" function of your C standard library (i.e. nor + * "new[]", neither a pointer to a buffer). The "free()" function of + * your C standard library will automatically be invoked on the + * "content" pointer. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) ( + void** content, + int64_t* size, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading a whole file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc + * reads a whole file from the storage area. + * + * @param target Memory buffer where to store the content of the file. It must be allocated by the + * plugin using OrthancPluginCreateMemoryBuffer64(). The core of Orthanc will free it. + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageReadWhole) ( + OrthancPluginMemoryBuffer64* target, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading a range of a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc + * reads a portion of a file from the storage area. Orthanc + * indicates the start position and the length of the range. + * + * @param target Memory buffer where to store the content of the range. + * The memory buffer is allocated and freed by Orthanc. The length of the range + * of interest corresponds to the size of this buffer. + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @param rangeStart Start position of the requested range in the file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageReadRange) ( + OrthancPluginMemoryBuffer64* target, + const char* uuid, + OrthancPluginContentType type, + uint64_t rangeStart); + + + + /** + * @brief Callback for removing a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area. + * + * @param uuid The UUID of the file to be removed. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) ( + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback to handle the C-Find SCP requests for worklists. + * + * Signature of a callback function that is triggered when Orthanc + * receives a C-Find SCP request against modality worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) ( + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const char* issuerAet, + const char* calledAet); + + + + /** + * @brief Callback to filter incoming HTTP requests received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives an HTTP/REST request, and that answers whether + * this request should be allowed. If the callback returns "0" + * ("false"), the server answers with HTTP status code 403 + * (Forbidden). + * + * Pay attention to the fact that this function may be invoked + * concurrently by different threads of the Web server of + * Orthanc. You must implement proper locking if applicable. + * + * @param method The HTTP method used by the request. + * @param uri The URI of interest. + * @param ip The IP address of the HTTP client. + * @param headersCount The number of HTTP headers. + * @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 Callbacks + * @deprecated Please instead use OrthancPluginIncomingHttpRequestFilter2() + **/ + typedef int32_t (*OrthancPluginIncomingHttpRequestFilter) ( + OrthancPluginHttpMethod method, + const char* uri, + const char* ip, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues); + + + + /** + * @brief Callback to filter incoming HTTP requests received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives an HTTP/REST request, and that answers whether + * this request should be allowed. If the callback returns "0" + * ("false"), the server answers with HTTP status code 403 + * (Forbidden). + * + * Pay attention to the fact that this function may be invoked + * concurrently by different threads of the Web server of + * Orthanc. You must implement proper locking if applicable. + * + * @param method The HTTP method used by the request. + * @param uri The URI of interest. + * @param ip The IP address of the HTTP client. + * @param headersCount The number of HTTP headers. + * @param headersKeys The keys of the HTTP headers (always converted to low-case). + * @param headersValues The values of the HTTP headers. + * @param getArgumentsCount The number of GET arguments (only for the GET HTTP method). + * @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 Callbacks + **/ + typedef int32_t (*OrthancPluginIncomingHttpRequestFilter2) ( + OrthancPluginHttpMethod method, + const char* uri, + const char* ip, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + uint32_t getArgumentsCount, + const char* const* getArgumentsKeys, + const char* const* getArgumentsValues); + + + + /** + * @brief Callback to handle incoming C-Find SCP requests. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Find SCP request not concerning modality + * worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) ( + OrthancPluginFindAnswers* answers, + const OrthancPluginFindQuery* query, + const char* issuerAet, + const char* calledAet); + + + + /** + * @brief Callback to handle incoming C-Move SCP requests. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Move SCP request. The callback receives the + * type of the resource of interest (study, series, instance...) + * together with the DICOM tags containing its identifiers. In turn, + * the plugin must create a driver object that will be responsible + * for driving the successive move suboperations. + * + * @param resourceType The type of the resource of interest. Note + * that this might be set to ResourceType_None if the + * QueryRetrieveLevel (0008,0052) tag was not provided by the + * issuer (i.e. the originator modality). + * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL. + * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL. + * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL. + * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL. + * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL. + * @param originatorAet The Application Entity Title (AET) of the + * modality from which the request originates. + * @param sourceAet The Application Entity Title (AET) of the + * modality that should send its DICOM files to another modality. + * @param targetAet The Application Entity Title (AET) of the + * modality that should receive the DICOM files. + * @param originatorId The Message ID issued by the originator modality, + * as found in tag (0000,0110) of the DICOM query emitted by the issuer. + * + * @return The NULL value if the plugin cannot deal with this query, + * or a pointer to the driver object that is responsible for + * handling the successive move suboperations. + * + * @note If targetAet equals sourceAet, this is actually a query/retrieve operation. + * @ingroup DicomCallbacks + **/ + typedef void* (*OrthancPluginMoveCallback) ( + OrthancPluginResourceType resourceType, + const char* patientId, + const char* accessionNumber, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid, + const char* originatorAet, + const char* sourceAet, + const char* targetAet, + uint16_t originatorId); + + + /** + * @brief Callback to read the size of a C-Move driver. + * + * Signature of a callback function that returns the number of + * C-Move suboperations that are to be achieved by the given C-Move + * driver. This driver is the return value of a previous call to the + * OrthancPluginMoveCallback() callback. + * + * @param moveDriver The C-Move driver of interest. + * @return The number of suboperations. + * @ingroup DicomCallbacks + **/ + typedef uint32_t (*OrthancPluginGetMoveSize) (void* moveDriver); + + + /** + * @brief Callback to apply one C-Move suboperation. + * + * Signature of a callback function that applies the next C-Move + * suboperation that os to be achieved by the given C-Move + * driver. This driver is the return value of a previous call to the + * OrthancPluginMoveCallback() callback. + * + * @param moveDriver The C-Move driver of interest. + * @return 0 if success, or the error code if failure. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginApplyMove) (void* moveDriver); + + + /** + * @brief Callback to free one C-Move driver. + * + * Signature of a callback function that releases the resources + * allocated by the given C-Move driver. This driver is the return + * value of a previous call to the OrthancPluginMoveCallback() + * callback. + * + * @param moveDriver The C-Move driver of interest. + * @ingroup DicomCallbacks + **/ + typedef void (*OrthancPluginFreeMove) (void* moveDriver); + + + /** + * @brief Callback to finalize one custom job. + * + * Signature of a callback function that releases all the resources + * allocated by the given job. This job is the argument provided to + * OrthancPluginCreateJob(). + * + * @param job The job of interest. + * @ingroup Toolbox + **/ + typedef void (*OrthancPluginJobFinalize) (void* job); + + + /** + * @brief Callback to check the progress of one custom job. + * + * Signature of a callback function that returns the progress of the + * job. + * + * @param job The job of interest. + * @return The progress, as a floating-point number ranging from 0 to 1. + * @ingroup Toolbox + **/ + typedef float (*OrthancPluginJobGetProgress) (void* job); + + + /** + * @brief Callback to retrieve the content of one custom job. + * + * Signature of a callback function that returns human-readable + * statistics about the job. This statistics must be formatted as a + * JSON object. This information is notably displayed in the "Jobs" + * tab of "Orthanc Explorer". + * + * @param job The job of interest. + * @return The statistics, as a JSON object encoded as a string. + * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. + **/ + typedef const char* (*OrthancPluginJobGetContent) (void* job); + + + /** + * @brief Callback to retrieve the content of one custom job. + * + * Signature of a callback function that returns human-readable + * statistics about the job. This statistics must be formatted as a + * JSON object. This information is notably displayed in the "Jobs" + * tab of "Orthanc Explorer". + * + * @param target The target memory buffer where to store the JSON string. + * This buffer must be allocated using OrthancPluginCreateMemoryBuffer() + * and will be freed by the Orthanc core. + * @param job The job of interest. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobGetContent2) (OrthancPluginMemoryBuffer* target, + void* job); + + + /** + * @brief Callback to serialize one custom job. + * + * Signature of a callback function that returns a serialized + * version of the job, formatted as a JSON object. This + * serialization is stored in the Orthanc database, and is used to + * reload the job on the restart of Orthanc. The "unserialization" + * callback (with OrthancPluginJobsUnserializer signature) will + * receive this serialized object. + * + * @param job The job of interest. + * @return The serialized job, as a JSON object encoded as a string. + * @see OrthancPluginRegisterJobsUnserializer() + * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. + **/ + typedef const char* (*OrthancPluginJobGetSerialized) (void* job); + + + /** + * @brief Callback to serialize one custom job. + * + * Signature of a callback function that returns a serialized + * version of the job, formatted as a JSON object. This + * serialization is stored in the Orthanc database, and is used to + * reload the job on the restart of Orthanc. The "unserialization" + * callback (with OrthancPluginJobsUnserializer signature) will + * receive this serialized object. + * + * @param target The target memory buffer where to store the JSON string. + * This buffer must be allocated using OrthancPluginCreateMemoryBuffer() + * and will be freed by the Orthanc core. + * @param job The job of interest. + * @return 1 if the serialization has succeeded, 0 if serialization is + * not implemented for this type of job, or -1 in case of error. + **/ + typedef int32_t (*OrthancPluginJobGetSerialized2) (OrthancPluginMemoryBuffer* target, + void* job); + + + /** + * @brief Callback to execute one step of a custom job. + * + * Signature of a callback function that executes one step in the + * job. The jobs engine of Orthanc will make successive calls to + * this method, as long as it returns + * OrthancPluginJobStepStatus_Continue. + * + * @param job The job of interest. + * @return The status of execution. + * @ingroup Toolbox + **/ + typedef OrthancPluginJobStepStatus (*OrthancPluginJobStep) (void* job); + + + /** + * @brief Callback executed once one custom job leaves the "running" state. + * + * Signature of a callback function that is invoked once a job + * leaves the "running" state. This can happen if the previous call + * to OrthancPluginJobStep has failed/succeeded, if the host Orthanc + * server is being stopped, or if the user manually tags the job as + * paused/canceled. This callback allows the plugin to free + * resources allocated for running this custom job (e.g. to stop + * threads, or to remove temporary files). + * + * Note that handling pauses might involves a specific treatment + * (such a stopping threads, but keeping temporary files on the + * disk). This "paused" situation can be checked by looking at the + * "reason" parameter. + * + * @param job The job of interest. + * @param reason The reason for leaving the "running" state. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobStop) (void* job, + OrthancPluginJobStopReason reason); + + + /** + * @brief Callback executed once one stopped custom job is started again. + * + * Signature of a callback function that is invoked once a job + * leaves the "failure/canceled" state, to be started again. This + * function will typically reset the progress to zero. Note that + * before being actually executed, the job would first be tagged as + * "pending" in the Orthanc jobs engine. + * + * @param job The job of interest. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobReset) (void* job); + + + /** + * @brief Callback executed to unserialize a custom job. + * + * Signature of a callback function that unserializes a job that was + * saved in the Orthanc database. + * + * @param jobType The type of the job, as provided to OrthancPluginCreateJob(). + * @param serialized The serialization of the job, as provided by OrthancPluginJobGetSerialized. + * @return The unserialized job (as created by OrthancPluginCreateJob()), or NULL + * if this unserializer cannot handle this job type. + * @see OrthancPluginRegisterJobsUnserializer() + * @ingroup Callbacks + **/ + typedef OrthancPluginJob* (*OrthancPluginJobsUnserializer) (const char* jobType, + const char* serialized); + + + + /** + * @brief Callback executed to update the metrics of the plugin. + * + * Signature of a callback function that is called by Orthanc + * whenever a monitoring tool (such as Prometheus) asks the current + * values of the metrics. This callback gives the plugin a chance to + * update its metrics, by calling OrthancPluginSetMetricsValue() or + * OrthancPluginSetMetricsIntegerValue(). + * This is typically useful for metrics that are expensive to + * acquire. + * + * @see OrthancPluginRegisterRefreshMetrics() + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginRefreshMetricsCallback) (); + + + + /** + * @brief Callback executed to encode a binary tag in DICOMweb. + * + * Signature of a callback function that is called by Orthanc + * whenever a DICOM tag that contains a binary value must be written + * to a JSON or XML node, while a DICOMweb document is being + * generated. The value representation (VR) of the DICOM tag can be + * OB, OD, OF, OL, OW, or UN. + * + * @see OrthancPluginEncodeDicomWebJson() and OrthancPluginEncodeDicomWebXml() + * @param node The node being generated, as provided by Orthanc. + * @param setter The setter to be used to encode the content of the node. If + * the setter is not called, the binary tag is not written to the output document. + * @param levelDepth The depth of the node in the DICOM hierarchy of sequences. + * This parameter gives the number of elements in the "levelTagGroup", + * "levelTagElement", and "levelIndex" arrays. + * @param levelTagGroup The group of the parent DICOM tags in the hierarchy. + * @param levelTagElement The element of the parent DICOM tags in the hierarchy. + * @param levelIndex The index of the node in the parent sequences of the hierarchy. + * @param tagGroup The group of the DICOM tag of interest. + * @param tagElement The element of the DICOM tag of interest. + * @param vr The value representation of the binary DICOM node. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginDicomWebBinaryCallback) ( + OrthancPluginDicomWebNode* node, + OrthancPluginDicomWebSetBinaryNode setter, + uint32_t levelDepth, + const uint16_t* levelTagGroup, + const uint16_t* levelTagElement, + const uint32_t* levelIndex, + uint16_t tagGroup, + uint16_t tagElement, + OrthancPluginValueRepresentation vr); + + + + /** + * @brief Callback executed to encode a binary tag in DICOMweb. + * + * Signature of a callback function that is called by Orthanc + * whenever a DICOM tag that contains a binary value must be written + * to a JSON or XML node, while a DICOMweb document is being + * generated. The value representation (VR) of the DICOM tag can be + * OB, OD, OF, OL, OW, or UN. + * + * @see OrthancPluginEncodeDicomWebJson() and OrthancPluginEncodeDicomWebXml() + * @param node The node being generated, as provided by Orthanc. + * @param setter The setter to be used to encode the content of the node. If + * the setter is not called, the binary tag is not written to the output document. + * @param levelDepth The depth of the node in the DICOM hierarchy of sequences. + * This parameter gives the number of elements in the "levelTagGroup", + * "levelTagElement", and "levelIndex" arrays. + * @param levelTagGroup The group of the parent DICOM tags in the hierarchy. + * @param levelTagElement The element of the parent DICOM tags in the hierarchy. + * @param levelIndex The index of the node in the parent sequences of the hierarchy. + * @param tagGroup The group of the DICOM tag of interest. + * @param tagElement The element of the DICOM tag of interest. + * @param vr The value representation of the binary DICOM node. + * @param payload The user payload. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginDicomWebBinaryCallback2) ( + OrthancPluginDicomWebNode* node, + OrthancPluginDicomWebSetBinaryNode setter, + uint32_t levelDepth, + const uint16_t* levelTagGroup, + const uint16_t* levelTagElement, + const uint32_t* levelIndex, + uint16_t tagGroup, + uint16_t tagElement, + OrthancPluginValueRepresentation vr, + void* payload); + + + + /** + * @brief Data structure that contains information about the Orthanc core. + **/ + typedef struct _OrthancPluginContext_t + { + void* pluginsManager; + const char* orthancVersion; + OrthancPluginFree Free; + OrthancPluginErrorCode (*InvokeService) (struct _OrthancPluginContext_t* context, + _OrthancPluginService service, + const void* params); + } OrthancPluginContext; + + + + /** + * @brief An entry in the dictionary of DICOM tags. + **/ + typedef struct + { + uint16_t group; /*!< The group of the tag */ + uint16_t element; /*!< The element of the tag */ + OrthancPluginValueRepresentation vr; /*!< The value representation of the tag */ + uint32_t minMultiplicity; /*!< The minimum multiplicity of the tag */ + uint32_t maxMultiplicity; /*!< The maximum multiplicity of the tag (0 means arbitrary) */ + } OrthancPluginDictionaryEntry; + + + + /** + * @brief Free a string. + * + * Free a string that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param str The string to be freed. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeString( + OrthancPluginContext* context, + char* str) + { + if (str != NULL) + { + context->Free(str); + } + } + + + /** + * @brief Check that the version of the hosting Orthanc is above a given version. + * + * This function checks whether the version of the Orthanc server + * running this plugin, is above the given version. Contrarily to + * OrthancPluginCheckVersion(), it is up to the developer of the + * plugin to make sure that all the Orthanc SDK services called by + * the plugin are actually implemented in the given version of + * Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param expectedMajor Expected major version. + * @param expectedMinor Expected minor version. + * @param expectedRevision Expected revision. + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @see OrthancPluginCheckVersion() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginCheckVersionAdvanced( + OrthancPluginContext* context, + int32_t expectedMajor, + int32_t expectedMinor, + int32_t expectedRevision) + { + int32_t major, minor, revision; + + if (sizeof(int) != sizeof(int32_t) || /* Ensure binary compatibility with Orthanc SDK <= 1.12.1 */ + sizeof(int32_t) != sizeof(OrthancPluginErrorCode) || + sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) || + sizeof(int32_t) != sizeof(_OrthancPluginService) || + sizeof(int32_t) != sizeof(_OrthancPluginProperty) || + sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) || + sizeof(int32_t) != sizeof(OrthancPluginContentType) || + sizeof(int32_t) != sizeof(OrthancPluginResourceType) || + sizeof(int32_t) != sizeof(OrthancPluginChangeType) || + sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || + sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || + sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) || + sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) || + sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) || + sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) || + sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) || + sizeof(int32_t) != sizeof(OrthancPluginConstraintType) || + sizeof(int32_t) != sizeof(OrthancPluginMetricsType) || + sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) || + sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) || + sizeof(int32_t) != sizeof(OrthancPluginLoadDicomInstanceMode) || + sizeof(int32_t) != sizeof(OrthancPluginLogLevel) || + sizeof(int32_t) != sizeof(OrthancPluginLogCategory)) + { + /* Mismatch in the size of the enumerations */ + return 0; + } + + /* Assume compatibility with the mainline */ + if (!strcmp(context->orthancVersion, "mainline")) + { + return 1; + } + + /* Parse the version of the Orthanc core */ + if ( +#ifdef _MSC_VER + sscanf_s +#else + sscanf +#endif + (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3) + { + return 0; + } + + /* Check the major number of the version */ + + if (major > expectedMajor) + { + return 1; + } + + if (major < expectedMajor) + { + return 0; + } + + /* Check the minor number of the version */ + + if (minor > expectedMinor) + { + return 1; + } + + if (minor < expectedMinor) + { + return 0; + } + + /* Check the revision number of the version */ + + if (revision >= expectedRevision) + { + return 1; + } + else + { + return 0; + } + } + + + /** + * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. + * + * This function checks whether the version of the Orthanc server + * running this plugin, is above the version of the current Orthanc + * SDK header. This guarantees that the plugin is compatible with + * the hosting Orthanc (i.e. it will not call unavailable services). + * The result of this function should always be checked in the + * OrthancPluginInitialize() entry point of the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @see OrthancPluginCheckVersionAdvanced() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginCheckVersion( + OrthancPluginContext* context) + { + return OrthancPluginCheckVersionAdvanced( + context, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + } + + + /** + * @brief Free a memory buffer. + * + * Free a memory buffer that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer to release. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* buffer) + { + context->Free(buffer->data); + } + + + /** + * @brief Free a memory buffer. + * + * Free a memory buffer that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer to release. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer64( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer64* buffer) + { + context->Free(buffer->data); + } + + + /** + * @brief Log an error. + * + * Log an error message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogError( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogError, message); + } + + + /** + * @brief Log a warning. + * + * Log a warning message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogWarning, message); + } + + + /** + * @brief Log an information. + * + * Log an information message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogInfo, message); + } + + + + typedef struct + { + const char* pathRegularExpression; + OrthancPluginRestCallback callback; + } _OrthancPluginRestCallback; + + /** + * @brief Register a REST callback. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Each REST callback is guaranteed to run in mutual exclusion. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallbackNoLock() + * + * @note + * The regular expression is case sensitive and must follow the + * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html). + * + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, ¶ms); + } + + + + /** + * @brief Register a REST callback, without locking. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Contrarily to OrthancPluginRegisterRestCallback(), the callback + * will NOT be invoked in mutual exclusion. This can be useful for + * high-performance plugins that must handle concurrent requests + * (Orthanc uses a pool of threads, one thread being assigned to + * each incoming HTTP request). Of course, if using this function, + * it is up to the plugin to implement the required locking + * mechanisms. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallback() + * + * @note + * The regular expression is case sensitive and must follow the + * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html). + * + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, ¶ms); + } + + + + typedef struct + { + OrthancPluginOnStoredInstanceCallback callback; + } _OrthancPluginOnStoredInstanceCallback; + + /** + * @brief Register a callback for received instances. + * + * This function registers a callback function that is called + * whenever a new DICOM instance is stored into the Orthanc core. + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback( + OrthancPluginContext* context, + OrthancPluginOnStoredInstanceCallback callback) + { + _OrthancPluginOnStoredInstanceCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const void* answer; + uint32_t answerSize; + const char* mimeType; + } _OrthancPluginAnswerBuffer; + + /** + * @brief Answer to a REST request. + * + * This function answers to a REST request with the content of a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the answer. + * @param answerSize Number of bytes of the answer. + * @param mimeType The MIME type of the answer. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const void* answer, + uint32_t answerSize, + const char* mimeType) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = mimeType; + context->InvokeService(context, _OrthancPluginService_AnswerBuffer, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + } _OrthancPluginCompressAndAnswerPngImage; + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressAndAnswerImage; + + + /** + * @brief Answer to a REST request with a PNG image. + * + * This function answers to a REST request with a PNG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a PNG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* No quality for PNG */ + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* instanceId; + } _OrthancPluginGetDicomForInstance; + + /** + * @brief Retrieve a DICOM instance using its Orthanc identifier. + * + * Retrieve a DICOM instance using its Orthanc identifier. The DICOM + * file is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instanceId The Orthanc identifier of the DICOM instance of interest. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetDicomForInstance( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* instanceId) + { + _OrthancPluginGetDicomForInstance params; + params.target = target; + params.instanceId = instanceId; + return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + } _OrthancPluginRestApiGet; + + /** + * @brief Make a GET call to the built-in Orthanc REST API. + * + * Make a GET call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiGetAfterPlugins() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGet, ¶ms); + } + + + + /** + * @brief Make a GET call to the REST API, as tainted by the plugins. + * + * Make a GET call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiGet() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGetAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + const void* body; + uint32_t bodySize; + } _OrthancPluginRestApiPostPut; + + /** + * @brief Make a POST call to the built-in Orthanc REST API. + * + * Make a POST call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPostAfterPlugins() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPost, ¶ms); + } + + + /** + * @brief Make a POST call to the REST API, as tainted by the plugins. + * + * Make a POST call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPost() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPostAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, ¶ms); + } + + + + /** + * @brief Make a DELETE call to the built-in Orthanc REST API. + * + * Make a DELETE call to the built-in Orthanc REST API. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiDeleteAfterPlugins() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDelete( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri); + } + + + /** + * @brief Make a DELETE call to the REST API, as tainted by the plugins. + * + * Make a DELETE call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiDelete() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDeleteAfterPlugins( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri); + } + + + + /** + * @brief Make a PUT call to the built-in Orthanc REST API. + * + * Make a PUT call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPutAfterPlugins() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPut, ¶ms); + } + + + + /** + * @brief Make a PUT call to the REST API, as tainted by the plugins. + * + * Make a PUT call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. + * @see OrthancPluginRestApiPut() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPutAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const void* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* argument; + } _OrthancPluginOutputPlusArgument; + + /** + * @brief Redirect a REST request. + * + * This function answers to a REST request by redirecting the user + * to another URI using HTTP status 301. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param redirection Where to redirect. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* redirection) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = redirection; + context->InvokeService(context, _OrthancPluginService_Redirect, ¶ms); + } + + + + typedef struct + { + char** result; + const char* argument; + } _OrthancPluginRetrieveDynamicString; + + /** + * @brief Look for a patient. + * + * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored patients). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param patientID The Patient ID of interest. + * @return The NULL value if the patient is non-existent, or a string containing the + * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient( + OrthancPluginContext* context, + const char* patientID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = patientID; + + if (context->InvokeService(context, _OrthancPluginService_LookupPatient, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study. + * + * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param studyUID The Study Instance UID of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy( + OrthancPluginContext* context, + const char* studyUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = studyUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudy, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study, using the accession number. + * + * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param accessionNumber The Accession Number of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber( + OrthancPluginContext* context, + const char* accessionNumber) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = accessionNumber; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a series. + * + * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored series). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param seriesUID The Series Instance UID of interest. + * @return The NULL value if the series is non-existent, or a string containing the + * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries( + OrthancPluginContext* context, + const char* seriesUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = seriesUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupSeries, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for an instance. + * + * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored instances). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param sopInstanceUID The SOP Instance UID of interest. + * @return The NULL value if the instance is non-existent, or a string containing the + * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance( + OrthancPluginContext* context, + const char* sopInstanceUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = sopInstanceUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + } _OrthancPluginSendHttpStatusCode; + + /** + * @brief Send a HTTP status code. + * + * This function answers to a REST request by sending a HTTP status + * code (such as "400 - Bad Request"). Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @ingroup REST + * @see OrthancPluginSendHttpStatus() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status) + { + _OrthancPluginSendHttpStatusCode params; + params.output = output; + params.status = status; + context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, ¶ms); + } + + + /** + * @brief Signal that a REST request is not authorized. + * + * This function answers to a REST request by signaling that it is + * not authorized. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param realm The realm for the authorization process. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* realm) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = realm; + context->InvokeService(context, _OrthancPluginService_SendUnauthorized, ¶ms); + } + + + /** + * @brief Signal that this URI does not support this HTTP method. + * + * This function answers to a REST request by signaling that the + * queried URI does not support this method. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* allowedMethods) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = allowedMethods; + context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* key; + const char* value; + } _OrthancPluginSetHttpHeader; + + /** + * @brief Set a cookie. + * + * This function sets a cookie in the HTTP client. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param cookie The cookie to be set. + * @param value The value of the cookie. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* cookie, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = cookie; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetCookie, ¶ms); + } + + + /** + * @brief Set some HTTP header. + * + * This function sets a HTTP header in the HTTP answer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param key The HTTP header to be set. + * @param value The value of the HTTP header. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* key, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = key; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetHttpHeader, ¶ms); + } + + + typedef struct + { + char** resultStringToFree; + const char** resultString; + int64_t* resultInt64; + const char* key; + const OrthancPluginDicomInstance* instance; + OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */ + } _OrthancPluginAccessDicomInstance; + + + /** + * @brief Get the AET of a DICOM instance. + * + * This function returns the Application Entity Title (AET) of the + * DICOM modality from which a DICOM instance originates. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The AET if success, NULL if error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the size of a DICOM file. + * + * This function returns the number of bytes of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The size of the file, -1 in case of error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + int64_t size; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &size; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return size; + } + } + + + /** + * @brief Get the data of a DICOM file. + * + * This function returns a pointer to the content of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The pointer to the DICOM data, NULL in case of error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE const void* OrthancPluginGetInstanceData( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file. + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file (with simplification). + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. In contrast with + * ::OrthancPluginGetInstanceJson(), the returned JSON file is in + * its simplified version. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Check whether a DICOM instance is associated with some metadata. + * + * This function checks whether the DICOM instance of interest is + * associated with some metadata. As of Orthanc 0.8.1, in the + * callbacks registered by + * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only + * possibly available metadata are "ReceptionDate", "RemoteAET" and + * "IndexInSeries". + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginHasInstanceMetadata( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + const char* metadata) + { + int64_t result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return (result != 0); + } + } + + + /** + * @brief Get the value of some metadata associated with a given DICOM instance. + * + * This functions returns the value of some metadata that is associated with the DICOM instance of interest. + * Before calling this function, the existence of the metadata must have been checked with + * ::OrthancPluginHasInstanceMetadata(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return The metadata value if success, NULL if error. Please note that the + * returned string belongs to the instance object and must NOT be + * deallocated. Please make a copy of the string if you wish to access + * it later. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + const char* metadata) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginStorageCreate create; + OrthancPluginStorageRead read; + OrthancPluginStorageRemove remove; + OrthancPluginFree free; + } _OrthancPluginRegisterStorageArea; + + /** + * @brief Register a custom storage area. + * + * This function registers a custom storage area, to replace the + * built-in way Orthanc stores its files on the filesystem. 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 create The callback function to store a file on the custom storage area. + * @param read The callback function to read a file from the custom storage area. + * @param remove The callback function to remove a file from the custom storage area. + * @ingroup Callbacks + * @deprecated Please use OrthancPluginRegisterStorageArea2() + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageRead read, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea params; + params.create = create; + params.read = read; + params.remove = remove; + +#ifdef __cplusplus + params.free = ::free; +#else + params.free = free; +#endif + + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, ¶ms); + } + + + + /** + * @brief Return the path to the Orthanc executable. + * + * This function returns the path to the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the directory containing the Orthanc. + * + * This function returns the path to the directory containing the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the path to the configuration file(s). + * + * This function returns the path to the configuration file(s) that + * was specified when starting Orthanc. Since version 0.9.1, this + * path can refer to a folder that stores a set of configuration + * files. This function is deprecated in favor of + * OrthancPluginGetConfiguration(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + * @see OrthancPluginGetConfiguration() + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginOnChangeCallback callback; + } _OrthancPluginOnChangeCallback; + + /** + * @brief Register a callback to monitor changes. + * + * This function registers a callback function that is called + * whenever a change happens to some DICOM resource. + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( + OrthancPluginContext* context, + OrthancPluginOnChangeCallback callback) + { + _OrthancPluginOnChangeCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); + } + + + + typedef struct + { + const char* plugin; + _OrthancPluginProperty property; + const char* value; + } _OrthancPluginSetPluginProperty; + + + /** + * @brief Set the URI where the plugin provides its Web interface. + * + * For plugins that come with a Web interface, this function + * declares the entry path where to find this interface. This + * information is notably used in the "Plugins" page of Orthanc + * Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The root URI for this plugin. + * + * @deprecated This function should not be used anymore because the + * result of the call to "OrthancPluginGetName()" depends on the + * system. Use "OrthancPluginSetRootUri2()" instead. + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri( + OrthancPluginContext* context, + const char* uri) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_RootUri; + params.value = uri; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Set the URI where the plugin provides its Web interface. + * + * For plugins that come with a Web interface, this function + * declares the entry path where to find this interface. This + * information is notably used in the "Plugins" page of Orthanc + * Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param plugin Identifier of your plugin (it must match "OrthancPluginGetName()"). + * @param uri The root URI for this plugin. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri2( + OrthancPluginContext* context, + const char* plugin, + const char* uri) + { + _OrthancPluginSetPluginProperty params; + params.plugin = plugin; + params.property = _OrthancPluginProperty_RootUri; + params.value = uri; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Set a description for this plugin. + * + * Set a description for this plugin. It is displayed in the + * "Plugins" page of Orthanc Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param description The description. + * + * @deprecated This function should not be used anymore because the + * result of the call to "OrthancPluginGetName()" depends on the + * system. Use "OrthancPluginSetDescription2()" instead. + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription( + OrthancPluginContext* context, + const char* description) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_Description; + params.value = description; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Set a description for this plugin. + * + * Set a description for this plugin. It is displayed in the + * "Plugins" page of Orthanc Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param plugin Identifier of your plugin (it must match "OrthancPluginGetName()"). + * @param description The description. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription2( + OrthancPluginContext* context, + const char* plugin, + const char* description) + { + _OrthancPluginSetPluginProperty params; + params.plugin = plugin; + params.property = _OrthancPluginProperty_Description; + params.value = description; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Extend the JavaScript code of Orthanc Explorer. + * + * Add JavaScript code to customize the default behavior of Orthanc + * Explorer. This can for instance be used to add new buttons. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param javascript The custom JavaScript code. + * + * @deprecated This function should not be used anymore because the + * result of the call to "OrthancPluginGetName()" depends on the + * system. Use "OrthancPluginExtendOrthancExplorer2()" instead. + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer( + OrthancPluginContext* context, + const char* javascript) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_OrthancExplorer; + params.value = javascript; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Extend the JavaScript code of Orthanc Explorer. + * + * Add JavaScript code to customize the default behavior of Orthanc + * Explorer. This can for instance be used to add new buttons. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param plugin Identifier of your plugin (it must match "OrthancPluginGetName()"). + * @param javascript The custom JavaScript code. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer2( + OrthancPluginContext* context, + const char* plugin, + const char* javascript) + { + _OrthancPluginSetPluginProperty params; + params.plugin = plugin; + params.property = _OrthancPluginProperty_OrthancExplorer; + params.value = javascript; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + typedef struct + { + char** result; + int32_t property; + const char* value; + } _OrthancPluginGlobalProperty; + + + /** + * @brief Get the value of a global property. + * + * Get the value of a global property that is stored in the Orthanc database. Global + * properties whose index is below 1024 are reserved by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param defaultValue The value to return, if the global property is unset. + * @return The value of the global property, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* defaultValue) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = property; + params.value = defaultValue; + + if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Set the value of a global property. + * + * Set the value of a global property into the Orthanc + * database. Setting a global property can be used by plugins to + * save their internal parameters. Plugins are only allowed to set + * properties whose index are above or equal to 1024 (properties + * below 1024 are read-only and reserved by Orthanc). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param value The value to be set in the global property. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* value) + { + _OrthancPluginGlobalProperty params; + params.result = NULL; + params.property = property; + params.value = value; + + return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, ¶ms); + } + + + + typedef struct + { + int32_t *resultInt32; + uint32_t *resultUint32; + int64_t *resultInt64; + uint64_t *resultUint64; + } _OrthancPluginReturnSingleValue; + + /** + * @brief Get the number of command-line arguments. + * + * Retrieve the number of command-line arguments that were used to launch Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of arguments. + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Get the value of a command-line argument. + * + * Get the value of one of the command-line arguments that were used + * to launch Orthanc. The number of available arguments can be + * retrieved by OrthancPluginGetCommandLineArgumentsCount(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param argument The index of the argument. + * @return The value of the argument, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument( + OrthancPluginContext* context, + uint32_t argument) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = (int32_t) argument; + params.value = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the expected version of the database schema. + * + * Retrieve the expected version of the database schema. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The version. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Return the content of the configuration file(s). + * + * This function returns the content of the configuration that is + * used by Orthanc, formatted as a JSON string. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the configuration. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* subType; + const char* contentType; + } _OrthancPluginStartMultipartAnswer; + + /** + * @brief Start an HTTP multipart answer. + * + * Initiates a HTTP multipart answer, as the result of a REST request. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param subType The sub-type of the multipart answer ("mixed" or "related"). + * @param contentType The MIME type of the items in the multipart answer. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* subType, + const char* contentType) + { + _OrthancPluginStartMultipartAnswer params; + params.output = output; + params.subType = subType; + params.contentType = contentType; + return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, ¶ms); + } + + + /** + * @brief Send an item as a part of some HTTP multipart answer. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const void* answer, + uint32_t answerSize) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = NULL; + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const void* source; + uint32_t size; + OrthancPluginCompressionType compression; + uint8_t uncompress; + } _OrthancPluginBufferCompression; + + + /** + * @brief Compress or decompress a buffer. + * + * This function compresses or decompresses a buffer, using the + * version of the zlib library that is used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param source The source buffer. + * @param size The size in bytes of the source buffer. + * @param compression The compression algorithm. + * @param uncompress If set to "0", the buffer must be compressed. + * If set to "1", the buffer must be uncompressed. + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const void* source, + uint32_t size, + OrthancPluginCompressionType compression, + uint8_t uncompress) + { + _OrthancPluginBufferCompression params; + params.target = target; + params.source = source; + params.size = size; + params.compression = compression; + params.uncompress = uncompress; + + return context->InvokeService(context, _OrthancPluginService_BufferCompression, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* path; + } _OrthancPluginReadFile; + + /** + * @brief Read a file. + * + * Read the content of a file on the filesystem, and returns it into + * a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param path The path of the file to be read. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReadFile( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* path) + { + _OrthancPluginReadFile params; + params.target = target; + params.path = path; + return context->InvokeService(context, _OrthancPluginService_ReadFile, ¶ms); + } + + + + typedef struct + { + const char* path; + const void* data; + uint32_t size; + } _OrthancPluginWriteFile; + + /** + * @brief Write a file. + * + * Write the content of a memory buffer to the filesystem. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param path The path of the file to be written. + * @param data The content of the memory buffer. + * @param size The size of the memory buffer. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWriteFile( + OrthancPluginContext* context, + const char* path, + const void* data, + uint32_t size) + { + _OrthancPluginWriteFile params; + params.path = path; + params.data = data; + params.size = size; + return context->InvokeService(context, _OrthancPluginService_WriteFile, ¶ms); + } + + + + typedef struct + { + const char** target; + OrthancPluginErrorCode error; + } _OrthancPluginGetErrorDescription; + + /** + * @brief Get the description of a given error code. + * + * This function returns the description of a given error code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param error The error code of interest. + * @return The error description. This is a statically-allocated + * string, do not free it. + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription( + OrthancPluginContext* context, + OrthancPluginErrorCode error) + { + const char* result = NULL; + + _OrthancPluginGetErrorDescription params; + params.target = &result; + params.error = error; + + if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, ¶ms) != OrthancPluginErrorCode_Success || + result == NULL) + { + return "Unknown error code"; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + const void* body; + uint32_t bodySize; + } _OrthancPluginSendHttpStatus; + + /** + * @brief Send a HTTP status, with a custom body. + * + * This function answers to a HTTP request by sending a HTTP status + * code (such as "400 - Bad Request"), together with a body + * describing the error. The body will only be returned if the + * configuration option "HttpDescribeErrors" of Orthanc is set to "true". + * + * Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @param body The body of the answer. + * @param bodySize The size of the body. + * @see OrthancPluginSendHttpStatusCode() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status, + const void* body, + uint32_t bodySize) + { + _OrthancPluginSendHttpStatus params; + params.output = output; + params.status = status; + params.body = body; + params.bodySize = bodySize; + context->InvokeService(context, _OrthancPluginService_SendHttpStatus, ¶ms); + } + + + + typedef struct + { + const OrthancPluginImage* image; + uint32_t* resultUint32; + OrthancPluginPixelFormat* resultPixelFormat; + void** resultBuffer; + } _OrthancPluginGetImageInfo; + + + /** + * @brief Return the pixel format of an image. + * + * This function returns the type of memory layout for the pixels of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pixel format. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat OrthancPluginGetImagePixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + OrthancPluginPixelFormat target; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultPixelFormat = ⌖ + + if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return OrthancPluginPixelFormat_Unknown; + } + else + { + return (OrthancPluginPixelFormat) target; + } + } + + + + /** + * @brief Return the width of an image. + * + * This function returns the width of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The width. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageWidth( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t width; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &width; + + if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return width; + } + } + + + + /** + * @brief Return the height of an image. + * + * This function returns the height of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The height. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageHeight( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t height; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &height; + + if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return height; + } + } + + + + /** + * @brief Return the pitch of an image. + * + * This function returns the pitch of the given image. The pitch is + * defined as the number of bytes between 2 successive lines of the + * image in the memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pitch. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImagePitch( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t pitch; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &pitch; + + if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return pitch; + } + } + + + + /** + * @brief Return a pointer to the content of an image. + * + * This function returns a pointer to the memory buffer that + * contains the pixels of the image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pointer. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void* OrthancPluginGetImageBuffer( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + void* target = NULL; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.resultBuffer = ⌖ + params.image = image; + + if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginImage** target; + const void* data; + uint32_t size; + OrthancPluginImageFormat format; + } _OrthancPluginUncompressImage; + + + /** + * @brief Decode a compressed image. + * + * This function decodes a compressed image from a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param data Pointer to a memory buffer containing the compressed image. + * @param size Size of the memory buffer containing the compressed image. + * @param format The file format of the compressed image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage( + OrthancPluginContext* context, + const void* data, + uint32_t size, + OrthancPluginImageFormat format) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginUncompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.data = data; + params.size = size; + params.format = format; + + if (context->InvokeService(context, _OrthancPluginService_UncompressImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + + typedef struct + { + OrthancPluginImage* image; + } _OrthancPluginFreeImage; + + /** + * @brief Free an image. + * + * This function frees an image that was decoded with OrthancPluginUncompressImage(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeImage( + OrthancPluginContext* context, + OrthancPluginImage* image) + { + _OrthancPluginFreeImage params; + params.image = image; + + context->InvokeService(context, _OrthancPluginService_FreeImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressImage; + + + /** + * @brief Encode a PNG image. + * + * This function compresses the given memory buffer containing an + * image using the PNG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginCompressAndAnswerPngImage() + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* Unused for PNG */ + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + /** + * @brief Encode a JPEG image. + * + * This function compresses the given memory buffer containing an + * image using the JPEG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + + /** + * @brief Answer to a REST request with a JPEG image. + * + * This function answers to a REST request with a JPEG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a JPEG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginHttpMethod method; + const char* url; + const char* username; + const char* password; + const void* body; + uint32_t bodySize; + } _OrthancPluginCallHttpClient; + + + /** + * @brief Issue a HTTP GET call. + * + * Make a HTTP GET call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiGet() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Get; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP POST call. + * + * Make a HTTP POST call to the given URL. The result to the query + * is stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPost() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const void* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Post; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP PUT call. + * + * Make a HTTP PUT call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPut() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const void* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Put; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP DELETE call. + * + * Make a HTTP DELETE call to the given URL. Favor + * OrthancPluginRestApiDelete() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpDelete( + OrthancPluginContext* context, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.method = OrthancPluginHttpMethod_Delete; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + const OrthancPluginImage* source; + OrthancPluginPixelFormat targetFormat; + } _OrthancPluginConvertPixelFormat; + + + /** + * @brief Change the pixel format of an image. + * + * This function creates a new image, changing the memory layout of the pixels. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param source The source image. + * @param targetFormat The target pixel format. + * @return The resulting image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* source, + OrthancPluginPixelFormat targetFormat) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginConvertPixelFormat params; + params.target = ⌖ + params.source = source; + params.targetFormat = targetFormat; + + if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Return the number of available fonts. + * + * This function returns the number of fonts that are built in the + * Orthanc core. These fonts can be used to draw texts on images + * through OrthancPluginDrawText(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of fonts. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + + typedef struct + { + uint32_t fontIndex; /* in */ + const char** name; /* out */ + uint32_t* size; /* out */ + } _OrthancPluginGetFontInfo; + + /** + * @brief Return the name of a font. + * + * This function returns the name of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font name. This is a statically-allocated string, do not free it. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName( + OrthancPluginContext* context, + uint32_t fontIndex) + { + const char* result = NULL; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.name = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the size of a font. + * + * This function returns the size of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font size. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize( + OrthancPluginContext* context, + uint32_t fontIndex) + { + uint32_t result; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.size = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginImage* image; + uint32_t fontIndex; + const char* utf8Text; + int32_t x; + int32_t y; + uint8_t r; + uint8_t g; + uint8_t b; + } _OrthancPluginDrawText; + + + /** + * @brief Draw text on an image. + * + * This function draws some text on some image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image upon which to draw the text. + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string. + * @param x The X position of the text over the image. + * @param y The Y position of the text over the image. + * @param r The value of the red color channel of the text. + * @param g The value of the green color channel of the text. + * @param b The value of the blue color channel of the text. + * @return 0 if success, other value if error. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDrawText( + OrthancPluginContext* context, + OrthancPluginImage* image, + uint32_t fontIndex, + const char* utf8Text, + int32_t x, + int32_t y, + uint8_t r, + uint8_t g, + uint8_t b) + { + _OrthancPluginDrawText params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.fontIndex = fontIndex; + params.utf8Text = utf8Text; + params.x = x; + params.y = y; + params.r = r; + params.g = g; + params.b = b; + + return context->InvokeService(context, _OrthancPluginService_DrawText, ¶ms); + } + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + const void* content; + uint64_t size; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaCreate; + + + /** + * @brief Create a file inside the storage area. + * + * This function creates a new file inside the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be created. + * @param content The content to store in the newly created file. + * @param size The size of the content. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiPut()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + const void* content, + uint64_t size, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaCreate params; + params.storageArea = storageArea; + params.uuid = uuid; + params.content = content; + params.size = size; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, ¶ms); + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRead; + + + /** + * @brief Read a file from the storage area. + * + * This function reads the content of a given file from the storage + * area that is currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be read. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiGet()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRead params; + params.target = target; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRemove; + + /** + * @brief Remove a file from the storage area. + * + * This function removes a given file from the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be removed. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated This function should not be used anymore. Use "OrthancPluginRestApiDelete()" on + * "/{patients|studies|series|instances}/{id}/attachments/{name}" instead. + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRemove params; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, ¶ms); + } + + + + typedef struct + { + OrthancPluginErrorCode* target; + int32_t code; + uint16_t httpStatus; + const char* message; + } _OrthancPluginRegisterErrorCode; + + /** + * @brief Declare a custom error code for this plugin. + * + * This function declares a custom error code that can be generated + * by this plugin. This declaration is used to enrich the body of + * the HTTP answer in the case of an error, and to set the proper + * HTTP status code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param code The error code that is internal to this plugin. + * @param httpStatus The HTTP status corresponding to this error. + * @param message The description of the error. + * @return The error code that has been assigned inside the Orthanc core. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterErrorCode( + OrthancPluginContext* context, + int32_t code, + uint16_t httpStatus, + const char* message) + { + OrthancPluginErrorCode target; + + _OrthancPluginRegisterErrorCode params; + params.target = ⌖ + params.code = code; + params.httpStatus = httpStatus; + params.message = message; + + if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, ¶ms) == OrthancPluginErrorCode_Success) + { + return target; + } + else + { + /* There was an error while assigned the error. Use a generic code. */ + return OrthancPluginErrorCode_Plugin; + } + } + + + + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + } _OrthancPluginRegisterDictionaryTag; + + /** + * @brief Register a new tag into the DICOM dictionary. + * + * This function declares a new public tag in the dictionary of + * DICOM tags that are known to Orthanc. This function should be + * used in the OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("n"). + * @return 0 if success, other value if error. + * @see OrthancPluginRegisterPrivateDictionaryTag() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity) + { + _OrthancPluginRegisterDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + + return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, ¶ms); + } + + + + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + const char* privateCreator; + } _OrthancPluginRegisterPrivateDictionaryTag; + + /** + * @brief Register a new private tag into the DICOM dictionary. + * + * This function declares a new private tag in the dictionary of + * DICOM tags that are known to Orthanc. This function should be + * used in the OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("n"). + * @param privateCreator The private creator of this private tag. + * @return 0 if success, other value if error. + * @see OrthancPluginRegisterDictionaryTag() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterPrivateDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity, + const char* privateCreator) + { + _OrthancPluginRegisterPrivateDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + params.privateCreator = privateCreator; + + return context->InvokeService(context, _OrthancPluginService_RegisterPrivateDictionaryTag, ¶ms); + } + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + OrthancPluginResourceType level; + } _OrthancPluginReconstructMainDicomTags; + + /** + * @brief Reconstruct the main DICOM tags. + * + * This function requests the Orthanc core to reconstruct the main + * DICOM tags of all the resources of the given type. This function + * can only be used as a part of the upgrade of a custom database + * back-end. A database transaction will be automatically setup. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param level The type of the resources of interest. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReconstructMainDicomTags( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + OrthancPluginResourceType level) + { + _OrthancPluginReconstructMainDicomTags params; + params.level = level; + params.storageArea = storageArea; + + return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, ¶ms); + } + + + typedef struct + { + char** result; + const char* instanceId; + const void* buffer; + uint32_t size; + OrthancPluginDicomToJsonFormat format; + OrthancPluginDicomToJsonFlags flags; + uint32_t maxStringLength; + } _OrthancPluginDicomToJson; + + + /** + * @brief Format a DICOM memory buffer as a JSON string. + * + * This function takes as input a memory buffer containing a DICOM + * file, and outputs a JSON string representing the tags of this + * DICOM file. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer containing the DICOM file. + * @param size The size of the memory buffer. + * @param format The output format. + * @param flags Flags governing the output. + * @param maxStringLength The maximum length of a field. Too long fields will + * be output as "null". The 0 value means no maximum length. + * @return The NULL value if the case of an error, or the JSON + * string. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + * @see OrthancPluginDicomInstanceToJson() + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson( + OrthancPluginContext* context, + const void* buffer, + uint32_t size, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + char* result; + + _OrthancPluginDicomToJson params; + memset(¶ms, 0, sizeof(params)); + params.result = &result; + params.buffer = buffer; + params.size = size; + params.format = format; + params.flags = flags; + params.maxStringLength = maxStringLength; + + if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Format a DICOM instance as a JSON string. + * + * This function formats a DICOM instance that is stored in Orthanc, + * and outputs a JSON string representing the tags of this DICOM + * instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instanceId The Orthanc identifier of the instance. + * @param format The output format. + * @param flags Flags governing the output. + * @param maxStringLength The maximum length of a field. Too long fields will + * be output as "null". The 0 value means no maximum length. + * @return The NULL value if the case of an error, or the JSON + * string. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + * @see OrthancPluginDicomInstanceToJson() + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson( + OrthancPluginContext* context, + const char* instanceId, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + char* result; + + _OrthancPluginDicomToJson params; + memset(¶ms, 0, sizeof(params)); + params.result = &result; + params.instanceId = instanceId; + params.format = format; + params.flags = flags; + params.maxStringLength = maxStringLength; + + if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + int32_t afterPlugins; + } _OrthancPluginRestApiGet2; + + /** + * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers. + * + * Make a GET call to the Orthanc REST API with extended + * parameters. The result to the query is stored into a newly + * allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param afterPlugins If 0, the built-in API of Orthanc is used. + * If 1, the API is tainted by the plugins. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet(), OrthancPluginRestApiGetAfterPlugins() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet2( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + int32_t afterPlugins) + { + _OrthancPluginRestApiGet2 params; + params.target = target; + params.uri = uri; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.afterPlugins = afterPlugins; + + return context->InvokeService(context, _OrthancPluginService_RestApiGet2, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistCallback callback; + } _OrthancPluginWorklistCallback; + + /** + * @brief Register a callback to handle modality worklists requests. + * + * This function registers a callback to handle C-Find SCP requests + * on modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback( + OrthancPluginContext* context, + OrthancPluginWorklistCallback callback) + { + _OrthancPluginWorklistCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistAnswers* answers; + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + } _OrthancPluginWorklistAnswersOperation; + + /** + * @brief Add one answer to some modality worklist request. + * + * This function adds one worklist (encoded as a DICOM file) to the + * set of answers corresponding to some C-Find SCP request against + * modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + * @see OrthancPluginCreateDicom() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddAnswer( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = query; + params.dicom = dicom; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, ¶ms); + } + + + /** + * @brief Mark the set of worklist answers as incomplete. + * + * This function marks as incomplete the set of answers + * corresponding to some C-Find SCP request against modality + * worklists. This must be used if canceling the handling of a + * request when too many answers are to be returned. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistMarkIncomplete( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = NULL; + params.dicom = NULL; + params.size = 0; + + return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, ¶ms); + } + + + typedef struct + { + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + int32_t* isMatch; + OrthancPluginMemoryBuffer* target; + } _OrthancPluginWorklistQueryOperation; + + /** + * @brief Test whether a worklist matches the query. + * + * This function checks whether one worklist (encoded as a DICOM + * file) matches the C-Find SCP query against modality + * worklists. This function must be called before adding the + * worklist as an answer through OrthancPluginWorklistAddAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 1 if the worklist matches the query, 0 otherwise. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginWorklistIsMatch( + OrthancPluginContext* context, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + int32_t isMatch = 0; + + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = dicom; + params.size = size; + params.isMatch = &isMatch; + params.target = NULL; + + if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, ¶ms) == OrthancPluginErrorCode_Success) + { + return isMatch; + } + else + { + /* Error: Assume non-match */ + return 0; + } + } + + + /** + * @brief Retrieve the worklist query as a DICOM file. + * + * This function retrieves the DICOM file that underlies a C-Find + * SCP query against modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param query The worklist query, as received by the callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistGetDicomQuery( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginWorklistQuery* query) + { + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = NULL; + params.size = 0; + params.isMatch = NULL; + params.target = target; + + return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, ¶ms); + } + + + /** + * @brief Get the origin of a DICOM file. + * + * This function returns the origin of a DICOM instance that has been received by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The origin of the instance. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + OrthancPluginInstanceOrigin origin; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultOrigin = &origin; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return OrthancPluginInstanceOrigin_Unknown; + } + else + { + return origin; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* json; + const OrthancPluginImage* pixelData; + OrthancPluginCreateDicomFlags flags; + } _OrthancPluginCreateDicom; + + /** + * @brief Create a DICOM instance from a JSON string and an image. + * + * This function takes as input a string containing a JSON file + * describing the content of a DICOM instance. As an output, it + * writes the corresponding DICOM instance to a newly allocated + * memory buffer. Additionally, an image to be encoded within the + * DICOM instance can also be provided. + * + * Private tags will be associated with the private creator whose + * value is specified in the "DefaultPrivateCreator" configuration + * option of Orthanc. The function OrthancPluginCreateDicom2() can + * be used if another private creator must be used to create this + * instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param json The input JSON file. + * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. + * @param flags Flags governing the output. + * @return 0 if success, other value if error. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom2() + * @see OrthancPluginDicomBufferToJson() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* json, + const OrthancPluginImage* pixelData, + OrthancPluginCreateDicomFlags flags) + { + _OrthancPluginCreateDicom params; + params.target = target; + params.json = json; + params.pixelData = pixelData; + params.flags = flags; + + return context->InvokeService(context, _OrthancPluginService_CreateDicom, ¶ms); + } + + + typedef struct + { + OrthancPluginDecodeImageCallback callback; + } _OrthancPluginDecodeImageCallback; + + /** + * @brief Register a callback to handle the decoding of DICOM images. + * + * This function registers a custom callback to decode DICOM images, + * extending the built-in decoder of Orthanc that uses + * DCMTK. Starting with Orthanc 1.7.0, the exact behavior is + * affected by the configuration option + * "BuiltinDecoderTranscoderOrder" of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback( + OrthancPluginContext* context, + OrthancPluginDecodeImageCallback callback) + { + _OrthancPluginDecodeImageCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + void* buffer; + const void* constBuffer; + uint32_t bufferSize; + uint32_t frameIndex; + } _OrthancPluginCreateImage; + + + /** + * @brief Create an image. + * + * This function creates an image of given size and format. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + + if (context->InvokeService(context, _OrthancPluginService_CreateImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Create an image pointing to a memory buffer. + * + * This function creates an image whose content points to a memory + * buffer managed by the plugin. Note that the buffer is directly + * accessed, no memory is allocated and no data is copied. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + + if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Decode one frame from a DICOM instance. + * + * This function decodes one frame of a DICOM image that is stored + * in a memory buffer. This function will give the same result as + * OrthancPluginUncompressImage() for single-frame DICOM images. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer Pointer to a memory buffer containing the DICOM image. + * @param bufferSize Size of the memory buffer containing the DICOM image. + * @param frameIndex The index of the frame of interest in a multi-frame image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + * @see OrthancPluginGetInstanceDecodedFrame() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage( + OrthancPluginContext* context, + const void* buffer, + uint32_t bufferSize, + uint32_t frameIndex) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.constBuffer = buffer; + params.bufferSize = bufferSize; + params.frameIndex = frameIndex; + + if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + char** result; + const void* buffer; + uint32_t size; + } _OrthancPluginComputeHash; + + /** + * @brief Compute an MD5 hash. + * + * This functions computes the MD5 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Compute a SHA-1 hash. + * + * This functions computes the SHA-1 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginDictionaryEntry* target; + const char* name; + } _OrthancPluginLookupDictionary; + + /** + * @brief Get information about the given DICOM tag. + * + * This functions makes a lookup in the dictionary of DICOM tags + * that are known to Orthanc, and returns information about this + * tag. The tag can be specified using its human-readable name + * (e.g. "PatientName") or a set of two hexadecimal numbers + * (e.g. "0010-0020"). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Where to store the information about the tag. + * @param name The name of the DICOM tag. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginLookupDictionary( + OrthancPluginContext* context, + OrthancPluginDictionaryEntry* target, + const char* name) + { + _OrthancPluginLookupDictionary params; + params.target = target; + params.name = name; + return context->InvokeService(context, _OrthancPluginService_LookupDictionary, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const void* answer; + uint32_t answerSize; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + } _OrthancPluginSendMultipartItem2; + + /** + * @brief Send an item as a part of some HTTP multipart answer, with custom headers. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to + * OrthancPluginSendMultipartItem(), this function will set HTTP header associated + * with the item. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers. + * @param headersValues Array containing the values of the HTTP headers. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const void* answer, + uint32_t answerSize, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues) + { + _OrthancPluginSendMultipartItem2 params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, ¶ms); + } + + + typedef struct + { + OrthancPluginIncomingHttpRequestFilter callback; + } _OrthancPluginIncomingHttpRequestFilter; + + /** + * @brief Register a callback to filter incoming HTTP requests. + * + * This function registers a custom callback to filter incoming HTTP/REST + * requests received by the HTTP server of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated Please instead use OrthancPluginRegisterIncomingHttpRequestFilter2() + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter( + OrthancPluginContext* context, + OrthancPluginIncomingHttpRequestFilter callback) + { + _OrthancPluginIncomingHttpRequestFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + OrthancPluginHttpMethod method; + const char* url; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + const void* body; + uint32_t bodySize; + const char* username; + const char* password; + uint32_t timeout; + const char* certificateFile; + const char* certificateKeyFile; + const char* certificateKeyPassword; + uint8_t pkcs11; + } _OrthancPluginCallHttpClient2; + + + + /** + * @brief Issue a HTTP call with full flexibility. + * + * Make a HTTP call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. The HTTP request + * will be done accordingly to the global configuration of Orthanc + * (in particular, the options "HttpProxy", "HttpTimeout", + * "HttpsVerifyPeers", "HttpsCACertificates", and "Pkcs11" will be + * taken into account). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * The value of this argument is ignored if the HTTP method is DELETE. + * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param method HTTP method to be used. + * @param url The URL of interest. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param timeout Timeout in seconds (0 for default timeout). + * @param certificateFile Path to the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyPassword Password to unlock the key of the client certificate + * (can be NULL if no client certificate or if not using HTTPS). + * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginCallPeerApi() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpClient( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + OrthancPluginHttpMethod method, + const char* url, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + const void* body, + uint32_t bodySize, + const char* username, + const char* password, + uint32_t timeout, + const char* certificateFile, + const char* certificateKeyFile, + const char* certificateKeyPassword, + uint8_t pkcs11) + { + _OrthancPluginCallHttpClient2 params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.method = method; + params.url = url; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + params.timeout = timeout; + params.certificateFile = certificateFile; + params.certificateKeyFile = certificateKeyFile; + params.certificateKeyPassword = certificateKeyPassword; + params.pkcs11 = pkcs11; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient2, ¶ms); + } + + + /** + * @brief Generate an UUID. + * + * Generate a random GUID/UUID (globally unique identifier). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the UUID. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateUuid( + OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GenerateUuid, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + + typedef struct + { + OrthancPluginFindCallback callback; + } _OrthancPluginFindCallback; + + /** + * @brief Register a callback to handle C-Find requests. + * + * This function registers a callback to handle C-Find SCP requests + * that are not related to modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterFindCallback( + OrthancPluginContext* context, + OrthancPluginFindCallback callback) + { + _OrthancPluginFindCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterFindCallback, ¶ms); + } + + + typedef struct + { + OrthancPluginFindAnswers *answers; + const OrthancPluginFindQuery *query; + const void *dicom; + uint32_t size; + uint32_t index; + uint32_t *resultUint32; + uint16_t *resultGroup; + uint16_t *resultElement; + char **resultString; + } _OrthancPluginFindOperation; + + /** + * @brief Add one answer to some C-Find request. + * + * This function adds one answer (encoded as a DICOM file) to the + * set of answers corresponding to some C-Find SCP request that is + * not related to modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @param dicom The answer to be added, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + * @see OrthancPluginCreateDicom() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginFindAddAnswer( + OrthancPluginContext* context, + OrthancPluginFindAnswers* answers, + const void* dicom, + uint32_t size) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.answers = answers; + params.dicom = dicom; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_FindAddAnswer, ¶ms); + } + + + /** + * @brief Mark the set of C-Find answers as incomplete. + * + * This function marks as incomplete the set of answers + * corresponding to some C-Find SCP request that is not related to + * modality worklists. This must be used if canceling the handling + * of a request when too many answers are to be returned. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginFindMarkIncomplete( + OrthancPluginContext* context, + OrthancPluginFindAnswers* answers) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.answers = answers; + + return context->InvokeService(context, _OrthancPluginService_FindMarkIncomplete, ¶ms); + } + + + + /** + * @brief Get the number of tags in a C-Find query. + * + * This function returns the number of tags that are contained in + * the given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @return The number of tags. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFindQuerySize( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query) + { + uint32_t count = 0; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQuerySize, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + /** + * @brief Get one tag in a C-Find query. + * + * This function returns the group and the element of one DICOM tag + * in the given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag (output). + * @param element The element of the tag (output). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetFindQueryTag( + OrthancPluginContext* context, + uint16_t* group, + uint16_t* element, + const OrthancPluginFindQuery* query, + uint32_t index) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultGroup = group; + params.resultElement = element; + + return context->InvokeService(context, _OrthancPluginService_GetFindQueryTag, ¶ms); + } + + + /** + * @brief Get the symbolic name of one tag in a C-Find query. + * + * This function returns the symbolic name of one DICOM tag in the + * given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return The NULL value in case of error, or a string containing the name of the tag. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetFindQueryTagName( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query, + uint32_t index) + { + char* result; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultString = &result; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQueryTagName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the value associated with one tag in a C-Find query. + * + * This function returns the value associated with one tag in the + * given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return The NULL value in case of error, or a string containing the value of the tag. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetFindQueryValue( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query, + uint32_t index) + { + char* result; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultString = &result; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQueryValue, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + + typedef struct + { + OrthancPluginMoveCallback callback; + OrthancPluginGetMoveSize getMoveSize; + OrthancPluginApplyMove applyMove; + OrthancPluginFreeMove freeMove; + } _OrthancPluginMoveCallback; + + /** + * @brief Register a callback to handle C-Move requests. + * + * This function registers a callback to handle C-Move SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The main callback. + * @param getMoveSize Callback to read the number of C-Move suboperations. + * @param applyMove Callback to apply one C-Move suboperation. + * @param freeMove Callback to free the C-Move driver. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback( + OrthancPluginContext* context, + OrthancPluginMoveCallback callback, + OrthancPluginGetMoveSize getMoveSize, + OrthancPluginApplyMove applyMove, + OrthancPluginFreeMove freeMove) + { + _OrthancPluginMoveCallback params; + params.callback = callback; + params.getMoveSize = getMoveSize; + params.applyMove = applyMove; + params.freeMove = freeMove; + + return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginFindMatcher** target; + const void* query; + uint32_t size; + } _OrthancPluginCreateFindMatcher; + + + /** + * @brief Create a C-Find matcher. + * + * This function creates a "matcher" object that can be used to + * check whether a DICOM instance matches a C-Find query. The C-Find + * query must be expressed as a DICOM buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find DICOM query. + * @param size The size of the DICOM query. + * @return The newly allocated matcher. It must be freed with OrthancPluginFreeFindMatcher(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginFindMatcher* OrthancPluginCreateFindMatcher( + OrthancPluginContext* context, + const void* query, + uint32_t size) + { + OrthancPluginFindMatcher* target = NULL; + + _OrthancPluginCreateFindMatcher params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.query = query; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_CreateFindMatcher, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginFindMatcher* matcher; + } _OrthancPluginFreeFindMatcher; + + /** + * @brief Free a C-Find matcher. + * + * This function frees a matcher that was created using OrthancPluginCreateFindMatcher(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param matcher The matcher of interest. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeFindMatcher( + OrthancPluginContext* context, + OrthancPluginFindMatcher* matcher) + { + _OrthancPluginFreeFindMatcher params; + params.matcher = matcher; + + context->InvokeService(context, _OrthancPluginService_FreeFindMatcher, ¶ms); + } + + + typedef struct + { + const OrthancPluginFindMatcher* matcher; + const void* dicom; + uint32_t size; + int32_t* isMatch; + } _OrthancPluginFindMatcherIsMatch; + + /** + * @brief Test whether a DICOM instance matches a C-Find query. + * + * This function checks whether one DICOM instance matches C-Find + * matcher that was previously allocated using + * OrthancPluginCreateFindMatcher(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param matcher The matcher of interest. + * @param dicom The DICOM instance to be matched. + * @param size The size of the DICOM instance. + * @return 1 if the DICOM instance matches the query, 0 otherwise. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginFindMatcherIsMatch( + OrthancPluginContext* context, + const OrthancPluginFindMatcher* matcher, + const void* dicom, + uint32_t size) + { + int32_t isMatch = 0; + + _OrthancPluginFindMatcherIsMatch params; + params.matcher = matcher; + params.dicom = dicom; + params.size = size; + params.isMatch = &isMatch; + + if (context->InvokeService(context, _OrthancPluginService_FindMatcherIsMatch, ¶ms) == OrthancPluginErrorCode_Success) + { + return isMatch; + } + else + { + /* Error: Assume non-match */ + return 0; + } + } + + + typedef struct + { + OrthancPluginIncomingHttpRequestFilter2 callback; + } _OrthancPluginIncomingHttpRequestFilter2; + + /** + * @brief Register a callback to filter incoming HTTP requests. + * + * This function registers a custom callback to filter incoming HTTP/REST + * requests received by the HTTP server of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter2( + OrthancPluginContext* context, + OrthancPluginIncomingHttpRequestFilter2 callback) + { + _OrthancPluginIncomingHttpRequestFilter2 params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter2, ¶ms); + } + + + + typedef struct + { + OrthancPluginPeers** peers; + } _OrthancPluginGetPeers; + + /** + * @brief Return the list of available Orthanc peers. + * + * This function returns the parameters of the Orthanc peers that are known to + * the Orthanc server hosting the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL if error, or a newly allocated opaque data structure containing the peers. + * This structure must be freed with OrthancPluginFreePeers(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPeers* OrthancPluginGetPeers( + OrthancPluginContext* context) + { + OrthancPluginPeers* peers = NULL; + + _OrthancPluginGetPeers params; + memset(¶ms, 0, sizeof(params)); + params.peers = &peers; + + if (context->InvokeService(context, _OrthancPluginService_GetPeers, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return peers; + } + } + + + typedef struct + { + OrthancPluginPeers* peers; + } _OrthancPluginFreePeers; + + /** + * @brief Free the list of available Orthanc peers. + * + * This function frees the data structure returned by OrthancPluginGetPeers(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreePeers( + OrthancPluginContext* context, + OrthancPluginPeers* peers) + { + _OrthancPluginFreePeers params; + params.peers = peers; + + context->InvokeService(context, _OrthancPluginService_FreePeers, ¶ms); + } + + + typedef struct + { + uint32_t* target; + const OrthancPluginPeers* peers; + } _OrthancPluginGetPeersCount; + + /** + * @brief Get the number of Orthanc peers. + * + * This function returns the number of Orthanc peers. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @result The number of peers. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetPeersCount( + OrthancPluginContext* context, + const OrthancPluginPeers* peers) + { + uint32_t target = 0; + + _OrthancPluginGetPeersCount params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + + if (context->InvokeService(context, _OrthancPluginService_GetPeersCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return target; + } + } + + + typedef struct + { + const char** target; + const OrthancPluginPeers* peers; + uint32_t peerIndex; + const char* userProperty; + } _OrthancPluginGetPeerProperty; + + /** + * @brief Get the symbolic name of an Orthanc peer. + * + * This function returns the symbolic name of the Orthanc peer, + * which corresponds to the key of the "OrthancPeers" configuration + * option of Orthanc. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @result The symbolic name, or NULL in the case of an error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerName( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Get the base URL of an Orthanc peer. + * + * This function returns the base URL to the REST API of some Orthanc peer. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @result The URL, or NULL in the case of an error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUrl( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerUrl, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Get some user-defined property of an Orthanc peer. + * + * This function returns some user-defined property of some Orthanc + * peer. An user-defined property is a property that is associated + * with the peer in the Orthanc configuration file, but that is not + * recognized by the Orthanc core. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @param userProperty The user property of interest. + * @result The value of the user property, or NULL if it is not defined. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUserProperty( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex, + const char* userProperty) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = userProperty; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerUserProperty, ¶ms) != OrthancPluginErrorCode_Success) + { + /* No such user property */ + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + const OrthancPluginPeers* peers; + uint32_t peerIndex; + OrthancPluginHttpMethod method; + const char* uri; + uint32_t additionalHeadersCount; + const char* const* additionalHeadersKeys; + const char* const* additionalHeadersValues; + const void* body; + uint32_t bodySize; + uint32_t timeout; + } _OrthancPluginCallPeerApi; + + /** + * @brief Call the REST API of an Orthanc peer. + * + * Make a REST call to the given URI in the REST API of a remote + * Orthanc peer. The result to the query is stored into a newly + * allocated memory buffer. The HTTP request will be done according + * to the "OrthancPeers" configuration option of Orthanc. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * The value of this argument is ignored if the HTTP method is DELETE. + * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @param method HTTP method to be used. + * @param uri The URI of interest in the REST API. + * @param additionalHeadersCount The number of HTTP headers to be added to the + * HTTP headers provided in the global configuration of Orthanc. + * @param additionalHeadersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param additionalHeadersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param timeout Timeout in seconds (0 for default timeout). + * @return 0 if success, or the error code if failure. + * @see OrthancPluginHttpClient() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCallPeerApi( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + const OrthancPluginPeers* peers, + uint32_t peerIndex, + OrthancPluginHttpMethod method, + const char* uri, + uint32_t additionalHeadersCount, + const char* const* additionalHeadersKeys, + const char* const* additionalHeadersValues, + const void* body, + uint32_t bodySize, + uint32_t timeout) + { + _OrthancPluginCallPeerApi params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.peers = peers; + params.peerIndex = peerIndex; + params.method = method; + params.uri = uri; + params.additionalHeadersCount = additionalHeadersCount; + params.additionalHeadersKeys = additionalHeadersKeys; + params.additionalHeadersValues = additionalHeadersValues; + params.body = body; + params.bodySize = bodySize; + params.timeout = timeout; + + return context->InvokeService(context, _OrthancPluginService_CallPeerApi, ¶ms); + } + + + + + + typedef struct + { + OrthancPluginJob** target; + void *job; + OrthancPluginJobFinalize finalize; + const char *type; + OrthancPluginJobGetProgress getProgress; + OrthancPluginJobGetContent getContent; + OrthancPluginJobGetSerialized getSerialized; + OrthancPluginJobStep step; + OrthancPluginJobStop stop; + OrthancPluginJobReset reset; + } _OrthancPluginCreateJob; + + /** + * @brief Create a custom job. + * + * This function creates a custom job to be run by the jobs engine + * of Orthanc. + * + * Orthanc starts one dedicated thread per custom job that is + * running. It is guaranteed that all the callbacks will only be + * called from this single dedicated thread, in mutual exclusion: As + * a consequence, it is *not* mandatory to protect the various + * callbacks by mutexes. + * + * The custom job can nonetheless launch its own processing threads + * on the first call to the "step()" callback, and stop them once + * the "stop()" callback is called. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job to be executed. + * @param finalize The finalization callback. + * @param type The type of the job, provided to the job unserializer. + * See OrthancPluginRegisterJobsUnserializer(). + * @param getProgress The progress callback. + * @param getContent The content callback. + * @param getSerialized The serialization callback. + * @param step The callback to execute the individual steps of the job. + * @param stop The callback that is invoked once the job leaves the "running" state. + * @param reset The callback that is invoked if a stopped job is started again. + * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(), + * as long as it is not submitted with OrthancPluginSubmitJob(). + * @ingroup Toolbox + * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3. + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob( + OrthancPluginContext *context, + void *job, + OrthancPluginJobFinalize finalize, + const char *type, + OrthancPluginJobGetProgress getProgress, + OrthancPluginJobGetContent getContent, + OrthancPluginJobGetSerialized getSerialized, + OrthancPluginJobStep step, + OrthancPluginJobStop stop, + OrthancPluginJobReset reset) + { + OrthancPluginJob* target = NULL; + + _OrthancPluginCreateJob params; + memset(¶ms, 0, sizeof(params)); + + params.target = ⌖ + params.job = job; + params.finalize = finalize; + params.type = type; + params.getProgress = getProgress; + params.getContent = getContent; + params.getSerialized = getSerialized; + params.step = step; + params.stop = stop; + params.reset = reset; + + if (context->InvokeService(context, _OrthancPluginService_CreateJob, ¶ms) != OrthancPluginErrorCode_Success || + target == NULL) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginJob** target; + void *job; + OrthancPluginJobFinalize finalize; + const char *type; + OrthancPluginJobGetProgress getProgress; + OrthancPluginJobGetContent2 getContent; + OrthancPluginJobGetSerialized2 getSerialized; + OrthancPluginJobStep step; + OrthancPluginJobStop stop; + OrthancPluginJobReset reset; + } _OrthancPluginCreateJob2; + + /** + * @brief Create a custom job. + * + * This function creates a custom job to be run by the jobs engine + * of Orthanc. + * + * Orthanc starts one dedicated thread per custom job that is + * running. It is guaranteed that all the callbacks will only be + * called from this single dedicated thread, in mutual exclusion: As + * a consequence, it is *not* mandatory to protect the various + * callbacks by mutexes. + * + * The custom job can nonetheless launch its own processing threads + * on the first call to the "step()" callback, and stop them once + * the "stop()" callback is called. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job to be executed. + * @param finalize The finalization callback. + * @param type The type of the job, provided to the job unserializer. + * See OrthancPluginRegisterJobsUnserializer(). + * @param getProgress The progress callback. + * @param getContent The content callback. + * @param getSerialized The serialization callback. + * @param step The callback to execute the individual steps of the job. + * @param stop The callback that is invoked once the job leaves the "running" state. + * @param reset The callback that is invoked if a stopped job is started again. + * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(), + * as long as it is not submitted with OrthancPluginSubmitJob(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob2( + OrthancPluginContext *context, + void *job, + OrthancPluginJobFinalize finalize, + const char *type, + OrthancPluginJobGetProgress getProgress, + OrthancPluginJobGetContent2 getContent, + OrthancPluginJobGetSerialized2 getSerialized, + OrthancPluginJobStep step, + OrthancPluginJobStop stop, + OrthancPluginJobReset reset) + { + OrthancPluginJob* target = NULL; + + _OrthancPluginCreateJob2 params; + memset(¶ms, 0, sizeof(params)); + + params.target = ⌖ + params.job = job; + params.finalize = finalize; + params.type = type; + params.getProgress = getProgress; + params.getContent = getContent; + params.getSerialized = getSerialized; + params.step = step; + params.stop = stop; + params.reset = reset; + + if (context->InvokeService(context, _OrthancPluginService_CreateJob2, ¶ms) != OrthancPluginErrorCode_Success || + target == NULL) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginJob* job; + } _OrthancPluginFreeJob; + + /** + * @brief Free a custom job. + * + * This function frees an image that was created with OrthancPluginCreateJob(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeJob( + OrthancPluginContext* context, + OrthancPluginJob* job) + { + _OrthancPluginFreeJob params; + params.job = job; + + context->InvokeService(context, _OrthancPluginService_FreeJob, ¶ms); + } + + + + typedef struct + { + char** resultId; + OrthancPluginJob *job; + int32_t priority; + } _OrthancPluginSubmitJob; + + /** + * @brief Submit a new job to the jobs engine of Orthanc. + * + * This function adds the given job to the pending jobs of + * Orthanc. Orthanc will take take of freeing it by invoking the + * finalization callback provided to OrthancPluginCreateJob(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job, as received by OrthancPluginCreateJob(). + * @param priority The priority of the job. + * @return ID of the newly-submitted job. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginSubmitJob( + OrthancPluginContext *context, + OrthancPluginJob *job, + int32_t priority) + { + char* resultId = NULL; + + _OrthancPluginSubmitJob params; + memset(¶ms, 0, sizeof(params)); + + params.resultId = &resultId; + params.job = job; + params.priority = priority; + + if (context->InvokeService(context, _OrthancPluginService_SubmitJob, ¶ms) != OrthancPluginErrorCode_Success || + resultId == NULL) + { + /* Error */ + return NULL; + } + else + { + return resultId; + } + } + + + + typedef struct + { + OrthancPluginJobsUnserializer unserializer; + } _OrthancPluginJobsUnserializer; + + /** + * @brief Register an unserializer for custom jobs. + * + * This function registers an unserializer that decodes custom jobs + * from a JSON string. This callback is invoked when the jobs engine + * of Orthanc is started (on Orthanc initialization), for each job + * that is stored in the Orthanc database. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param unserializer The job unserializer. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterJobsUnserializer( + OrthancPluginContext* context, + OrthancPluginJobsUnserializer unserializer) + { + _OrthancPluginJobsUnserializer params; + params.unserializer = unserializer; + + context->InvokeService(context, _OrthancPluginService_RegisterJobsUnserializer, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* details; + uint8_t log; + } _OrthancPluginSetHttpErrorDetails; + + /** + * @brief Provide a detailed description for an HTTP error. + * + * This function sets the detailed description associated with an + * HTTP error. This description will be displayed in the "Details" + * field of the JSON body of the HTTP answer. It is only taken into + * consideration if the REST callback returns an error code that is + * different from "OrthancPluginErrorCode_Success", and if the + * "HttpDescribeErrors" configuration option of Orthanc is set to + * "true". + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param details The details of the error message. + * @param log Whether to also write the detailed error to the Orthanc logs. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpErrorDetails( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* details, + uint8_t log) + { + _OrthancPluginSetHttpErrorDetails params; + params.output = output; + params.details = details; + params.log = log; + context->InvokeService(context, _OrthancPluginService_SetHttpErrorDetails, ¶ms); + } + + + + typedef struct + { + const char** result; + const char* argument; + } _OrthancPluginRetrieveStaticString; + + /** + * @brief Detect the MIME type of a file. + * + * This function returns the MIME type of a file by inspecting its extension. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param path Path to the file. + * @return The MIME type. This is a statically-allocated + * string, do not free it. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginAutodetectMimeType( + OrthancPluginContext* context, + const char* path) + { + const char* result = NULL; + + _OrthancPluginRetrieveStaticString params; + params.result = &result; + params.argument = path; + + if (context->InvokeService(context, _OrthancPluginService_AutodetectMimeType, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + const char* name; + float value; + OrthancPluginMetricsType type; + } _OrthancPluginSetMetricsValue; + + /** + * @brief Set the value of a floating-point metrics. + * + * This function sets the value of a floating-point metrics to + * monitor the behavior of the plugin through tools such as + * Prometheus. The values of all the metrics are stored within the + * Orthanc context. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param name The name of the metrics to be set. + * @param value The value of the metrics. + * @param type The type of the metrics. This parameter is only taken into consideration + * the first time this metrics is set. + * @ingroup Toolbox + * @see OrthancPluginSetMetricsIntegerValue() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetMetricsValue( + OrthancPluginContext* context, + const char* name, + float value, + OrthancPluginMetricsType type) + { + _OrthancPluginSetMetricsValue params; + params.name = name; + params.value = value; + params.type = type; + context->InvokeService(context, _OrthancPluginService_SetMetricsValue, ¶ms); + } + + + + typedef struct + { + OrthancPluginRefreshMetricsCallback callback; + } _OrthancPluginRegisterRefreshMetricsCallback; + + /** + * @brief Register a callback to refresh the metrics. + * + * This function registers a callback to refresh the metrics. The + * callback must make calls to OrthancPluginSetMetricsValue() or + * OrthancPluginSetMetricsIntegerValue(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function to handle the refresh. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRefreshMetricsCallback( + OrthancPluginContext* context, + OrthancPluginRefreshMetricsCallback callback) + { + _OrthancPluginRegisterRefreshMetricsCallback params; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRefreshMetricsCallback, ¶ms); + } + + + + + typedef struct + { + char** target; + const void* dicom; + uint32_t dicomSize; + OrthancPluginDicomWebBinaryCallback callback; + } _OrthancPluginEncodeDicomWeb; + + /** + * @brief Convert a DICOM instance to DICOMweb JSON. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb JSON representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @see OrthancPluginCreateDicom() + * @return The NULL value in case of error, or the JSON document. This string must + * be freed by OrthancPluginFreeString(). + * @deprecated OrthancPluginEncodeDicomWebJson2() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback callback) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb XML. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb XML representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @return The NULL value in case of error, or the XML document. This string must + * be freed by OrthancPluginFreeString(). + * @see OrthancPluginCreateDicom() + * @deprecated OrthancPluginEncodeDicomWebXml2() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback callback) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebXml, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + char** target; + const void* dicom; + uint32_t dicomSize; + OrthancPluginDicomWebBinaryCallback2 callback; + void* payload; + } _OrthancPluginEncodeDicomWeb2; + + /** + * @brief Convert a DICOM instance to DICOMweb JSON. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb JSON representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the JSON document. This string must + * be freed by OrthancPluginFreeString(). + * @see OrthancPluginCreateDicom() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson2( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb2 params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + params.payload = payload; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebJson2, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb XML. + * + * This function converts a memory buffer containing a DICOM instance, + * into its DICOMweb XML representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom Pointer to the DICOM instance. + * @param dicomSize Size of the DICOM instance. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the XML document. This string must + * be freed by OrthancPluginFreeString(). + * @see OrthancPluginCreateDicom() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml2( + OrthancPluginContext* context, + const void* dicom, + uint32_t dicomSize, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginEncodeDicomWeb2 params; + params.target = ⌖ + params.dicom = dicom; + params.dicomSize = dicomSize; + params.callback = callback; + params.payload = payload; + + if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebXml2, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Callback executed when a HTTP header is received during a chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, as soon as it + * receives one HTTP header from the answer of the remote HTTP + * server. + * + * @see OrthancPluginChunkedHttpClient() + * @param answer The user payload, as provided by the calling plugin. + * @param key The key of the HTTP header. + * @param value The value of the HTTP header. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddHeader) ( + void* answer, + const char* key, + const char* value); + + + /** + * @brief Callback executed when an answer chunk is received during a chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, as soon as it + * receives one data chunk from the answer of the remote HTTP + * server. + * + * @see OrthancPluginChunkedHttpClient() + * @param answer The user payload, as provided by the calling plugin. + * @param data The content of the data chunk. + * @param size The size of the data chunk. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddChunk) ( + void* answer, + const void* data, + uint32_t size); + + + /** + * @brief Callback to know whether the request body is entirely read during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. The plugin must answer "1" as + * soon as the body is entirely read: The "request" data structure + * must act as an iterator. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return "1" if the body is over, or "0" if there is still data to be read. + * @ingroup Toolbox + **/ + typedef uint8_t (*OrthancPluginChunkedClientRequestIsDone) (void* request); + + + /** + * @brief Callback to advance in the request body during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. This function asks the plugin + * to advance to the next chunk of data of the request body: The + * "request" data structure must act as an iterator. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientRequestNext) (void* request); + + + /** + * @brief Callback to read the current chunk of the request body during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. The plugin must provide the + * content of the current chunk of data of the request body. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return The content of the current request chunk. + * @ingroup Toolbox + **/ + typedef const void* (*OrthancPluginChunkedClientRequestGetChunkData) (void* request); + + + /** + * @brief Callback to read the size of the current request chunk during a chunked transfer + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP client during a chunked HTTP transfer, while reading + * the body of a POST or PUT request. The plugin must provide the + * size of the current chunk of data of the request body. + * + * @see OrthancPluginChunkedHttpClient() + * @param request The user payload, as provided by the calling plugin. + * @return The size of the current request chunk. + * @ingroup Toolbox + **/ + typedef uint32_t (*OrthancPluginChunkedClientRequestGetChunkSize) (void* request); + + + typedef struct + { + void* answer; + OrthancPluginChunkedClientAnswerAddChunk answerAddChunk; + OrthancPluginChunkedClientAnswerAddHeader answerAddHeader; + uint16_t* httpStatus; + OrthancPluginHttpMethod method; + const char* url; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + void* request; + OrthancPluginChunkedClientRequestIsDone requestIsDone; + OrthancPluginChunkedClientRequestGetChunkData requestChunkData; + OrthancPluginChunkedClientRequestGetChunkSize requestChunkSize; + OrthancPluginChunkedClientRequestNext requestNext; + const char* username; + const char* password; + uint32_t timeout; + const char* certificateFile; + const char* certificateKeyFile; + const char* certificateKeyPassword; + uint8_t pkcs11; + } _OrthancPluginChunkedHttpClient; + + + /** + * @brief Issue a HTTP call, using chunked HTTP transfers. + * + * Make a HTTP call to the given URL using chunked HTTP + * transfers. The request body is provided as an iterator over data + * chunks. The answer is provided as a sequence of function calls + * with the individual HTTP headers and answer chunks. + * + * Contrarily to OrthancPluginHttpClient() that entirely stores the + * request body and the answer body in memory buffers, this function + * uses chunked HTTP transfers. This results in a lower memory + * consumption. Pay attention to the fact that Orthanc servers with + * version <= 1.5.6 do not support chunked transfers: You must use + * OrthancPluginHttpClient() if contacting such older servers. + * + * The HTTP request will be done accordingly to the global + * configuration of Orthanc (in particular, the options "HttpProxy", + * "HttpTimeout", "HttpsVerifyPeers", "HttpsCACertificates", and + * "Pkcs11" will be taken into account). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answer The user payload for the answer body. It will be provided to the callbacks for the answer. + * @param answerAddChunk Callback function to report a data chunk from the answer body. + * @param answerAddHeader Callback function to report an HTTP header sent by the remote server. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param method HTTP method to be used. + * @param url The URL of interest. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param request The user payload containing the request body, and acting as an iterator. + * It will be provided to the callbacks for the request. + * @param requestIsDone Callback function to tell whether the request body is entirely read. + * @param requestChunkData Callback function to get the content of the current data chunk of the request body. + * @param requestChunkSize Callback function to get the size of the current data chunk of the request body. + * @param requestNext Callback function to advance to the next data chunk of the request body. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @param timeout Timeout in seconds (0 for default timeout). + * @param certificateFile Path to the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format + * (can be NULL if no client certificate or if not using HTTPS). + * @param certificateKeyPassword Password to unlock the key of the client certificate + * (can be NULL if no client certificate or if not using HTTPS). + * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginHttpClient() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginChunkedHttpClient( + OrthancPluginContext* context, + void* answer, + OrthancPluginChunkedClientAnswerAddChunk answerAddChunk, + OrthancPluginChunkedClientAnswerAddHeader answerAddHeader, + uint16_t* httpStatus, + OrthancPluginHttpMethod method, + const char* url, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + void* request, + OrthancPluginChunkedClientRequestIsDone requestIsDone, + OrthancPluginChunkedClientRequestGetChunkData requestChunkData, + OrthancPluginChunkedClientRequestGetChunkSize requestChunkSize, + OrthancPluginChunkedClientRequestNext requestNext, + const char* username, + const char* password, + uint32_t timeout, + const char* certificateFile, + const char* certificateKeyFile, + const char* certificateKeyPassword, + uint8_t pkcs11) + { + _OrthancPluginChunkedHttpClient params; + memset(¶ms, 0, sizeof(params)); + + /* In common with OrthancPluginHttpClient() */ + params.httpStatus = httpStatus; + params.method = method; + params.url = url; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.username = username; + params.password = password; + params.timeout = timeout; + params.certificateFile = certificateFile; + params.certificateKeyFile = certificateKeyFile; + params.certificateKeyPassword = certificateKeyPassword; + params.pkcs11 = pkcs11; + + /* For chunked body/answer */ + params.answer = answer; + params.answerAddChunk = answerAddChunk; + params.answerAddHeader = answerAddHeader; + params.request = request; + params.requestIsDone = requestIsDone; + params.requestChunkData = requestChunkData; + params.requestChunkSize = requestChunkSize; + params.requestNext = requestNext; + + return context->InvokeService(context, _OrthancPluginService_ChunkedHttpClient, ¶ms); + } + + + + /** + * @brief Opaque structure that reads the content of a HTTP request body during a chunked HTTP transfer. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginServerChunkedRequestReader_t OrthancPluginServerChunkedRequestReader; + + + + /** + * @brief Callback to create a reader to handle incoming chunked HTTP transfers. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This + * callback is only invoked if the HTTP method is POST or PUT. The + * callback must create an user-specific "reader" object that will + * be fed with the body of the incoming body. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader Memory location that must be filled with the newly-created reader. + * @param url The URI that is accessed. + * @param request The body of the HTTP request. Note that "body" and "bodySize" are not used. + * @return 0 if success, or the error code if failure. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderFactory) ( + OrthancPluginServerChunkedRequestReader** reader, + const char* url, + const OrthancPluginHttpRequest* request); + + + /** + * @brief Callback invoked whenever a new data chunk is available during a chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This callback + * is invoked as soon as a new data chunk is available for the request body. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback. + * @param data The content of the data chunk. + * @param size The size of the data chunk. + * @return 0 if success, or the error code if failure. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderAddChunk) ( + OrthancPluginServerChunkedRequestReader* reader, + const void* data, + uint32_t size); + + + /** + * @brief Callback invoked whenever the request body is entirely received. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This + * callback is invoked as soon as the full body of the HTTP request + * is available. The plugin can then send its answer thanks to the + * provided "output" object. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback. + * @param output The HTTP connection to the client application. + * @return 0 if success, or the error code if failure. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderExecute) ( + OrthancPluginServerChunkedRequestReader* reader, + OrthancPluginRestOutput* output); + + + /** + * @brief Callback invoked to release the resources associated with an incoming HTTP chunked transfer. + * + * Signature of a callback function that is called by Orthanc acting + * as a HTTP server that supports chunked HTTP transfers. This + * callback is invoked to release all the resources allocated by the + * given reader. Note that this function might be invoked even if + * the entire body was not read, to deal with client error or + * disconnection. + * + * @see OrthancPluginRegisterChunkedRestCallback() + * @param reader The user payload, as created by the OrthancPluginServerChunkedRequestReaderFactory() callback. + **/ + typedef void (*OrthancPluginServerChunkedRequestReaderFinalize) ( + OrthancPluginServerChunkedRequestReader* reader); + + typedef struct + { + const char* pathRegularExpression; + OrthancPluginRestCallback getHandler; + OrthancPluginServerChunkedRequestReaderFactory postHandler; + OrthancPluginRestCallback deleteHandler; + OrthancPluginServerChunkedRequestReaderFactory putHandler; + OrthancPluginServerChunkedRequestReaderAddChunk addChunk; + OrthancPluginServerChunkedRequestReaderExecute execute; + OrthancPluginServerChunkedRequestReaderFinalize finalize; + } _OrthancPluginChunkedRestCallback; + + + /** + * @brief Register a REST callback to handle chunked HTTP transfers. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Contrarily to OrthancPluginRegisterRestCallback(), the callbacks + * will NOT be invoked in mutual exclusion, so it is up to the + * plugin to implement the required locking mechanisms. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param getHandler The callback function to handle REST calls using the GET HTTP method. + * @param postHandler The callback function to handle REST calls using the POST HTTP method. + * @param deleteHandler The callback function to handle REST calls using the DELETE HTTP method. + * @param putHandler The callback function to handle REST calls using the PUT HTTP method. + * @param addChunk The callback invoked when a new chunk is available for the request body of a POST or PUT call. + * @param execute The callback invoked once the entire body of a POST or PUT call is read. + * @param finalize The callback invoked to release the resources associated with a POST or PUT call. + * @see OrthancPluginRegisterRestCallbackNoLock() + * + * @note + * The regular expression is case sensitive and must follow the + * [Perl syntax](https://www.boost.org/doc/libs/1_67_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html). + * + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterChunkedRestCallback( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback getHandler, + OrthancPluginServerChunkedRequestReaderFactory postHandler, + OrthancPluginRestCallback deleteHandler, + OrthancPluginServerChunkedRequestReaderFactory putHandler, + OrthancPluginServerChunkedRequestReaderAddChunk addChunk, + OrthancPluginServerChunkedRequestReaderExecute execute, + OrthancPluginServerChunkedRequestReaderFinalize finalize) + { + _OrthancPluginChunkedRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.getHandler = getHandler; + params.postHandler = postHandler; + params.deleteHandler = deleteHandler; + params.putHandler = putHandler; + params.addChunk = addChunk; + params.execute = execute; + params.finalize = finalize; + + context->InvokeService(context, _OrthancPluginService_RegisterChunkedRestCallback, ¶ms); + } + + + + + + typedef struct + { + char** result; + uint16_t group; + uint16_t element; + const char* privateCreator; + } _OrthancPluginGetTagName; + + /** + * @brief Returns the symbolic name of a DICOM tag. + * + * This function makes a lookup to the dictionary of DICOM tags that + * are known to Orthanc, and returns the symbolic name of a DICOM tag. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param privateCreator For private tags, the name of the private creator (can be NULL). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetTagName( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + const char* privateCreator) + { + char* result; + + _OrthancPluginGetTagName params; + params.result = &result; + params.group = group; + params.element = element; + params.privateCreator = privateCreator; + + if (context->InvokeService(context, _OrthancPluginService_GetTagName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + /** + * @brief Callback executed by the storage commitment SCP. + * + * Signature of a factory function that creates an object to handle + * one incoming storage commitment request. + * + * @remark The factory receives the list of the SOP class/instance + * UIDs of interest to the remote storage commitment SCU. This gives + * the factory the possibility to start some prefetch process + * upfront in the background, before the handler object is actually + * queried about the status of these DICOM instances. + * + * @param handler Output variable where the factory puts the handler object it created. + * @param jobId ID of the Orthanc job that is responsible for handling + * the storage commitment request. This job will successively look for the + * status of all the individual queried DICOM instances. + * @param transactionUid UID of the storage commitment transaction + * provided by the storage commitment SCU. It contains the value of the + * (0008,1195) DICOM tag. + * @param sopClassUids Array of the SOP class UIDs (0008,0016) that are queried by the SCU. + * @param sopInstanceUids Array of the SOP instance UIDs (0008,0018) that are queried by the SCU. + * @param countInstances Number of DICOM instances that are queried. This is the size + * of the `sopClassUids` and `sopInstanceUids` arrays. + * @param remoteAet The AET of the storage commitment SCU. + * @param calledAet The AET used by the SCU to contact the storage commitment SCP (i.e. Orthanc). + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentFactory) ( + void** handler /* out */, + const char* jobId, + const char* transactionUid, + const char* const* sopClassUids, + const char* const* sopInstanceUids, + uint32_t countInstances, + const char* remoteAet, + const char* calledAet); + + + /** + * @brief Callback to free one storage commitment SCP handler. + * + * Signature of a callback function that releases the resources + * allocated by the factory of the storage commitment SCP. The + * handler is the return value of a previous call to the + * OrthancPluginStorageCommitmentFactory() callback. + * + * @param handler The handler object to be destructed. + * @ingroup DicomCallbacks + **/ + typedef void (*OrthancPluginStorageCommitmentDestructor) (void* handler); + + + /** + * @brief Callback to get the status of one DICOM instance in the + * storage commitment SCP. + * + * Signature of a callback function that is successively invoked for + * each DICOM instance that is queried by the remote storage + * commitment SCU. The function must be tought of as a method of + * the handler object that was created by a previous call to the + * OrthancPluginStorageCommitmentFactory() callback. After each call + * to this method, the progress of the associated Orthanc job is + * updated. + * + * @param target Output variable where to put the status for the queried instance. + * @param handler The handler object associated with this storage commitment request. + * @param sopClassUid The SOP class UID (0008,0016) of interest. + * @param sopInstanceUid The SOP instance UID (0008,0018) of interest. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentLookup) ( + OrthancPluginStorageCommitmentFailureReason* target, + void* handler, + const char* sopClassUid, + const char* sopInstanceUid); + + + typedef struct + { + OrthancPluginStorageCommitmentFactory factory; + OrthancPluginStorageCommitmentDestructor destructor; + OrthancPluginStorageCommitmentLookup lookup; + } _OrthancPluginRegisterStorageCommitmentScpCallback; + + /** + * @brief Register a callback to handle incoming requests to the storage commitment SCP. + * + * This function registers a callback to handle storage commitment SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @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 function to get the status of one DICOM instance. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterStorageCommitmentScpCallback( + OrthancPluginContext* context, + OrthancPluginStorageCommitmentFactory factory, + OrthancPluginStorageCommitmentDestructor destructor, + OrthancPluginStorageCommitmentLookup lookup) + { + _OrthancPluginRegisterStorageCommitmentScpCallback params; + params.factory = factory; + params.destructor = destructor; + params.lookup = lookup; + return context->InvokeService(context, _OrthancPluginService_RegisterStorageCommitmentScpCallback, ¶ms); + } + + + + /** + * @brief Callback to filter incoming DICOM instances received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a new DICOM instance (e.g. through REST API or + * DICOM protocol), and that answers whether this DICOM instance + * should be accepted or discarded by Orthanc. + * + * Note that the metadata information is not available + * (i.e. GetInstanceMetadata() should not be used on "instance"). + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param instance The received DICOM instance. + * @return 0 to discard the instance, 1 to store the instance, -1 if error. + * @ingroup Callbacks + **/ + typedef int32_t (*OrthancPluginIncomingDicomInstanceFilter) ( + const OrthancPluginDicomInstance* instance); + + + typedef struct + { + OrthancPluginIncomingDicomInstanceFilter callback; + } _OrthancPluginIncomingDicomInstanceFilter; + + /** + * @brief Register a callback to filter incoming DICOM instances. + * + * This function registers a custom callback to filter incoming + * DICOM instances received by Orthanc (either through the REST API + * or through the DICOM protocol). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingDicomInstanceFilter( + OrthancPluginContext* context, + OrthancPluginIncomingDicomInstanceFilter callback) + { + _OrthancPluginIncomingDicomInstanceFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingDicomInstanceFilter, ¶ms); + } + + + /** + * @brief Callback to filter incoming DICOM instances received by + * Orthanc through C-STORE. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a new DICOM instance using DICOM C-STORE, and + * that answers whether this DICOM instance should be accepted or + * discarded by Orthanc. If the instance is discarded, the callback + * can specify the DIMSE error code answered by the Orthanc C-STORE + * SCP. + * + * Note that the metadata information is not available + * (i.e. GetInstanceMetadata() should not be used on "instance"). + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param dimseStatus If the DICOM instance is discarded, + * 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 Callbacks + **/ + typedef int32_t (*OrthancPluginIncomingCStoreInstanceFilter) ( + uint16_t* dimseStatus /* out */, + const OrthancPluginDicomInstance* instance); + + + typedef struct + { + OrthancPluginIncomingCStoreInstanceFilter callback; + } _OrthancPluginIncomingCStoreInstanceFilter; + + /** + * @brief Register a callback to filter incoming DICOM instances + * received by Orthanc through C-STORE. + * + * This function registers a custom callback to filter incoming + * DICOM instances received by Orthanc through the DICOM protocol. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingCStoreInstanceFilter( + OrthancPluginContext* context, + OrthancPluginIncomingCStoreInstanceFilter callback) + { + _OrthancPluginIncomingCStoreInstanceFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingCStoreInstanceFilter, ¶ms); + } + + /** + * @brief Callback to keep/discard/modify a DICOM instance received + * by Orthanc from any source (C-STORE or REST API) + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a new DICOM instance (through DICOM protocol or + * REST API), and that specifies an action to be applied to this + * newly received instance. The instance can be kept as it is, can + * be modified by the plugin, or can be discarded. + * + * This callback is invoked immediately after reception, i.e. before + * transcoding and before filtering + * (cf. OrthancPluginRegisterIncomingDicomInstanceFilter()). + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particularly visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param modifiedDicomBuffer A buffer containing the modified DICOM (output). + * This buffer must be allocated using OrthancPluginCreateMemoryBuffer64() + * and will be freed by the Orthanc core. + * @param receivedDicomBuffer A buffer containing the received DICOM (input). + * @param receivedDicomBufferSize The size of the received DICOM (input). + * @param origin The origin of the DICOM instance (input). + * @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 Callbacks + **/ + typedef OrthancPluginReceivedInstanceAction (*OrthancPluginReceivedInstanceCallback) ( + OrthancPluginMemoryBuffer64* modifiedDicomBuffer, + const void* receivedDicomBuffer, + uint64_t receivedDicomBufferSize, + OrthancPluginInstanceOrigin origin); + + + typedef struct + { + OrthancPluginReceivedInstanceCallback callback; + } _OrthancPluginReceivedInstanceCallback; + + /** + * @brief Register a callback to keep/discard/modify a DICOM instance received + * by Orthanc from any source (C-STORE or REST API) + * + * This function registers a custom callback to keep/discard/modify + * incoming DICOM instances received by Orthanc from any source + * (C-STORE or REST API). + * + * @warning Contrarily to + * OrthancPluginRegisterIncomingCStoreInstanceFilter() and + * OrthancPluginRegisterIncomingDicomInstanceFilter() that can be + * called by multiple plugins, + * OrthancPluginRegisterReceivedInstanceCallback() can only be used + * by one single plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterReceivedInstanceCallback( + OrthancPluginContext* context, + OrthancPluginReceivedInstanceCallback callback) + { + _OrthancPluginReceivedInstanceCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterReceivedInstanceCallback, ¶ms); + } + + /** + * @brief Get the transfer syntax of a DICOM file. + * + * This function returns a pointer to a newly created string that + * contains the transfer syntax UID of the DICOM instance. The empty + * string might be returned if this information is unknown. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the + * transfer syntax UID. This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceTransferSyntaxUid( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceTransferSyntaxUid, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Check whether the DICOM file has pixel data. + * + * This function returns a Boolean value indicating whether the + * DICOM instance contains the pixel data (7FE0,0010) tag. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return "1" if the DICOM instance contains pixel data, or "0" if + * the tag is missing, or "-1" in the case of an error. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginHasInstancePixelData( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + int64_t hasPixelData; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &hasPixelData; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_HasInstancePixelData, ¶ms) != OrthancPluginErrorCode_Success || + hasPixelData < 0 || + hasPixelData > 1) + { + /* Error */ + return -1; + } + else + { + return (hasPixelData != 0); + } + } + + + + + + + typedef struct + { + OrthancPluginDicomInstance** target; + const void* buffer; + uint32_t size; + const char* transferSyntax; + } _OrthancPluginCreateDicomInstance; + + /** + * @brief Parse a DICOM instance. + * + * This function parses a memory buffer that contains a DICOM + * file. The function returns a new pointer to a data structure that + * is managed by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer containing the DICOM instance. + * @param size The size of the memory buffer. + * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginCreateDicomInstance( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + OrthancPluginDicomInstance* target = NULL; + + _OrthancPluginCreateDicomInstance params; + params.target = ⌖ + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_CreateDicomInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + typedef struct + { + OrthancPluginDicomInstance* dicom; + } _OrthancPluginFreeDicomInstance; + + /** + * @brief Free a DICOM instance. + * + * This function frees a DICOM instance that was parsed using + * OrthancPluginCreateDicomInstance(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param dicom The DICOM instance. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeDicomInstance( + OrthancPluginContext* context, + OrthancPluginDicomInstance* dicom) + { + _OrthancPluginFreeDicomInstance params; + params.dicom = dicom; + + context->InvokeService(context, _OrthancPluginService_FreeDicomInstance, ¶ms); + } + + + typedef struct + { + uint32_t* targetUint32; + OrthancPluginMemoryBuffer* targetBuffer; + OrthancPluginImage** targetImage; + char** targetStringToFree; + const OrthancPluginDicomInstance* instance; + uint32_t frameIndex; + OrthancPluginDicomToJsonFormat format; + OrthancPluginDicomToJsonFlags flags; + uint32_t maxStringLength; + OrthancPluginDicomWebBinaryCallback2 dicomWebCallback; + void* dicomWebPayload; + } _OrthancPluginAccessDicomInstance2; + + /** + * @brief Get the number of frames in a DICOM instance. + * + * This function returns the number of frames that are part of a + * DICOM image managed by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The number of frames (will be zero in the case of an error). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetInstanceFramesCount( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + uint32_t count; + + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetUint32 = &count; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceFramesCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + /** + * @brief Get the raw content of a frame in a DICOM instance. + * + * This function returns a memory buffer containing the raw content + * of a frame in a DICOM instance that is managed by the Orthanc + * core. This is notably useful for compressed transfer syntaxes, as + * it gives access to the embedded files (such as JPEG, JPEG-LS or + * JPEG2k). The Orthanc core transparently reassembles the fragments + * to extract the raw frame. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instance The instance of interest. + * @param frameIndex The index of the frame of interest. + * @return 0 if success, or the error code if failure. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetInstanceRawFrame( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginDicomInstance* instance, + uint32_t frameIndex) + { + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetBuffer = target; + params.instance = instance; + params.frameIndex = frameIndex; + + return context->InvokeService(context, _OrthancPluginService_GetInstanceRawFrame, ¶ms); + } + + + /** + * @brief Decode one frame from a DICOM instance. + * + * This function decodes one frame of a DICOM image that is managed + * by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param frameIndex The index of the frame of interest. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginGetInstanceDecodedFrame( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + uint32_t frameIndex) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetImage = ⌖ + params.instance = instance; + params.frameIndex = frameIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceDecodedFrame, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Parse and transcode a DICOM instance. + * + * This function parses a memory buffer that contains a DICOM file, + * then transcodes it to the given transfer syntax. The function + * returns a new pointer to a data structure that is managed by the + * Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer containing the DICOM instance. + * @param size The size of the memory buffer. + * @param transferSyntax The transfer syntax UID for the transcoding. + * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginTranscodeDicomInstance( + OrthancPluginContext* context, + const void* buffer, + uint32_t size, + const char* transferSyntax) + { + OrthancPluginDicomInstance* target = NULL; + + _OrthancPluginCreateDicomInstance params; + params.target = ⌖ + params.buffer = buffer; + params.size = size; + params.transferSyntax = transferSyntax; + + if (context->InvokeService(context, _OrthancPluginService_TranscodeDicomInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + /** + * @brief Writes a DICOM instance to a memory buffer. + * + * This function returns a memory buffer containing the + * serialization of a DICOM instance that is managed by the Orthanc + * core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instance The instance of interest. + * @return 0 if success, or the error code if failure. + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSerializeDicomInstance( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginDicomInstance* instance) + { + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetBuffer = target; + params.instance = instance; + + return context->InvokeService(context, _OrthancPluginService_SerializeDicomInstance, ¶ms); + } + + + /** + * @brief Format a DICOM memory buffer as a JSON string. + * + * This function takes as DICOM instance managed by the Orthanc + * core, and outputs a JSON string representing the tags of this + * DICOM file. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The DICOM instance of interest. + * @param format The output format. + * @param flags Flags governing the output. + * @param maxStringLength The maximum length of a field. Too long fields will + * be output as "null". The 0 value means no maximum length. + * @return The NULL value if the case of an error, or the JSON + * string. This string must be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + * @see OrthancPluginDicomBufferToJson() + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceAdvancedJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + char* result = NULL; + + _OrthancPluginAccessDicomInstance2 params; + memset(¶ms, 0, sizeof(params)); + params.targetStringToFree = &result; + params.instance = instance; + params.format = format; + params.flags = flags; + params.maxStringLength = maxStringLength; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceAdvancedJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb JSON. + * + * This function converts a DICOM instance that is managed by the + * Orthanc core, into its DICOMweb JSON representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The DICOM instance of interest. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the JSON document. This string must + * be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceDicomWebJson( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginAccessDicomInstance2 params; + params.targetStringToFree = ⌖ + params.instance = instance; + params.dicomWebCallback = callback; + params.dicomWebPayload = payload; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceDicomWebJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Convert a DICOM instance to DICOMweb XML. + * + * This function converts a DICOM instance that is managed by the + * Orthanc core, into its DICOMweb XML representation. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The DICOM instance of interest. + * @param callback Callback to set the value of the binary tags. + * @param payload User payload. + * @return The NULL value in case of error, or the XML document. This string must + * be freed by OrthancPluginFreeString(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceDicomWebXml( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + OrthancPluginDicomWebBinaryCallback2 callback, + void* payload) + { + char* target = NULL; + + _OrthancPluginAccessDicomInstance2 params; + params.targetStringToFree = ⌖ + params.instance = instance; + params.dicomWebCallback = callback; + params.dicomWebPayload = payload; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceDicomWebXml, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Signature of a callback function to transcode a DICOM instance. + * @param transcoded Target memory buffer. It must be allocated by the + * plugin using OrthancPluginCreateMemoryBuffer(). + * @param buffer Memory buffer containing the source DICOM instance. + * @param size Size of the source memory buffer. + * @param allowedSyntaxes A C array of possible transfer syntaxes UIDs for the + * result of the transcoding. The plugin must choose by itself the + * transfer syntax that will be used for the resulting DICOM image. + * @param countSyntaxes The number of transfer syntaxes that are contained + * in the "allowedSyntaxes" array. + * @param allowNewSopInstanceUid Whether the transcoding plugin can select + * a transfer syntax that will change the SOP instance UID (or, in other + * terms, whether the plugin can transcode using lossy compression). + * @return 0 if success (i.e. image successfully transcoded and stored into + * "transcoded"), or the error code if failure. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginTranscoderCallback) ( + OrthancPluginMemoryBuffer* transcoded /* out */, + const void* buffer, + uint64_t size, + const char* const* allowedSyntaxes, + uint32_t countSyntaxes, + uint8_t allowNewSopInstanceUid); + + + typedef struct + { + OrthancPluginTranscoderCallback callback; + } _OrthancPluginTranscoderCallback; + + /** + * @brief Register a callback to handle the transcoding of DICOM images. + * + * This function registers a custom callback to transcode DICOM + * images, extending the built-in transcoder of Orthanc that uses + * DCMTK. The exact behavior is affected by the configuration option + * "BuiltinDecoderTranscoderOrder" of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterTranscoderCallback( + OrthancPluginContext* context, + OrthancPluginTranscoderCallback callback) + { + _OrthancPluginTranscoderCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterTranscoderCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + uint32_t size; + } _OrthancPluginCreateMemoryBuffer; + + /** + * @brief Create a 32-bit memory buffer. + * + * This function creates a memory buffer that is managed by the + * Orthanc core. The main use case of this function is for plugins + * that act as DICOM transcoders. + * + * Your plugin should never call "free()" on the resulting memory + * buffer, as the C library that is used by the plugin is in general + * not the same as the one used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param size Size of the memory buffer to be created. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateMemoryBuffer( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + uint32_t size) + { + _OrthancPluginCreateMemoryBuffer params; + params.target = target; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_CreateMemoryBuffer, ¶ms); + } + + + /** + * @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 + * of Orthanc using an external HTTP client. Using this function + * avoids the need of adding a separate user in the + * "RegisteredUsers" configuration of Orthanc, which eases + * deployments. + * + * This feature is notably useful in multiprocess scenarios, where a + * subprocess created by a plugin has no access to the + * "OrthancPluginContext", and thus cannot call + * "OrthancPluginRestApi[Get|Post|Put|Delete]()". + * + * This situation is frequently encountered in Python plugins, where + * the "multiprocessing" package can be used to bypass the Global + * Interpreter Lock (GIL) and thus to improve performance and + * concurrency. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The authorization token, or NULL value in the case of an error. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateRestApiAuthorizationToken( + OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GenerateRestApiAuthorizationToken, + ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginMemoryBuffer64* target; + uint64_t size; + } _OrthancPluginCreateMemoryBuffer64; + + /** + * @brief Create a 64-bit memory buffer. + * + * This function creates a 64-bit memory buffer that is managed by + * the Orthanc core. The main use case of this function is for + * plugins that read files from the storage area. + * + * Your plugin should never call "free()" on the resulting memory + * buffer, as the C library that is used by the plugin is in general + * not the same as the one used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param size Size of the memory buffer to be created. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateMemoryBuffer64( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer64* target, + uint64_t size) + { + _OrthancPluginCreateMemoryBuffer64 params; + params.target = target; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_CreateMemoryBuffer64, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageCreate create; + OrthancPluginStorageReadWhole readWhole; + OrthancPluginStorageReadRange readRange; + OrthancPluginStorageRemove remove; + } _OrthancPluginRegisterStorageArea2; + + /** + * @brief Register a custom storage area, with support for range request. + * + * This function registers a custom storage area, to replace the + * built-in way Orthanc stores its files on the filesystem. 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 create The callback function to store a file on the custom storage area. + * @param readWhole The callback function to read a whole file from the custom storage area. + * @param readRange The callback function to read some range of a file from the custom storage area. + * If this feature is not supported by the plugin, this value can be set to NULL. + * @param remove The callback function to remove a file from the custom storage area. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea2( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageReadWhole readWhole, + OrthancPluginStorageReadRange readRange, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea2 params; + params.create = create; + params.readWhole = readWhole; + params.readRange = readRange; + params.remove = remove; + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea2, ¶ms); + } + + + + typedef struct + { + _OrthancPluginCreateDicom createDicom; + const char* privateCreator; + } _OrthancPluginCreateDicom2; + + /** + * @brief Create a DICOM instance from a JSON string and an image, with a private creator. + * + * This function takes as input a string containing a JSON file + * describing the content of a DICOM instance. As an output, it + * writes the corresponding DICOM instance to a newly allocated + * memory buffer. Additionally, an image to be encoded within the + * DICOM instance can also be provided. + * + * Contrarily to the function OrthancPluginCreateDicom(), this + * function can be explicitly provided with a private creator. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param json The input JSON file. + * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. + * @param flags Flags governing the output. + * @param privateCreator The private creator to be used for the private DICOM tags. + * Check out the global configuration option "Dictionary" of Orthanc. + * @return 0 if success, other value if error. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom() + * @see OrthancPluginDicomBufferToJson() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom2( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* json, + const OrthancPluginImage* pixelData, + OrthancPluginCreateDicomFlags flags, + const char* privateCreator) + { + _OrthancPluginCreateDicom2 params; + params.createDicom.target = target; + params.createDicom.json = json; + params.createDicom.pixelData = pixelData; + params.createDicom.flags = flags; + params.privateCreator = privateCreator; + + return context->InvokeService(context, _OrthancPluginService_CreateDicom2, ¶ms); + } + + + + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + OrthancPluginHttpMethod method; + const char* uri; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + const void* body; + uint32_t bodySize; + uint8_t afterPlugins; + } _OrthancPluginCallRestApi; + + /** + * @brief Call the REST API of Orthanc with full flexibility. + * + * Make a call to the given URI in the REST API of Orthanc. The + * result to the query is stored into a newly allocated memory + * buffer. This function is always granted full access to the REST + * API (no credentials, nor security token is needed). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * The value of this argument is ignored if the HTTP method is DELETE. + * @param answerHeaders The target memory buffer for the HTTP headers in the answer (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the answer HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param method HTTP method to be used. + * @param uri The URI of interest. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param afterPlugins If 0, the built-in API of Orthanc is used. + * If 1, the API is tainted by the plugins. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet2(), OrthancPluginRestApiPost(), OrthancPluginRestApiPut(), OrthancPluginRestApiDelete() + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCallRestApi( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + OrthancPluginHttpMethod method, + const char* uri, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + const void* body, + uint32_t bodySize, + uint8_t afterPlugins) + { + _OrthancPluginCallRestApi params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.method = method; + params.uri = uri; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.body = body; + params.bodySize = bodySize; + params.afterPlugins = afterPlugins; + + return context->InvokeService(context, _OrthancPluginService_CallRestApi, ¶ms); + } + + + + /** + * @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); + } + + + /** + * @brief Gets the DatabaseServerIdentifier. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return the database server identifier. This is a statically-allocated + * string, do not free it. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetDatabaseServerIdentifier( + OrthancPluginContext* context) + { + const char* result; + + _OrthancPluginRetrieveStaticString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetDatabaseServerIdentifier, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Signature of a callback function that is triggered when + * the Orthanc core requests an operation from the database plugin. + * Both request and response are encoded as protobuf buffers. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginCallDatabaseBackendV4) ( + OrthancPluginMemoryBuffer64* response, + void* backend, + const void* request, + uint64_t requestSize); + + /** + * @brief Signature of a callback function that is triggered when + * the database plugin must be finalized. + * @ingroup Callbacks + **/ + typedef void (*OrthancPluginFinalizeDatabaseBackendV4) (void* backend); + + typedef struct + { + void* backend; + uint32_t maxDatabaseRetries; + OrthancPluginCallDatabaseBackendV4 operations; + OrthancPluginFinalizeDatabaseBackendV4 finalize; + } _OrthancPluginRegisterDatabaseBackendV4; + + /** + * @brief Register a custom database back-end. + * + * This function was added in Orthanc SDK 1.12.0. It uses Google + * Protocol Buffers for the communications between the Orthanc core + * and database plugins. Check out "OrthancDatabasePlugin.proto" for + * the definition of the protobuf messages. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param backend Pointer to the custom database backend. + * @param maxDatabaseRetries Maximum number of retries if transaction doesn't succeed. + * If no retry is successful, OrthancPluginErrorCode_DatabaseCannotSerialize is generated. + * @param operations Access to the operations of the custom database backend. + * @param finalize Callback to deallocate the custom database backend. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDatabaseBackendV4( + OrthancPluginContext* context, + void* backend, + uint32_t maxDatabaseRetries, + OrthancPluginCallDatabaseBackendV4 operations, + OrthancPluginFinalizeDatabaseBackendV4 finalize) + { + _OrthancPluginRegisterDatabaseBackendV4 params; + params.backend = backend; + params.maxDatabaseRetries = maxDatabaseRetries; + params.operations = operations; + params.finalize = finalize; + + return context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV4, ¶ms); + } + + + typedef struct + { + OrthancPluginDicomInstance** target; + const char* instanceId; + OrthancPluginLoadDicomInstanceMode mode; + } _OrthancPluginLoadDicomInstance; + + /** + * @brief Load a DICOM instance from the Orthanc server. + * + * This function loads a DICOM instance from the content of the + * Orthanc database. The function returns a new pointer to a data + * structure that is managed by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instanceId The Orthanc identifier of the DICOM instance of interest. + * @param mode Flag specifying how to deal with pixel data. + * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance(). + * @ingroup DicomInstance + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginLoadDicomInstance( + OrthancPluginContext* context, + const char* instanceId, + OrthancPluginLoadDicomInstanceMode mode) + { + OrthancPluginDicomInstance* target = NULL; + + _OrthancPluginLoadDicomInstance params; + params.target = ⌖ + params.instanceId = instanceId; + params.mode = mode; + + if (context->InvokeService(context, _OrthancPluginService_LoadDicomInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + const char* name; + int64_t value; + OrthancPluginMetricsType type; + } _OrthancPluginSetMetricsIntegerValue; + + /** + * @brief Set the value of an integer metrics. + * + * This function sets the value of an integer metrics to monitor the + * behavior of the plugin through tools such as Prometheus. The + * values of all the metrics are stored within the Orthanc context. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param name The name of the metrics to be set. + * @param value The value of the metrics. + * @param type The type of the metrics. This parameter is only taken into consideration + * the first time this metrics is set. + * @ingroup Toolbox + * @see OrthancPluginSetMetricsValue() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetMetricsIntegerValue( + OrthancPluginContext* context, + const char* name, + int64_t value, + OrthancPluginMetricsType type) + { + _OrthancPluginSetMetricsIntegerValue params; + params.name = name; + params.value = value; + params.type = type; + context->InvokeService(context, _OrthancPluginService_SetMetricsIntegerValue, ¶ms); + } + + + /** + * @brief Set the name of the current thread. + * + * This function gives a name to the thread that is calling this + * function. This name is used in the Orthanc logs. This function + * must only be called from threads that the plugin has created + * itself. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param threadName The name of the current thread. A thread name cannot be longer than 16 characters. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetCurrentThreadName( + OrthancPluginContext* context, + const char* threadName) + { + return context->InvokeService(context, _OrthancPluginService_SetCurrentThreadName, threadName); + } + + + typedef struct + { + /* Note: This structure is also defined in Logging.h and it must be binary compatible */ + const char* message; + const char* plugin; + const char* file; + uint32_t line; + OrthancPluginLogCategory category; + OrthancPluginLogLevel level; + } _OrthancPluginLogMessage; + + + /** + * @brief Log a message. + * + * Log a message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + * @param plugin The plugin name. + * @param file The filename in the plugin code. + * @param line The file line in the plugin code. + * @param category The category. + * @param level The level of the message. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogMessage( + OrthancPluginContext* context, + const char* message, + const char* plugin, + const char* file, + uint32_t line, + OrthancPluginLogCategory category, + OrthancPluginLogLevel level) + { + _OrthancPluginLogMessage m; + m.message = message; + m.plugin = plugin; + m.file = file; + m.line = line; + m.category = category; + m.level = level; + context->InvokeService(context, _OrthancPluginService_LogMessage, &m); + } + + +#ifdef __cplusplus +} +#endif + + +/** @} */ + diff -r 82f73188b58d -r f18e46d7dbf8 Resources/Orthanc/Sdk-1.12.4/orthanc/OrthancDatabasePlugin.proto --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Orthanc/Sdk-1.12.4/orthanc/OrthancDatabasePlugin.proto Tue Sep 24 15:04:21 2024 +0200 @@ -0,0 +1,950 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 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 . + **/ + + +/** + * This Protocol Buffers prototype describes the exchanges between the + * Orthanc core and its database plugins. The various calls correspond + * to the "IDatabaseWrapper" interface in the source code of Orthanc. + * + * WARNING: *NEVER* modify or remove existing entries. It is only + * allowed to *add* new stuff. + **/ + +syntax = "proto3"; + +/** + * Turn off protobuf reflection to avoid clashes between the Orthanc + * core and the database plugin, otherwise both will try to register + * the same messages in the process-wide descriptor pool, which would + * result in protobuf error "File already exists in database". + **/ +option optimize_for = LITE_RUNTIME; + +package Orthanc.DatabasePluginMessages; + + +/** + * Data structures that are common with the Orthanc core. + **/ + +message FileInfo { + string uuid = 1; + int32 content_type = 2; // opaque "FileContentType" in Orthanc + uint64 uncompressed_size = 3; + string uncompressed_hash = 4; + int32 compression_type = 5; // opaque "CompressionType" in Orthanc + uint64 compressed_size = 6; + string compressed_hash = 7; +} + +enum ResourceType { + RESOURCE_PATIENT = 0; + RESOURCE_STUDY = 1; + RESOURCE_SERIES = 2; + RESOURCE_INSTANCE = 3; +} + +enum ConstraintType { + CONSTRAINT_EQUAL = 0; + CONSTRAINT_SMALLER_OR_EQUAL = 1; + CONSTRAINT_GREATER_OR_EQUAL = 2; + CONSTRAINT_WILDCARD = 3; + CONSTRAINT_LIST = 4; +} + +enum LabelsConstraintType { + LABELS_CONSTRAINT_ALL = 0; + LABELS_CONSTRAINT_ANY = 1; + LABELS_CONSTRAINT_NONE = 2; +} + +message ServerIndexChange { + int64 seq = 1; + int32 change_type = 2; // opaque "ChangeType" in Orthanc + ResourceType resource_type = 3; + string public_id = 4; + string date = 5; +} + +message ExportedResource { + int64 seq = 1; + ResourceType resource_type = 2; + string public_id = 3; + string modality = 4; + string date = 5; + string patient_id = 6; + string study_instance_uid = 7; + string series_instance_uid = 8; + string sop_instance_uid = 9; +} + +message DatabaseConstraint { + ResourceType level = 1; + uint32 tag_group = 2; + uint32 tag_element = 3; + bool is_identifier_tag = 4; + bool is_case_sensitive = 5; + bool is_mandatory = 6; + ConstraintType type = 7; + repeated string values = 8; +} + + +/** + * Database-level operations. + **/ + +enum DatabaseOperation { + OPERATION_GET_SYSTEM_INFORMATION = 0; + OPERATION_OPEN = 1; + OPERATION_CLOSE = 2; + OPERATION_FLUSH_TO_DISK = 3; + OPERATION_START_TRANSACTION = 4; + OPERATION_UPGRADE = 5; + OPERATION_FINALIZE_TRANSACTION = 6; + OPERATION_MEASURE_LATENCY = 7; // New in Orthanc 1.12.3 +} + +enum TransactionType { + TRANSACTION_READ_ONLY = 0; + TRANSACTION_READ_WRITE = 1; +} + +message GetSystemInformation { + message Request { + } + message Response { + uint32 database_version = 1; + bool supports_flush_to_disk = 2; + bool supports_revisions = 3; + bool supports_labels = 4; + bool supports_increment_global_property = 5; + bool has_update_and_get_statistics = 6; + bool has_measure_latency = 7; + } +} + +message Open { + message Request { + message IdentifierTag { + ResourceType level = 1; + uint32 group = 2; + uint32 element = 3; + string name = 4; + } + repeated IdentifierTag identifier_tags = 1; + } + message Response { + } +} + +message Close { + message Request { + } + message Response { + } +} + +message FlushToDisk { + message Request { + } + message Response { + } +} + +message StartTransaction { + message Request { + TransactionType type = 1; + } + message Response { + sfixed64 transaction = 1; + } +} + +message Upgrade { + /** + * It is guaranteed that a read-write transaction is created by the + * Orthanc core before executing this operation. + **/ + message Request { + uint32 target_version = 1; + sfixed64 storage_area = 2; + sfixed64 transaction = 3; + } + message Response { + } +} + +message FinalizeTransaction { + message Request { + sfixed64 transaction = 1; + } + message Response { + } +} + +message MeasureLatency { + message Request { + } + message Response { + int64 latency_us = 1; + } +} + + +message DatabaseRequest { + sfixed64 database = 1; + DatabaseOperation operation = 2; + + GetSystemInformation.Request get_system_information = 100; + Open.Request open = 101; + Close.Request close = 102; + FlushToDisk.Request flush_to_disk = 103; + StartTransaction.Request start_transaction = 104; + Upgrade.Request upgrade = 105; + FinalizeTransaction.Request finalize_transaction = 106; + MeasureLatency.Request measure_latency = 107; +} + +message DatabaseResponse { + GetSystemInformation.Response get_system_information = 100; + Open.Response open = 101; + Close.Response close = 102; + FlushToDisk.Response flush_to_disk = 103; + StartTransaction.Response start_transaction = 104; + Upgrade.Response upgrade = 105; + FinalizeTransaction.Response finalize_transaction = 106; + MeasureLatency.Response measure_latency = 107; +} + + +/** + * Transaction-level operations. + **/ + +enum TransactionOperation { + OPERATION_ROLLBACK = 0; + OPERATION_COMMIT = 1; + OPERATION_ADD_ATTACHMENT = 2; + OPERATION_CLEAR_CHANGES = 3; + OPERATION_CLEAR_EXPORTED_RESOURCES = 4; + OPERATION_DELETE_ATTACHMENT = 5; + OPERATION_DELETE_METADATA = 6; + OPERATION_DELETE_RESOURCE = 7; + OPERATION_GET_ALL_METADATA = 8; + OPERATION_GET_ALL_PUBLIC_IDS = 9; + OPERATION_GET_ALL_PUBLIC_IDS_WITH_LIMITS = 10; + OPERATION_GET_CHANGES = 11; + OPERATION_GET_CHILDREN_INTERNAL_ID = 12; + OPERATION_GET_CHILDREN_PUBLIC_ID = 13; + OPERATION_GET_EXPORTED_RESOURCES = 14; + OPERATION_GET_LAST_CHANGE = 15; + OPERATION_GET_LAST_EXPORTED_RESOURCE = 16; + OPERATION_GET_MAIN_DICOM_TAGS = 17; + OPERATION_GET_PUBLIC_ID = 18; + OPERATION_GET_RESOURCES_COUNT = 19; + OPERATION_GET_RESOURCE_TYPE = 20; + OPERATION_GET_TOTAL_COMPRESSED_SIZE = 21; + OPERATION_GET_TOTAL_UNCOMPRESSED_SIZE = 22; + OPERATION_IS_PROTECTED_PATIENT = 23; + OPERATION_LIST_AVAILABLE_ATTACHMENTS = 24; + OPERATION_LOG_CHANGE = 25; + OPERATION_LOG_EXPORTED_RESOURCE = 26; + OPERATION_LOOKUP_ATTACHMENT = 27; + OPERATION_LOOKUP_GLOBAL_PROPERTY = 28; + OPERATION_LOOKUP_METADATA = 29; + OPERATION_LOOKUP_PARENT = 30; + OPERATION_LOOKUP_RESOURCE = 31; + OPERATION_SELECT_PATIENT_TO_RECYCLE = 32; + OPERATION_SELECT_PATIENT_TO_RECYCLE_WITH_AVOID = 33; + OPERATION_SET_GLOBAL_PROPERTY = 34; + OPERATION_CLEAR_MAIN_DICOM_TAGS = 35; + OPERATION_SET_METADATA = 36; + OPERATION_SET_PROTECTED_PATIENT = 37; + OPERATION_IS_DISK_SIZE_ABOVE = 38; + OPERATION_LOOKUP_RESOURCES = 39; + OPERATION_CREATE_INSTANCE = 40; + OPERATION_SET_RESOURCES_CONTENT = 41; + OPERATION_GET_CHILDREN_METADATA = 42; + OPERATION_GET_LAST_CHANGE_INDEX = 43; + OPERATION_LOOKUP_RESOURCE_AND_PARENT = 44; + OPERATION_ADD_LABEL = 45; // New in Orthanc 1.12.0 + OPERATION_REMOVE_LABEL = 46; // New in Orthanc 1.12.0 + OPERATION_LIST_LABELS = 47; // New in Orthanc 1.12.0 + OPERATION_INCREMENT_GLOBAL_PROPERTY = 48; // New in Orthanc 1.12.3 + OPERATION_UPDATE_AND_GET_STATISTICS = 49; // New in Orthanc 1.12.3 +} + +message Rollback { + message Request { + } + message Response { + } +} + +message Commit { + message Request { + int64 file_size_delta = 1; + } + message Response { + } +} + +message AddAttachment { + message Request { + int64 id = 1; + FileInfo attachment = 2; + int64 revision = 3; + } + message Response { + } +} + +message ClearChanges { + message Request { + } + message Response { + } +} + +message ClearExportedResources { + message Request { + } + message Response { + } +} + +message DeleteAttachment { + message Request { + int64 id = 1; + int32 type = 2; + } + message Response { + FileInfo deleted_attachment = 1; + } +} + +message DeleteMetadata { + message Request { + int64 id = 1; + int32 type = 2; + } + message Response { + } +} + +message DeleteResource { + message Request { + int64 id = 1; + } + message Response { + message Resource { + ResourceType level = 1; + string public_id = 2; + } + repeated FileInfo deleted_attachments = 1; + repeated Resource deleted_resources = 2; + bool is_remaining_ancestor = 3; + Resource remaining_ancestor = 4; + } +} + +message GetAllMetadata { + message Request { + int64 id = 1; + } + message Response { + message Metadata { + int32 type = 1; + string value = 2; + } + repeated Metadata metadata = 1; + } +} + +message GetAllPublicIds { + message Request { + ResourceType resource_type = 1; + } + message Response { + repeated string ids = 1; + } +} + +message GetAllPublicIdsWithLimits { + message Request { + ResourceType resource_type = 1; + int64 since = 2; + uint32 limit = 3; + } + message Response { + repeated string ids = 1; + } +} + +message GetChanges { + message Request { + int64 since = 1; + uint32 limit = 2; + } + message Response { + repeated ServerIndexChange changes = 1; + bool done = 2; + } +} + +message GetChildrenInternalId { + message Request { + int64 id = 1; + } + message Response { + repeated int64 ids = 1; + } +} + +message GetChildrenPublicId { + message Request { + int64 id = 1; + } + message Response { + repeated string ids = 1; + } +} + +message GetExportedResources { + message Request { + int64 since = 1; + uint32 limit = 2; + } + message Response { + repeated ExportedResource resources = 1; + bool done = 2; + } +} + +message GetLastChange { + message Request { + } + message Response { + bool found = 1; + ServerIndexChange change = 2; + } +} + +message GetLastExportedResource { + message Request { + } + message Response { + bool found = 1; + ExportedResource resource = 2; + } +} + +message GetMainDicomTags { + message Request { + int64 id = 1; + } + message Response { + message Tag { + uint32 group = 1; + uint32 element = 2; + string value = 3; + } + repeated Tag tags = 1; + } +} + +message GetPublicId { + message Request { + int64 id = 1; + } + message Response { + string id = 1; + } +} + +message GetResourcesCount { + message Request { + ResourceType type = 1; + } + message Response { + uint64 count = 1; + } +} + +message GetResourceType { + message Request { + int64 id = 1; + } + message Response { + ResourceType type = 1; + } +} + +message GetTotalCompressedSize { + message Request { + } + message Response { + uint64 size = 1; + } +} + +message GetTotalUncompressedSize { + message Request { + } + message Response { + uint64 size = 1; + } +} + +message IsProtectedPatient { + message Request { + int64 patient_id = 1; + } + message Response { + bool protected_patient = 1; + } +} + +message ListAvailableAttachments { + message Request { + int64 id = 1; + } + message Response { + repeated int32 attachments = 1; + } +} + +message LogChange { + message Request { + int32 change_type = 1; + ResourceType resource_type = 2; + int64 resource_id = 3; + string date = 4; + } + message Response { + } +} + +message LogExportedResource { + message Request { + ResourceType resource_type = 1; + string public_id = 2; + string modality = 3; + string date = 4; + string patient_id = 5; + string study_instance_uid = 6; + string series_instance_uid = 7; + string sop_instance_uid = 8; + } + message Response { + } +} + +message LookupAttachment { + message Request { + int64 id = 1; + int32 content_type = 2; + } + message Response { + bool found = 1; + FileInfo attachment = 2; + int64 revision = 3; + } +} + +message LookupGlobalProperty { + message Request { + string server_id = 1; + int32 property = 2; + } + message Response { + bool found = 1; + string value = 2; + } +} + +message LookupMetadata { + message Request { + int64 id = 1; + int32 metadata_type = 2; + } + message Response { + bool found = 1; + string value = 2; + int64 revision = 3; + } +} + +message LookupParent { + message Request { + int64 id = 1; + } + message Response { + bool found = 1; + int64 parent = 2; + } +} + +message LookupResource { + message Request { + string public_id = 1; + } + message Response { + bool found = 1; + int64 internal_id = 2; + ResourceType type = 3; + } +} + +message SelectPatientToRecycle { + message Request { + } + message Response { + bool found = 1; + int64 patient_id = 2; + } +} + +message SelectPatientToRecycleWithAvoid { + message Request { + int64 patient_id_to_avoid = 1; + } + message Response { + bool found = 1; + int64 patient_id = 2; + } +} + +message SetGlobalProperty { + message Request { + string server_id = 1; + int32 property = 2; + string value = 3; + } + message Response { + } +} + +message IncrementGlobalProperty { + message Request { + string server_id = 1; + int32 property = 2; + int64 increment = 3; + } + message Response { + int64 new_value = 1; + } +} + +message UpdateAndGetStatistics { + message Request { + } + message Response { + int64 patients_count = 1; + int64 studies_count = 2; + int64 series_count = 3; + int64 instances_count = 4; + int64 total_compressed_size = 5; + int64 total_uncompressed_size = 6; + } +} + +message ClearMainDicomTags { + message Request { + int64 id = 1; + } + message Response { + } +} + +message SetMetadata { + message Request { + int64 id = 1; + int32 metadata_type = 2; + string value = 3; + int64 revision = 4; + } + message Response { + } +} + +message SetProtectedPatient { + message Request { + int64 patient_id = 1; + bool protected_patient = 2; + } + message Response { + } +} + +message IsDiskSizeAbove { + message Request { + uint64 threshold = 1; + } + message Response { + bool result = 1; + } +} + +message LookupResources { + message Request { + repeated DatabaseConstraint lookup = 1; + ResourceType query_level = 2; + uint32 limit = 3; + bool retrieve_instances_ids = 4; + repeated string labels = 5; // New in Orthanc 1.12.0 + LabelsConstraintType labels_constraint = 6; // New in Orthanc 1.12.0 + } + message Response { + repeated string resources_ids = 1; + repeated string instances_ids = 2; // Only filled if "retrieve_instances" is true + } +} + +message CreateInstance { + message Request { + string patient = 1; + string study = 2; + string series = 3; + string instance = 4; + } + message Response { + bool is_new_instance = 1; + int64 instance_id = 2; + + // The fields below are only set if "is_new_instance" is true + bool is_new_patient = 3; + bool is_new_study = 4; + bool is_new_series = 5; + int64 patient_id = 6; + int64 study_id = 7; + int64 series_id = 8; + } +} + +message SetResourcesContent { + message Request { + message Tag { + int64 resource_id = 1; + bool is_identifier = 2; + uint32 group = 3; + uint32 element = 4; + string value = 5; + } + + message Metadata { + int64 resource_id = 1; + int32 metadata = 2; + string value = 3; + } + + repeated Tag tags = 1; + repeated Metadata metadata = 2; + } + message Response { + } +} + +message GetChildrenMetadata { + message Request { + int64 id = 1; + int32 metadata = 2; + } + message Response { + repeated string values = 1; + } +} + +message GetLastChangeIndex { + message Request { + } + message Response { + int64 result = 1; + } +} + +message LookupResourceAndParent { + message Request { + string public_id = 1; + } + message Response { + bool found = 1; + int64 id = 2; + ResourceType type = 3; + string parent_public_id = 4; // Only for study, series, or instance + } +} + +message AddLabel { + message Request { + int64 id = 1; + string label = 2; + } + message Response { + } +} + +message RemoveLabel { + message Request { + int64 id = 1; + string label = 2; + } + message Response { + } +} + +message ListLabels { + message Request { + bool single_resource = 1; + int64 id = 2; // Only if "single_resource" is "true" + } + message Response { + repeated string labels = 1; + } +} + +message TransactionRequest { + sfixed64 transaction = 1; + TransactionOperation operation = 2; + + Rollback.Request rollback = 100; + Commit.Request commit = 101; + AddAttachment.Request add_attachment = 102; + ClearChanges.Request clear_changes = 103; + ClearExportedResources.Request clear_exported_resources = 104; + DeleteAttachment.Request delete_attachment = 105; + DeleteMetadata.Request delete_metadata = 106; + DeleteResource.Request delete_resource = 107; + GetAllMetadata.Request get_all_metadata = 108; + GetAllPublicIds.Request get_all_public_ids = 109; + GetAllPublicIdsWithLimits.Request get_all_public_ids_with_limits = 110; + GetChanges.Request get_changes = 111; + GetChildrenInternalId.Request get_children_internal_id = 112; + GetChildrenPublicId.Request get_children_public_id = 113; + GetExportedResources.Request get_exported_resources = 114; + GetLastChange.Request get_last_change = 115; + GetLastExportedResource.Request get_last_exported_resource = 116; + GetMainDicomTags.Request get_main_dicom_tags = 117; + GetPublicId.Request get_public_id = 118; + GetResourcesCount.Request get_resources_count = 119; + GetResourceType.Request get_resource_type = 120; + GetTotalCompressedSize.Request get_total_compressed_size = 121; + GetTotalUncompressedSize.Request get_total_uncompressed_size = 122; + IsProtectedPatient.Request is_protected_patient = 123; + ListAvailableAttachments.Request list_available_attachments = 124; + LogChange.Request log_change = 125; + LogExportedResource.Request log_exported_resource = 126; + LookupAttachment.Request lookup_attachment = 127; + LookupGlobalProperty.Request lookup_global_property = 128; + LookupMetadata.Request lookup_metadata = 129; + LookupParent.Request lookup_parent = 130; + LookupResource.Request lookup_resource = 131; + SelectPatientToRecycle.Request select_patient_to_recycle = 132; + SelectPatientToRecycleWithAvoid.Request select_patient_to_recycle_with_avoid = 133; + SetGlobalProperty.Request set_global_property = 134; + ClearMainDicomTags.Request clear_main_dicom_tags = 135; + SetMetadata.Request set_metadata = 136; + SetProtectedPatient.Request set_protected_patient = 137; + IsDiskSizeAbove.Request is_disk_size_above = 138; + LookupResources.Request lookup_resources = 139; + CreateInstance.Request create_instance = 140; + SetResourcesContent.Request set_resources_content = 141; + GetChildrenMetadata.Request get_children_metadata = 142; + GetLastChangeIndex.Request get_last_change_index = 143; + LookupResourceAndParent.Request lookup_resource_and_parent = 144; + AddLabel.Request add_label = 145; + RemoveLabel.Request remove_label = 146; + ListLabels.Request list_labels = 147; + IncrementGlobalProperty.Request increment_global_property = 148; + UpdateAndGetStatistics.Request update_and_get_statistics = 149; +} + +message TransactionResponse { + Rollback.Response rollback = 100; + Commit.Response commit = 101; + AddAttachment.Response add_attachment = 102; + ClearChanges.Response clear_changes = 103; + ClearExportedResources.Response clear_exported_resources = 104; + DeleteAttachment.Response delete_attachment = 105; + DeleteMetadata.Response delete_metadata = 106; + DeleteResource.Response delete_resource = 107; + GetAllMetadata.Response get_all_metadata = 108; + GetAllPublicIds.Response get_all_public_ids = 109; + GetAllPublicIdsWithLimits.Response get_all_public_ids_with_limits = 110; + GetChanges.Response get_changes = 111; + GetChildrenInternalId.Response get_children_internal_id = 112; + GetChildrenPublicId.Response get_children_public_id = 113; + GetExportedResources.Response get_exported_resources = 114; + GetLastChange.Response get_last_change = 115; + GetLastExportedResource.Response get_last_exported_resource = 116; + GetMainDicomTags.Response get_main_dicom_tags = 117; + GetPublicId.Response get_public_id = 118; + GetResourcesCount.Response get_resources_count = 119; + GetResourceType.Response get_resource_type = 120; + GetTotalCompressedSize.Response get_total_compressed_size = 121; + GetTotalUncompressedSize.Response get_total_uncompressed_size = 122; + IsProtectedPatient.Response is_protected_patient = 123; + ListAvailableAttachments.Response list_available_attachments = 124; + LogChange.Response log_change = 125; + LogExportedResource.Response log_exported_resource = 126; + LookupAttachment.Response lookup_attachment = 127; + LookupGlobalProperty.Response lookup_global_property = 128; + LookupMetadata.Response lookup_metadata = 129; + LookupParent.Response lookup_parent = 130; + LookupResource.Response lookup_resource = 131; + SelectPatientToRecycle.Response select_patient_to_recycle = 132; + SelectPatientToRecycleWithAvoid.Response select_patient_to_recycle_with_avoid = 133; + SetGlobalProperty.Response set_global_property = 134; + ClearMainDicomTags.Response clear_main_dicom_tags = 135; + SetMetadata.Response set_metadata = 136; + SetProtectedPatient.Response set_protected_patient = 137; + IsDiskSizeAbove.Response is_disk_size_above = 138; + LookupResources.Response lookup_resources = 139; + CreateInstance.Response create_instance = 140; + SetResourcesContent.Response set_resources_content = 141; + GetChildrenMetadata.Response get_children_metadata = 142; + GetLastChangeIndex.Response get_last_change_index = 143; + LookupResourceAndParent.Response lookup_resource_and_parent = 144; + AddLabel.Response add_label = 145; + RemoveLabel.Response remove_label = 146; + ListLabels.Response list_labels = 147; + IncrementGlobalProperty.Response increment_global_property = 148; + UpdateAndGetStatistics.Response update_and_get_statistics = 149; +} + +enum RequestType { + REQUEST_DATABASE = 0; + REQUEST_TRANSACTION = 1; +} + +message Request { + RequestType type = 1; + DatabaseRequest database_request = 2; + TransactionRequest transaction_request = 3; +} + +message Response { + DatabaseResponse database_response = 2; + TransactionResponse transaction_response = 3; +} diff -r 82f73188b58d -r f18e46d7dbf8 Resources/SyncOrthancFolder.py --- a/Resources/SyncOrthancFolder.py Wed Feb 01 16:24:37 2023 +0100 +++ b/Resources/SyncOrthancFolder.py Tue Sep 24 15:04:21 2024 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # # This maintenance script updates the content of the "Orthanc" folder @@ -8,12 +8,18 @@ import multiprocessing import os import stat -import urllib2 +import sys + +if sys.version_info[0] < 3: + from urllib2 import urlopen +else: + from urllib.request import urlopen TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc') PLUGIN_SDK_VERSION_OLD = [ '0.9.5', '1.4.0', '1.5.2', '1.5.4' ] -PLUGIN_SDK_VERSION_NEW = [ '1.9.2' ] -REPOSITORY = 'https://hg.orthanc-server.com/orthanc/raw-file' +PLUGIN_SDK_VERSION_NEW = [ '1.9.2', '1.12.0', '1.12.3', '1.12.4' ] +HAS_PROTOCOL_BUFFERS = [ '1.12.0', '1.12.3', '1.12.4' ] +REPOSITORY = 'https://orthanc.uclouvain.be/hg/orthanc/raw-file' FILES = [ ('default', 'OrthancFramework/Resources/CMake/AutoGeneratedCode.cmake', 'CMake'), @@ -21,23 +27,19 @@ ('default', 'OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake', 'CMake'), ('default', 'OrthancFramework/Resources/CMake/DownloadPackage.cmake', 'CMake'), ('default', 'OrthancFramework/Resources/CMake/GoogleTestConfiguration.cmake', 'CMake'), + ('default', 'OrthancFramework/Resources/CMake/OpenSslConfiguration.cmake', 'CMake'), + ('default', 'OrthancFramework/Resources/CMake/ProtobufConfiguration.cmake', 'CMake'), ('default', 'OrthancFramework/Resources/EmbedResources.py', '.'), ('default', 'OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake', '.'), ('default', 'OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain32.cmake', '.'), ('default', 'OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain64.cmake', '.'), ('default', 'OrthancFramework/Resources/Toolchains/MinGWToolchain.cmake', '.'), - - # TODO - Replace branch "openssl-3.x" by "default" once it is reintegrated into mainline - ('openssl-3.x', 'OrthancServer/Plugins/Samples/Common/ExportedSymbolsPlugins.list', 'Plugins'), - ('openssl-3.x', 'OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp', 'Plugins'), - ('openssl-3.x', 'OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h', 'Plugins'), - ('openssl-3.x', 'OrthancServer/Plugins/Samples/Common/OrthancPluginException.h', 'Plugins'), - ('openssl-3.x', 'OrthancServer/Plugins/Samples/Common/OrthancPluginsExports.cmake', 'Plugins'), - ('openssl-3.x', 'OrthancServer/Plugins/Samples/Common/VersionScriptPlugins.map', 'Plugins'), - ('openssl-3.x', 'OrthancServer/Sources/Search/DatabaseConstraint.cpp', 'Databases'), - ('openssl-3.x', 'OrthancServer/Sources/Search/DatabaseConstraint.h', 'Databases'), - ('openssl-3.x', 'OrthancServer/Sources/Search/ISqlLookupFormatter.cpp', 'Databases'), - ('openssl-3.x', 'OrthancServer/Sources/Search/ISqlLookupFormatter.h', 'Databases'), + ('default', 'OrthancServer/Plugins/Samples/Common/ExportedSymbolsPlugins.list', 'Plugins'), + ('default', 'OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp', 'Plugins'), + ('default', 'OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h', 'Plugins'), + ('default', 'OrthancServer/Plugins/Samples/Common/OrthancPluginException.h', 'Plugins'), + ('default', 'OrthancServer/Plugins/Samples/Common/OrthancPluginsExports.cmake', 'Plugins'), + ('default', 'OrthancServer/Plugins/Samples/Common/VersionScriptPlugins.map', 'Plugins'), ] SDK = [ @@ -45,12 +47,18 @@ 'orthanc/OrthancCDatabasePlugin.h', ] +SDK_PROTOCOL_BUFFERS = [ + 'orthanc/OrthancCPlugin.h', + 'orthanc/OrthancCDatabasePlugin.h', + 'orthanc/OrthancDatabasePlugin.proto', +] + def Download(x): branch = x[0] source = x[1] target = os.path.join(TARGET, x[2]) - print target + print(target) try: os.makedirs(os.path.dirname(target)) @@ -60,8 +68,8 @@ url = '%s/%s/%s' % (REPOSITORY, branch, source) try: - with open(target, 'w') as f: - f.write(urllib2.urlopen(url).read()) + with open(target, 'wb') as f: + f.write(urlopen(url).read()) except Exception as e: raise Exception('Cannot download: %s' % url) @@ -82,7 +90,12 @@ ]) for version in PLUGIN_SDK_VERSION_NEW: - for f in SDK: + if version in HAS_PROTOCOL_BUFFERS: + files = SDK_PROTOCOL_BUFFERS + else: + files = SDK + + for f in files: commands.append([ 'Orthanc-%s' % version, 'OrthancServer/Plugins/Include/%s' % f, diff -r 82f73188b58d -r f18e46d7dbf8 SQLite/CMakeLists.txt --- a/SQLite/CMakeLists.txt Wed Feb 01 16:24:37 2023 +0100 +++ b/SQLite/CMakeLists.txt Tue Sep 24 15:04:21 2024 +0200 @@ -1,7 +1,9 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2021 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License @@ -22,15 +24,26 @@ set(ORTHANC_PLUGIN_VERSION "mainline") +# This is the preferred version of the Orthanc SDK for this plugin +set(ORTHANC_SDK_DEFAULT_VERSION "1.12.0") + +# This is the list of the versions of the Orthanc SDK against which +# this plugin will compile +set(ORTHANC_SDK_COMPATIBLE_VERSIONS "0.9.5" "1.4.0" "1.5.2" "1.5.4" "1.9.2" "1.12.0" "1.12.3" "1.12.4") + +# This is the minimal version of the Orthanc runtime that will provide +# best performance. If the version of the Orthanc runtime is below +# this minimal version, a warning message will be printed (but the +# plugin will still start). set(ORTHANC_OPTIMAL_VERSION_MAJOR 1) -set(ORTHANC_OPTIMAL_VERSION_MINOR 9) -set(ORTHANC_OPTIMAL_VERSION_REVISION 2) +set(ORTHANC_OPTIMAL_VERSION_MINOR 12) +set(ORTHANC_OPTIMAL_VERSION_REVISION 0) if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline") set(ORTHANC_FRAMEWORK_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_VERSION "1.9.6") + set(ORTHANC_FRAMEWORK_VERSION "1.12.4") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif() @@ -45,6 +58,23 @@ SQLITE_INSTALL_CUSTOM_DATA ${CMAKE_SOURCE_DIR}/Plugins/InstallCustomData.sql ) +if (EXISTS ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto) + add_custom_command( + COMMAND + ${PROTOC_EXECUTABLE} ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto --cpp_out=${AUTOGENERATED_DIR} -I${ORTHANC_SDK_ROOT}/orthanc/ + DEPENDS + ProtobufCompiler + ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto + OUTPUT + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.h + ) + + list(APPEND AUTOGENERATED_SOURCES + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ) +endif() + add_custom_target( AutogeneratedTarget DEPENDS @@ -100,3 +130,8 @@ set_target_properties(UnitTests PROPERTIES COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=0 ) + +if (COMMAND DefineSourceBasenameForTarget) + DefineSourceBasenameForTarget(OrthancSQLiteIndex) + DefineSourceBasenameForTarget(UnitTests) +endif() diff -r 82f73188b58d -r f18e46d7dbf8 SQLite/Plugins/IndexPlugin.cpp --- a/SQLite/Plugins/IndexPlugin.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/SQLite/Plugins/IndexPlugin.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -24,12 +26,18 @@ #include +#include + +#define ORTHANC_PLUGIN_NAME "sqlite-index" + extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { - if (!OrthancDatabases::InitializePlugin(context, "SQLite", true)) + GOOGLE_PROTOBUF_VERIFY_VERSION; + + if (!OrthancDatabases::InitializePlugin(context, ORTHANC_PLUGIN_NAME, "SQLite", true)) { return -1; } @@ -83,12 +91,13 @@ { LOG(WARNING) << "SQLite index is finalizing"; OrthancDatabases::IndexBackend::Finalize(); + google::protobuf::ShutdownProtobufLibrary(); } ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { - return "sqlite-index"; + return ORTHANC_PLUGIN_NAME; } diff -r 82f73188b58d -r f18e46d7dbf8 SQLite/Plugins/SQLiteIndex.cpp --- a/SQLite/Plugins/SQLiteIndex.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/SQLite/Plugins/SQLiteIndex.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -84,7 +86,9 @@ } - void SQLiteIndex::ConfigureDatabase(DatabaseManager& manager) + void SQLiteIndex::ConfigureDatabase(DatabaseManager& manager, + bool hasIdentifierTags, + const std::list& identifierTags) { uint32_t expectedVersion = 6; @@ -180,6 +184,23 @@ t.Commit(); } + + { + DatabaseManager::Transaction t(manager, TransactionType_ReadWrite); + + if (!t.GetDatabaseTransaction().DoesTableExist("Labels")) + { + t.GetDatabaseTransaction().ExecuteMultiLines( + "CREATE TABLE Labels(" + " id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE," + " label TEXT NOT NULL," + " PRIMARY KEY(id, label));" + "CREATE INDEX LabelsIndex1 ON Labels(id);" + "CREATE INDEX LabelsIndex2 ON Labels(label);"); + } + + t.Commit(); + } } @@ -253,4 +274,24 @@ } } } + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + bool SQLiteIndex::HasFindSupport() const + { + // TODO-FIND + return false; + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + void SQLiteIndex::ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, + DatabaseManager& manager, + const Orthanc::DatabasePluginMessages::Find_Request& request) + { + // TODO-FIND + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } +#endif } diff -r 82f73188b58d -r f18e46d7dbf8 SQLite/Plugins/SQLiteIndex.h --- a/SQLite/Plugins/SQLiteIndex.h Wed Feb 01 16:24:37 2023 +0100 +++ b/SQLite/Plugins/SQLiteIndex.h Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -44,7 +46,9 @@ virtual IDatabaseFactory* CreateDatabaseFactory() ORTHANC_OVERRIDE; - virtual void ConfigureDatabase(DatabaseManager& manager) ORTHANC_OVERRIDE; + virtual void ConfigureDatabase(DatabaseManager& manager, + bool hasIdentifierTags, + const std::list& identifierTags) ORTHANC_OVERRIDE; virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE { @@ -62,5 +66,21 @@ // New primitive since Orthanc 1.5.2 virtual int64_t GetLastChangeIndex(DatabaseManager& manager) ORTHANC_OVERRIDE; + + // New primitive since Orthanc 1.12.0 + virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE + { + return true; + } + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + virtual bool HasFindSupport() const ORTHANC_OVERRIDE; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) + virtual void ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response, + DatabaseManager& manager, + const Orthanc::DatabasePluginMessages::Find_Request& request) ORTHANC_OVERRIDE; +#endif }; } diff -r 82f73188b58d -r f18e46d7dbf8 SQLite/UnitTests/UnitTestsMain.cpp --- a/SQLite/UnitTests/UnitTestsMain.cpp Wed Feb 01 16:24:37 2023 +0100 +++ b/SQLite/UnitTests/UnitTestsMain.cpp Tue Sep 24 15:04:21 2024 +0200 @@ -2,7 +2,9 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2021 Osimis S.A., Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License @@ -34,28 +36,30 @@ TEST(SQLiteIndex, Lock) { + std::list identifierTags; + { // No locking if using memory backend OrthancDatabases::SQLiteIndex db1(NULL); - std::unique_ptr manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1)); + std::unique_ptr manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1, false, identifierTags)); OrthancDatabases::SQLiteIndex db2(NULL); - std::unique_ptr manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2)); + std::unique_ptr manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2, false, identifierTags)); } Orthanc::SystemToolbox::RemoveFile("index.db"); { OrthancDatabases::SQLiteIndex db1(NULL, "index.db"); - std::unique_ptr manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1)); + std::unique_ptr manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1, false, identifierTags)); OrthancDatabases::SQLiteIndex db2(NULL, "index.db"); - ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2), Orthanc::OrthancException); + ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2, false, identifierTags), Orthanc::OrthancException); } { OrthancDatabases::SQLiteIndex db3(NULL, "index.db"); - std::unique_ptr manager3(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3)); + std::unique_ptr manager3(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3, false, identifierTags)); } } diff -r 82f73188b58d -r f18e46d7dbf8 TODO --- a/TODO Wed Feb 01 16:24:37 2023 +0100 +++ b/TODO Tue Sep 24 15:04:21 2024 +0200 @@ -10,6 +10,15 @@ * Performance of joins in LookupResources: Create cached statement for LookupResources, that are grouped to search up to, say, 10 tags, instead of recompiling for each request +* Do not log "DatabaseCannotSerialize" errors in the plugin but only + in Orthanc after all retries have been made. +* Try to avoid the use of temporary tables: + https://discourse.orthanc-server.org/t/image-insert-are-too-slow-databse-performance-too-poor/3820 + + +* Implement "large queries" for: + - updating all metadata of a resource at once + - update all maindicomtags of 4 resource levels at once --------------------- @@ -18,7 +27,7 @@ * Implement the "StoreDicom" option from the default filesystem storage area to run MySQL/PostgreSQL storage in index-only mode: - https://book.orthanc-server.com/contributing.html + https://orthanc.uclouvain.be/book/contributing.html * Performance: Add a pool of connections to PostgreSQL/MySQL, as already done in the database index plugins @@ -27,14 +36,23 @@ PostgreSQL ---------- -* reported issue to validate: uploading lots of files through HTTP with: - - 1 client and IndexConnectionsCount==1 took 100s - - 2 client and IndexConnectionsCount==2 took 600s (lots of transaction replayed) +* Check if we can force the schema that is used. By default, Orthanc + is using the 'public' schema but, after a wrong command, we have seen + a DB where there was a 'AttachedFiles' table in the public schema and another one in a + 'MyPacs' schema and Orthanc was actually using the 'MyPacs.AttachedFiles' table !!! + Orthanc was then seeing only the most recent attached files !!! + +* Have a separate "thread" to UpdateStatistics to avoid large computations ? + * Seems Orthanc might deadlock when there are plenty of conflicting transactions: https://groups.google.com/g/orthanc-users/c/xQelEcKqL9U/m/HsvxwlkvAQAJ https://groups.google.com/g/orthanc-users/c/1bkClfZ0KBA/m/s4AlwVh3CQAJ +* use ReadCommited mode by default +* use 40 index connection count by default (not 50 because, by default, PG has 100 connections and we want at least + a few connections to remain available for sys admins if e.g, 2 Orthanc instances are running on the same server) + ----- MySQL ----- @@ -44,6 +62,7 @@ * MySQL performance => implement GlobalProperty_GetTotalSizeIsFast: https://groups.google.com/d/msg/orthanc-users/kSR4a110zDo/D7e4ITR8BwAJ + https://discourse.orthanc-server.org/t/when-running-housekeeping-mysqld-exe-searching-and-dicom-transfer-is-slow/4921/9 * Add index to speed up wildcard search, as already done in PostgreSQL: @@ -51,3 +70,10 @@ ALTER TABLE table ADD FULLTEXT index_name(column1); - https://dev.mysql.com/doc/refman/8.0/en/index-btree-hash.html + + +---- +ODBC +---- + +* https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=224 \ No newline at end of file