6795 lines
208 KiB
C++
6795 lines
208 KiB
C++
/**
|
|
* Orthanc - A Lightweight, RESTful DICOM Store
|
|
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
|
|
* Department, University Hospital of Liege, Belgium
|
|
* Copyright (C) 2017-2023 Osimis S.A., Belgium
|
|
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
|
|
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
|
|
*
|
|
* This program is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
|
|
#include "../../Sources/PrecompiledHeadersServer.h"
|
|
#include "OrthancPlugins.h"
|
|
|
|
#if ORTHANC_ENABLE_PLUGINS != 1
|
|
#error The plugin support is disabled
|
|
#endif
|
|
|
|
#if !defined(DCMTK_VERSION_NUMBER)
|
|
# error The macro DCMTK_VERSION_NUMBER must be defined
|
|
#endif
|
|
|
|
|
|
#include "../../../OrthancFramework/Sources/Compression/GzipCompressor.h"
|
|
#include "../../../OrthancFramework/Sources/Compression/ZlibCompressor.h"
|
|
#include "../../../OrthancFramework/Sources/DicomFormat/DicomArray.h"
|
|
#include "../../../OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.h"
|
|
#include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
|
|
#include "../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h"
|
|
#include "../../../OrthancFramework/Sources/FileStorage/PluginStorageAreaAdapter.h"
|
|
#include "../../../OrthancFramework/Sources/HttpServer/HttpServer.h"
|
|
#include "../../../OrthancFramework/Sources/HttpServer/HttpToolbox.h"
|
|
#include "../../../OrthancFramework/Sources/Images/Image.h"
|
|
#include "../../../OrthancFramework/Sources/Images/ImageProcessing.h"
|
|
#include "../../../OrthancFramework/Sources/Images/JpegReader.h"
|
|
#include "../../../OrthancFramework/Sources/Images/JpegWriter.h"
|
|
#include "../../../OrthancFramework/Sources/Images/PngReader.h"
|
|
#include "../../../OrthancFramework/Sources/Images/PngWriter.h"
|
|
#include "../../../OrthancFramework/Sources/Logging.h"
|
|
#include "../../../OrthancFramework/Sources/Lua/LuaFunctionCall.h"
|
|
#include "../../../OrthancFramework/Sources/MallocMemoryBuffer.h"
|
|
#include "../../../OrthancFramework/Sources/MetricsRegistry.h"
|
|
#include "../../../OrthancFramework/Sources/OrthancException.h"
|
|
#include "../../../OrthancFramework/Sources/SerializationToolbox.h"
|
|
#include "../../../OrthancFramework/Sources/Toolbox.h"
|
|
#include "../../Sources/Database/VoidDatabaseListener.h"
|
|
#include "../../Sources/OrthancConfiguration.h"
|
|
#include "../../Sources/OrthancFindRequestHandler.h"
|
|
#include "../../Sources/Search/HierarchicalMatcher.h"
|
|
#include "../../Sources/ServerContext.h"
|
|
#include "../../Sources/ServerToolbox.h"
|
|
#include "OrthancPluginDatabase.h"
|
|
#include "OrthancPluginDatabaseV3.h"
|
|
#include "OrthancPluginDatabaseV4.h"
|
|
#include "PluginMemoryBuffer32.h"
|
|
#include "PluginsEnumerations.h"
|
|
#include "PluginsJob.h"
|
|
|
|
#include <boost/math/special_functions/round.hpp>
|
|
#include <boost/regex.hpp>
|
|
#include <dcmtk/dcmdata/dcdicent.h>
|
|
#include <dcmtk/dcmnet/dimse.h>
|
|
|
|
#define ERROR_MESSAGE_64BIT "A 64bit version of the Orthanc SDK is necessary to use buffers > 4GB, but is currently not available"
|
|
|
|
|
|
namespace Orthanc
|
|
{
|
|
class OrthancPlugins::IDicomInstance : public boost::noncopyable
|
|
{
|
|
public:
|
|
virtual ~IDicomInstance()
|
|
{
|
|
}
|
|
|
|
virtual bool CanBeFreed() const = 0;
|
|
|
|
virtual const DicomInstanceToStore& GetInstance() const = 0;
|
|
};
|
|
|
|
|
|
class OrthancPlugins::DicomInstanceFromCallback : public IDicomInstance
|
|
{
|
|
private:
|
|
const DicomInstanceToStore& instance_;
|
|
|
|
public:
|
|
explicit DicomInstanceFromCallback(const DicomInstanceToStore& instance) :
|
|
instance_(instance)
|
|
{
|
|
}
|
|
|
|
virtual bool CanBeFreed() const ORTHANC_OVERRIDE
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
|
|
{
|
|
return instance_;
|
|
};
|
|
};
|
|
|
|
|
|
class OrthancPlugins::DicomInstanceFromBuffer : public IDicomInstance
|
|
{
|
|
private:
|
|
std::string buffer_;
|
|
std::unique_ptr<DicomInstanceToStore> instance_;
|
|
|
|
void Setup(const void* buffer,
|
|
size_t size)
|
|
{
|
|
buffer_.assign(reinterpret_cast<const char*>(buffer), size);
|
|
|
|
instance_.reset(DicomInstanceToStore::CreateFromBuffer(buffer_));
|
|
instance_->SetOrigin(DicomInstanceOrigin::FromPlugins());
|
|
}
|
|
|
|
public:
|
|
DicomInstanceFromBuffer(const void* buffer,
|
|
size_t size)
|
|
{
|
|
Setup(buffer, size);
|
|
}
|
|
|
|
explicit DicomInstanceFromBuffer(const std::string& buffer)
|
|
{
|
|
Setup(buffer.empty() ? NULL : buffer.c_str(), buffer.size());
|
|
}
|
|
|
|
virtual bool CanBeFreed() const ORTHANC_OVERRIDE
|
|
{
|
|
return true;
|
|
}
|
|
|
|
virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
|
|
{
|
|
return *instance_;
|
|
};
|
|
};
|
|
|
|
|
|
class OrthancPlugins::DicomInstanceFromParsed : public IDicomInstance
|
|
{
|
|
private:
|
|
std::unique_ptr<ParsedDicomFile> parsed_;
|
|
std::unique_ptr<DicomInstanceToStore> instance_;
|
|
|
|
void Setup(ParsedDicomFile* parsed)
|
|
{
|
|
parsed_.reset(parsed);
|
|
|
|
if (parsed_.get() == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
else
|
|
{
|
|
instance_.reset(DicomInstanceToStore::CreateFromParsedDicomFile(*parsed_));
|
|
instance_->SetOrigin(DicomInstanceOrigin::FromPlugins());
|
|
}
|
|
}
|
|
|
|
public:
|
|
explicit DicomInstanceFromParsed(IDicomTranscoder::DicomImage& transcoded)
|
|
{
|
|
Setup(transcoded.ReleaseAsParsedDicomFile());
|
|
}
|
|
|
|
explicit DicomInstanceFromParsed(ParsedDicomFile* parsed /* takes ownership */)
|
|
{
|
|
Setup(parsed);
|
|
}
|
|
|
|
virtual bool CanBeFreed() const ORTHANC_OVERRIDE
|
|
{
|
|
return true;
|
|
}
|
|
|
|
virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
|
|
{
|
|
return *instance_;
|
|
};
|
|
};
|
|
|
|
|
|
class OrthancPlugins::WebDavCollection : public IWebDavBucket
|
|
{
|
|
private:
|
|
PluginsErrorDictionary& errorDictionary_;
|
|
std::string uri_;
|
|
OrthancPluginWebDavIsExistingFolderCallback isExistingFolder_;
|
|
OrthancPluginWebDavListFolderCallback listFolder_;
|
|
OrthancPluginWebDavRetrieveFileCallback retrieveFile_;
|
|
OrthancPluginWebDavStoreFileCallback storeFile_;
|
|
OrthancPluginWebDavCreateFolderCallback createFolder_;
|
|
OrthancPluginWebDavDeleteItemCallback deleteItem_;
|
|
void* payload_;
|
|
|
|
class PathHelper : public boost::noncopyable
|
|
{
|
|
private:
|
|
std::vector<const char*> items_;
|
|
|
|
public:
|
|
explicit PathHelper(const std::vector<std::string>& path)
|
|
{
|
|
items_.resize(path.size());
|
|
for (size_t i = 0; i < path.size(); i++)
|
|
{
|
|
items_[i] = path[i].c_str();
|
|
}
|
|
}
|
|
|
|
uint32_t GetSize() const
|
|
{
|
|
return static_cast<uint32_t>(items_.size());
|
|
}
|
|
|
|
const char* const* GetItems() const
|
|
{
|
|
return (items_.empty() ? NULL : &items_[0]);
|
|
}
|
|
};
|
|
|
|
|
|
static MimeType ParseMimeType(const char* mimeType)
|
|
{
|
|
MimeType mime;
|
|
if (LookupMimeType(mime, mimeType))
|
|
{
|
|
return mime;
|
|
}
|
|
else
|
|
{
|
|
LOG(WARNING) << "Unknown MIME type in plugin: " << mimeType;
|
|
return MimeType_Binary;
|
|
}
|
|
}
|
|
|
|
static OrthancPluginErrorCode AddFile(
|
|
OrthancPluginWebDavCollection* collection,
|
|
const char* displayName,
|
|
uint64_t contentSize,
|
|
const char* mimeType,
|
|
const char* creationTime)
|
|
{
|
|
try
|
|
{
|
|
std::unique_ptr<File> f(new File(displayName));
|
|
f->SetCreationTime(boost::posix_time::from_iso_string(creationTime));
|
|
f->SetContentLength(contentSize);
|
|
|
|
if (mimeType == NULL ||
|
|
std::string(mimeType).empty())
|
|
{
|
|
f->SetMimeType(SystemToolbox::AutodetectMimeType(displayName));
|
|
}
|
|
else
|
|
{
|
|
f->SetMimeType(ParseMimeType(mimeType));
|
|
}
|
|
|
|
reinterpret_cast<Collection*>(collection)->AddResource(f.release());
|
|
return OrthancPluginErrorCode_Success;
|
|
}
|
|
catch (OrthancException& e)
|
|
{
|
|
return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
|
|
}
|
|
catch (...)
|
|
{
|
|
return OrthancPluginErrorCode_InternalError;
|
|
}
|
|
}
|
|
|
|
static OrthancPluginErrorCode AddFolder(
|
|
OrthancPluginWebDavCollection* collection,
|
|
const char* displayName,
|
|
const char* creationTime)
|
|
{
|
|
try
|
|
{
|
|
std::unique_ptr<Folder> f(new Folder(displayName));
|
|
f->SetCreationTime(boost::posix_time::from_iso_string(creationTime));
|
|
reinterpret_cast<Collection*>(collection)->AddResource(f.release());
|
|
return OrthancPluginErrorCode_Success;
|
|
}
|
|
catch (OrthancException& e)
|
|
{
|
|
return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
|
|
}
|
|
catch (boost::bad_lexical_cast&)
|
|
{
|
|
LOG(ERROR) << "Presumably ill-formed date in the plugin";
|
|
return OrthancPluginErrorCode_ParameterOutOfRange;
|
|
}
|
|
catch (...)
|
|
{
|
|
return OrthancPluginErrorCode_InternalError;
|
|
}
|
|
}
|
|
|
|
|
|
class ContentTarget : public boost::noncopyable
|
|
{
|
|
private:
|
|
bool isSent_;
|
|
MimeType& mime_;
|
|
std::string& content_;
|
|
boost::posix_time::ptime& modificationTime_;
|
|
|
|
public:
|
|
ContentTarget(const std::string& displayName,
|
|
MimeType& mime,
|
|
std::string& content,
|
|
boost::posix_time::ptime& modificationTime) :
|
|
isSent_(false),
|
|
mime_(mime),
|
|
content_(content),
|
|
modificationTime_(modificationTime)
|
|
{
|
|
mime = SystemToolbox::AutodetectMimeType(displayName);
|
|
}
|
|
|
|
bool IsSent() const
|
|
{
|
|
return isSent_;
|
|
}
|
|
|
|
static OrthancPluginErrorCode RetrieveFile(
|
|
OrthancPluginWebDavCollection* collection,
|
|
const void* data,
|
|
uint64_t size,
|
|
const char* mimeType,
|
|
const char* creationTime)
|
|
{
|
|
ContentTarget& target = *reinterpret_cast<ContentTarget*>(collection);
|
|
|
|
if (target.isSent_)
|
|
{
|
|
return OrthancPluginErrorCode_BadSequenceOfCalls;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
target.isSent_ = true;
|
|
|
|
if (mimeType != NULL &&
|
|
!std::string(mimeType).empty())
|
|
{
|
|
target.mime_ = ParseMimeType(mimeType);
|
|
}
|
|
|
|
target.content_.assign(reinterpret_cast<const char*>(data), size);
|
|
target.modificationTime_ = boost::posix_time::from_iso_string(creationTime);
|
|
return OrthancPluginErrorCode_Success;
|
|
}
|
|
catch (Orthanc::OrthancException& e)
|
|
{
|
|
return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
|
|
}
|
|
catch (boost::bad_lexical_cast&)
|
|
{
|
|
LOG(ERROR) << "Presumably ill-formed date in the plugin";
|
|
return OrthancPluginErrorCode_ParameterOutOfRange;
|
|
}
|
|
catch (...)
|
|
{
|
|
return OrthancPluginErrorCode_InternalError;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
public:
|
|
WebDavCollection(PluginsErrorDictionary& errorDictionary,
|
|
const _OrthancPluginRegisterWebDavCollection& p) :
|
|
errorDictionary_(errorDictionary),
|
|
uri_(p.uri),
|
|
isExistingFolder_(p.isExistingFolder),
|
|
listFolder_(p.listFolder),
|
|
retrieveFile_(p.retrieveFile),
|
|
storeFile_(p.storeFile),
|
|
createFolder_(p.createFolder),
|
|
deleteItem_(p.deleteItem),
|
|
payload_(p.payload)
|
|
{
|
|
}
|
|
|
|
const std::string& GetUri() const
|
|
{
|
|
return uri_;
|
|
}
|
|
|
|
virtual bool IsExistingFolder(const std::vector<std::string>& path)
|
|
{
|
|
PathHelper helper(path);
|
|
|
|
uint8_t isExisting;
|
|
OrthancPluginErrorCode code = isExistingFolder_(&isExisting, helper.GetSize(), helper.GetItems(), payload_);
|
|
|
|
if (code == OrthancPluginErrorCode_Success)
|
|
{
|
|
return (isExisting != 0);
|
|
}
|
|
else
|
|
{
|
|
errorDictionary_.LogError(code, true);
|
|
throw OrthancException(static_cast<ErrorCode>(code));
|
|
}
|
|
}
|
|
|
|
virtual bool ListCollection(Collection& collection,
|
|
const std::vector<std::string>& path)
|
|
{
|
|
PathHelper helper(path);
|
|
|
|
uint8_t isExisting;
|
|
OrthancPluginErrorCode code = listFolder_(&isExisting, reinterpret_cast<OrthancPluginWebDavCollection*>(&collection),
|
|
AddFile, AddFolder, helper.GetSize(), helper.GetItems(), payload_);
|
|
|
|
if (code == OrthancPluginErrorCode_Success)
|
|
{
|
|
return (isExisting != 0);
|
|
}
|
|
else
|
|
{
|
|
errorDictionary_.LogError(code, true);
|
|
throw OrthancException(static_cast<ErrorCode>(code));
|
|
}
|
|
}
|
|
|
|
virtual bool GetFileContent(MimeType& mime,
|
|
std::string& content,
|
|
boost::posix_time::ptime& modificationTime,
|
|
const std::vector<std::string>& path)
|
|
{
|
|
PathHelper helper(path);
|
|
|
|
ContentTarget target(path.back(), mime, content, modificationTime);
|
|
OrthancPluginErrorCode code = retrieveFile_(
|
|
reinterpret_cast<OrthancPluginWebDavCollection*>(&target),
|
|
ContentTarget::RetrieveFile, helper.GetSize(), helper.GetItems(), payload_);
|
|
|
|
if (code == OrthancPluginErrorCode_Success)
|
|
{
|
|
return target.IsSent();
|
|
}
|
|
else
|
|
{
|
|
errorDictionary_.LogError(code, true);
|
|
throw OrthancException(static_cast<ErrorCode>(code));
|
|
}
|
|
}
|
|
|
|
virtual bool StoreFile(const std::string& content,
|
|
const std::vector<std::string>& path)
|
|
{
|
|
PathHelper helper(path);
|
|
|
|
uint8_t isReadOnly;
|
|
OrthancPluginErrorCode code = storeFile_(&isReadOnly, helper.GetSize(), helper.GetItems(),
|
|
content.empty() ? NULL : content.c_str(), content.size(), payload_);
|
|
|
|
if (code == OrthancPluginErrorCode_Success)
|
|
{
|
|
return (isReadOnly != 0);
|
|
}
|
|
else
|
|
{
|
|
errorDictionary_.LogError(code, true);
|
|
throw OrthancException(static_cast<ErrorCode>(code));
|
|
}
|
|
}
|
|
|
|
virtual bool CreateFolder(const std::vector<std::string>& path)
|
|
{
|
|
PathHelper helper(path);
|
|
|
|
uint8_t isReadOnly;
|
|
OrthancPluginErrorCode code = createFolder_(&isReadOnly, helper.GetSize(), helper.GetItems(), payload_);
|
|
|
|
if (code == OrthancPluginErrorCode_Success)
|
|
{
|
|
return (isReadOnly != 0);
|
|
}
|
|
else
|
|
{
|
|
errorDictionary_.LogError(code, true);
|
|
throw OrthancException(static_cast<ErrorCode>(code));
|
|
}
|
|
}
|
|
|
|
virtual bool DeleteItem(const std::vector<std::string>& path)
|
|
{
|
|
PathHelper helper(path);
|
|
|
|
uint8_t isReadOnly;
|
|
OrthancPluginErrorCode code = deleteItem_(&isReadOnly, helper.GetSize(), helper.GetItems(), payload_);
|
|
|
|
if (code == OrthancPluginErrorCode_Success)
|
|
{
|
|
return (isReadOnly != 0);
|
|
}
|
|
else
|
|
{
|
|
errorDictionary_.LogError(code, true);
|
|
throw OrthancException(static_cast<ErrorCode>(code));
|
|
}
|
|
}
|
|
|
|
virtual void Start()
|
|
{
|
|
}
|
|
|
|
virtual void Stop()
|
|
{
|
|
}
|
|
};
|
|
|
|
|
|
static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer* target,
|
|
const void* data,
|
|
size_t size)
|
|
{
|
|
PluginMemoryBuffer32 buffer;
|
|
buffer.Assign(data, size);
|
|
buffer.Release(target);
|
|
}
|
|
|
|
|
|
static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer* target,
|
|
const std::string& str)
|
|
{
|
|
PluginMemoryBuffer32 buffer;
|
|
buffer.Assign(str);
|
|
buffer.Release(target);
|
|
}
|
|
|
|
|
|
static char* CopyString(const std::string& str)
|
|
{
|
|
char *result = reinterpret_cast<char*>(malloc(str.size() + 1));
|
|
if (result == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NotEnoughMemory);
|
|
}
|
|
|
|
if (!str.empty())
|
|
{
|
|
memcpy(result, str.c_str(), str.size());
|
|
}
|
|
|
|
result[str.size()] = '\0'; // Add the null terminator of the string
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
static void CopyDictionary(PluginMemoryBuffer32& target,
|
|
const std::map<std::string, std::string>& dictionary)
|
|
{
|
|
Json::Value json = Json::objectValue;
|
|
|
|
for (HttpClient::HttpHeaders::const_iterator
|
|
it = dictionary.begin(); it != dictionary.end(); ++it)
|
|
{
|
|
json[it->first] = it->second;
|
|
}
|
|
|
|
target.Assign(json.toStyledString());
|
|
}
|
|
|
|
|
|
namespace
|
|
{
|
|
static IMemoryBuffer* GetRangeFromWhole(std::unique_ptr<IMemoryBuffer>& whole,
|
|
uint64_t start /* inclusive */,
|
|
uint64_t end /* exclusive */)
|
|
{
|
|
if (start > end)
|
|
{
|
|
throw OrthancException(ErrorCode_BadRange);
|
|
}
|
|
else if (start == end)
|
|
{
|
|
return new PluginMemoryBuffer64; // Empty
|
|
}
|
|
else
|
|
{
|
|
if (start == 0 &&
|
|
end == whole->GetSize())
|
|
{
|
|
return whole.release();
|
|
}
|
|
else if (end > whole->GetSize())
|
|
{
|
|
throw OrthancException(ErrorCode_BadRange);
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<PluginMemoryBuffer64> range(new PluginMemoryBuffer64);
|
|
range->Assign(reinterpret_cast<const char*>(whole->GetData()) + start, end - start);
|
|
assert(range->GetSize() > 0);
|
|
|
|
return range.release();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// "legacy" storage plugins don't store customData -> derive from IStorageArea
|
|
class StorageAreaWithoutCustomData : public IStorageArea
|
|
{
|
|
private:
|
|
OrthancPluginStorageCreate create_;
|
|
OrthancPluginStorageRemove remove_;
|
|
PluginsErrorDictionary& errorDictionary_;
|
|
|
|
protected:
|
|
PluginsErrorDictionary& GetErrorDictionary() const
|
|
{
|
|
return errorDictionary_;
|
|
}
|
|
|
|
public:
|
|
StorageAreaWithoutCustomData(OrthancPluginStorageCreate create,
|
|
OrthancPluginStorageRemove remove,
|
|
PluginsErrorDictionary& errorDictionary) :
|
|
create_(create),
|
|
remove_(remove),
|
|
errorDictionary_(errorDictionary)
|
|
{
|
|
if (create_ == NULL ||
|
|
remove_ == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin, "Storage area plugin doesn't implement all the required primitives");
|
|
}
|
|
}
|
|
|
|
virtual void Create(const std::string& uuid,
|
|
const void* content,
|
|
size_t size,
|
|
FileContentType type) ORTHANC_OVERRIDE
|
|
{
|
|
OrthancPluginErrorCode error = create_
|
|
(uuid.c_str(), content, size, Plugins::Convert(type));
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
errorDictionary_.LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
|
|
virtual void Remove(const std::string& uuid,
|
|
FileContentType type) ORTHANC_OVERRIDE
|
|
{
|
|
OrthancPluginErrorCode error = remove_
|
|
(uuid.c_str(), Plugins::Convert(type));
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
errorDictionary_.LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
class PluginStorageAreaV1 : public StorageAreaWithoutCustomData
|
|
{
|
|
private:
|
|
OrthancPluginStorageRead read_;
|
|
OrthancPluginFree free_;
|
|
|
|
public:
|
|
PluginStorageAreaV1(const _OrthancPluginRegisterStorageArea& callbacks,
|
|
PluginsErrorDictionary& errorDictionary) :
|
|
StorageAreaWithoutCustomData(callbacks.create, callbacks.remove, errorDictionary),
|
|
read_(callbacks.read),
|
|
free_(callbacks.free)
|
|
{
|
|
if (read_ == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin, "Storage area plugin doesn't implement the \"Read\" primitive");
|
|
}
|
|
}
|
|
|
|
virtual IMemoryBuffer* ReadRange(const std::string& uuid,
|
|
FileContentType type,
|
|
uint64_t start /* inclusive */,
|
|
uint64_t end /* exclusive */) ORTHANC_OVERRIDE
|
|
{
|
|
std::unique_ptr<IMemoryBuffer> whole(new MallocMemoryBuffer);
|
|
|
|
void* buffer = NULL;
|
|
int64_t size = 0;
|
|
|
|
OrthancPluginErrorCode error = read_(&buffer, &size, uuid.c_str(), Plugins::Convert(type));
|
|
|
|
if (error == OrthancPluginErrorCode_Success)
|
|
{
|
|
// Beware that the buffer must be unallocated by the "free_" function provided by the plugin,
|
|
// so we cannot use "PluginMemoryBuffer64"
|
|
dynamic_cast<MallocMemoryBuffer&>(*whole).Assign(buffer, size, free_);
|
|
|
|
return GetRangeFromWhole(whole, start, end);
|
|
}
|
|
else
|
|
{
|
|
GetErrorDictionary().LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
|
|
virtual bool HasEfficientReadRange() const ORTHANC_OVERRIDE
|
|
{
|
|
return false;
|
|
}
|
|
};
|
|
|
|
|
|
// New in Orthanc 1.9.0
|
|
class PluginStorageAreaV2 : public StorageAreaWithoutCustomData
|
|
{
|
|
private:
|
|
OrthancPluginStorageReadWhole readWhole_;
|
|
OrthancPluginStorageReadRange readRange_;
|
|
|
|
public:
|
|
PluginStorageAreaV2(const _OrthancPluginRegisterStorageArea2& callbacks,
|
|
PluginsErrorDictionary& errorDictionary) :
|
|
StorageAreaWithoutCustomData(callbacks.create, callbacks.remove, errorDictionary),
|
|
readWhole_(callbacks.readWhole),
|
|
readRange_(callbacks.readRange)
|
|
{
|
|
if (readWhole_ == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin, "Storage area plugin doesn't implement the \"ReadWhole\" primitive");
|
|
}
|
|
}
|
|
|
|
virtual IMemoryBuffer* ReadRange(const std::string& uuid,
|
|
FileContentType type,
|
|
uint64_t start /* inclusive */,
|
|
uint64_t end /* exclusive */) ORTHANC_OVERRIDE
|
|
{
|
|
if (readRange_ == NULL)
|
|
{
|
|
std::unique_ptr<IMemoryBuffer> whole(new PluginMemoryBuffer64);
|
|
|
|
OrthancPluginErrorCode error = readWhole_(dynamic_cast<PluginMemoryBuffer64&>(*whole).GetObject(),
|
|
uuid.c_str(), Plugins::Convert(type));
|
|
|
|
if (error == OrthancPluginErrorCode_Success)
|
|
{
|
|
return GetRangeFromWhole(whole, start, end);
|
|
}
|
|
else
|
|
{
|
|
GetErrorDictionary().LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<PluginMemoryBuffer64> buffer(new PluginMemoryBuffer64);
|
|
|
|
if (start > end)
|
|
{
|
|
throw OrthancException(ErrorCode_BadRange);
|
|
}
|
|
else if (start == end)
|
|
{
|
|
return buffer.release();
|
|
}
|
|
else
|
|
{
|
|
buffer->Resize(end - start);
|
|
assert(buffer->GetSize() > 0);
|
|
|
|
OrthancPluginErrorCode error =
|
|
readRange_(buffer->GetObject(), uuid.c_str(), Plugins::Convert(type), start);
|
|
|
|
if (error == OrthancPluginErrorCode_Success)
|
|
{
|
|
return buffer.release();
|
|
}
|
|
else
|
|
{
|
|
GetErrorDictionary().LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual bool HasEfficientReadRange() const ORTHANC_OVERRIDE
|
|
{
|
|
return (readRange_ != NULL);
|
|
}
|
|
};
|
|
|
|
|
|
// New in Orthanc 1.12.8
|
|
class PluginStorageAreaV3 : public IPluginStorageArea
|
|
{
|
|
private:
|
|
OrthancPluginStorageCreate2 create_;
|
|
OrthancPluginStorageReadRange2 readRange_;
|
|
OrthancPluginStorageRemove2 remove_;
|
|
PluginsErrorDictionary& errorDictionary_;
|
|
|
|
protected:
|
|
PluginsErrorDictionary& GetErrorDictionary() const
|
|
{
|
|
return errorDictionary_;
|
|
}
|
|
|
|
public:
|
|
PluginStorageAreaV3(const _OrthancPluginRegisterStorageArea3& callbacks,
|
|
PluginsErrorDictionary& errorDictionary) :
|
|
create_(callbacks.create),
|
|
readRange_(callbacks.readRange),
|
|
remove_(callbacks.remove),
|
|
errorDictionary_(errorDictionary)
|
|
{
|
|
if (create_ == NULL ||
|
|
readRange_ == NULL ||
|
|
remove_ == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin, "Storage area plugin does not implement all the required primitives (create, remove, and readRange)");
|
|
}
|
|
}
|
|
|
|
virtual void Create(std::string& customData /* out */,
|
|
const std::string& uuid,
|
|
const void* content,
|
|
size_t size,
|
|
FileContentType type,
|
|
CompressionType compression,
|
|
const DicomInstanceToStore* dicomInstance /* can be NULL if not a DICOM instance */) ORTHANC_OVERRIDE
|
|
{
|
|
PluginMemoryBuffer32 customDataBuffer;
|
|
OrthancPluginErrorCode error;
|
|
|
|
if (dicomInstance != NULL)
|
|
{
|
|
Orthanc::OrthancPlugins::DicomInstanceFromCallback wrapped(*dicomInstance);
|
|
error = create_(customDataBuffer.GetObject(), uuid.c_str(), content, size, Plugins::Convert(type), Plugins::Convert(compression),
|
|
reinterpret_cast<OrthancPluginDicomInstance*>(&wrapped));
|
|
}
|
|
else
|
|
{
|
|
error = create_(customDataBuffer.GetObject(), uuid.c_str(), content, size, Plugins::Convert(type), Plugins::Convert(compression), NULL);
|
|
}
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
errorDictionary_.LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
else
|
|
{
|
|
customDataBuffer.MoveToString(customData);
|
|
}
|
|
}
|
|
|
|
virtual void Remove(const std::string& uuid,
|
|
FileContentType type,
|
|
const std::string& customData) ORTHANC_OVERRIDE
|
|
{
|
|
OrthancPluginErrorCode error = remove_(uuid.c_str(), Plugins::Convert(type),
|
|
customData.empty() ? NULL : customData.c_str(), customData.size());
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
errorDictionary_.LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
|
|
virtual IMemoryBuffer* ReadRange(const std::string& uuid,
|
|
FileContentType type,
|
|
uint64_t start /* inclusive */,
|
|
uint64_t end /* exclusive */,
|
|
const std::string& customData) ORTHANC_OVERRIDE
|
|
{
|
|
if (start > end)
|
|
{
|
|
throw OrthancException(ErrorCode_BadRange);
|
|
}
|
|
else if (start == end)
|
|
{
|
|
return new PluginMemoryBuffer64;
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<PluginMemoryBuffer64> buffer(new PluginMemoryBuffer64);
|
|
buffer->Resize(end - start);
|
|
assert(buffer->GetSize() > 0);
|
|
|
|
OrthancPluginErrorCode error =
|
|
readRange_(buffer->GetObject(), uuid.c_str(), Plugins::Convert(type), start, customData.empty() ? NULL : customData.c_str(), customData.size());
|
|
|
|
if (error == OrthancPluginErrorCode_Success)
|
|
{
|
|
return buffer.release();
|
|
}
|
|
else
|
|
{
|
|
GetErrorDictionary().LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual bool HasEfficientReadRange() const ORTHANC_OVERRIDE
|
|
{
|
|
return true;
|
|
}
|
|
};
|
|
|
|
|
|
class StorageAreaFactory : public boost::noncopyable
|
|
{
|
|
private:
|
|
enum Version
|
|
{
|
|
Version1,
|
|
Version2,
|
|
Version3
|
|
};
|
|
|
|
SharedLibrary& sharedLibrary_;
|
|
Version version_;
|
|
_OrthancPluginRegisterStorageArea callbacks1_;
|
|
_OrthancPluginRegisterStorageArea2 callbacks2_;
|
|
_OrthancPluginRegisterStorageArea3 callbacks3_;
|
|
PluginsErrorDictionary& errorDictionary_;
|
|
|
|
static void WarnNoReadRange()
|
|
{
|
|
LOG(WARNING) << "Performance warning: The storage area plugin doesn't implement reading of file ranges";
|
|
}
|
|
|
|
public:
|
|
StorageAreaFactory(SharedLibrary& sharedLibrary,
|
|
const _OrthancPluginRegisterStorageArea& callbacks,
|
|
PluginsErrorDictionary& errorDictionary) :
|
|
sharedLibrary_(sharedLibrary),
|
|
version_(Version1),
|
|
callbacks1_(callbacks),
|
|
errorDictionary_(errorDictionary)
|
|
{
|
|
WarnNoReadRange();
|
|
}
|
|
|
|
StorageAreaFactory(SharedLibrary& sharedLibrary,
|
|
const _OrthancPluginRegisterStorageArea2& callbacks,
|
|
PluginsErrorDictionary& errorDictionary) :
|
|
sharedLibrary_(sharedLibrary),
|
|
version_(Version2),
|
|
callbacks2_(callbacks),
|
|
errorDictionary_(errorDictionary)
|
|
{
|
|
if (callbacks.readRange == NULL)
|
|
{
|
|
WarnNoReadRange();
|
|
}
|
|
}
|
|
|
|
StorageAreaFactory(SharedLibrary& sharedLibrary,
|
|
const _OrthancPluginRegisterStorageArea3& callbacks,
|
|
PluginsErrorDictionary& errorDictionary) :
|
|
sharedLibrary_(sharedLibrary),
|
|
version_(Version3),
|
|
callbacks3_(callbacks),
|
|
errorDictionary_(errorDictionary)
|
|
{
|
|
if (callbacks.readRange == NULL)
|
|
{
|
|
WarnNoReadRange();
|
|
}
|
|
}
|
|
|
|
SharedLibrary& GetSharedLibrary()
|
|
{
|
|
return sharedLibrary_;
|
|
}
|
|
|
|
IPluginStorageArea* Create() const
|
|
{
|
|
switch (version_)
|
|
{
|
|
case Version1:
|
|
return new PluginStorageAreaAdapter(new PluginStorageAreaV1(callbacks1_, errorDictionary_));
|
|
|
|
case Version2:
|
|
return new PluginStorageAreaAdapter(new PluginStorageAreaV2(callbacks2_, errorDictionary_));
|
|
|
|
case Version3:
|
|
return new PluginStorageAreaV3(callbacks3_, errorDictionary_);
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
class OrthancPeers : public boost::noncopyable
|
|
{
|
|
private:
|
|
std::vector<std::string> names_;
|
|
std::vector<WebServiceParameters> parameters_;
|
|
|
|
void CheckIndex(size_t i) const
|
|
{
|
|
assert(names_.size() == parameters_.size());
|
|
if (i >= names_.size())
|
|
{
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
}
|
|
|
|
public:
|
|
OrthancPeers()
|
|
{
|
|
OrthancConfiguration::ReaderLock lock;
|
|
|
|
std::set<std::string> peers;
|
|
lock.GetConfiguration().GetListOfOrthancPeers(peers);
|
|
|
|
names_.reserve(peers.size());
|
|
parameters_.reserve(peers.size());
|
|
|
|
for (std::set<std::string>::const_iterator
|
|
it = peers.begin(); it != peers.end(); ++it)
|
|
{
|
|
WebServiceParameters peer;
|
|
if (lock.GetConfiguration().LookupOrthancPeer(peer, *it))
|
|
{
|
|
names_.push_back(*it);
|
|
parameters_.push_back(peer);
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t GetPeersCount() const
|
|
{
|
|
return names_.size();
|
|
}
|
|
|
|
const std::string& GetPeerName(size_t i) const
|
|
{
|
|
CheckIndex(i);
|
|
return names_[i];
|
|
}
|
|
|
|
const WebServiceParameters& GetPeerParameters(size_t i) const
|
|
{
|
|
CheckIndex(i);
|
|
return parameters_[i];
|
|
}
|
|
};
|
|
|
|
|
|
class DicomWebBinaryFormatter : public DicomWebJsonVisitor::IBinaryFormatter
|
|
{
|
|
private:
|
|
OrthancPluginDicomWebBinaryCallback oldCallback_;
|
|
OrthancPluginDicomWebBinaryCallback2 newCallback_; // New in Orthanc 1.7.0
|
|
void* newPayload_; // New in Orthanc 1.7.0
|
|
DicomWebJsonVisitor::BinaryMode currentMode_;
|
|
std::string currentBulkDataUri_;
|
|
|
|
static void Setter(OrthancPluginDicomWebNode* node,
|
|
OrthancPluginDicomWebBinaryMode mode,
|
|
const char* bulkDataUri)
|
|
{
|
|
DicomWebBinaryFormatter& that = *reinterpret_cast<DicomWebBinaryFormatter*>(node);
|
|
|
|
switch (mode)
|
|
{
|
|
case OrthancPluginDicomWebBinaryMode_Ignore:
|
|
that.currentMode_ = DicomWebJsonVisitor::BinaryMode_Ignore;
|
|
break;
|
|
|
|
case OrthancPluginDicomWebBinaryMode_InlineBinary:
|
|
that.currentMode_ = DicomWebJsonVisitor::BinaryMode_InlineBinary;
|
|
break;
|
|
|
|
case OrthancPluginDicomWebBinaryMode_BulkDataUri:
|
|
if (bulkDataUri == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
that.currentBulkDataUri_ = bulkDataUri;
|
|
that.currentMode_ = DicomWebJsonVisitor::BinaryMode_BulkDataUri;
|
|
break;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
}
|
|
|
|
public:
|
|
explicit DicomWebBinaryFormatter(OrthancPluginDicomWebBinaryCallback callback) :
|
|
oldCallback_(callback),
|
|
newCallback_(NULL),
|
|
newPayload_(NULL),
|
|
currentMode_(DicomWebJsonVisitor::BinaryMode_Ignore)
|
|
{
|
|
}
|
|
|
|
DicomWebBinaryFormatter(OrthancPluginDicomWebBinaryCallback2 callback,
|
|
void* payload) :
|
|
oldCallback_(NULL),
|
|
newCallback_(callback),
|
|
newPayload_(payload),
|
|
currentMode_(DicomWebJsonVisitor::BinaryMode_Ignore)
|
|
{
|
|
}
|
|
|
|
virtual DicomWebJsonVisitor::BinaryMode Format(std::string& bulkDataUri,
|
|
const std::vector<DicomTag>& parentTags,
|
|
const std::vector<size_t>& parentIndexes,
|
|
const DicomTag& tag,
|
|
ValueRepresentation vr) ORTHANC_OVERRIDE
|
|
{
|
|
if (oldCallback_ == NULL &&
|
|
newCallback_ == NULL)
|
|
{
|
|
return DicomWebJsonVisitor::BinaryMode_InlineBinary;
|
|
}
|
|
else
|
|
{
|
|
assert(parentTags.size() == parentIndexes.size());
|
|
std::vector<uint16_t> groups(parentTags.size());
|
|
std::vector<uint16_t> elements(parentTags.size());
|
|
std::vector<uint32_t> indexes(parentTags.size());
|
|
|
|
for (size_t i = 0; i < parentTags.size(); i++)
|
|
{
|
|
groups[i] = parentTags[i].GetGroup();
|
|
elements[i] = parentTags[i].GetElement();
|
|
indexes[i] = static_cast<uint32_t>(parentIndexes[i]);
|
|
}
|
|
bool empty = parentTags.empty();
|
|
|
|
currentMode_ = DicomWebJsonVisitor::BinaryMode_Ignore;
|
|
|
|
if (oldCallback_ != NULL)
|
|
{
|
|
oldCallback_(reinterpret_cast<OrthancPluginDicomWebNode*>(this),
|
|
DicomWebBinaryFormatter::Setter,
|
|
static_cast<uint32_t>(parentTags.size()),
|
|
(empty ? NULL : &groups[0]),
|
|
(empty ? NULL : &elements[0]),
|
|
(empty ? NULL : &indexes[0]),
|
|
tag.GetGroup(),
|
|
tag.GetElement(),
|
|
Plugins::Convert(vr));
|
|
}
|
|
else
|
|
{
|
|
assert(newCallback_ != NULL);
|
|
newCallback_(reinterpret_cast<OrthancPluginDicomWebNode*>(this),
|
|
DicomWebBinaryFormatter::Setter,
|
|
static_cast<uint32_t>(parentTags.size()),
|
|
(empty ? NULL : &groups[0]),
|
|
(empty ? NULL : &elements[0]),
|
|
(empty ? NULL : &indexes[0]),
|
|
tag.GetGroup(),
|
|
tag.GetElement(),
|
|
Plugins::Convert(vr),
|
|
newPayload_);
|
|
}
|
|
|
|
bulkDataUri = currentBulkDataUri_;
|
|
return currentMode_;
|
|
}
|
|
}
|
|
|
|
void Apply(char** target,
|
|
bool isJson,
|
|
const ParsedDicomFile& dicom)
|
|
{
|
|
DicomWebJsonVisitor visitor;
|
|
visitor.SetFormatter(*this);
|
|
|
|
dicom.Apply(visitor);
|
|
|
|
std::string s;
|
|
|
|
if (isJson)
|
|
{
|
|
s = visitor.GetResult().toStyledString();
|
|
}
|
|
else
|
|
{
|
|
visitor.FormatXml(s);
|
|
}
|
|
|
|
*target = CopyString(s);
|
|
}
|
|
|
|
|
|
void Apply(char** target,
|
|
bool isJson,
|
|
const void* dicom,
|
|
size_t dicomSize)
|
|
{
|
|
ParsedDicomFile parsed(dicom, dicomSize);
|
|
Apply(target, isJson, parsed);
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
class OrthancPlugins::PImpl
|
|
{
|
|
private:
|
|
boost::mutex contextMutex_;
|
|
boost::condition_variable contextCond_;
|
|
size_t contextRefCount_;
|
|
|
|
ServerContext* context_;
|
|
|
|
public:
|
|
class PluginHttpOutput : public boost::noncopyable
|
|
{
|
|
private:
|
|
enum State
|
|
{
|
|
State_None,
|
|
State_MultipartFirstPart,
|
|
State_MultipartSecondPart,
|
|
State_MultipartNextParts,
|
|
State_WritingStream
|
|
};
|
|
|
|
HttpOutput& output_;
|
|
std::unique_ptr<std::string> errorDetails_;
|
|
bool logDetails_;
|
|
State state_;
|
|
std::string multipartSubType_;
|
|
std::string multipartContentType_;
|
|
std::string multipartFirstPart_;
|
|
std::map<std::string, std::string> multipartFirstHeaders_;
|
|
|
|
public:
|
|
explicit PluginHttpOutput(HttpOutput& output) :
|
|
output_(output),
|
|
logDetails_(false),
|
|
state_(State_None)
|
|
{
|
|
}
|
|
|
|
HttpOutput& GetOutput()
|
|
{
|
|
if (state_ == State_None)
|
|
{
|
|
return output_;
|
|
}
|
|
else
|
|
{
|
|
// Must use "SendMultipartItem()" on multipart streams or "SendStreamChunk()"
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
}
|
|
|
|
void SetErrorDetails(const std::string& details,
|
|
bool logDetails)
|
|
{
|
|
errorDetails_.reset(new std::string(details));
|
|
logDetails_ = logDetails;
|
|
}
|
|
|
|
bool HasErrorDetails() const
|
|
{
|
|
return errorDetails_.get() != NULL;
|
|
}
|
|
|
|
bool IsLogDetails() const
|
|
{
|
|
return logDetails_;
|
|
}
|
|
|
|
const std::string& GetErrorDetails() const
|
|
{
|
|
if (errorDetails_.get() == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
else
|
|
{
|
|
return *errorDetails_;
|
|
}
|
|
}
|
|
|
|
void StartMultipart(const char* subType,
|
|
const char* contentType)
|
|
{
|
|
if (state_ != State_None)
|
|
{
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
else
|
|
{
|
|
state_ = State_MultipartFirstPart;
|
|
multipartSubType_ = subType;
|
|
multipartContentType_ = contentType;
|
|
}
|
|
}
|
|
|
|
void StartStream(const char* contentType)
|
|
{
|
|
if (state_ != State_None)
|
|
{
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
else
|
|
{
|
|
output_.StartStream(contentType);
|
|
state_ = State_WritingStream;
|
|
}
|
|
}
|
|
|
|
void SendStreamItem(const void* data,
|
|
size_t size)
|
|
{
|
|
if (state_ != State_WritingStream)
|
|
{
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
else
|
|
{
|
|
output_.SendStreamItem(data, size);
|
|
}
|
|
}
|
|
|
|
|
|
void SendMultipartItem(const void* data,
|
|
size_t size,
|
|
const std::map<std::string, std::string>& headers)
|
|
{
|
|
if (size != 0 && data == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
switch (state_)
|
|
{
|
|
case State_None:
|
|
// Must call "StartMultipart()" before
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
|
|
case State_MultipartFirstPart:
|
|
multipartFirstPart_.assign(reinterpret_cast<const char*>(data), size);
|
|
multipartFirstHeaders_ = headers;
|
|
state_ = State_MultipartSecondPart;
|
|
break;
|
|
|
|
case State_MultipartSecondPart:
|
|
// Start an actual stream for chunked transfer as soon as
|
|
// there are more than 2 elements in the multipart stream
|
|
output_.StartMultipart(multipartSubType_, multipartContentType_);
|
|
output_.SendMultipartItem(multipartFirstPart_.c_str(), multipartFirstPart_.size(),
|
|
multipartFirstHeaders_);
|
|
multipartFirstPart_.clear(); // Release memory
|
|
|
|
output_.SendMultipartItem(data, size, headers);
|
|
state_ = State_MultipartNextParts;
|
|
break;
|
|
|
|
case State_MultipartNextParts:
|
|
output_.SendMultipartItem(data, size, headers);
|
|
break;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
}
|
|
|
|
void Close(OrthancPluginErrorCode error,
|
|
PluginsErrorDictionary& dictionary)
|
|
{
|
|
if (error == OrthancPluginErrorCode_Success)
|
|
{
|
|
switch (state_)
|
|
{
|
|
case State_None:
|
|
assert(!output_.IsWritingMultipart());
|
|
break;
|
|
|
|
case State_MultipartFirstPart: // Multipart started, but no part was sent
|
|
case State_MultipartSecondPart: // Multipart started, first part is pending
|
|
{
|
|
assert(!output_.IsWritingMultipart());
|
|
std::vector<const void*> parts;
|
|
std::vector<size_t> sizes;
|
|
std::vector<const std::map<std::string, std::string>*> headers;
|
|
|
|
if (state_ == State_MultipartSecondPart)
|
|
{
|
|
parts.push_back(multipartFirstPart_.c_str());
|
|
sizes.push_back(multipartFirstPart_.size());
|
|
headers.push_back(&multipartFirstHeaders_);
|
|
}
|
|
|
|
output_.AnswerMultipartWithoutChunkedTransfer(multipartSubType_, multipartContentType_,
|
|
parts, sizes, headers);
|
|
break;
|
|
}
|
|
|
|
case State_MultipartNextParts:
|
|
assert(output_.IsWritingMultipart());
|
|
output_.CloseMultipart();
|
|
break;
|
|
|
|
case State_WritingStream:
|
|
assert(output_.IsWritingStream());
|
|
output_.CloseStream();
|
|
break;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dictionary.LogError(error, false);
|
|
|
|
if (HasErrorDetails())
|
|
{
|
|
throw OrthancException(static_cast<ErrorCode>(error),
|
|
GetErrorDetails(),
|
|
IsLogDetails());
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
class RestCallback : public boost::noncopyable
|
|
{
|
|
private:
|
|
boost::regex regex_;
|
|
OrthancPluginRestCallback callback_;
|
|
bool mutualExclusion_;
|
|
|
|
OrthancPluginErrorCode InvokeInternal(PluginHttpOutput& output,
|
|
const std::string& flatUri,
|
|
const OrthancPluginHttpRequest& request)
|
|
{
|
|
return callback_(reinterpret_cast<OrthancPluginRestOutput*>(&output),
|
|
flatUri.c_str(),
|
|
&request);
|
|
}
|
|
|
|
public:
|
|
RestCallback(const char* regex,
|
|
OrthancPluginRestCallback callback,
|
|
bool mutualExclusion) :
|
|
regex_(regex),
|
|
callback_(callback),
|
|
mutualExclusion_(mutualExclusion)
|
|
{
|
|
}
|
|
|
|
const boost::regex& GetRegularExpression() const
|
|
{
|
|
return regex_;
|
|
}
|
|
|
|
OrthancPluginErrorCode Invoke(boost::recursive_mutex& invokationMutex,
|
|
PluginHttpOutput& output,
|
|
const std::string& flatUri,
|
|
const OrthancPluginHttpRequest& request)
|
|
{
|
|
if (mutualExclusion_)
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(invokationMutex);
|
|
return InvokeInternal(output, flatUri, request);
|
|
}
|
|
else
|
|
{
|
|
return InvokeInternal(output, flatUri, request);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
class ChunkedRestCallback : public boost::noncopyable
|
|
{
|
|
private:
|
|
_OrthancPluginChunkedRestCallback parameters_;
|
|
boost::regex regex_;
|
|
|
|
public:
|
|
explicit ChunkedRestCallback(const _OrthancPluginChunkedRestCallback& parameters) :
|
|
parameters_(parameters),
|
|
regex_(parameters.pathRegularExpression)
|
|
{
|
|
}
|
|
|
|
const boost::regex& GetRegularExpression() const
|
|
{
|
|
return regex_;
|
|
}
|
|
|
|
const _OrthancPluginChunkedRestCallback& GetParameters() const
|
|
{
|
|
return parameters_;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
class StorageCommitmentScp : public IStorageCommitmentFactory
|
|
{
|
|
private:
|
|
class Handler : public IStorageCommitmentFactory::ILookupHandler
|
|
{
|
|
private:
|
|
_OrthancPluginRegisterStorageCommitmentScpCallback parameters_;
|
|
void* handler_;
|
|
|
|
public:
|
|
Handler(const _OrthancPluginRegisterStorageCommitmentScpCallback& parameters,
|
|
void* handler) :
|
|
parameters_(parameters),
|
|
handler_(handler)
|
|
{
|
|
if (handler == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
}
|
|
|
|
virtual ~Handler()
|
|
{
|
|
assert(handler_ != NULL);
|
|
parameters_.destructor(handler_);
|
|
handler_ = NULL;
|
|
}
|
|
|
|
virtual StorageCommitmentFailureReason Lookup(
|
|
const std::string& sopClassUid,
|
|
const std::string& sopInstanceUid) ORTHANC_OVERRIDE
|
|
{
|
|
assert(handler_ != NULL);
|
|
OrthancPluginStorageCommitmentFailureReason reason =
|
|
OrthancPluginStorageCommitmentFailureReason_Success;
|
|
OrthancPluginErrorCode error = parameters_.lookup(
|
|
&reason, handler_, sopClassUid.c_str(), sopInstanceUid.c_str());
|
|
if (error == OrthancPluginErrorCode_Success)
|
|
{
|
|
return Plugins::Convert(reason);
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
};
|
|
|
|
_OrthancPluginRegisterStorageCommitmentScpCallback parameters_;
|
|
|
|
public:
|
|
explicit StorageCommitmentScp(const _OrthancPluginRegisterStorageCommitmentScpCallback& parameters) :
|
|
parameters_(parameters)
|
|
{
|
|
}
|
|
|
|
virtual ILookupHandler* CreateStorageCommitment(
|
|
const std::string& jobId,
|
|
const std::string& transactionUid,
|
|
const std::vector<std::string>& sopClassUids,
|
|
const std::vector<std::string>& sopInstanceUids,
|
|
const std::string& remoteAet,
|
|
const std::string& calledAet) ORTHANC_OVERRIDE
|
|
{
|
|
const size_t n = sopClassUids.size();
|
|
|
|
if (sopInstanceUids.size() != n)
|
|
{
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
std::vector<const char*> a, b;
|
|
a.resize(n);
|
|
b.resize(n);
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
{
|
|
a[i] = sopClassUids[i].c_str();
|
|
b[i] = sopInstanceUids[i].c_str();
|
|
}
|
|
|
|
void* handler = NULL;
|
|
OrthancPluginErrorCode error = parameters_.factory(
|
|
&handler, jobId.c_str(), transactionUid.c_str(),
|
|
a.empty() ? NULL : &a[0], b.empty() ? NULL : &b[0], static_cast<uint32_t>(n),
|
|
remoteAet.c_str(), calledAet.c_str());
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
else if (handler == NULL)
|
|
{
|
|
// This plugin won't handle this storage commitment request
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
return new Handler(parameters_, handler);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// This class ensures that the Context remains valid while being used.
|
|
// But it does not prevent multiple users to use the context at the same time.
|
|
// (new behavior in 1.12.2. In previous version, only one user could use the "locked" context)
|
|
class ServerContextReference
|
|
{
|
|
private:
|
|
ServerContext* context_;
|
|
boost::mutex& mutex_;
|
|
boost::condition_variable& cond_;
|
|
size_t& refCount_;
|
|
|
|
public:
|
|
explicit ServerContextReference(PImpl& that) :
|
|
context_(that.context_),
|
|
mutex_(that.contextMutex_),
|
|
cond_(that.contextCond_),
|
|
refCount_(that.contextRefCount_)
|
|
{
|
|
if (context_ == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabaseNotInitialized);
|
|
}
|
|
|
|
boost::mutex::scoped_lock lock(mutex_);
|
|
refCount_++;
|
|
}
|
|
|
|
~ServerContextReference()
|
|
{
|
|
boost::mutex::scoped_lock lock(mutex_);
|
|
refCount_--;
|
|
cond_.notify_one();
|
|
}
|
|
|
|
ServerContext& GetContext()
|
|
{
|
|
assert(context_ != NULL);
|
|
return *context_;
|
|
}
|
|
};
|
|
|
|
|
|
void SetServerContext(ServerContext* context)
|
|
{
|
|
// update only the context while nobody is using it
|
|
boost::mutex::scoped_lock lock(contextMutex_);
|
|
|
|
while (contextRefCount_ > 0)
|
|
{
|
|
contextCond_.wait(lock);
|
|
}
|
|
context_ = context;
|
|
}
|
|
|
|
|
|
typedef std::pair<std::string, _OrthancPluginProperty> Property;
|
|
typedef std::list<RestCallback*> RestCallbacks;
|
|
typedef std::list<ChunkedRestCallback*> ChunkedRestCallbacks;
|
|
typedef std::list<OrthancPluginOnStoredInstanceCallback> OnStoredCallbacks;
|
|
typedef std::list<OrthancPluginOnChangeCallback> OnChangeCallbacks;
|
|
typedef std::list<OrthancPluginIncomingHttpRequestFilter> IncomingHttpRequestFilters;
|
|
typedef std::list<OrthancPluginIncomingHttpRequestFilter2> IncomingHttpRequestFilters2;
|
|
typedef std::list<OrthancPluginIncomingDicomInstanceFilter> IncomingDicomInstanceFilters;
|
|
typedef std::list<OrthancPluginIncomingCStoreInstanceFilter> IncomingCStoreInstanceFilters;
|
|
typedef std::list<OrthancPluginDecodeImageCallback> DecodeImageCallbacks;
|
|
typedef std::list<OrthancPluginTranscoderCallback> TranscoderCallbacks;
|
|
typedef std::list<OrthancPluginJobsUnserializer> JobsUnserializers;
|
|
typedef std::list<OrthancPluginRefreshMetricsCallback> RefreshMetricsCallbacks;
|
|
typedef std::list<StorageCommitmentScp*> StorageCommitmentScpCallbacks;
|
|
typedef std::map<Property, std::string> Properties;
|
|
typedef std::list<WebDavCollection*> WebDavCollections;
|
|
|
|
PluginsManager manager_;
|
|
|
|
RestCallbacks restCallbacks_;
|
|
ChunkedRestCallbacks chunkedRestCallbacks_;
|
|
OnStoredCallbacks onStoredCallbacks_;
|
|
OnChangeCallbacks onChangeCallbacks_;
|
|
OrthancPluginFindCallback findCallback_;
|
|
OrthancPluginWorklistCallback worklistCallback_;
|
|
DecodeImageCallbacks decodeImageCallbacks_;
|
|
TranscoderCallbacks transcoderCallbacks_;
|
|
JobsUnserializers jobsUnserializers_;
|
|
_OrthancPluginMoveCallback moveCallbacks_;
|
|
IncomingHttpRequestFilters incomingHttpRequestFilters_;
|
|
IncomingHttpRequestFilters2 incomingHttpRequestFilters2_;
|
|
IncomingDicomInstanceFilters incomingDicomInstanceFilters_;
|
|
IncomingCStoreInstanceFilters incomingCStoreInstanceFilters_; // New in Orthanc 1.10.0
|
|
OrthancPluginReceivedInstanceCallback receivedInstanceCallback_; // New in Orthanc 1.10.0
|
|
RefreshMetricsCallbacks refreshMetricsCallbacks_;
|
|
StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_;
|
|
WebDavCollections webDavCollections_; // New in Orthanc 1.10.1
|
|
std::unique_ptr<StorageAreaFactory> storageArea_;
|
|
std::set<std::string> authorizationTokens_;
|
|
|
|
boost::recursive_mutex restCallbackInvokationMutex_;
|
|
boost::shared_mutex restCallbackRegistrationMutex_; // New in Orthanc 1.9.0
|
|
boost::recursive_mutex storedCallbackMutex_;
|
|
boost::recursive_mutex changeCallbackMutex_;
|
|
boost::mutex findCallbackMutex_;
|
|
boost::mutex worklistCallbackMutex_;
|
|
boost::shared_mutex decoderTranscoderMutex_; // Changed from "boost::mutex" in Orthanc 1.7.0
|
|
boost::mutex jobsUnserializersMutex_;
|
|
boost::mutex refreshMetricsMutex_;
|
|
boost::mutex storageCommitmentScpMutex_;
|
|
boost::recursive_mutex invokeServiceMutex_;
|
|
boost::shared_mutex incomingHttpRequestFilterMutex_; // New in Orthanc 1.8.2
|
|
|
|
Properties properties_;
|
|
int argc_;
|
|
char** argv_;
|
|
std::unique_ptr<OrthancPluginDatabase> database_;
|
|
std::unique_ptr<OrthancPluginDatabaseV3> databaseV3_; // New in Orthanc 1.9.2
|
|
std::unique_ptr<OrthancPluginDatabaseV4> databaseV4_; // New in Orthanc 1.12.0
|
|
PluginsErrorDictionary dictionary_;
|
|
std::string databaseServerIdentifier_; // New in Orthanc 1.9.2
|
|
unsigned int maxDatabaseRetries_; // New in Orthanc 1.9.2
|
|
bool hasStorageAreaCustomData_; // New in Orthanc 1.12.8
|
|
|
|
explicit PImpl(const std::string& databaseServerIdentifier) :
|
|
contextRefCount_(0),
|
|
context_(NULL),
|
|
findCallback_(NULL),
|
|
worklistCallback_(NULL),
|
|
receivedInstanceCallback_(NULL),
|
|
argc_(1),
|
|
argv_(NULL),
|
|
databaseServerIdentifier_(databaseServerIdentifier),
|
|
maxDatabaseRetries_(0),
|
|
hasStorageAreaCustomData_(false)
|
|
{
|
|
memset(&moveCallbacks_, 0, sizeof(moveCallbacks_));
|
|
}
|
|
};
|
|
|
|
|
|
|
|
class OrthancPlugins::WorklistHandler : public IWorklistRequestHandler
|
|
{
|
|
private:
|
|
OrthancPlugins& that_;
|
|
std::unique_ptr<HierarchicalMatcher> matcher_;
|
|
std::unique_ptr<ParsedDicomFile> filtered_;
|
|
ParsedDicomFile* currentQuery_;
|
|
|
|
void Reset()
|
|
{
|
|
matcher_.reset();
|
|
filtered_.reset();
|
|
currentQuery_ = NULL;
|
|
}
|
|
|
|
public:
|
|
explicit WorklistHandler(OrthancPlugins& that) : that_(that)
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
virtual void Handle(DicomFindAnswers& answers,
|
|
ParsedDicomFile& query,
|
|
const std::string& remoteIp,
|
|
const std::string& remoteAet,
|
|
const std::string& calledAet,
|
|
ModalityManufacturer manufacturer) ORTHANC_OVERRIDE
|
|
{
|
|
{
|
|
static const char* LUA_CALLBACK = "IncomingWorklistRequestFilter";
|
|
|
|
PImpl::ServerContextReference lock(*that_.pimpl_);
|
|
LuaScripting::Lock lua(lock.GetContext().GetLuaScripting());
|
|
|
|
if (!lua.GetLua().IsExistingFunction(LUA_CALLBACK))
|
|
{
|
|
currentQuery_ = &query;
|
|
}
|
|
else
|
|
{
|
|
Json::Value source, origin;
|
|
query.DatasetToJson(source, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
|
|
|
|
OrthancFindRequestHandler::FormatOrigin
|
|
(origin, remoteIp, remoteAet, calledAet, manufacturer);
|
|
|
|
LuaFunctionCall call(lua.GetLua(), LUA_CALLBACK);
|
|
call.PushJson(source);
|
|
call.PushJson(origin);
|
|
|
|
Json::Value target;
|
|
call.ExecuteToJson(target, true);
|
|
|
|
filtered_.reset(ParsedDicomFile::CreateFromJson(target, DicomFromJsonFlags_None,
|
|
"" /* no private creator */));
|
|
currentQuery_ = filtered_.get();
|
|
}
|
|
}
|
|
|
|
matcher_.reset(new HierarchicalMatcher(*currentQuery_));
|
|
|
|
{
|
|
boost::mutex::scoped_lock lock(that_.pimpl_->worklistCallbackMutex_);
|
|
|
|
if (that_.pimpl_->worklistCallback_)
|
|
{
|
|
OrthancPluginErrorCode error = that_.pimpl_->worklistCallback_
|
|
(reinterpret_cast<OrthancPluginWorklistAnswers*>(&answers),
|
|
reinterpret_cast<const OrthancPluginWorklistQuery*>(this),
|
|
remoteAet.c_str(),
|
|
calledAet.c_str());
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
Reset();
|
|
that_.GetErrorDictionary().LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
|
|
Reset();
|
|
}
|
|
}
|
|
|
|
void GetDicomQuery(OrthancPluginMemoryBuffer* target) const
|
|
{
|
|
if (currentQuery_ == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin);
|
|
}
|
|
|
|
std::string dicom;
|
|
currentQuery_->SaveToMemoryBuffer(dicom);
|
|
CopyToMemoryBuffer(target, dicom);
|
|
}
|
|
|
|
bool IsMatch(const void* dicom,
|
|
size_t size) const
|
|
{
|
|
if (matcher_.get() == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin);
|
|
}
|
|
|
|
ParsedDicomFile f(dicom, size);
|
|
return matcher_->Match(f);
|
|
}
|
|
|
|
void AddAnswer(OrthancPluginWorklistAnswers* answers,
|
|
const void* dicom,
|
|
size_t size) const
|
|
{
|
|
if (matcher_.get() == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin);
|
|
}
|
|
|
|
ParsedDicomFile f(dicom, size);
|
|
std::unique_ptr<ParsedDicomFile> summary(matcher_->Extract(f));
|
|
reinterpret_cast<DicomFindAnswers*>(answers)->Add(*summary);
|
|
}
|
|
};
|
|
|
|
|
|
class OrthancPlugins::FindHandler : public IFindRequestHandler
|
|
{
|
|
private:
|
|
OrthancPlugins& that_;
|
|
std::unique_ptr<DicomArray> currentQuery_;
|
|
|
|
void Reset()
|
|
{
|
|
currentQuery_.reset(NULL);
|
|
}
|
|
|
|
public:
|
|
explicit FindHandler(OrthancPlugins& that) : that_(that)
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
virtual void Handle(DicomFindAnswers& answers,
|
|
const DicomMap& input,
|
|
const std::list<DicomTag>& sequencesToReturn,
|
|
const std::string& remoteIp,
|
|
const std::string& remoteAet,
|
|
const std::string& calledAet,
|
|
ModalityManufacturer manufacturer) ORTHANC_OVERRIDE
|
|
{
|
|
DicomMap tmp;
|
|
tmp.Assign(input);
|
|
|
|
for (std::list<DicomTag>::const_iterator it = sequencesToReturn.begin();
|
|
it != sequencesToReturn.end(); ++it)
|
|
{
|
|
if (!input.HasTag(*it))
|
|
{
|
|
tmp.SetValue(*it, "", false);
|
|
}
|
|
}
|
|
|
|
{
|
|
boost::mutex::scoped_lock lock(that_.pimpl_->findCallbackMutex_);
|
|
currentQuery_.reset(new DicomArray(tmp));
|
|
|
|
if (that_.pimpl_->findCallback_)
|
|
{
|
|
OrthancPluginErrorCode error = that_.pimpl_->findCallback_
|
|
(reinterpret_cast<OrthancPluginFindAnswers*>(&answers),
|
|
reinterpret_cast<const OrthancPluginFindQuery*>(this),
|
|
remoteAet.c_str(),
|
|
calledAet.c_str());
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
Reset();
|
|
that_.GetErrorDictionary().LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
|
|
Reset();
|
|
}
|
|
}
|
|
|
|
void Invoke(_OrthancPluginService service,
|
|
const _OrthancPluginFindOperation& operation) const
|
|
{
|
|
if (currentQuery_.get() == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin);
|
|
}
|
|
|
|
switch (service)
|
|
{
|
|
case _OrthancPluginService_GetFindQuerySize:
|
|
*operation.resultUint32 = currentQuery_->GetSize();
|
|
break;
|
|
|
|
case _OrthancPluginService_GetFindQueryTag:
|
|
{
|
|
const DicomTag& tag = currentQuery_->GetElement(operation.index).GetTag();
|
|
*operation.resultGroup = tag.GetGroup();
|
|
*operation.resultElement = tag.GetElement();
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginService_GetFindQueryTagName:
|
|
{
|
|
const DicomElement& element = currentQuery_->GetElement(operation.index);
|
|
*operation.resultString = CopyString(FromDcmtkBridge::GetTagName(element));
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginService_GetFindQueryValue:
|
|
{
|
|
*operation.resultString = CopyString(currentQuery_->GetElement(operation.index).GetValue().GetContent());
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
class OrthancPlugins::MoveHandler : public IMoveRequestHandler
|
|
{
|
|
private:
|
|
class Driver : public IMoveRequestIterator
|
|
{
|
|
private:
|
|
void* driver_;
|
|
unsigned int count_;
|
|
unsigned int pos_;
|
|
OrthancPluginApplyMove apply_;
|
|
OrthancPluginFreeMove free_;
|
|
|
|
public:
|
|
Driver(void* driver,
|
|
unsigned int count,
|
|
OrthancPluginApplyMove apply,
|
|
OrthancPluginFreeMove free) :
|
|
driver_(driver),
|
|
count_(count),
|
|
pos_(0),
|
|
apply_(apply),
|
|
free_(free)
|
|
{
|
|
if (driver_ == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin);
|
|
}
|
|
}
|
|
|
|
virtual ~Driver()
|
|
{
|
|
if (driver_ != NULL)
|
|
{
|
|
free_(driver_);
|
|
driver_ = NULL;
|
|
}
|
|
}
|
|
|
|
virtual unsigned int GetSubOperationCount() const ORTHANC_OVERRIDE
|
|
{
|
|
return count_;
|
|
}
|
|
|
|
virtual Status DoNext() ORTHANC_OVERRIDE
|
|
{
|
|
if (pos_ >= count_)
|
|
{
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
else
|
|
{
|
|
OrthancPluginErrorCode error = apply_(driver_);
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
LOG(ERROR) << "Error while doing C-Move from plugin: "
|
|
<< EnumerationToString(static_cast<ErrorCode>(error));
|
|
return Status_Failure;
|
|
}
|
|
else
|
|
{
|
|
pos_++;
|
|
return Status_Success;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
_OrthancPluginMoveCallback params_;
|
|
|
|
|
|
static std::string ReadTag(const DicomMap& input,
|
|
const DicomTag& tag)
|
|
{
|
|
const DicomValue* value = input.TestAndGetValue(tag);
|
|
if (value != NULL &&
|
|
!value->IsBinary() &&
|
|
!value->IsNull())
|
|
{
|
|
return value->GetContent();
|
|
}
|
|
else
|
|
{
|
|
return std::string();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
explicit MoveHandler(OrthancPlugins& that)
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(that.pimpl_->invokeServiceMutex_);
|
|
params_ = that.pimpl_->moveCallbacks_;
|
|
|
|
if (params_.callback == NULL ||
|
|
params_.getMoveSize == NULL ||
|
|
params_.applyMove == NULL ||
|
|
params_.freeMove == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin);
|
|
}
|
|
}
|
|
|
|
virtual IMoveRequestIterator* Handle(const std::string& targetAet,
|
|
const DicomMap& input,
|
|
const std::string& originatorIp,
|
|
const std::string& originatorAet,
|
|
const std::string& calledAet,
|
|
uint16_t originatorId) ORTHANC_OVERRIDE
|
|
{
|
|
std::string levelString = ReadTag(input, DICOM_TAG_QUERY_RETRIEVE_LEVEL);
|
|
std::string patientId = ReadTag(input, DICOM_TAG_PATIENT_ID);
|
|
std::string accessionNumber = ReadTag(input, DICOM_TAG_ACCESSION_NUMBER);
|
|
std::string studyInstanceUid = ReadTag(input, DICOM_TAG_STUDY_INSTANCE_UID);
|
|
std::string seriesInstanceUid = ReadTag(input, DICOM_TAG_SERIES_INSTANCE_UID);
|
|
std::string sopInstanceUid = ReadTag(input, DICOM_TAG_SOP_INSTANCE_UID);
|
|
|
|
OrthancPluginResourceType level = OrthancPluginResourceType_None;
|
|
|
|
if (!levelString.empty())
|
|
{
|
|
level = Plugins::Convert(StringToResourceType(levelString.c_str()));
|
|
}
|
|
|
|
void* driver = params_.callback(level,
|
|
patientId.empty() ? NULL : patientId.c_str(),
|
|
accessionNumber.empty() ? NULL : accessionNumber.c_str(),
|
|
studyInstanceUid.empty() ? NULL : studyInstanceUid.c_str(),
|
|
seriesInstanceUid.empty() ? NULL : seriesInstanceUid.c_str(),
|
|
sopInstanceUid.empty() ? NULL : sopInstanceUid.c_str(),
|
|
originatorAet.c_str(),
|
|
calledAet.c_str(),
|
|
targetAet.c_str(),
|
|
originatorId);
|
|
|
|
if (driver == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin,
|
|
"Plugin cannot create a driver for an incoming C-MOVE request");
|
|
}
|
|
|
|
unsigned int size = params_.getMoveSize(driver);
|
|
|
|
return new Driver(driver, size, params_.applyMove, params_.freeMove);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
class OrthancPlugins::HttpClientChunkedRequest : public HttpClient::IRequestBody
|
|
{
|
|
private:
|
|
const _OrthancPluginChunkedHttpClient& params_;
|
|
PluginsErrorDictionary& errorDictionary_;
|
|
|
|
public:
|
|
HttpClientChunkedRequest(const _OrthancPluginChunkedHttpClient& params,
|
|
PluginsErrorDictionary& errorDictionary) :
|
|
params_(params),
|
|
errorDictionary_(errorDictionary)
|
|
{
|
|
}
|
|
|
|
virtual bool ReadNextChunk(std::string& chunk) ORTHANC_OVERRIDE
|
|
{
|
|
if (params_.requestIsDone(params_.request))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
size_t size = params_.requestChunkSize(params_.request);
|
|
|
|
chunk.resize(size);
|
|
|
|
if (size != 0)
|
|
{
|
|
const void* data = params_.requestChunkData(params_.request);
|
|
memcpy(&chunk[0], data, size);
|
|
}
|
|
|
|
OrthancPluginErrorCode error = params_.requestNext(params_.request);
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
errorDictionary_.LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
class OrthancPlugins::HttpClientChunkedAnswer : public HttpClient::IAnswer
|
|
{
|
|
private:
|
|
const _OrthancPluginChunkedHttpClient& params_;
|
|
PluginsErrorDictionary& errorDictionary_;
|
|
|
|
public:
|
|
HttpClientChunkedAnswer(const _OrthancPluginChunkedHttpClient& params,
|
|
PluginsErrorDictionary& errorDictionary) :
|
|
params_(params),
|
|
errorDictionary_(errorDictionary)
|
|
{
|
|
}
|
|
|
|
virtual void AddHeader(const std::string& key,
|
|
const std::string& value) ORTHANC_OVERRIDE
|
|
{
|
|
OrthancPluginErrorCode error = params_.answerAddHeader(params_.answer, key.c_str(), value.c_str());
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
errorDictionary_.LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
|
|
virtual void AddChunk(const void* data,
|
|
size_t size) ORTHANC_OVERRIDE
|
|
{
|
|
OrthancPluginErrorCode error = params_.answerAddChunk(params_.answer, data, size);
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
errorDictionary_.LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
OrthancPlugins::OrthancPlugins(const std::string& databaseServerIdentifier)
|
|
{
|
|
/* Sanity check of the compiler */
|
|
if (sizeof(int32_t) != sizeof(int) || // 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(OrthancPluginDicomToJsonFlags) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginJobStopReason) ||
|
|
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) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginLoadDicomInstanceMode) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginLogLevel) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginLogCategory) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginStoreStatus) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginQueueOrigin) ||
|
|
|
|
// From OrthancCDatabasePlugin.h
|
|
sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginDatabaseTransactionType) ||
|
|
sizeof(int32_t) != sizeof(OrthancPluginDatabaseEventType) ||
|
|
|
|
static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast<int>(DicomToJsonFlags_IncludeBinary) ||
|
|
static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast<int>(DicomToJsonFlags_IncludePrivateTags) ||
|
|
static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast<int>(DicomToJsonFlags_IncludeUnknownTags) ||
|
|
static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePixelData) != static_cast<int>(DicomToJsonFlags_IncludePixelData) ||
|
|
static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToNull) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToNull) ||
|
|
static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToAscii) ||
|
|
static_cast<int>(OrthancPluginDicomToJsonFlags_StopAfterPixelData) != static_cast<int>(DicomToJsonFlags_StopAfterPixelData) ||
|
|
static_cast<int>(OrthancPluginDicomToJsonFlags_SkipGroupLengths) != static_cast<int>(DicomToJsonFlags_SkipGroupLengths) ||
|
|
static_cast<int>(OrthancPluginCreateDicomFlags_DecodeDataUriScheme) != static_cast<int>(DicomFromJsonFlags_DecodeDataUriScheme) ||
|
|
static_cast<int>(OrthancPluginCreateDicomFlags_GenerateIdentifiers) != static_cast<int>(DicomFromJsonFlags_GenerateIdentifiers))
|
|
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin);
|
|
}
|
|
|
|
pimpl_.reset(new PImpl(databaseServerIdentifier));
|
|
pimpl_->manager_.RegisterServiceProvider(*this);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::SetServerContext(ServerContext& context)
|
|
{
|
|
pimpl_->SetServerContext(&context);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::ResetServerContext()
|
|
{
|
|
pimpl_->SetServerContext(NULL);
|
|
}
|
|
|
|
|
|
OrthancPlugins::~OrthancPlugins()
|
|
{
|
|
for (PImpl::RestCallbacks::iterator it = pimpl_->restCallbacks_.begin();
|
|
it != pimpl_->restCallbacks_.end(); ++it)
|
|
{
|
|
delete *it;
|
|
}
|
|
|
|
for (PImpl::ChunkedRestCallbacks::iterator it = pimpl_->chunkedRestCallbacks_.begin();
|
|
it != pimpl_->chunkedRestCallbacks_.end(); ++it)
|
|
{
|
|
delete *it;
|
|
}
|
|
|
|
for (PImpl::StorageCommitmentScpCallbacks::iterator
|
|
it = pimpl_->storageCommitmentScpCallbacks_.begin();
|
|
it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it)
|
|
{
|
|
delete *it;
|
|
}
|
|
|
|
for (PImpl::WebDavCollections::iterator it = pimpl_->webDavCollections_.begin();
|
|
it != pimpl_->webDavCollections_.end(); ++it)
|
|
{
|
|
delete *it;
|
|
}
|
|
}
|
|
|
|
|
|
static void ArgumentsToPlugin(std::vector<const char*>& keys,
|
|
std::vector<const char*>& values,
|
|
const HttpToolbox::Arguments& arguments)
|
|
{
|
|
keys.resize(arguments.size());
|
|
values.resize(arguments.size());
|
|
|
|
size_t pos = 0;
|
|
for (HttpToolbox::Arguments::const_iterator
|
|
it = arguments.begin(); it != arguments.end(); ++it)
|
|
{
|
|
keys[pos] = it->first.c_str();
|
|
values[pos] = it->second.c_str();
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
|
|
static void ArgumentsToPlugin(std::vector<const char*>& keys,
|
|
std::vector<const char*>& values,
|
|
const HttpToolbox::GetArguments& arguments)
|
|
{
|
|
keys.resize(arguments.size());
|
|
values.resize(arguments.size());
|
|
|
|
for (size_t i = 0; i < arguments.size(); i++)
|
|
{
|
|
keys[i] = arguments[i].first.c_str();
|
|
values[i] = arguments[i].second.c_str();
|
|
}
|
|
}
|
|
|
|
|
|
namespace
|
|
{
|
|
class RestCallbackMatcher : public boost::noncopyable
|
|
{
|
|
private:
|
|
std::string flatUri_;
|
|
std::vector<std::string> groups_;
|
|
std::vector<const char*> cgroups_;
|
|
|
|
public:
|
|
explicit RestCallbackMatcher(const UriComponents& uri) :
|
|
flatUri_(Toolbox::FlattenUri(uri))
|
|
{
|
|
}
|
|
|
|
bool IsMatch(const boost::regex& re)
|
|
{
|
|
// Check whether the regular expression associated to this
|
|
// callback matches the URI
|
|
boost::cmatch what;
|
|
|
|
if (boost::regex_match(flatUri_.c_str(), what, re))
|
|
{
|
|
// Extract the value of the free parameters of the regular expression
|
|
if (what.size() > 1)
|
|
{
|
|
groups_.resize(what.size() - 1);
|
|
cgroups_.resize(what.size() - 1);
|
|
for (size_t i = 1; i < what.size(); i++)
|
|
{
|
|
groups_[i - 1] = what[i];
|
|
cgroups_[i - 1] = groups_[i - 1].c_str();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Not a match
|
|
return false;
|
|
}
|
|
}
|
|
|
|
uint32_t GetGroupsCount() const
|
|
{
|
|
return cgroups_.size();
|
|
}
|
|
|
|
const char* const* GetGroups() const
|
|
{
|
|
return cgroups_.empty() ? NULL : &cgroups_[0];
|
|
}
|
|
|
|
const std::string& GetFlatUri() const
|
|
{
|
|
return flatUri_;
|
|
}
|
|
};
|
|
|
|
|
|
// WARNING - The lifetime of this internal object must be smaller
|
|
// than "matcher", "headers" and "getArguments" objects
|
|
class HttpRequestConverter
|
|
{
|
|
private:
|
|
std::vector<const char*> getKeys_;
|
|
std::vector<const char*> getValues_;
|
|
std::vector<const char*> headersKeys_;
|
|
std::vector<const char*> headersValues_;
|
|
OrthancPluginHttpRequest converted_;
|
|
|
|
public:
|
|
HttpRequestConverter(const RestCallbackMatcher& matcher,
|
|
HttpMethod method,
|
|
const HttpToolbox::Arguments& headers)
|
|
{
|
|
memset(&converted_, 0, sizeof(OrthancPluginHttpRequest));
|
|
|
|
ArgumentsToPlugin(headersKeys_, headersValues_, headers);
|
|
assert(headersKeys_.size() == headersValues_.size());
|
|
|
|
switch (method)
|
|
{
|
|
case HttpMethod_Get:
|
|
converted_.method = OrthancPluginHttpMethod_Get;
|
|
break;
|
|
|
|
case HttpMethod_Post:
|
|
converted_.method = OrthancPluginHttpMethod_Post;
|
|
break;
|
|
|
|
case HttpMethod_Delete:
|
|
converted_.method = OrthancPluginHttpMethod_Delete;
|
|
break;
|
|
|
|
case HttpMethod_Put:
|
|
converted_.method = OrthancPluginHttpMethod_Put;
|
|
break;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
|
|
converted_.groups = matcher.GetGroups();
|
|
converted_.groupsCount = matcher.GetGroupsCount();
|
|
converted_.getCount = 0;
|
|
converted_.getKeys = NULL;
|
|
converted_.getValues = NULL;
|
|
converted_.body = NULL;
|
|
converted_.bodySize = 0;
|
|
converted_.headersCount = headers.size();
|
|
|
|
if (headers.size() > 0)
|
|
{
|
|
converted_.headersKeys = &headersKeys_[0];
|
|
converted_.headersValues = &headersValues_[0];
|
|
}
|
|
}
|
|
|
|
void SetGetArguments(const HttpToolbox::GetArguments& getArguments)
|
|
{
|
|
ArgumentsToPlugin(getKeys_, getValues_, getArguments);
|
|
assert(getKeys_.size() == getValues_.size());
|
|
|
|
converted_.getCount = getArguments.size();
|
|
|
|
if (getArguments.size() > 0)
|
|
{
|
|
converted_.getKeys = &getKeys_[0];
|
|
converted_.getValues = &getValues_[0];
|
|
}
|
|
}
|
|
|
|
OrthancPluginHttpRequest& GetRequest()
|
|
{
|
|
return converted_;
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
static std::string GetAllowedMethods(_OrthancPluginChunkedRestCallback parameters)
|
|
{
|
|
std::string s;
|
|
|
|
if (parameters.getHandler != NULL)
|
|
{
|
|
s += "GET";
|
|
}
|
|
|
|
if (parameters.postHandler != NULL)
|
|
{
|
|
if (!s.empty())
|
|
{
|
|
s+= ",";
|
|
}
|
|
|
|
s += "POST";
|
|
}
|
|
|
|
if (parameters.deleteHandler != NULL)
|
|
{
|
|
if (!s.empty())
|
|
{
|
|
s+= ",";
|
|
}
|
|
|
|
s += "DELETE";
|
|
}
|
|
|
|
if (parameters.putHandler != NULL)
|
|
{
|
|
if (!s.empty())
|
|
{
|
|
s+= ",";
|
|
}
|
|
|
|
s += "PUT";
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::HandleChunkedGetDelete(HttpOutput& output,
|
|
HttpMethod method,
|
|
const UriComponents& uri,
|
|
const HttpToolbox::Arguments& headers,
|
|
const HttpToolbox::GetArguments& getArguments)
|
|
{
|
|
RestCallbackMatcher matcher(uri);
|
|
|
|
PImpl::ChunkedRestCallback* callback = NULL;
|
|
|
|
// Loop over the callbacks registered by the plugins
|
|
boost::shared_lock<boost::shared_mutex> lock(pimpl_->restCallbackRegistrationMutex_);
|
|
for (PImpl::ChunkedRestCallbacks::const_iterator it = pimpl_->chunkedRestCallbacks_.begin();
|
|
it != pimpl_->chunkedRestCallbacks_.end(); ++it)
|
|
{
|
|
if (matcher.IsMatch((*it)->GetRegularExpression()))
|
|
{
|
|
callback = *it;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (callback == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Delegating HTTP request to plugin for URI: " << matcher.GetFlatUri();
|
|
|
|
OrthancPluginRestCallback handler;
|
|
|
|
switch (method)
|
|
{
|
|
case HttpMethod_Get:
|
|
handler = callback->GetParameters().getHandler;
|
|
break;
|
|
|
|
case HttpMethod_Delete:
|
|
handler = callback->GetParameters().deleteHandler;
|
|
break;
|
|
|
|
default:
|
|
handler = NULL;
|
|
break;
|
|
}
|
|
|
|
if (handler == NULL)
|
|
{
|
|
output.SendMethodNotAllowed(GetAllowedMethods(callback->GetParameters()));
|
|
}
|
|
else
|
|
{
|
|
HttpRequestConverter converter(matcher, method, headers);
|
|
converter.SetGetArguments(getArguments);
|
|
|
|
PImpl::PluginHttpOutput pluginOutput(output);
|
|
|
|
OrthancPluginErrorCode error = handler(
|
|
reinterpret_cast<OrthancPluginRestOutput*>(&pluginOutput),
|
|
matcher.GetFlatUri().c_str(), &converter.GetRequest());
|
|
|
|
pluginOutput.Close(error, GetErrorDictionary());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::Handle(HttpOutput& output,
|
|
RequestOrigin /*origin*/,
|
|
const char* /*remoteIp*/,
|
|
const char* /*username*/,
|
|
HttpMethod method,
|
|
const UriComponents& uri,
|
|
const HttpToolbox::Arguments& headers,
|
|
const HttpToolbox::GetArguments& getArguments,
|
|
const void* bodyData,
|
|
size_t bodySize)
|
|
{
|
|
RestCallbackMatcher matcher(uri);
|
|
|
|
PImpl::RestCallback* callback = NULL;
|
|
|
|
// Loop over the callbacks registered by the plugins
|
|
boost::shared_lock<boost::shared_mutex> lock(pimpl_->restCallbackRegistrationMutex_);
|
|
for (PImpl::RestCallbacks::const_iterator it = pimpl_->restCallbacks_.begin();
|
|
it != pimpl_->restCallbacks_.end(); ++it)
|
|
{
|
|
if (matcher.IsMatch((*it)->GetRegularExpression()))
|
|
{
|
|
callback = *it;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (callback == NULL)
|
|
{
|
|
// Callback not found, try to find a chunked callback
|
|
return HandleChunkedGetDelete(output, method, uri, headers, getArguments);
|
|
}
|
|
|
|
CLOG(INFO, PLUGINS) << "Delegating HTTP request to plugin for URI: " << matcher.GetFlatUri();
|
|
|
|
HttpRequestConverter converter(matcher, method, headers);
|
|
converter.SetGetArguments(getArguments);
|
|
converter.GetRequest().body = bodyData;
|
|
converter.GetRequest().bodySize = bodySize;
|
|
|
|
PImpl::PluginHttpOutput pluginOutput(output);
|
|
|
|
assert(callback != NULL);
|
|
OrthancPluginErrorCode error = callback->Invoke
|
|
(pimpl_->restCallbackInvokationMutex_, pluginOutput, matcher.GetFlatUri(), converter.GetRequest());
|
|
|
|
pluginOutput.Close(error, GetErrorDictionary());
|
|
return true;
|
|
}
|
|
|
|
|
|
void OrthancPlugins::SignalStoredInstance(const std::string& instanceId,
|
|
const DicomInstanceToStore& instance,
|
|
const Json::Value& simplifiedTags)
|
|
{
|
|
DicomInstanceFromCallback wrapped(instance);
|
|
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->storedCallbackMutex_);
|
|
|
|
for (PImpl::OnStoredCallbacks::const_iterator
|
|
callback = pimpl_->onStoredCallbacks_.begin();
|
|
callback != pimpl_->onStoredCallbacks_.end(); ++callback)
|
|
{
|
|
OrthancPluginErrorCode error = (*callback) (
|
|
reinterpret_cast<OrthancPluginDicomInstance*>(&wrapped),
|
|
instanceId.c_str());
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
GetErrorDictionary().LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::FilterIncomingInstance(const DicomInstanceToStore& instance,
|
|
const Json::Value& simplified)
|
|
{
|
|
DicomInstanceFromCallback wrapped(instance);
|
|
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
|
|
|
|
for (PImpl::IncomingDicomInstanceFilters::const_iterator
|
|
filter = pimpl_->incomingDicomInstanceFilters_.begin();
|
|
filter != pimpl_->incomingDicomInstanceFilters_.end(); ++filter)
|
|
{
|
|
int32_t allowed = (*filter) (reinterpret_cast<const OrthancPluginDicomInstance*>(&wrapped));
|
|
|
|
if (allowed == 0)
|
|
{
|
|
return false;
|
|
}
|
|
else if (allowed != 1)
|
|
{
|
|
// The callback is only allowed to answer 0 or 1
|
|
throw OrthancException(ErrorCode_Plugin);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
bool OrthancPlugins::FilterIncomingCStoreInstance(uint16_t& dimseStatus,
|
|
const DicomInstanceToStore& instance,
|
|
const Json::Value& simplified)
|
|
{
|
|
DicomInstanceFromCallback wrapped(instance);
|
|
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
|
|
|
|
for (PImpl::IncomingCStoreInstanceFilters::const_iterator
|
|
filter = pimpl_->incomingCStoreInstanceFilters_.begin();
|
|
filter != pimpl_->incomingCStoreInstanceFilters_.end(); ++filter)
|
|
{
|
|
int32_t result = (*filter) (&dimseStatus, reinterpret_cast<const OrthancPluginDicomInstance*>(&wrapped));
|
|
|
|
if (result == 0)
|
|
{
|
|
// The instance must be discarded
|
|
return false;
|
|
}
|
|
else if (result == 1)
|
|
{
|
|
// The instance is accepted
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Error
|
|
throw OrthancException(ErrorCode_Plugin);
|
|
}
|
|
}
|
|
|
|
return true; // By default, the instance is accepted
|
|
}
|
|
|
|
|
|
OrthancPluginReceivedInstanceAction OrthancPlugins::ApplyReceivedInstanceCallbacks(PluginMemoryBuffer64& modified,
|
|
const void* receivedDicom,
|
|
size_t receivedDicomSize,
|
|
RequestOrigin origin)
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
|
|
|
|
modified.Clear();
|
|
|
|
if (pimpl_->receivedInstanceCallback_ == NULL)
|
|
{
|
|
return OrthancPluginReceivedInstanceAction_KeepAsIs;
|
|
}
|
|
else
|
|
{
|
|
return (*pimpl_->receivedInstanceCallback_) (modified.GetObject(), receivedDicom, receivedDicomSize, Plugins::Convert(origin));
|
|
}
|
|
}
|
|
|
|
void OrthancPlugins::SignalChangeInternal(OrthancPluginChangeType changeType,
|
|
OrthancPluginResourceType resourceType,
|
|
const char* resource)
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->changeCallbackMutex_);
|
|
|
|
for (std::list<OrthancPluginOnChangeCallback>::const_iterator
|
|
callback = pimpl_->onChangeCallbacks_.begin();
|
|
callback != pimpl_->onChangeCallbacks_.end(); ++callback)
|
|
{
|
|
OrthancPluginErrorCode error = (*callback) (changeType, resourceType, resource);
|
|
|
|
if (error != OrthancPluginErrorCode_Success)
|
|
{
|
|
GetErrorDictionary().LogError(error, true);
|
|
throw OrthancException(static_cast<ErrorCode>(error));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void OrthancPlugins::SignalChange(const ServerIndexChange& change)
|
|
{
|
|
SignalChangeInternal(Plugins::Convert(change.GetChangeType()),
|
|
Plugins::Convert(change.GetResourceType()),
|
|
change.GetPublicId().c_str());
|
|
}
|
|
|
|
|
|
void OrthancPlugins::SignalJobEvent(const JobEvent& event)
|
|
{
|
|
// job events are actually considered as changes inside plugins -> translate
|
|
switch (event.GetEventType())
|
|
{
|
|
case JobEventType_Submitted:
|
|
SignalChangeInternal(OrthancPluginChangeType_JobSubmitted, OrthancPluginResourceType_None, event.GetJobId().c_str());
|
|
break;
|
|
case JobEventType_Success:
|
|
SignalChangeInternal(OrthancPluginChangeType_JobSuccess, OrthancPluginResourceType_None, event.GetJobId().c_str());
|
|
break;
|
|
case JobEventType_Failure:
|
|
SignalChangeInternal(OrthancPluginChangeType_JobFailure, OrthancPluginResourceType_None, event.GetJobId().c_str());
|
|
break;
|
|
default:
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterRestCallback(const void* parameters,
|
|
bool mutualExclusion)
|
|
{
|
|
const _OrthancPluginRestCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginRestCallback*>(parameters);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a REST callback "
|
|
<< (mutualExclusion ? "with" : "without")
|
|
<< " mutual exclusion on: "
|
|
<< p.pathRegularExpression;
|
|
|
|
{
|
|
boost::unique_lock<boost::shared_mutex> lock(pimpl_->restCallbackRegistrationMutex_);
|
|
pimpl_->restCallbacks_.push_back(new PImpl::RestCallback(p.pathRegularExpression, p.callback, mutualExclusion));
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterChunkedRestCallback(const void* parameters)
|
|
{
|
|
const _OrthancPluginChunkedRestCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginChunkedRestCallback*>(parameters);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a REST callback for chunked streams on: "
|
|
<< p.pathRegularExpression;
|
|
|
|
{
|
|
boost::unique_lock<boost::shared_mutex> lock(pimpl_->restCallbackRegistrationMutex_);
|
|
pimpl_->chunkedRestCallbacks_.push_back(new PImpl::ChunkedRestCallback(p));
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterOnStoredInstanceCallback(const void* parameters)
|
|
{
|
|
const _OrthancPluginOnStoredInstanceCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginOnStoredInstanceCallback*>(parameters);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered an OnStoredInstance callback";
|
|
pimpl_->onStoredCallbacks_.push_back(p.callback);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterOnChangeCallback(const void* parameters)
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->changeCallbackMutex_);
|
|
|
|
const _OrthancPluginOnChangeCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginOnChangeCallback*>(parameters);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered an OnChange callback";
|
|
pimpl_->onChangeCallbacks_.push_back(p.callback);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterWorklistCallback(const void* parameters)
|
|
{
|
|
const _OrthancPluginWorklistCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginWorklistCallback*>(parameters);
|
|
|
|
boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_);
|
|
|
|
if (pimpl_->worklistCallback_ != NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin,
|
|
"Can only register one plugin to handle modality worklists");
|
|
}
|
|
else
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to handle modality worklists";
|
|
pimpl_->worklistCallback_ = p.callback;
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterFindCallback(const void* parameters)
|
|
{
|
|
const _OrthancPluginFindCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginFindCallback*>(parameters);
|
|
|
|
boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_);
|
|
|
|
if (pimpl_->findCallback_ != NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin,
|
|
"Can only register one plugin to handle C-FIND requests");
|
|
}
|
|
else
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to handle C-FIND requests";
|
|
pimpl_->findCallback_ = p.callback;
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterMoveCallback(const void* parameters)
|
|
{
|
|
// invokeServiceMutex_ is assumed to be locked
|
|
|
|
const _OrthancPluginMoveCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginMoveCallback*>(parameters);
|
|
|
|
if (pimpl_->moveCallbacks_.callback != NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin,
|
|
"Can only register one plugin to handle C-MOVE requests");
|
|
}
|
|
else
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to handle C-MOVE requests";
|
|
pimpl_->moveCallbacks_ = p;
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterDecodeImageCallback(const void* parameters)
|
|
{
|
|
const _OrthancPluginDecodeImageCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginDecodeImageCallback*>(parameters);
|
|
|
|
boost::unique_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
|
|
|
|
pimpl_->decodeImageCallbacks_.push_back(p.callback);
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to decode DICOM images ("
|
|
<< pimpl_->decodeImageCallbacks_.size() << " decoder(s) now active)";
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterTranscoderCallback(const void* parameters)
|
|
{
|
|
const _OrthancPluginTranscoderCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginTranscoderCallback*>(parameters);
|
|
|
|
boost::unique_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
|
|
|
|
pimpl_->transcoderCallbacks_.push_back(p.callback);
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to transcode DICOM images ("
|
|
<< pimpl_->transcoderCallbacks_.size() << " transcoder(s) now active)";
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterJobsUnserializer(const void* parameters)
|
|
{
|
|
const _OrthancPluginJobsUnserializer& p =
|
|
*reinterpret_cast<const _OrthancPluginJobsUnserializer*>(parameters);
|
|
|
|
boost::mutex::scoped_lock lock(pimpl_->jobsUnserializersMutex_);
|
|
|
|
pimpl_->jobsUnserializers_.push_back(p.unserializer);
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to unserialize jobs ("
|
|
<< pimpl_->jobsUnserializers_.size() << " unserializer(s) now active)";
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterIncomingHttpRequestFilter(const void* parameters)
|
|
{
|
|
const _OrthancPluginIncomingHttpRequestFilter& p =
|
|
*reinterpret_cast<const _OrthancPluginIncomingHttpRequestFilter*>(parameters);
|
|
|
|
boost::unique_lock<boost::shared_mutex> lock(pimpl_->incomingHttpRequestFilterMutex_);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to filter incoming HTTP requests";
|
|
pimpl_->incomingHttpRequestFilters_.push_back(p.callback);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterIncomingHttpRequestFilter2(const void* parameters)
|
|
{
|
|
const _OrthancPluginIncomingHttpRequestFilter2& p =
|
|
*reinterpret_cast<const _OrthancPluginIncomingHttpRequestFilter2*>(parameters);
|
|
|
|
boost::unique_lock<boost::shared_mutex> lock(pimpl_->incomingHttpRequestFilterMutex_);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to filter incoming HTTP requests";
|
|
pimpl_->incomingHttpRequestFilters2_.push_back(p.callback);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterIncomingDicomInstanceFilter(const void* parameters)
|
|
{
|
|
const _OrthancPluginIncomingDicomInstanceFilter& p =
|
|
*reinterpret_cast<const _OrthancPluginIncomingDicomInstanceFilter*>(parameters);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to filter incoming DICOM instances";
|
|
pimpl_->incomingDicomInstanceFilters_.push_back(p.callback);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterIncomingCStoreInstanceFilter(const void* parameters)
|
|
{
|
|
const _OrthancPluginIncomingCStoreInstanceFilter& p =
|
|
*reinterpret_cast<const _OrthancPluginIncomingCStoreInstanceFilter*>(parameters);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to filter incoming C-Store DICOM instances";
|
|
pimpl_->incomingCStoreInstanceFilters_.push_back(p.callback);
|
|
}
|
|
|
|
void OrthancPlugins::RegisterReceivedInstanceCallback(const void* parameters)
|
|
{
|
|
const _OrthancPluginReceivedInstanceCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginReceivedInstanceCallback*>(parameters);
|
|
|
|
if (pimpl_->receivedInstanceCallback_ != NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin,
|
|
"Can only register one plugin callback to process received instances");
|
|
}
|
|
else
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a received instance callback";
|
|
pimpl_->receivedInstanceCallback_ = p.callback;
|
|
}
|
|
}
|
|
|
|
void OrthancPlugins::RegisterRefreshMetricsCallback(const void* parameters)
|
|
{
|
|
const _OrthancPluginRegisterRefreshMetricsCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterRefreshMetricsCallback*>(parameters);
|
|
|
|
boost::mutex::scoped_lock lock(pimpl_->refreshMetricsMutex_);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to refresh its metrics";
|
|
pimpl_->refreshMetricsCallbacks_.push_back(p.callback);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterStorageCommitmentScpCallback(const void* parameters)
|
|
{
|
|
const _OrthancPluginRegisterStorageCommitmentScpCallback& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterStorageCommitmentScpCallback*>(parameters);
|
|
|
|
boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_);
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a storage commitment callback";
|
|
|
|
pimpl_->storageCommitmentScpCallbacks_.push_back(new PImpl::StorageCommitmentScp(p));
|
|
}
|
|
|
|
|
|
void OrthancPlugins::AnswerBuffer(const void* parameters)
|
|
{
|
|
const _OrthancPluginAnswerBuffer& p =
|
|
*reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
|
|
|
|
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
|
|
translatedOutput.SetContentType(p.mimeType);
|
|
translatedOutput.Answer(p.answer, p.answerSize);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::Redirect(const void* parameters)
|
|
{
|
|
const _OrthancPluginOutputPlusArgument& p =
|
|
*reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
|
|
|
|
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
|
|
translatedOutput.Redirect(p.argument);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::SendHttpStatusCode(const void* parameters)
|
|
{
|
|
const _OrthancPluginSendHttpStatusCode& p =
|
|
*reinterpret_cast<const _OrthancPluginSendHttpStatusCode*>(parameters);
|
|
|
|
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
|
|
translatedOutput.SendStatus(static_cast<HttpStatus>(p.status));
|
|
}
|
|
|
|
|
|
void OrthancPlugins::SendHttpStatus(const void* parameters)
|
|
{
|
|
const _OrthancPluginSendHttpStatus& p =
|
|
*reinterpret_cast<const _OrthancPluginSendHttpStatus*>(parameters);
|
|
|
|
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
|
|
HttpStatus status = static_cast<HttpStatus>(p.status);
|
|
|
|
if (p.bodySize > 0 && p.body != NULL)
|
|
{
|
|
translatedOutput.SendStatus(status, reinterpret_cast<const char*>(p.body), p.bodySize);
|
|
}
|
|
else
|
|
{
|
|
translatedOutput.SendStatus(status);
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::SendUnauthorized(const void* parameters)
|
|
{
|
|
const _OrthancPluginOutputPlusArgument& p =
|
|
*reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
|
|
|
|
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
|
|
translatedOutput.SendUnauthorized(p.argument);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::SendMethodNotAllowed(const void* parameters)
|
|
{
|
|
const _OrthancPluginOutputPlusArgument& p =
|
|
*reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
|
|
|
|
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
|
|
translatedOutput.SendMethodNotAllowed(p.argument);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::SetCookie(const void* parameters)
|
|
{
|
|
const _OrthancPluginSetHttpHeader& p =
|
|
*reinterpret_cast<const _OrthancPluginSetHttpHeader*>(parameters);
|
|
|
|
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
|
|
translatedOutput.SetCookie(p.key, p.value);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::SetHttpHeader(const void* parameters)
|
|
{
|
|
const _OrthancPluginSetHttpHeader& p =
|
|
*reinterpret_cast<const _OrthancPluginSetHttpHeader*>(parameters);
|
|
|
|
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
|
|
translatedOutput.AddHeader(p.key, p.value);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::SetHttpErrorDetails(const void* parameters)
|
|
{
|
|
const _OrthancPluginSetHttpErrorDetails& p =
|
|
*reinterpret_cast<const _OrthancPluginSetHttpErrorDetails*>(parameters);
|
|
|
|
PImpl::PluginHttpOutput* output =
|
|
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output);
|
|
output->SetErrorDetails(p.details, (p.log != 0));
|
|
}
|
|
|
|
|
|
void OrthancPlugins::CompressAndAnswerPngImage(const void* parameters)
|
|
{
|
|
// Bridge for backward compatibility with Orthanc <= 0.9.3
|
|
const _OrthancPluginCompressAndAnswerPngImage& p =
|
|
*reinterpret_cast<const _OrthancPluginCompressAndAnswerPngImage*>(parameters);
|
|
|
|
_OrthancPluginCompressAndAnswerImage p2;
|
|
p2.output = p.output;
|
|
p2.imageFormat = OrthancPluginImageFormat_Png;
|
|
p2.pixelFormat = p.format;
|
|
p2.width = p.width;
|
|
p2.height = p.height;
|
|
p2.pitch = p.height;
|
|
p2.buffer = p.buffer;
|
|
p2.quality = 0;
|
|
|
|
CompressAndAnswerImage(&p2);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::CompressAndAnswerImage(const void* parameters)
|
|
{
|
|
const _OrthancPluginCompressAndAnswerImage& p =
|
|
*reinterpret_cast<const _OrthancPluginCompressAndAnswerImage*>(parameters);
|
|
|
|
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
|
|
|
|
ImageAccessor accessor;
|
|
accessor.AssignReadOnly(Plugins::Convert(p.pixelFormat), p.width, p.height, p.pitch, p.buffer);
|
|
|
|
std::string compressed;
|
|
|
|
switch (p.imageFormat)
|
|
{
|
|
case OrthancPluginImageFormat_Png:
|
|
{
|
|
PngWriter writer;
|
|
IImageWriter::WriteToMemory(writer, compressed, accessor);
|
|
translatedOutput.SetContentType(MimeType_Png);
|
|
break;
|
|
}
|
|
|
|
case OrthancPluginImageFormat_Jpeg:
|
|
{
|
|
JpegWriter writer;
|
|
writer.SetQuality(p.quality);
|
|
IImageWriter::WriteToMemory(writer, compressed, accessor);
|
|
translatedOutput.SetContentType(MimeType_Jpeg);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
translatedOutput.Answer(compressed);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::GetDicomForInstance(const void* parameters)
|
|
{
|
|
const _OrthancPluginGetDicomForInstance& p =
|
|
*reinterpret_cast<const _OrthancPluginGetDicomForInstance*>(parameters);
|
|
|
|
std::string dicom;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
lock.GetContext().ReadDicom(dicom, p.instanceId);
|
|
}
|
|
|
|
CopyToMemoryBuffer(p.target, dicom);
|
|
}
|
|
|
|
static void ThrowOnHttpError(HttpStatus httpStatus)
|
|
{
|
|
int intHttpStatus = static_cast<int>(httpStatus);
|
|
if (intHttpStatus >= 200 && intHttpStatus <= 300)
|
|
{
|
|
return; // not an error
|
|
}
|
|
else if (intHttpStatus == 401 || intHttpStatus == 403)
|
|
{
|
|
throw OrthancException(ErrorCode_Unauthorized);
|
|
}
|
|
else if (intHttpStatus == 404)
|
|
{
|
|
throw OrthancException(ErrorCode_UnknownResource);
|
|
}
|
|
else if (intHttpStatus == 415)
|
|
{
|
|
throw OrthancException(ErrorCode_UnsupportedMediaType);
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_BadRequest);
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RestApiGet(const void* parameters,
|
|
bool afterPlugins)
|
|
{
|
|
const _OrthancPluginRestApiGet& p =
|
|
*reinterpret_cast<const _OrthancPluginRestApiGet*>(parameters);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin making REST GET call on URI " << p.uri
|
|
<< (afterPlugins ? " (after plugins)" : " (built-in API)");
|
|
|
|
IHttpHandler* handler;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins);
|
|
}
|
|
|
|
std::map<std::string, std::string> httpHeaders;
|
|
|
|
std::string result;
|
|
|
|
ThrowOnHttpError(IHttpHandler::SimpleGet(result, NULL, *handler, RequestOrigin_Plugins, p.uri, httpHeaders));
|
|
CopyToMemoryBuffer(p.target, result);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RestApiGet2(const void* parameters)
|
|
{
|
|
const _OrthancPluginRestApiGet2& p =
|
|
*reinterpret_cast<const _OrthancPluginRestApiGet2*>(parameters);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin making REST GET call on URI " << p.uri
|
|
<< (p.afterPlugins ? " (after plugins)" : " (built-in API)");
|
|
|
|
HttpToolbox::Arguments headers;
|
|
|
|
for (uint32_t i = 0; i < p.headersCount; i++)
|
|
{
|
|
std::string name(p.headersKeys[i]);
|
|
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
|
headers[name] = p.headersValues[i];
|
|
}
|
|
|
|
IHttpHandler* handler;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!p.afterPlugins);
|
|
}
|
|
|
|
std::string result;
|
|
|
|
ThrowOnHttpError(IHttpHandler::SimpleGet(result, NULL, *handler, RequestOrigin_Plugins, p.uri, headers));
|
|
CopyToMemoryBuffer(p.target, result);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RestApiPostPut(bool isPost,
|
|
const void* parameters,
|
|
bool afterPlugins)
|
|
{
|
|
const _OrthancPluginRestApiPostPut& p =
|
|
*reinterpret_cast<const _OrthancPluginRestApiPostPut*>(parameters);
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin making REST " << EnumerationToString(isPost ? HttpMethod_Post : HttpMethod_Put)
|
|
<< " call on URI " << p.uri << (afterPlugins ? " (after plugins)" : " (built-in API)");
|
|
|
|
IHttpHandler* handler;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins);
|
|
}
|
|
|
|
std::map<std::string, std::string> httpHeaders;
|
|
|
|
std::string result;
|
|
|
|
ThrowOnHttpError((isPost ?
|
|
IHttpHandler::SimplePost(result, NULL, *handler, RequestOrigin_Plugins, p.uri,
|
|
p.body, p.bodySize, httpHeaders) :
|
|
IHttpHandler::SimplePut(result, NULL, *handler, RequestOrigin_Plugins, p.uri,
|
|
p.body, p.bodySize, httpHeaders)));
|
|
CopyToMemoryBuffer(p.target, result);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RestApiDelete(const void* parameters,
|
|
bool afterPlugins)
|
|
{
|
|
const char* uri = reinterpret_cast<const char*>(parameters);
|
|
CLOG(INFO, PLUGINS) << "Plugin making REST DELETE call on URI " << uri
|
|
<< (afterPlugins ? " (after plugins)" : " (built-in API)");
|
|
|
|
IHttpHandler* handler;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins);
|
|
}
|
|
|
|
std::map<std::string, std::string> httpHeaders;
|
|
|
|
ThrowOnHttpError(IHttpHandler::SimpleDelete(NULL, *handler, RequestOrigin_Plugins, uri, httpHeaders));
|
|
}
|
|
|
|
|
|
void OrthancPlugins::LookupResource(_OrthancPluginService service,
|
|
const void* parameters)
|
|
{
|
|
const _OrthancPluginRetrieveDynamicString& p =
|
|
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters);
|
|
|
|
/**
|
|
* The enumeration below only uses the tags that are indexed in
|
|
* the Orthanc database. It reflects the
|
|
* "CandidateResources::ApplyFilter()" method of the
|
|
* "OrthancFindRequestHandler" class.
|
|
**/
|
|
|
|
DicomTag tag(0, 0);
|
|
ResourceType level;
|
|
switch (service)
|
|
{
|
|
case _OrthancPluginService_LookupPatient:
|
|
tag = DICOM_TAG_PATIENT_ID;
|
|
level = ResourceType_Patient;
|
|
break;
|
|
|
|
case _OrthancPluginService_LookupStudy:
|
|
tag = DICOM_TAG_STUDY_INSTANCE_UID;
|
|
level = ResourceType_Study;
|
|
break;
|
|
|
|
case _OrthancPluginService_LookupStudyWithAccessionNumber:
|
|
tag = DICOM_TAG_ACCESSION_NUMBER;
|
|
level = ResourceType_Study;
|
|
break;
|
|
|
|
case _OrthancPluginService_LookupSeries:
|
|
tag = DICOM_TAG_SERIES_INSTANCE_UID;
|
|
level = ResourceType_Series;
|
|
break;
|
|
|
|
case _OrthancPluginService_LookupInstance:
|
|
tag = DICOM_TAG_SOP_INSTANCE_UID;
|
|
level = ResourceType_Instance;
|
|
break;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
|
|
std::vector<std::string> result;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
lock.GetContext().GetIndex().LookupIdentifierExact(result, level, tag, p.argument);
|
|
}
|
|
|
|
if (result.size() == 1)
|
|
{
|
|
*p.result = CopyString(result[0]);
|
|
}
|
|
else
|
|
{
|
|
if (result.size() > 1)
|
|
{
|
|
LOG(WARNING) << "LookupResource(): Multiple resources match the query (instead of 0 or 1), which indicates "
|
|
<< "your DICOM database breaks the DICOM model of the real world";
|
|
}
|
|
|
|
throw OrthancException(ErrorCode_UnknownResource);
|
|
}
|
|
}
|
|
|
|
|
|
static void AccessInstanceMetadataInternal(bool checkExistence,
|
|
const _OrthancPluginAccessDicomInstance& params,
|
|
const DicomInstanceToStore& instance)
|
|
{
|
|
MetadataType metadata;
|
|
|
|
try
|
|
{
|
|
metadata = StringToMetadata(params.key);
|
|
}
|
|
catch (OrthancException&)
|
|
{
|
|
// Unknown metadata
|
|
if (checkExistence)
|
|
{
|
|
*params.resultInt64 = -1;
|
|
}
|
|
else
|
|
{
|
|
*params.resultString = NULL;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
ServerIndex::MetadataMap::const_iterator it =
|
|
instance.GetMetadata().find(std::make_pair(ResourceType_Instance, metadata));
|
|
|
|
if (checkExistence)
|
|
{
|
|
if (it != instance.GetMetadata().end())
|
|
{
|
|
*params.resultInt64 = 1;
|
|
}
|
|
else
|
|
{
|
|
*params.resultInt64 = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (it != instance.GetMetadata().end())
|
|
{
|
|
*params.resultString = it->second.c_str();
|
|
}
|
|
else
|
|
{
|
|
// Error: Missing metadata
|
|
*params.resultString = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::AccessDicomInstance(_OrthancPluginService service,
|
|
const void* parameters)
|
|
{
|
|
const _OrthancPluginAccessDicomInstance& p =
|
|
*reinterpret_cast<const _OrthancPluginAccessDicomInstance*>(parameters);
|
|
|
|
if (p.instance == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
const DicomInstanceToStore& instance =
|
|
reinterpret_cast<const IDicomInstance*>(p.instance)->GetInstance();
|
|
|
|
switch (service)
|
|
{
|
|
case _OrthancPluginService_GetInstanceRemoteAet:
|
|
*p.resultString = instance.GetOrigin().GetRemoteAetC();
|
|
return;
|
|
|
|
case _OrthancPluginService_GetInstanceSize:
|
|
*p.resultInt64 = instance.GetBufferSize();
|
|
return;
|
|
|
|
case _OrthancPluginService_GetInstanceData:
|
|
*p.resultString = reinterpret_cast<const char*>(instance.GetBufferData());
|
|
return;
|
|
|
|
case _OrthancPluginService_HasInstanceMetadata:
|
|
AccessInstanceMetadataInternal(true, p, instance);
|
|
return;
|
|
|
|
case _OrthancPluginService_GetInstanceMetadata:
|
|
AccessInstanceMetadataInternal(false, p, instance);
|
|
return;
|
|
|
|
case _OrthancPluginService_GetInstanceJson:
|
|
case _OrthancPluginService_GetInstanceSimplifiedJson:
|
|
{
|
|
Json::Value dicomAsJson;
|
|
std::set<DicomTag> ignoreTagLength;
|
|
instance.GetDicomAsJson(dicomAsJson, ignoreTagLength);
|
|
|
|
std::string s;
|
|
if (service == _OrthancPluginService_GetInstanceJson)
|
|
{
|
|
Toolbox::WriteStyledJson(s, dicomAsJson);
|
|
}
|
|
else
|
|
{
|
|
Json::Value simplified;
|
|
Toolbox::SimplifyDicomAsJson(simplified, dicomAsJson, DicomToJsonFormat_Human);
|
|
Toolbox::WriteStyledJson(s, simplified);
|
|
}
|
|
|
|
*p.resultStringToFree = CopyString(s);
|
|
return;
|
|
}
|
|
|
|
case _OrthancPluginService_GetInstanceOrigin: // New in Orthanc 0.9.5
|
|
*p.resultOrigin = Plugins::Convert(instance.GetOrigin().GetRequestOrigin());
|
|
return;
|
|
|
|
case _OrthancPluginService_GetInstanceTransferSyntaxUid: // New in Orthanc 1.6.1
|
|
{
|
|
DicomTransferSyntax s;
|
|
if (instance.LookupTransferSyntax(s))
|
|
{
|
|
*p.resultStringToFree = CopyString(GetTransferSyntaxUid(s));
|
|
}
|
|
else
|
|
{
|
|
*p.resultStringToFree = CopyString("");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case _OrthancPluginService_HasInstancePixelData: // New in Orthanc 1.6.1
|
|
*p.resultInt64 = instance.HasPixelData();
|
|
return;
|
|
|
|
case _OrthancPluginService_GetInstanceFramesCount: // New in Orthanc 1.7.0
|
|
*p.resultInt64 = instance.GetFramesCount();
|
|
return;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::BufferCompression(const void* parameters)
|
|
{
|
|
const _OrthancPluginBufferCompression& p =
|
|
*reinterpret_cast<const _OrthancPluginBufferCompression*>(parameters);
|
|
|
|
std::string result;
|
|
|
|
{
|
|
std::unique_ptr<DeflateBaseCompressor> compressor;
|
|
|
|
switch (p.compression)
|
|
{
|
|
case OrthancPluginCompressionType_Zlib:
|
|
{
|
|
compressor.reset(new ZlibCompressor);
|
|
compressor->SetPrefixWithUncompressedSize(false);
|
|
break;
|
|
}
|
|
|
|
case OrthancPluginCompressionType_ZlibWithSize:
|
|
{
|
|
compressor.reset(new ZlibCompressor);
|
|
compressor->SetPrefixWithUncompressedSize(true);
|
|
break;
|
|
}
|
|
|
|
case OrthancPluginCompressionType_Gzip:
|
|
{
|
|
compressor.reset(new GzipCompressor);
|
|
compressor->SetPrefixWithUncompressedSize(false);
|
|
break;
|
|
}
|
|
|
|
case OrthancPluginCompressionType_GzipWithSize:
|
|
{
|
|
compressor.reset(new GzipCompressor);
|
|
compressor->SetPrefixWithUncompressedSize(true);
|
|
break;
|
|
}
|
|
|
|
case OrthancPluginCompressionType_None:
|
|
{
|
|
CopyToMemoryBuffer(p.target, p.source, p.size);
|
|
return;
|
|
}
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
if (p.uncompress)
|
|
{
|
|
compressor->Uncompress(result, p.source, p.size);
|
|
}
|
|
else
|
|
{
|
|
compressor->Compress(result, p.source, p.size);
|
|
}
|
|
}
|
|
|
|
CopyToMemoryBuffer(p.target, result);
|
|
}
|
|
|
|
|
|
static OrthancPluginImage* ReturnImage(std::unique_ptr<ImageAccessor>& image)
|
|
{
|
|
// Images returned to plugins are assumed to be writeable. If the
|
|
// input image is read-only, we return a copy so that it can be modified.
|
|
|
|
if (image.get() == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
if (image->IsReadOnly())
|
|
{
|
|
std::unique_ptr<Image> copy(new Image(image->GetFormat(), image->GetWidth(), image->GetHeight(), false));
|
|
ImageProcessing::Copy(*copy, *image);
|
|
image.reset(NULL);
|
|
return reinterpret_cast<OrthancPluginImage*>(copy.release());
|
|
}
|
|
else
|
|
{
|
|
return reinterpret_cast<OrthancPluginImage*>(image.release());
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::AccessDicomInstance2(_OrthancPluginService service,
|
|
const void* parameters)
|
|
{
|
|
const _OrthancPluginAccessDicomInstance2& p =
|
|
*reinterpret_cast<const _OrthancPluginAccessDicomInstance2*>(parameters);
|
|
|
|
if (p.instance == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
const DicomInstanceToStore& instance =
|
|
reinterpret_cast<const IDicomInstance*>(p.instance)->GetInstance();
|
|
|
|
switch (service)
|
|
{
|
|
case _OrthancPluginService_GetInstanceFramesCount:
|
|
*p.targetUint32 = instance.GetFramesCount();
|
|
return;
|
|
|
|
case _OrthancPluginService_GetInstanceRawFrame:
|
|
{
|
|
if (p.targetBuffer == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
p.targetBuffer->data = NULL;
|
|
p.targetBuffer->size = 0;
|
|
|
|
MimeType mime;
|
|
std::string frame;
|
|
instance.GetParsedDicomFile().GetRawFrame(frame, mime, p.frameIndex);
|
|
CopyToMemoryBuffer(p.targetBuffer, frame);
|
|
return;
|
|
}
|
|
|
|
case _OrthancPluginService_GetInstanceDecodedFrame:
|
|
{
|
|
if (p.targetImage == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
std::unique_ptr<ImageAccessor> decoded;
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
decoded.reset(lock.GetContext().DecodeDicomFrame(instance, p.frameIndex));
|
|
}
|
|
|
|
*(p.targetImage) = ReturnImage(decoded);
|
|
return;
|
|
}
|
|
|
|
case _OrthancPluginService_SerializeDicomInstance:
|
|
{
|
|
if (p.targetBuffer == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
p.targetBuffer->data = NULL;
|
|
p.targetBuffer->size = 0;
|
|
CopyToMemoryBuffer(p.targetBuffer, instance.GetBufferData(), instance.GetBufferSize());
|
|
return;
|
|
}
|
|
|
|
case _OrthancPluginService_GetInstanceAdvancedJson:
|
|
{
|
|
if (p.targetStringToFree == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
Json::Value json;
|
|
instance.DatasetToJson(json, Plugins::Convert(p.format),
|
|
static_cast<DicomToJsonFlags>(p.flags), p.maxStringLength);
|
|
|
|
std::string s;
|
|
Toolbox::WriteFastJson(s, json);
|
|
*p.targetStringToFree = CopyString(s);
|
|
return;
|
|
}
|
|
|
|
case _OrthancPluginService_GetInstanceDicomWebJson:
|
|
case _OrthancPluginService_GetInstanceDicomWebXml:
|
|
{
|
|
if (p.targetStringToFree == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
DicomWebBinaryFormatter formatter(p.dicomWebCallback, p.dicomWebPayload);
|
|
formatter.Apply(p.targetStringToFree,
|
|
(service == _OrthancPluginService_GetInstanceDicomWebJson),
|
|
instance.GetParsedDicomFile());
|
|
return;
|
|
}
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::UncompressImage(const void* parameters)
|
|
{
|
|
const _OrthancPluginUncompressImage& p = *reinterpret_cast<const _OrthancPluginUncompressImage*>(parameters);
|
|
|
|
std::unique_ptr<ImageAccessor> image;
|
|
|
|
switch (p.format)
|
|
{
|
|
case OrthancPluginImageFormat_Png:
|
|
{
|
|
image.reset(new PngReader);
|
|
reinterpret_cast<PngReader&>(*image).ReadFromMemory(p.data, p.size);
|
|
break;
|
|
}
|
|
|
|
case OrthancPluginImageFormat_Jpeg:
|
|
{
|
|
image.reset(new JpegReader);
|
|
reinterpret_cast<JpegReader&>(*image).ReadFromMemory(p.data, p.size);
|
|
break;
|
|
}
|
|
|
|
case OrthancPluginImageFormat_Dicom:
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
image.reset(lock.GetContext().DecodeDicomFrame(p.data, p.size, 0));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
*(p.target) = ReturnImage(image);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::CompressImage(const void* parameters)
|
|
{
|
|
const _OrthancPluginCompressImage& p = *reinterpret_cast<const _OrthancPluginCompressImage*>(parameters);
|
|
|
|
std::string compressed;
|
|
|
|
ImageAccessor accessor;
|
|
accessor.AssignReadOnly(Plugins::Convert(p.pixelFormat), p.width, p.height, p.pitch, p.buffer);
|
|
|
|
switch (p.imageFormat)
|
|
{
|
|
case OrthancPluginImageFormat_Png:
|
|
{
|
|
PngWriter writer;
|
|
IImageWriter::WriteToMemory(writer, compressed, accessor);
|
|
break;
|
|
}
|
|
|
|
case OrthancPluginImageFormat_Jpeg:
|
|
{
|
|
JpegWriter writer;
|
|
writer.SetQuality(p.quality);
|
|
IImageWriter::WriteToMemory(writer, compressed, accessor);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
CopyToMemoryBuffer(p.target, compressed);
|
|
}
|
|
|
|
|
|
static void SetupHttpClient(HttpClient& client,
|
|
const _OrthancPluginCallHttpClient2& parameters)
|
|
{
|
|
client.SetUrl(parameters.url);
|
|
client.SetConvertHeadersToLowerCase(false);
|
|
|
|
if (parameters.timeout != 0)
|
|
{
|
|
client.SetTimeout(parameters.timeout);
|
|
}
|
|
|
|
if (parameters.username != NULL &&
|
|
parameters.password != NULL)
|
|
{
|
|
client.SetCredentials(parameters.username, parameters.password);
|
|
}
|
|
|
|
if (parameters.certificateFile != NULL)
|
|
{
|
|
std::string certificate(parameters.certificateFile);
|
|
std::string key, password;
|
|
|
|
if (parameters.certificateKeyFile)
|
|
{
|
|
key.assign(parameters.certificateKeyFile);
|
|
}
|
|
|
|
if (parameters.certificateKeyPassword)
|
|
{
|
|
password.assign(parameters.certificateKeyPassword);
|
|
}
|
|
|
|
client.SetClientCertificate(certificate, key, password);
|
|
}
|
|
|
|
client.SetPkcs11Enabled(parameters.pkcs11 ? true : false);
|
|
|
|
for (uint32_t i = 0; i < parameters.headersCount; i++)
|
|
{
|
|
if (parameters.headersKeys[i] == NULL ||
|
|
parameters.headersValues[i] == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
client.AddHeader(parameters.headersKeys[i], parameters.headersValues[i]);
|
|
}
|
|
|
|
switch (parameters.method)
|
|
{
|
|
case OrthancPluginHttpMethod_Get:
|
|
client.SetMethod(HttpMethod_Get);
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Post:
|
|
client.SetMethod(HttpMethod_Post);
|
|
client.SetExternalBody(parameters.body, parameters.bodySize);
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Put:
|
|
client.SetMethod(HttpMethod_Put);
|
|
client.SetExternalBody(parameters.body, parameters.bodySize);
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Delete:
|
|
client.SetMethod(HttpMethod_Delete);
|
|
break;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
}
|
|
|
|
|
|
static void ExecuteHttpClientWithoutChunkedBody(uint16_t& httpStatus,
|
|
OrthancPluginMemoryBuffer* answerBody,
|
|
OrthancPluginMemoryBuffer* answerHeaders,
|
|
HttpClient& client)
|
|
{
|
|
std::string body;
|
|
HttpClient::HttpHeaders headers;
|
|
|
|
bool success = client.Apply(body, headers);
|
|
|
|
// The HTTP request has succeeded
|
|
httpStatus = static_cast<uint16_t>(client.GetLastStatus());
|
|
|
|
if (!success)
|
|
{
|
|
HttpClient::ThrowException(client.GetLastStatus());
|
|
}
|
|
|
|
// Copy the HTTP headers of the answer, if the plugin requested them
|
|
PluginMemoryBuffer32 tmpHeaders;
|
|
if (answerHeaders != NULL)
|
|
{
|
|
CopyDictionary(tmpHeaders, headers);
|
|
}
|
|
|
|
// Copy the body of the answer if it makes sense
|
|
PluginMemoryBuffer32 tmpBody;
|
|
if (client.GetMethod() != HttpMethod_Delete &&
|
|
answerBody != NULL)
|
|
{
|
|
tmpBody.Assign(body);
|
|
}
|
|
|
|
// All the memory has been allocated at this point, so we can safely release the buffers
|
|
if (answerHeaders != NULL)
|
|
{
|
|
tmpHeaders.Release(answerHeaders);
|
|
}
|
|
|
|
if (answerBody != NULL)
|
|
{
|
|
tmpBody.Release(answerBody);
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::CallHttpClient(const void* parameters)
|
|
{
|
|
const _OrthancPluginCallHttpClient& p = *reinterpret_cast<const _OrthancPluginCallHttpClient*>(parameters);
|
|
|
|
HttpClient client;
|
|
|
|
{
|
|
_OrthancPluginCallHttpClient2 converted;
|
|
memset(&converted, 0, sizeof(converted));
|
|
|
|
converted.answerBody = NULL;
|
|
converted.answerHeaders = NULL;
|
|
converted.httpStatus = NULL;
|
|
converted.method = p.method;
|
|
converted.url = p.url;
|
|
converted.headersCount = 0;
|
|
converted.headersKeys = NULL;
|
|
converted.headersValues = NULL;
|
|
converted.body = p.body;
|
|
converted.bodySize = p.bodySize;
|
|
converted.username = p.username;
|
|
converted.password = p.password;
|
|
converted.timeout = 0; // Use default timeout
|
|
converted.certificateFile = NULL;
|
|
converted.certificateKeyFile = NULL;
|
|
converted.certificateKeyPassword = NULL;
|
|
converted.pkcs11 = false;
|
|
|
|
SetupHttpClient(client, converted);
|
|
}
|
|
|
|
uint16_t status;
|
|
ExecuteHttpClientWithoutChunkedBody(status, p.target, NULL, client);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::CallHttpClient2(const void* parameters)
|
|
{
|
|
const _OrthancPluginCallHttpClient2& p = *reinterpret_cast<const _OrthancPluginCallHttpClient2*>(parameters);
|
|
|
|
if (p.httpStatus == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
HttpClient client;
|
|
|
|
if (p.method == OrthancPluginHttpMethod_Post ||
|
|
p.method == OrthancPluginHttpMethod_Put)
|
|
{
|
|
client.SetExternalBody(p.body, p.bodySize);
|
|
}
|
|
|
|
SetupHttpClient(client, p);
|
|
ExecuteHttpClientWithoutChunkedBody(*p.httpStatus, p.answerBody, p.answerHeaders, client);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::ChunkedHttpClient(const void* parameters)
|
|
{
|
|
const _OrthancPluginChunkedHttpClient& p =
|
|
*reinterpret_cast<const _OrthancPluginChunkedHttpClient*>(parameters);
|
|
|
|
if (p.httpStatus == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
HttpClient client;
|
|
|
|
{
|
|
_OrthancPluginCallHttpClient2 converted;
|
|
memset(&converted, 0, sizeof(converted));
|
|
|
|
converted.answerBody = NULL;
|
|
converted.answerHeaders = NULL;
|
|
converted.httpStatus = NULL;
|
|
converted.method = p.method;
|
|
converted.url = p.url;
|
|
converted.headersCount = p.headersCount;
|
|
converted.headersKeys = p.headersKeys;
|
|
converted.headersValues = p.headersValues;
|
|
converted.body = NULL;
|
|
converted.bodySize = 0;
|
|
converted.username = p.username;
|
|
converted.password = p.password;
|
|
converted.timeout = p.timeout;
|
|
converted.certificateFile = p.certificateFile;
|
|
converted.certificateKeyFile = p.certificateKeyFile;
|
|
converted.certificateKeyPassword = p.certificateKeyPassword;
|
|
converted.pkcs11 = p.pkcs11;
|
|
|
|
SetupHttpClient(client, converted);
|
|
}
|
|
|
|
HttpClientChunkedRequest body(p, pimpl_->dictionary_);
|
|
client.SetBody(body);
|
|
|
|
HttpClientChunkedAnswer answer(p, pimpl_->dictionary_);
|
|
|
|
bool success = client.Apply(answer);
|
|
|
|
*p.httpStatus = static_cast<uint16_t>(client.GetLastStatus());
|
|
|
|
if (!success)
|
|
{
|
|
HttpClient::ThrowException(client.GetLastStatus());
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::CallRestApi(const void* parameters)
|
|
{
|
|
const _OrthancPluginCallRestApi& p = *reinterpret_cast<const _OrthancPluginCallRestApi*>(parameters);
|
|
|
|
if (p.httpStatus == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
const char* methodString;
|
|
switch (p.method)
|
|
{
|
|
case OrthancPluginHttpMethod_Get:
|
|
methodString = "GET";
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Post:
|
|
methodString = "POST";
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Put:
|
|
methodString = "PUT";
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Delete:
|
|
methodString = "DELETE";
|
|
break;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
CLOG(INFO, PLUGINS) << "Plugin making REST " << methodString << " call to URI " << p.uri
|
|
<< (p.afterPlugins ? " (after plugins)" : " (built-in API)");
|
|
|
|
HttpToolbox::Arguments headers;
|
|
|
|
for (uint32_t i = 0; i < p.headersCount; i++)
|
|
{
|
|
std::string name(p.headersKeys[i]);
|
|
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
|
headers[name] = p.headersValues[i];
|
|
}
|
|
|
|
IHttpHandler* handler;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!p.afterPlugins);
|
|
}
|
|
|
|
std::string answerBody;
|
|
std::map<std::string, std::string> answerHeaders;
|
|
HttpStatus status;
|
|
|
|
switch (p.method)
|
|
{
|
|
case OrthancPluginHttpMethod_Get:
|
|
status = IHttpHandler::SimpleGet(
|
|
answerBody, &answerHeaders, *handler, RequestOrigin_Plugins, p.uri, headers);
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Post:
|
|
status = IHttpHandler::SimplePost(
|
|
answerBody, &answerHeaders, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, headers);
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Put:
|
|
status = IHttpHandler::SimplePut(
|
|
answerBody, &answerHeaders, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, headers);
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Delete:
|
|
status = IHttpHandler::SimpleDelete(
|
|
&answerHeaders, *handler, RequestOrigin_Plugins, p.uri, headers);
|
|
break;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
*p.httpStatus = static_cast<uint16_t>(status);
|
|
|
|
PluginMemoryBuffer32 tmpHeaders;
|
|
if (p.answerHeaders != NULL)
|
|
{
|
|
CopyDictionary(tmpHeaders, answerHeaders);
|
|
}
|
|
|
|
PluginMemoryBuffer32 tmpBody;
|
|
if (p.method != OrthancPluginHttpMethod_Delete &&
|
|
p.answerBody != NULL)
|
|
{
|
|
tmpBody.Assign(answerBody);
|
|
}
|
|
|
|
// All the memory has been allocated at this point, so we can safely release the buffers
|
|
if (p.answerHeaders != NULL)
|
|
{
|
|
tmpHeaders.Release(p.answerHeaders);
|
|
}
|
|
|
|
if (p.answerBody != NULL)
|
|
{
|
|
tmpBody.Release(p.answerBody);
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::CallPeerApi(const void* parameters)
|
|
{
|
|
const _OrthancPluginCallPeerApi& p = *reinterpret_cast<const _OrthancPluginCallPeerApi*>(parameters);
|
|
const OrthancPeers& peers = *reinterpret_cast<const OrthancPeers*>(p.peers);
|
|
|
|
HttpClient client(peers.GetPeerParameters(p.peerIndex), p.uri);
|
|
client.SetConvertHeadersToLowerCase(false);
|
|
|
|
if (p.timeout != 0)
|
|
{
|
|
client.SetTimeout(p.timeout);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < p.additionalHeadersCount; i++)
|
|
{
|
|
if (p.additionalHeadersKeys[i] == NULL ||
|
|
p.additionalHeadersValues[i] == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
client.AddHeader(p.additionalHeadersKeys[i], p.additionalHeadersValues[i]);
|
|
}
|
|
|
|
switch (p.method)
|
|
{
|
|
case OrthancPluginHttpMethod_Get:
|
|
client.SetMethod(HttpMethod_Get);
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Post:
|
|
client.SetMethod(HttpMethod_Post);
|
|
client.SetExternalBody(p.body, p.bodySize);
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Put:
|
|
client.SetMethod(HttpMethod_Put);
|
|
client.SetExternalBody(p.body, p.bodySize);
|
|
break;
|
|
|
|
case OrthancPluginHttpMethod_Delete:
|
|
client.SetMethod(HttpMethod_Delete);
|
|
break;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
std::string body;
|
|
HttpClient::HttpHeaders headers;
|
|
|
|
bool success = client.Apply(body, headers);
|
|
|
|
// The HTTP request has succeeded
|
|
*p.httpStatus = static_cast<uint16_t>(client.GetLastStatus());
|
|
|
|
if (!success)
|
|
{
|
|
HttpClient::ThrowException(client.GetLastStatus());
|
|
}
|
|
|
|
// Copy the HTTP headers of the answer, if the plugin requested them
|
|
PluginMemoryBuffer32 tmpHeaders;
|
|
if (p.answerHeaders != NULL)
|
|
{
|
|
CopyDictionary(tmpHeaders, headers);
|
|
}
|
|
|
|
// Copy the body of the answer if it makes sense
|
|
PluginMemoryBuffer32 tmpBody;
|
|
if (p.method != OrthancPluginHttpMethod_Delete &&
|
|
p.answerBody != NULL)
|
|
{
|
|
tmpBody.Assign(body);
|
|
}
|
|
|
|
// All the memory has been allocated at this point, so we can safely release the buffers
|
|
if (p.answerHeaders != NULL)
|
|
{
|
|
tmpHeaders.Release(p.answerHeaders);
|
|
}
|
|
|
|
if (p.answerBody != NULL)
|
|
{
|
|
tmpBody.Release(p.answerBody);
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::ConvertPixelFormat(const void* parameters)
|
|
{
|
|
const _OrthancPluginConvertPixelFormat& p = *reinterpret_cast<const _OrthancPluginConvertPixelFormat*>(parameters);
|
|
const ImageAccessor& source = *reinterpret_cast<const ImageAccessor*>(p.source);
|
|
|
|
std::unique_ptr<ImageAccessor> target(new Image(Plugins::Convert(p.targetFormat), source.GetWidth(), source.GetHeight(), false));
|
|
ImageProcessing::Convert(*target, source);
|
|
|
|
*(p.target) = ReturnImage(target);
|
|
}
|
|
|
|
|
|
|
|
void OrthancPlugins::GetFontInfo(const void* parameters)
|
|
{
|
|
const _OrthancPluginGetFontInfo& p = *reinterpret_cast<const _OrthancPluginGetFontInfo*>(parameters);
|
|
|
|
{
|
|
OrthancConfiguration::ReaderLock lock;
|
|
|
|
const Font& font = lock.GetConfiguration().GetFontRegistry().GetFont(p.fontIndex);
|
|
|
|
if (p.name != NULL)
|
|
{
|
|
*(p.name) = font.GetName().c_str();
|
|
}
|
|
else if (p.size != NULL)
|
|
{
|
|
*(p.size) = font.GetSize();
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::DrawText(const void* parameters)
|
|
{
|
|
const _OrthancPluginDrawText& p = *reinterpret_cast<const _OrthancPluginDrawText*>(parameters);
|
|
|
|
{
|
|
OrthancConfiguration::ReaderLock lock;
|
|
const Font& font = lock.GetConfiguration().GetFontRegistry().GetFont(p.fontIndex);
|
|
|
|
ImageAccessor& target = *reinterpret_cast<ImageAccessor*>(p.image);
|
|
font.Draw(target, p.utf8Text, p.x, p.y, p.r, p.g, p.b);
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::ApplyDicomToJson(_OrthancPluginService service,
|
|
const void* parameters)
|
|
{
|
|
const _OrthancPluginDicomToJson& p =
|
|
*reinterpret_cast<const _OrthancPluginDicomToJson*>(parameters);
|
|
|
|
std::unique_ptr<ParsedDicomFile> dicom;
|
|
|
|
if (service == _OrthancPluginService_DicomBufferToJson)
|
|
{
|
|
dicom.reset(new ParsedDicomFile(p.buffer, p.size));
|
|
}
|
|
else
|
|
{
|
|
if (p.instanceId == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
|
|
std::string content;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
lock.GetContext().ReadDicom(content, p.instanceId);
|
|
}
|
|
|
|
dicom.reset(new ParsedDicomFile(content));
|
|
}
|
|
|
|
Json::Value json;
|
|
dicom->DatasetToJson(json, Plugins::Convert(p.format),
|
|
static_cast<DicomToJsonFlags>(p.flags), p.maxStringLength);
|
|
|
|
std::string s;
|
|
Toolbox::WriteFastJson(s, json);
|
|
*p.result = CopyString(s);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::ApplyCreateDicom(const _OrthancPluginCreateDicom& parameters,
|
|
const char* privateCreatorC)
|
|
{
|
|
Json::Value json;
|
|
|
|
if (parameters.json == NULL)
|
|
{
|
|
json = Json::objectValue;
|
|
}
|
|
else if (!Toolbox::ReadJson(json, parameters.json))
|
|
{
|
|
throw OrthancException(ErrorCode_BadJson);
|
|
}
|
|
|
|
std::string dicom;
|
|
|
|
{
|
|
// Fix issue 168 (Plugins can't read private tags from the
|
|
// configuration file)
|
|
// https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=168
|
|
std::string privateCreator;
|
|
|
|
if (privateCreatorC == NULL)
|
|
{
|
|
OrthancConfiguration::ReaderLock lock;
|
|
privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator();
|
|
}
|
|
else
|
|
{
|
|
// New in Orthanc 1.9.0
|
|
privateCreator.assign(privateCreatorC);
|
|
}
|
|
|
|
std::unique_ptr<ParsedDicomFile> file
|
|
(ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(parameters.flags),
|
|
privateCreator));
|
|
|
|
if (parameters.pixelData)
|
|
{
|
|
file->EmbedImage(*reinterpret_cast<const ImageAccessor*>(parameters.pixelData));
|
|
}
|
|
|
|
file->SaveToMemoryBuffer(dicom);
|
|
}
|
|
|
|
CopyToMemoryBuffer(parameters.target, dicom);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::ComputeHash(_OrthancPluginService service,
|
|
const void* parameters)
|
|
{
|
|
const _OrthancPluginComputeHash& p =
|
|
*reinterpret_cast<const _OrthancPluginComputeHash*>(parameters);
|
|
|
|
std::string hash;
|
|
switch (service)
|
|
{
|
|
case _OrthancPluginService_ComputeMd5:
|
|
Toolbox::ComputeMD5(hash, p.buffer, p.size);
|
|
break;
|
|
|
|
case _OrthancPluginService_ComputeSha1:
|
|
Toolbox::ComputeSHA1(hash, p.buffer, p.size);
|
|
break;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
*p.result = CopyString(hash);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::GetTagName(const void* parameters)
|
|
{
|
|
const _OrthancPluginGetTagName& p =
|
|
*reinterpret_cast<const _OrthancPluginGetTagName*>(parameters);
|
|
|
|
std::string privateCreator;
|
|
|
|
if (p.privateCreator != NULL)
|
|
{
|
|
privateCreator = p.privateCreator;
|
|
}
|
|
|
|
DicomTag tag(p.group, p.element);
|
|
*p.result = CopyString(FromDcmtkBridge::GetTagName(tag, privateCreator));
|
|
}
|
|
|
|
|
|
void OrthancPlugins::ApplyCreateImage(_OrthancPluginService service,
|
|
const void* parameters)
|
|
{
|
|
const _OrthancPluginCreateImage& p =
|
|
*reinterpret_cast<const _OrthancPluginCreateImage*>(parameters);
|
|
|
|
std::unique_ptr<ImageAccessor> result;
|
|
|
|
switch (service)
|
|
{
|
|
case _OrthancPluginService_CreateImage:
|
|
result.reset(new Image(Plugins::Convert(p.format), p.width, p.height, false));
|
|
break;
|
|
|
|
case _OrthancPluginService_CreateImageAccessor:
|
|
result.reset(new ImageAccessor);
|
|
result->AssignWritable(Plugins::Convert(p.format), p.width, p.height, p.pitch, p.buffer);
|
|
break;
|
|
|
|
case _OrthancPluginService_DecodeDicomImage:
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
result.reset(lock.GetContext().DecodeDicomFrame(p.constBuffer, p.bufferSize, p.frameIndex));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
|
|
*(p.target) = ReturnImage(result);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::ApplySendMultipartItem(const void* parameters)
|
|
{
|
|
// An exception might be raised in this function if the
|
|
// connection was closed by the HTTP client.
|
|
const _OrthancPluginAnswerBuffer& p =
|
|
*reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
|
|
|
|
std::map<std::string, std::string> headers; // No custom headers
|
|
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendMultipartItem(p.answer, p.answerSize, headers);
|
|
}
|
|
|
|
|
|
void OrthancPlugins::ApplySendMultipartItem2(const void* parameters)
|
|
{
|
|
// An exception might be raised in this function if the
|
|
// connection was closed by the HTTP client.
|
|
const _OrthancPluginSendMultipartItem2& p =
|
|
*reinterpret_cast<const _OrthancPluginSendMultipartItem2*>(parameters);
|
|
|
|
std::map<std::string, std::string> headers;
|
|
for (uint32_t i = 0; i < p.headersCount; i++)
|
|
{
|
|
headers[p.headersKeys[i]] = p.headersValues[i];
|
|
}
|
|
|
|
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendMultipartItem(p.answer, p.answerSize, headers);
|
|
}
|
|
|
|
void OrthancPlugins::ApplyAdoptDicomInstance(const _OrthancPluginAdoptDicomInstance& parameters)
|
|
{
|
|
if (!pimpl_->hasStorageAreaCustomData_)
|
|
{
|
|
LOG(WARNING) << "The adoption of a DICOM instance should only be used in combination with a custom "
|
|
<< "storage area registered using OrthancPluginRegisterStorageArea3()";
|
|
}
|
|
|
|
std::string md5;
|
|
Toolbox::ComputeMD5(md5, parameters.dicom, parameters.dicomSize);
|
|
|
|
std::unique_ptr<DicomInstanceToStore> dicom(DicomInstanceToStore::CreateFromBuffer(parameters.dicom, parameters.dicomSize));
|
|
dicom->SetOrigin(DicomInstanceOrigin::FromPlugins());
|
|
|
|
const std::string attachmentUuid = Toolbox::GenerateUuid();
|
|
|
|
FileInfo adoptedFile(attachmentUuid, FileContentType_Dicom, parameters.dicomSize, md5);
|
|
adoptedFile.SetCustomData(parameters.customData, parameters.customDataSize);
|
|
|
|
std::string instanceId;
|
|
ServerContext::StoreResult result;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
result = lock.GetContext().AdoptDicomInstance(instanceId, *dicom, StoreInstanceMode_Default, adoptedFile);
|
|
}
|
|
|
|
CopyToMemoryBuffer(parameters.attachmentUuid, attachmentUuid);
|
|
CopyToMemoryBuffer(parameters.instanceId, instanceId);
|
|
*(parameters.storeStatus) = Plugins::Convert(result.GetStatus());
|
|
}
|
|
|
|
static void CheckAttachmentCustomDataSupport(ServerContext& context)
|
|
{
|
|
if (!context.GetIndex().HasAttachmentCustomDataSupport())
|
|
{
|
|
throw OrthancException(ErrorCode_NotImplemented, "The database engine does not support custom data for attachments");
|
|
}
|
|
}
|
|
|
|
void OrthancPlugins::ApplyGetAttachmentCustomData(const _OrthancPluginGetAttachmentCustomData& parameters)
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
|
|
CheckAttachmentCustomDataSupport(lock.GetContext());
|
|
|
|
std::string customData;
|
|
lock.GetContext().GetIndex().GetAttachmentCustomData(customData, parameters.attachmentUuid);
|
|
|
|
CopyToMemoryBuffer(parameters.customData, customData);
|
|
}
|
|
|
|
void OrthancPlugins::ApplySetAttachmentCustomData(const _OrthancPluginSetAttachmentCustomData& parameters)
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
|
|
CheckAttachmentCustomDataSupport(lock.GetContext());
|
|
|
|
lock.GetContext().GetIndex().SetAttachmentCustomData(parameters.attachmentUuid, parameters.customData, parameters.customDataSize);
|
|
}
|
|
|
|
static void CheckKeyValueStoresSupport(ServerContext& context)
|
|
{
|
|
if (!context.GetIndex().HasKeyValueStoresSupport())
|
|
{
|
|
throw OrthancException(ErrorCode_NotImplemented, "The database engine does not support key-value stores");
|
|
}
|
|
}
|
|
|
|
void OrthancPlugins::ApplyStoreKeyValue(const _OrthancPluginStoreKeyValue& parameters)
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
|
|
CheckKeyValueStoresSupport(lock.GetContext());
|
|
|
|
lock.GetContext().GetIndex().StoreKeyValue(parameters.storeId, parameters.key, parameters.value, parameters.valueSize);
|
|
}
|
|
|
|
void OrthancPlugins::ApplyDeleteKeyValue(const _OrthancPluginDeleteKeyValue& parameters)
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
|
|
CheckKeyValueStoresSupport(lock.GetContext());
|
|
|
|
lock.GetContext().GetIndex().DeleteKeyValue(parameters.storeId, parameters.key);
|
|
}
|
|
|
|
void OrthancPlugins::ApplyGetKeyValue(const _OrthancPluginGetKeyValue& parameters)
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
|
|
CheckKeyValueStoresSupport(lock.GetContext());
|
|
|
|
std::string value;
|
|
|
|
if (lock.GetContext().GetIndex().GetKeyValue(value, parameters.storeId, parameters.key))
|
|
{
|
|
CopyToMemoryBuffer(parameters.target, value);
|
|
*parameters.found = true;
|
|
}
|
|
else
|
|
{
|
|
*parameters.found = false;
|
|
}
|
|
}
|
|
|
|
void OrthancPlugins::ApplyCreateKeysValuesIterator(const _OrthancPluginCreateKeysValuesIterator& parameters)
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
|
|
CheckKeyValueStoresSupport(lock.GetContext());
|
|
|
|
*parameters.target = reinterpret_cast<OrthancPluginKeysValuesIterator*>(
|
|
new StatelessDatabaseOperations::KeysValuesIterator(lock.GetContext().GetIndex(), parameters.storeId));
|
|
}
|
|
|
|
static void CheckQueuesSupport(ServerContext& context)
|
|
{
|
|
if (!context.GetIndex().HasQueuesSupport())
|
|
{
|
|
throw OrthancException(ErrorCode_NotImplemented, "The database engine does not support queues");
|
|
}
|
|
}
|
|
|
|
void OrthancPlugins::ApplyEnqueueValue(const _OrthancPluginEnqueueValue& parameters)
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
|
|
CheckQueuesSupport(lock.GetContext());
|
|
|
|
lock.GetContext().GetIndex().EnqueueValue(parameters.queueId, parameters.value, parameters.valueSize);
|
|
}
|
|
|
|
void OrthancPlugins::ApplyDequeueValue(const _OrthancPluginDequeueValue& parameters)
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
|
|
CheckQueuesSupport(lock.GetContext());
|
|
|
|
std::string value;
|
|
|
|
if (lock.GetContext().GetIndex().DequeueValue(value, parameters.queueId, Plugins::Convert(parameters.origin)))
|
|
{
|
|
CopyToMemoryBuffer(parameters.target, value);
|
|
*parameters.found = true;
|
|
}
|
|
else
|
|
{
|
|
*parameters.found = false;
|
|
}
|
|
}
|
|
|
|
void OrthancPlugins::ApplyGetQueueSize(const _OrthancPluginGetQueueSize& parameters)
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
|
|
CheckQueuesSupport(lock.GetContext());
|
|
|
|
*parameters.size = lock.GetContext().GetIndex().GetQueueSize(parameters.queueId);
|
|
}
|
|
|
|
void OrthancPlugins::ApplyLoadDicomInstance(const _OrthancPluginLoadDicomInstance& params)
|
|
{
|
|
std::unique_ptr<IDicomInstance> target;
|
|
|
|
switch (params.mode)
|
|
{
|
|
case OrthancPluginLoadDicomInstanceMode_WholeDicom:
|
|
{
|
|
std::string buffer;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
lock.GetContext().ReadDicom(buffer, params.instanceId);
|
|
}
|
|
|
|
target.reset(new DicomInstanceFromBuffer(buffer));
|
|
break;
|
|
}
|
|
|
|
case OrthancPluginLoadDicomInstanceMode_UntilPixelData:
|
|
case OrthancPluginLoadDicomInstanceMode_EmptyPixelData:
|
|
{
|
|
std::unique_ptr<ParsedDicomFile> parsed;
|
|
|
|
{
|
|
std::string buffer;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
if (!lock.GetContext().ReadDicomUntilPixelData(buffer, params.instanceId))
|
|
{
|
|
lock.GetContext().ReadDicom(buffer, params.instanceId);
|
|
}
|
|
}
|
|
|
|
parsed.reset(new ParsedDicomFile(buffer));
|
|
}
|
|
|
|
parsed->RemoveFromPixelData();
|
|
|
|
if (params.mode == OrthancPluginLoadDicomInstanceMode_EmptyPixelData)
|
|
{
|
|
bool hasPixelData = false;
|
|
ValueRepresentation pixelDataVR = parsed->GuessPixelDataValueRepresentation();
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
|
|
std::string s;
|
|
if (lock.GetContext().GetIndex().LookupMetadata(
|
|
s, params.instanceId,
|
|
ResourceType_Instance, MetadataType_Instance_PixelDataVR))
|
|
{
|
|
hasPixelData = true;
|
|
if (s == "OB")
|
|
{
|
|
pixelDataVR = ValueRepresentation_OtherByte;
|
|
}
|
|
else if (s == "OW")
|
|
{
|
|
pixelDataVR = ValueRepresentation_OtherWord;
|
|
}
|
|
else
|
|
{
|
|
LOG(WARNING) << "Corrupted PixelDataVR metadata associated with instance "
|
|
<< params.instanceId << ": " << s;
|
|
}
|
|
}
|
|
else if (lock.GetContext().GetIndex().LookupMetadata(
|
|
s, params.instanceId,
|
|
ResourceType_Instance, MetadataType_Instance_PixelDataOffset))
|
|
{
|
|
// This file was stored by an older version of Orthanc,
|
|
// "PixelDataVR" is not available, so use the guess
|
|
hasPixelData = true;
|
|
}
|
|
else
|
|
{
|
|
hasPixelData = false;
|
|
}
|
|
}
|
|
|
|
if (hasPixelData)
|
|
{
|
|
parsed->InjectEmptyPixelData(pixelDataVR);
|
|
}
|
|
}
|
|
|
|
target.reset(new DicomInstanceFromParsed(parsed.release()));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
if (target.get() == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
else
|
|
{
|
|
*params.target = reinterpret_cast<OrthancPluginDicomInstance*>(target.release());
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::DatabaseAnswer(const void* parameters)
|
|
{
|
|
const _OrthancPluginDatabaseAnswer& p =
|
|
*reinterpret_cast<const _OrthancPluginDatabaseAnswer*>(parameters);
|
|
|
|
if (pimpl_->database_.get() != NULL)
|
|
{
|
|
pimpl_->database_->AnswerReceived(p);
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_BadRequest,
|
|
"Cannot invoke this service without a custom database back-end");
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::ApplyLookupDictionary(const void* parameters)
|
|
{
|
|
const _OrthancPluginLookupDictionary& p =
|
|
*reinterpret_cast<const _OrthancPluginLookupDictionary*>(parameters);
|
|
|
|
DicomTag tag(FromDcmtkBridge::ParseTag(p.name));
|
|
DcmTagKey tag2(tag.GetGroup(), tag.GetElement());
|
|
|
|
{
|
|
FromDcmtkBridge::DictionaryReaderLock lock;
|
|
|
|
const DcmDictEntry* entry = NULL; // This value is only valid while "lock" is active
|
|
|
|
if (tag.IsPrivate())
|
|
{
|
|
// Fix issue 168 (Plugins can't read private tags from the
|
|
// configuration file)
|
|
// https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=168
|
|
std::string privateCreator;
|
|
{
|
|
OrthancConfiguration::ReaderLock configurationLock;
|
|
privateCreator = configurationLock.GetConfiguration().GetDefaultPrivateCreator();
|
|
}
|
|
|
|
entry = lock.GetDictionary().findEntry(tag2, privateCreator.c_str());
|
|
}
|
|
else
|
|
{
|
|
entry = lock.GetDictionary().findEntry(tag2, NULL);
|
|
}
|
|
|
|
if (entry == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_UnknownDicomTag, p.name);
|
|
}
|
|
else
|
|
{
|
|
p.target->group = entry->getKey().getGroup();
|
|
p.target->element = entry->getKey().getElement();
|
|
p.target->vr = Plugins::Convert(FromDcmtkBridge::Convert(entry->getEVR()));
|
|
p.target->minMultiplicity = static_cast<uint32_t>(entry->getVMMin());
|
|
p.target->maxMultiplicity = (entry->getVMMax() == DcmVariableVM ? 0 : static_cast<uint32_t>(entry->getVMMax()));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::InvokeSafeService(SharedLibrary& plugin,
|
|
_OrthancPluginService service,
|
|
const void* parameters)
|
|
{
|
|
// Services that can be run without mutual exclusion
|
|
|
|
switch (service)
|
|
{
|
|
case _OrthancPluginService_GetOrthancPath:
|
|
{
|
|
std::string s = SystemToolbox::GetPathToExecutable();
|
|
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetOrthancDirectory:
|
|
{
|
|
std::string s = SystemToolbox::GetDirectoryOfExecutable();
|
|
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetConfigurationPath:
|
|
{
|
|
std::string s;
|
|
|
|
{
|
|
OrthancConfiguration::ReaderLock lock;
|
|
s = lock.GetConfiguration().GetConfigurationAbsolutePath();
|
|
}
|
|
|
|
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetConfiguration:
|
|
{
|
|
std::string s;
|
|
|
|
{
|
|
OrthancConfiguration::ReaderLock lock;
|
|
lock.GetConfiguration().Format(s);
|
|
}
|
|
|
|
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_BufferCompression:
|
|
BufferCompression(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_AnswerBuffer:
|
|
AnswerBuffer(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_CompressAndAnswerPngImage:
|
|
CompressAndAnswerPngImage(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_CompressAndAnswerImage:
|
|
CompressAndAnswerImage(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_GetDicomForInstance:
|
|
GetDicomForInstance(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RestApiGet:
|
|
RestApiGet(parameters, false);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RestApiGetAfterPlugins:
|
|
RestApiGet(parameters, true);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RestApiGet2:
|
|
RestApiGet2(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RestApiPost:
|
|
RestApiPostPut(true, parameters, false);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RestApiPostAfterPlugins:
|
|
RestApiPostPut(true, parameters, true);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RestApiDelete:
|
|
RestApiDelete(parameters, false);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RestApiDeleteAfterPlugins:
|
|
RestApiDelete(parameters, true);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RestApiPut:
|
|
RestApiPostPut(false, parameters, false);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RestApiPutAfterPlugins:
|
|
RestApiPostPut(false, parameters, true);
|
|
return true;
|
|
|
|
case _OrthancPluginService_Redirect:
|
|
Redirect(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_SendUnauthorized:
|
|
SendUnauthorized(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_SendMethodNotAllowed:
|
|
SendMethodNotAllowed(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_SendHttpStatus:
|
|
SendHttpStatus(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_SendHttpStatusCode:
|
|
SendHttpStatusCode(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_SetCookie:
|
|
SetCookie(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_SetHttpHeader:
|
|
SetHttpHeader(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_SetHttpErrorDetails:
|
|
SetHttpErrorDetails(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_LookupPatient:
|
|
case _OrthancPluginService_LookupStudy:
|
|
case _OrthancPluginService_LookupStudyWithAccessionNumber:
|
|
case _OrthancPluginService_LookupSeries:
|
|
case _OrthancPluginService_LookupInstance:
|
|
LookupResource(service, parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_GetInstanceRemoteAet:
|
|
case _OrthancPluginService_GetInstanceSize:
|
|
case _OrthancPluginService_GetInstanceData:
|
|
case _OrthancPluginService_GetInstanceJson:
|
|
case _OrthancPluginService_GetInstanceSimplifiedJson:
|
|
case _OrthancPluginService_HasInstanceMetadata:
|
|
case _OrthancPluginService_GetInstanceMetadata:
|
|
case _OrthancPluginService_GetInstanceOrigin:
|
|
case _OrthancPluginService_GetInstanceTransferSyntaxUid:
|
|
case _OrthancPluginService_HasInstancePixelData:
|
|
AccessDicomInstance(service, parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_GetInstanceFramesCount:
|
|
case _OrthancPluginService_GetInstanceRawFrame:
|
|
case _OrthancPluginService_GetInstanceDecodedFrame:
|
|
case _OrthancPluginService_SerializeDicomInstance:
|
|
case _OrthancPluginService_GetInstanceAdvancedJson:
|
|
case _OrthancPluginService_GetInstanceDicomWebJson:
|
|
case _OrthancPluginService_GetInstanceDicomWebXml:
|
|
AccessDicomInstance2(service, parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_SetGlobalProperty:
|
|
{
|
|
const _OrthancPluginGlobalProperty& p =
|
|
*reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
|
|
if (p.property < 1024)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// TODO - Plugins can only access global properties of their
|
|
// own Orthanc server (no access to the shared global properties)
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
lock.GetContext().GetIndex().SetGlobalProperty(static_cast<GlobalProperty>(p.property),
|
|
false /* not shared */, p.value);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
case _OrthancPluginService_GetGlobalProperty:
|
|
{
|
|
const _OrthancPluginGlobalProperty& p =
|
|
*reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
|
|
|
|
std::string result;
|
|
|
|
{
|
|
// TODO - Plugins can only access global properties of their
|
|
// own Orthanc server (no access to the shared global properties)
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
result = lock.GetContext().GetIndex().GetGlobalProperty(static_cast<GlobalProperty>(p.property),
|
|
false /* not shared */, p.value);
|
|
}
|
|
|
|
*(p.result) = CopyString(result);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetExpectedDatabaseVersion:
|
|
{
|
|
const _OrthancPluginReturnSingleValue& p =
|
|
*reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters);
|
|
*(p.resultUint32) = ORTHANC_DATABASE_VERSION;
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_StartMultipartAnswer:
|
|
{
|
|
const _OrthancPluginStartMultipartAnswer& p =
|
|
*reinterpret_cast<const _OrthancPluginStartMultipartAnswer*>(parameters);
|
|
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->StartMultipart(p.subType, p.contentType);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_SendMultipartItem:
|
|
ApplySendMultipartItem(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_SendMultipartItem2:
|
|
ApplySendMultipartItem2(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_StartStreamAnswer:
|
|
{
|
|
const _OrthancPluginStartStreamAnswer& p =
|
|
*reinterpret_cast<const _OrthancPluginStartStreamAnswer*>(parameters);
|
|
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->StartStream(p.contentType);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_SendStreamChunk:
|
|
{
|
|
const _OrthancPluginAnswerBuffer& p =
|
|
*reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
|
|
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendStreamItem(p.answer, p.answerSize);
|
|
return true;
|
|
}
|
|
case _OrthancPluginService_ReadFile:
|
|
{
|
|
const _OrthancPluginReadFile& p =
|
|
*reinterpret_cast<const _OrthancPluginReadFile*>(parameters);
|
|
|
|
std::string content;
|
|
SystemToolbox::ReadFile(content, p.path);
|
|
CopyToMemoryBuffer(p.target, content);
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_WriteFile:
|
|
{
|
|
const _OrthancPluginWriteFile& p =
|
|
*reinterpret_cast<const _OrthancPluginWriteFile*>(parameters);
|
|
SystemToolbox::WriteFile(p.data, p.size, p.path, true /* run fsync() */);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetErrorDescription:
|
|
{
|
|
const _OrthancPluginGetErrorDescription& p =
|
|
*reinterpret_cast<const _OrthancPluginGetErrorDescription*>(parameters);
|
|
*(p.target) = EnumerationToString(static_cast<ErrorCode>(p.error));
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetImagePixelFormat:
|
|
{
|
|
const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
|
|
*(p.resultPixelFormat) = Plugins::Convert(reinterpret_cast<const ImageAccessor*>(p.image)->GetFormat());
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetImageWidth:
|
|
{
|
|
const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
|
|
*(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetWidth();
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetImageHeight:
|
|
{
|
|
const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
|
|
*(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetHeight();
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetImagePitch:
|
|
{
|
|
const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
|
|
*(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetPitch();
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetImageBuffer:
|
|
{
|
|
const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
|
|
*(p.resultBuffer) = const_cast<void*>(reinterpret_cast<const ImageAccessor*>(p.image)->GetConstBuffer());
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_FreeImage:
|
|
{
|
|
const _OrthancPluginFreeImage& p = *reinterpret_cast<const _OrthancPluginFreeImage*>(parameters);
|
|
|
|
if (p.image != NULL)
|
|
{
|
|
delete reinterpret_cast<ImageAccessor*>(p.image);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_UncompressImage:
|
|
UncompressImage(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_CompressImage:
|
|
CompressImage(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_CallHttpClient:
|
|
CallHttpClient(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_CallHttpClient2:
|
|
CallHttpClient2(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_ChunkedHttpClient:
|
|
ChunkedHttpClient(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_CallRestApi:
|
|
CallRestApi(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_ConvertPixelFormat:
|
|
ConvertPixelFormat(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_GetFontsCount:
|
|
{
|
|
const _OrthancPluginReturnSingleValue& p =
|
|
*reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters);
|
|
|
|
{
|
|
OrthancConfiguration::ReaderLock lock;
|
|
*(p.resultUint32) = lock.GetConfiguration().GetFontRegistry().GetSize();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetFontInfo:
|
|
GetFontInfo(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_DrawText:
|
|
DrawText(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_StorageAreaCreate:
|
|
throw OrthancException(ErrorCode_NotImplemented, "The SDK function OrthancPluginStorageAreaCreate() is only available in Orthanc <= 1.12.6");
|
|
|
|
case _OrthancPluginService_StorageAreaRead:
|
|
throw OrthancException(ErrorCode_NotImplemented, "The SDK function OrthancPluginStorageAreaRead() is only available in Orthanc <= 1.12.6");
|
|
|
|
case _OrthancPluginService_StorageAreaRemove:
|
|
throw OrthancException(ErrorCode_NotImplemented, "The SDK function OrthancPluginStorageAreaRemove() is only available in Orthanc <= 1.12.6");
|
|
|
|
case _OrthancPluginService_DicomBufferToJson:
|
|
case _OrthancPluginService_DicomInstanceToJson:
|
|
ApplyDicomToJson(service, parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_CreateDicom:
|
|
{
|
|
const _OrthancPluginCreateDicom& p =
|
|
*reinterpret_cast<const _OrthancPluginCreateDicom*>(parameters);
|
|
ApplyCreateDicom(p, NULL);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_CreateDicom2:
|
|
{
|
|
// New in Orthanc 1.9.0
|
|
const _OrthancPluginCreateDicom2& p =
|
|
*reinterpret_cast<const _OrthancPluginCreateDicom2*>(parameters);
|
|
ApplyCreateDicom(p.createDicom, p.privateCreator);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_WorklistAddAnswer:
|
|
{
|
|
const _OrthancPluginWorklistAnswersOperation& p =
|
|
*reinterpret_cast<const _OrthancPluginWorklistAnswersOperation*>(parameters);
|
|
reinterpret_cast<const WorklistHandler*>(p.query)->AddAnswer(p.answers, p.dicom, p.size);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_WorklistMarkIncomplete:
|
|
{
|
|
const _OrthancPluginWorklistAnswersOperation& p =
|
|
*reinterpret_cast<const _OrthancPluginWorklistAnswersOperation*>(parameters);
|
|
reinterpret_cast<DicomFindAnswers*>(p.answers)->SetComplete(false);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_WorklistIsMatch:
|
|
{
|
|
const _OrthancPluginWorklistQueryOperation& p =
|
|
*reinterpret_cast<const _OrthancPluginWorklistQueryOperation*>(parameters);
|
|
*p.isMatch = reinterpret_cast<const WorklistHandler*>(p.query)->IsMatch(p.dicom, p.size);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_WorklistGetDicomQuery:
|
|
{
|
|
const _OrthancPluginWorklistQueryOperation& p =
|
|
*reinterpret_cast<const _OrthancPluginWorklistQueryOperation*>(parameters);
|
|
reinterpret_cast<const WorklistHandler*>(p.query)->GetDicomQuery(p.target);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_FindAddAnswer:
|
|
{
|
|
const _OrthancPluginFindOperation& p =
|
|
*reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
|
|
reinterpret_cast<DicomFindAnswers*>(p.answers)->Add(p.dicom, p.size);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_FindMarkIncomplete:
|
|
{
|
|
const _OrthancPluginFindOperation& p =
|
|
*reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
|
|
reinterpret_cast<DicomFindAnswers*>(p.answers)->SetComplete(false);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetFindQuerySize:
|
|
case _OrthancPluginService_GetFindQueryTag:
|
|
case _OrthancPluginService_GetFindQueryTagName:
|
|
case _OrthancPluginService_GetFindQueryValue:
|
|
{
|
|
const _OrthancPluginFindOperation& p =
|
|
*reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
|
|
reinterpret_cast<const FindHandler*>(p.query)->Invoke(service, p);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_CreateImage:
|
|
case _OrthancPluginService_CreateImageAccessor:
|
|
case _OrthancPluginService_DecodeDicomImage:
|
|
ApplyCreateImage(service, parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_ComputeMd5:
|
|
case _OrthancPluginService_ComputeSha1:
|
|
ComputeHash(service, parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_LookupDictionary:
|
|
ApplyLookupDictionary(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_GenerateUuid:
|
|
{
|
|
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result =
|
|
CopyString(Toolbox::GenerateUuid());
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_CreateFindMatcher:
|
|
{
|
|
const _OrthancPluginCreateFindMatcher& p =
|
|
*reinterpret_cast<const _OrthancPluginCreateFindMatcher*>(parameters);
|
|
ParsedDicomFile query(p.query, p.size);
|
|
*(p.target) = reinterpret_cast<OrthancPluginFindMatcher*>(new HierarchicalMatcher(query));
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_FreeFindMatcher:
|
|
{
|
|
const _OrthancPluginFreeFindMatcher& p =
|
|
*reinterpret_cast<const _OrthancPluginFreeFindMatcher*>(parameters);
|
|
|
|
if (p.matcher != NULL)
|
|
{
|
|
delete reinterpret_cast<HierarchicalMatcher*>(p.matcher);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_FindMatcherIsMatch:
|
|
{
|
|
const _OrthancPluginFindMatcherIsMatch& p =
|
|
*reinterpret_cast<const _OrthancPluginFindMatcherIsMatch*>(parameters);
|
|
|
|
if (p.matcher == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
else
|
|
{
|
|
ParsedDicomFile query(p.dicom, p.size);
|
|
*p.isMatch = reinterpret_cast<const HierarchicalMatcher*>(p.matcher)->Match(query) ? 1 : 0;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
case _OrthancPluginService_GetPeers:
|
|
{
|
|
const _OrthancPluginGetPeers& p =
|
|
*reinterpret_cast<const _OrthancPluginGetPeers*>(parameters);
|
|
*(p.peers) = reinterpret_cast<OrthancPluginPeers*>(new OrthancPeers);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_FreePeers:
|
|
{
|
|
const _OrthancPluginFreePeers& p =
|
|
*reinterpret_cast<const _OrthancPluginFreePeers*>(parameters);
|
|
|
|
if (p.peers != NULL)
|
|
{
|
|
delete reinterpret_cast<OrthancPeers*>(p.peers);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetPeersCount:
|
|
{
|
|
const _OrthancPluginGetPeersCount& p =
|
|
*reinterpret_cast<const _OrthancPluginGetPeersCount*>(parameters);
|
|
|
|
if (p.peers == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
else
|
|
{
|
|
*(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeersCount();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
case _OrthancPluginService_GetPeerName:
|
|
{
|
|
const _OrthancPluginGetPeerProperty& p =
|
|
*reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters);
|
|
|
|
if (p.peers == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
else
|
|
{
|
|
*(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerName(p.peerIndex).c_str();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
case _OrthancPluginService_GetPeerUrl:
|
|
{
|
|
const _OrthancPluginGetPeerProperty& p =
|
|
*reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters);
|
|
|
|
if (p.peers == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
else
|
|
{
|
|
*(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerParameters(p.peerIndex).GetUrl().c_str();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
case _OrthancPluginService_GetPeerUserProperty:
|
|
{
|
|
const _OrthancPluginGetPeerProperty& p =
|
|
*reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters);
|
|
|
|
if (p.peers == NULL ||
|
|
p.userProperty == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
else
|
|
{
|
|
const WebServiceParameters::Dictionary& properties =
|
|
reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerParameters(p.peerIndex).GetUserProperties();
|
|
|
|
WebServiceParameters::Dictionary::const_iterator found =
|
|
properties.find(p.userProperty);
|
|
|
|
if (found == properties.end())
|
|
{
|
|
*(p.target) = NULL;
|
|
}
|
|
else
|
|
{
|
|
*(p.target) = found->second.c_str();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
case _OrthancPluginService_CallPeerApi:
|
|
CallPeerApi(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_CreateJob:
|
|
{
|
|
const _OrthancPluginCreateJob& p =
|
|
*reinterpret_cast<const _OrthancPluginCreateJob*>(parameters);
|
|
*(p.target) = reinterpret_cast<OrthancPluginJob*>(new PluginsJob(p));
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_CreateJob2:
|
|
{
|
|
const _OrthancPluginCreateJob2& p =
|
|
*reinterpret_cast<const _OrthancPluginCreateJob2*>(parameters);
|
|
*(p.target) = reinterpret_cast<OrthancPluginJob*>(new PluginsJob(p));
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_FreeJob:
|
|
{
|
|
const _OrthancPluginFreeJob& p =
|
|
*reinterpret_cast<const _OrthancPluginFreeJob*>(parameters);
|
|
|
|
if (p.job != NULL)
|
|
{
|
|
delete reinterpret_cast<PluginsJob*>(p.job);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_SubmitJob:
|
|
{
|
|
const _OrthancPluginSubmitJob& p =
|
|
*reinterpret_cast<const _OrthancPluginSubmitJob*>(parameters);
|
|
|
|
std::string uuid;
|
|
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
lock.GetContext().GetJobsEngine().GetRegistry().Submit
|
|
(uuid, reinterpret_cast<PluginsJob*>(p.job), p.priority);
|
|
|
|
*p.resultId = CopyString(uuid);
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_AutodetectMimeType:
|
|
{
|
|
const _OrthancPluginRetrieveStaticString& p =
|
|
*reinterpret_cast<const _OrthancPluginRetrieveStaticString*>(parameters);
|
|
*p.result = EnumerationToString(SystemToolbox::AutodetectMimeType(p.argument));
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_SetMetricsValue:
|
|
{
|
|
const _OrthancPluginSetMetricsValue& p =
|
|
*reinterpret_cast<const _OrthancPluginSetMetricsValue*>(parameters);
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
lock.GetContext().GetMetricsRegistry().SetFloatValue(p.name, p.value, Plugins::Convert(p.type));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_SetMetricsIntegerValue:
|
|
{
|
|
const _OrthancPluginSetMetricsIntegerValue& p =
|
|
*reinterpret_cast<const _OrthancPluginSetMetricsIntegerValue*>(parameters);
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
lock.GetContext().GetMetricsRegistry().SetIntegerValue(p.name, p.value, Plugins::Convert(p.type));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_EncodeDicomWebJson:
|
|
case _OrthancPluginService_EncodeDicomWebXml:
|
|
{
|
|
const _OrthancPluginEncodeDicomWeb& p =
|
|
*reinterpret_cast<const _OrthancPluginEncodeDicomWeb*>(parameters);
|
|
|
|
DicomWebBinaryFormatter formatter(p.callback);
|
|
formatter.Apply(p.target,
|
|
(service == _OrthancPluginService_EncodeDicomWebJson),
|
|
p.dicom, p.dicomSize);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_EncodeDicomWebJson2:
|
|
case _OrthancPluginService_EncodeDicomWebXml2:
|
|
{
|
|
const _OrthancPluginEncodeDicomWeb2& p =
|
|
*reinterpret_cast<const _OrthancPluginEncodeDicomWeb2*>(parameters);
|
|
|
|
DicomWebBinaryFormatter formatter(p.callback, p.payload);
|
|
formatter.Apply(p.target,
|
|
(service == _OrthancPluginService_EncodeDicomWebJson2),
|
|
p.dicom, p.dicomSize);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetTagName:
|
|
GetTagName(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_CreateDicomInstance:
|
|
{
|
|
const _OrthancPluginCreateDicomInstance& p =
|
|
*reinterpret_cast<const _OrthancPluginCreateDicomInstance*>(parameters);
|
|
*(p.target) = reinterpret_cast<OrthancPluginDicomInstance*>(
|
|
new DicomInstanceFromBuffer(p.buffer, p.size));
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_FreeDicomInstance:
|
|
{
|
|
const _OrthancPluginFreeDicomInstance& p =
|
|
*reinterpret_cast<const _OrthancPluginFreeDicomInstance*>(parameters);
|
|
|
|
if (p.dicom != NULL)
|
|
{
|
|
IDicomInstance* obj = reinterpret_cast<IDicomInstance*>(p.dicom);
|
|
|
|
if (obj->CanBeFreed())
|
|
{
|
|
delete obj;
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_Plugin, "Cannot free a DICOM instance provided to a callback");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_TranscodeDicomInstance:
|
|
{
|
|
const _OrthancPluginCreateDicomInstance& p =
|
|
*reinterpret_cast<const _OrthancPluginCreateDicomInstance*>(parameters);
|
|
|
|
DicomTransferSyntax transferSyntax;
|
|
if (p.transferSyntax == NULL ||
|
|
!LookupTransferSyntax(transferSyntax, p.transferSyntax))
|
|
{
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange, "Unsupported transfer syntax: " +
|
|
std::string(p.transferSyntax == NULL ? "(null)" : p.transferSyntax));
|
|
}
|
|
else
|
|
{
|
|
std::set<DicomTransferSyntax> syntaxes;
|
|
syntaxes.insert(transferSyntax);
|
|
|
|
IDicomTranscoder::DicomImage source;
|
|
source.SetExternalBuffer(p.buffer, p.size);
|
|
|
|
IDicomTranscoder::DicomImage transcoded;
|
|
bool success;
|
|
|
|
{
|
|
PImpl::ServerContextReference lock(*pimpl_);
|
|
success = lock.GetContext().Transcode(
|
|
transcoded, source, syntaxes, true /* allow new sop */);
|
|
}
|
|
|
|
if (success)
|
|
{
|
|
*(p.target) = reinterpret_cast<OrthancPluginDicomInstance*>(
|
|
new DicomInstanceFromParsed(transcoded));
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_NotImplemented, "Cannot transcode image");
|
|
}
|
|
}
|
|
}
|
|
|
|
case _OrthancPluginService_CreateMemoryBuffer:
|
|
{
|
|
const _OrthancPluginCreateMemoryBuffer& p =
|
|
*reinterpret_cast<const _OrthancPluginCreateMemoryBuffer*>(parameters);
|
|
|
|
PluginMemoryBuffer32 buffer;
|
|
buffer.Resize(p.size);
|
|
buffer.Release(p.target);
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_CreateMemoryBuffer64:
|
|
{
|
|
const _OrthancPluginCreateMemoryBuffer64& p =
|
|
*reinterpret_cast<const _OrthancPluginCreateMemoryBuffer64*>(parameters);
|
|
|
|
PluginMemoryBuffer64 buffer;
|
|
buffer.Resize(p.size);
|
|
buffer.Release(p.target);
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_RegisterIncomingHttpRequestFilter:
|
|
RegisterIncomingHttpRequestFilter(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterIncomingHttpRequestFilter2:
|
|
RegisterIncomingHttpRequestFilter2(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_LoadDicomInstance:
|
|
{
|
|
const _OrthancPluginLoadDicomInstance& p =
|
|
*reinterpret_cast<const _OrthancPluginLoadDicomInstance*>(parameters);
|
|
ApplyLoadDicomInstance(p);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_SetCurrentThreadName:
|
|
{
|
|
Logging::SetCurrentThreadName(std::string(reinterpret_cast<const char*>(parameters)));
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_AdoptDicomInstance:
|
|
{
|
|
const _OrthancPluginAdoptDicomInstance& p = *reinterpret_cast<const _OrthancPluginAdoptDicomInstance*>(parameters);
|
|
ApplyAdoptDicomInstance(p);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetAttachmentCustomData:
|
|
{
|
|
const _OrthancPluginGetAttachmentCustomData& p = *reinterpret_cast<const _OrthancPluginGetAttachmentCustomData*>(parameters);
|
|
ApplyGetAttachmentCustomData(p);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_SetAttachmentCustomData:
|
|
{
|
|
const _OrthancPluginSetAttachmentCustomData& p = *reinterpret_cast<const _OrthancPluginSetAttachmentCustomData*>(parameters);
|
|
ApplySetAttachmentCustomData(p);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_StoreKeyValue:
|
|
{
|
|
const _OrthancPluginStoreKeyValue& p = *reinterpret_cast<const _OrthancPluginStoreKeyValue*>(parameters);
|
|
ApplyStoreKeyValue(p);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_DeleteKeyValue:
|
|
{
|
|
const _OrthancPluginDeleteKeyValue& p = *reinterpret_cast<const _OrthancPluginDeleteKeyValue*>(parameters);
|
|
ApplyDeleteKeyValue(p);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetKeyValue:
|
|
{
|
|
const _OrthancPluginGetKeyValue& p = *reinterpret_cast<const _OrthancPluginGetKeyValue*>(parameters);
|
|
ApplyGetKeyValue(p);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_CreateKeysValuesIterator:
|
|
{
|
|
const _OrthancPluginCreateKeysValuesIterator& p = *reinterpret_cast<const _OrthancPluginCreateKeysValuesIterator*>(parameters);
|
|
ApplyCreateKeysValuesIterator(p);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_FreeKeysValuesIterator:
|
|
{
|
|
const _OrthancPluginFreeKeysValuesIterator& p = *reinterpret_cast<const _OrthancPluginFreeKeysValuesIterator*>(parameters);
|
|
delete reinterpret_cast<StatelessDatabaseOperations::KeysValuesIterator*>(p.iterator);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_KeysValuesIteratorNext:
|
|
{
|
|
const _OrthancPluginKeysValuesIteratorNext& p = *reinterpret_cast<const _OrthancPluginKeysValuesIteratorNext*>(parameters);
|
|
StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast<StatelessDatabaseOperations::KeysValuesIterator*>(p.iterator);
|
|
*p.done = iterator.Next() ? 1 : 0;
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_KeysValuesIteratorGetKey:
|
|
{
|
|
const _OrthancPluginKeysValuesIteratorGetKey& p = *reinterpret_cast<const _OrthancPluginKeysValuesIteratorGetKey*>(parameters);
|
|
StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast<StatelessDatabaseOperations::KeysValuesIterator*>(p.iterator);
|
|
*p.target = iterator.GetKey().c_str();
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_KeysValuesIteratorGetValue:
|
|
{
|
|
const _OrthancPluginKeysValuesIteratorGetValue& p = *reinterpret_cast<const _OrthancPluginKeysValuesIteratorGetValue*>(parameters);
|
|
StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast<StatelessDatabaseOperations::KeysValuesIterator*>(p.iterator);
|
|
CopyToMemoryBuffer(p.target, iterator.GetValue());
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_EnqueueValue:
|
|
{
|
|
const _OrthancPluginEnqueueValue& p = *reinterpret_cast<const _OrthancPluginEnqueueValue*>(parameters);
|
|
ApplyEnqueueValue(p);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_DequeueValue:
|
|
{
|
|
const _OrthancPluginDequeueValue& p = *reinterpret_cast<const _OrthancPluginDequeueValue*>(parameters);
|
|
ApplyDequeueValue(p);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetQueueSize:
|
|
{
|
|
const _OrthancPluginGetQueueSize& p = *reinterpret_cast<const _OrthancPluginGetQueueSize*>(parameters);
|
|
ApplyGetQueueSize(p);
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool OrthancPlugins::InvokeProtectedService(SharedLibrary& plugin,
|
|
_OrthancPluginService service,
|
|
const void* parameters)
|
|
{
|
|
// Services that must be run in mutual exclusion. Guideline:
|
|
// Whenever "pimpl_" is directly accessed by the service, it
|
|
// should be listed here.
|
|
|
|
switch (service)
|
|
{
|
|
case _OrthancPluginService_RegisterRestCallback:
|
|
RegisterRestCallback(parameters, true);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterRestCallbackNoLock:
|
|
RegisterRestCallback(parameters, false);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterChunkedRestCallback:
|
|
RegisterChunkedRestCallback(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterOnStoredInstanceCallback:
|
|
RegisterOnStoredInstanceCallback(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterOnChangeCallback:
|
|
RegisterOnChangeCallback(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterWorklistCallback:
|
|
RegisterWorklistCallback(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterFindCallback:
|
|
RegisterFindCallback(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterMoveCallback:
|
|
RegisterMoveCallback(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterDecodeImageCallback:
|
|
RegisterDecodeImageCallback(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterTranscoderCallback:
|
|
RegisterTranscoderCallback(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterJobsUnserializer:
|
|
RegisterJobsUnserializer(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterIncomingDicomInstanceFilter:
|
|
RegisterIncomingDicomInstanceFilter(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterIncomingCStoreInstanceFilter:
|
|
RegisterIncomingCStoreInstanceFilter(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterReceivedInstanceCallback:
|
|
RegisterReceivedInstanceCallback(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterRefreshMetricsCallback:
|
|
RegisterRefreshMetricsCallback(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterStorageCommitmentScpCallback:
|
|
RegisterStorageCommitmentScpCallback(parameters);
|
|
return true;
|
|
|
|
case _OrthancPluginService_RegisterStorageArea:
|
|
case _OrthancPluginService_RegisterStorageArea2:
|
|
case _OrthancPluginService_RegisterStorageArea3:
|
|
{
|
|
if (pimpl_->storageArea_.get() == NULL)
|
|
{
|
|
if (service == _OrthancPluginService_RegisterStorageArea)
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a custom storage area (v1)";
|
|
|
|
const _OrthancPluginRegisterStorageArea& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterStorageArea*>(parameters);
|
|
pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary()));
|
|
}
|
|
else if (service == _OrthancPluginService_RegisterStorageArea2)
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a custom storage area (v2)";
|
|
|
|
const _OrthancPluginRegisterStorageArea2& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterStorageArea2*>(parameters);
|
|
pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary()));
|
|
}
|
|
else if (service == _OrthancPluginService_RegisterStorageArea3)
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a custom storage area (v3)";
|
|
|
|
const _OrthancPluginRegisterStorageArea3& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterStorageArea3*>(parameters);
|
|
pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary()));
|
|
pimpl_->hasStorageAreaCustomData_ = true;
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_StorageAreaAlreadyRegistered);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_SetPluginProperty:
|
|
{
|
|
const _OrthancPluginSetPluginProperty& p =
|
|
*reinterpret_cast<const _OrthancPluginSetPluginProperty*>(parameters);
|
|
pimpl_->properties_[std::make_pair(p.plugin, p.property)] = p.value;
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetCommandLineArgumentsCount:
|
|
{
|
|
const _OrthancPluginReturnSingleValue& p =
|
|
*reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters);
|
|
*(p.resultUint32) = pimpl_->argc_ - 1;
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetCommandLineArgument:
|
|
{
|
|
const _OrthancPluginGlobalProperty& p =
|
|
*reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
|
|
|
|
if (p.property + 1 > pimpl_->argc_)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
std::string arg = std::string(pimpl_->argv_[p.property + 1]);
|
|
*(p.result) = CopyString(arg);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
case _OrthancPluginService_RegisterDatabaseBackend:
|
|
{
|
|
LOG(WARNING) << "Performance warning: Plugin has registered a custom database back-end with an old API (version 1)";
|
|
LOG(WARNING) << "The database backend has *no* support for revisions of metadata and attachments";
|
|
|
|
const _OrthancPluginRegisterDatabaseBackend& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterDatabaseBackend*>(parameters);
|
|
|
|
if (pimpl_->database_.get() == NULL &&
|
|
pimpl_->databaseV3_.get() == NULL &&
|
|
pimpl_->databaseV4_.get() == NULL)
|
|
{
|
|
pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(),
|
|
*p.backend, NULL, 0, p.payload));
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered);
|
|
}
|
|
|
|
*(p.result) = reinterpret_cast<OrthancPluginDatabaseContext*>(pimpl_->database_.get());
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_RegisterDatabaseBackendV2:
|
|
{
|
|
LOG(WARNING) << "Performance warning: Plugin has registered a custom database back-end with an old API (version 2)";
|
|
LOG(WARNING) << "The database backend has *no* support for revisions of metadata and attachments";
|
|
|
|
const _OrthancPluginRegisterDatabaseBackendV2& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterDatabaseBackendV2*>(parameters);
|
|
|
|
if (pimpl_->database_.get() == NULL &&
|
|
pimpl_->databaseV3_.get() == NULL &&
|
|
pimpl_->databaseV4_.get() == NULL)
|
|
{
|
|
pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(),
|
|
*p.backend, p.extensions,
|
|
p.extensionsSize, p.payload));
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered);
|
|
}
|
|
|
|
*(p.result) = reinterpret_cast<OrthancPluginDatabaseContext*>(pimpl_->database_.get());
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_RegisterDatabaseBackendV3:
|
|
{
|
|
LOG(WARNING) << "Performance warning: Plugin has registered a custom database back-end with an old API (version 3)";
|
|
|
|
const _OrthancPluginRegisterDatabaseBackendV3& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterDatabaseBackendV3*>(parameters);
|
|
|
|
if (pimpl_->database_.get() == NULL &&
|
|
pimpl_->databaseV3_.get() == NULL &&
|
|
pimpl_->databaseV4_.get() == NULL)
|
|
{
|
|
pimpl_->databaseV3_.reset(new OrthancPluginDatabaseV3(plugin, GetErrorDictionary(), p.backend,
|
|
p.backendSize, p.database, pimpl_->databaseServerIdentifier_));
|
|
pimpl_->maxDatabaseRetries_ = p.maxDatabaseRetries;
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_RegisterDatabaseBackendV4:
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a custom database back-end (v4)";
|
|
|
|
const _OrthancPluginRegisterDatabaseBackendV4& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterDatabaseBackendV4*>(parameters);
|
|
|
|
if (pimpl_->database_.get() == NULL &&
|
|
pimpl_->databaseV3_.get() == NULL &&
|
|
pimpl_->databaseV4_.get() == NULL)
|
|
{
|
|
pimpl_->databaseV4_.reset(new OrthancPluginDatabaseV4(plugin, GetErrorDictionary(), p, pimpl_->databaseServerIdentifier_));
|
|
pimpl_->maxDatabaseRetries_ = p.maxDatabaseRetries;
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_DatabaseAnswer:
|
|
throw OrthancException(ErrorCode_InternalError); // Implemented before locking (*)
|
|
|
|
case _OrthancPluginService_RegisterErrorCode:
|
|
{
|
|
const _OrthancPluginRegisterErrorCode& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterErrorCode*>(parameters);
|
|
*(p.target) = pimpl_->dictionary_.Register(plugin, p.code, p.httpStatus, p.message);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_RegisterDictionaryTag:
|
|
{
|
|
const _OrthancPluginRegisterDictionaryTag& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterDictionaryTag*>(parameters);
|
|
FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element),
|
|
Plugins::Convert(p.vr), p.name,
|
|
p.minMultiplicity, p.maxMultiplicity, "");
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_RegisterPrivateDictionaryTag:
|
|
{
|
|
const _OrthancPluginRegisterPrivateDictionaryTag& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterPrivateDictionaryTag*>(parameters);
|
|
FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element),
|
|
Plugins::Convert(p.vr), p.name,
|
|
p.minMultiplicity, p.maxMultiplicity, p.privateCreator);
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_ReconstructMainDicomTags:
|
|
{
|
|
const _OrthancPluginReconstructMainDicomTags& p =
|
|
*reinterpret_cast<const _OrthancPluginReconstructMainDicomTags*>(parameters);
|
|
|
|
if (pimpl_->database_.get() == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin,
|
|
"The service ReconstructMainDicomTags can only be invoked by custom database plugins");
|
|
}
|
|
|
|
VoidDatabaseListener listener;
|
|
|
|
{
|
|
IPluginStorageArea& storage = *reinterpret_cast<IPluginStorageArea*>(p.storageArea);
|
|
|
|
std::unique_ptr<IDatabaseWrapper::ITransaction> transaction(
|
|
pimpl_->database_->StartTransaction(TransactionType_ReadWrite, listener));
|
|
ServerToolbox::ReconstructMainDicomTags(*transaction, storage, Plugins::Convert(p.level));
|
|
transaction->Commit(0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GenerateRestApiAuthorizationToken:
|
|
{
|
|
const _OrthancPluginRetrieveDynamicString& p =
|
|
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters);
|
|
const std::string token = Toolbox::GenerateUuid();
|
|
|
|
pimpl_->authorizationTokens_.insert(token);
|
|
*p.result = CopyString("Bearer " + token);
|
|
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_RegisterWebDavCollection:
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Plugin has registered a WebDAV collection";
|
|
const _OrthancPluginRegisterWebDavCollection& p =
|
|
*reinterpret_cast<const _OrthancPluginRegisterWebDavCollection*>(parameters);
|
|
pimpl_->webDavCollections_.push_back(new WebDavCollection(GetErrorDictionary(), p));
|
|
return true;
|
|
}
|
|
|
|
case _OrthancPluginService_GetDatabaseServerIdentifier:
|
|
{
|
|
const _OrthancPluginRetrieveStaticString& p =
|
|
*reinterpret_cast<const _OrthancPluginRetrieveStaticString*>(parameters);
|
|
|
|
*p.result = pimpl_->databaseServerIdentifier_.c_str();
|
|
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
{
|
|
// This service is unknown to the Orthanc plugin engine
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool OrthancPlugins::InvokeService(SharedLibrary& plugin,
|
|
_OrthancPluginService service,
|
|
const void* parameters)
|
|
{
|
|
CLOG(TRACE, PLUGINS) << "Calling service " << service << " from plugin " << plugin.GetPath();
|
|
|
|
if (service == _OrthancPluginService_DatabaseAnswer)
|
|
{
|
|
// This case solves a deadlock at (*) reported by James Webster
|
|
// on 2015-10-27 that was present in versions of Orthanc <=
|
|
// 0.9.4 and related to database plugins implementing a custom
|
|
// index. The problem was that locking the database is already
|
|
// ensured by the "ServerIndex" class if the invoked service is
|
|
// "DatabaseAnswer".
|
|
DatabaseAnswer(parameters);
|
|
return true;
|
|
}
|
|
|
|
if (InvokeSafeService(plugin, service, parameters))
|
|
{
|
|
// The invoked service does not require locking
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// The invoked service requires locking
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); // (*)
|
|
return InvokeProtectedService(plugin, service, parameters);
|
|
}
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::HasStorageArea() const
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
|
|
return pimpl_->storageArea_.get() != NULL;
|
|
}
|
|
|
|
bool OrthancPlugins::HasDatabaseBackend() const
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
|
|
return (pimpl_->database_.get() != NULL ||
|
|
pimpl_->databaseV3_.get() != NULL ||
|
|
pimpl_->databaseV4_.get() != NULL);
|
|
}
|
|
|
|
|
|
IPluginStorageArea* OrthancPlugins::CreateStorageArea()
|
|
{
|
|
if (!HasStorageArea())
|
|
{
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
else
|
|
{
|
|
return pimpl_->storageArea_->Create();
|
|
}
|
|
}
|
|
|
|
|
|
const SharedLibrary& OrthancPlugins::GetStorageAreaLibrary() const
|
|
{
|
|
if (!HasStorageArea())
|
|
{
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
else
|
|
{
|
|
return pimpl_->storageArea_->GetSharedLibrary();
|
|
}
|
|
}
|
|
|
|
|
|
IDatabaseWrapper& OrthancPlugins::GetDatabaseBackend()
|
|
{
|
|
if (pimpl_->database_.get() != NULL)
|
|
{
|
|
return *pimpl_->database_;
|
|
}
|
|
else if (pimpl_->databaseV3_.get() != NULL)
|
|
{
|
|
return *pimpl_->databaseV3_;
|
|
}
|
|
else if (pimpl_->databaseV4_.get() != NULL)
|
|
{
|
|
return *pimpl_->databaseV4_;
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
}
|
|
|
|
|
|
const SharedLibrary& OrthancPlugins::GetDatabaseBackendLibrary() const
|
|
{
|
|
if (pimpl_->database_.get() != NULL)
|
|
{
|
|
return pimpl_->database_->GetSharedLibrary();
|
|
}
|
|
else if (pimpl_->databaseV3_.get() != NULL)
|
|
{
|
|
return pimpl_->databaseV3_->GetSharedLibrary();
|
|
}
|
|
else if (pimpl_->databaseV4_.get() != NULL)
|
|
{
|
|
return pimpl_->databaseV4_->GetSharedLibrary();
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
}
|
|
|
|
|
|
const char* OrthancPlugins::GetProperty(const char* plugin,
|
|
_OrthancPluginProperty property) const
|
|
{
|
|
PImpl::Property p = std::make_pair(plugin, property);
|
|
PImpl::Properties::const_iterator it = pimpl_->properties_.find(p);
|
|
|
|
if (it == pimpl_->properties_.end())
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
return it->second.c_str();
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPlugins::SetCommandLineArguments(int argc, char* argv[])
|
|
{
|
|
if (argc < 1 || argv == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
pimpl_->argc_ = argc;
|
|
pimpl_->argv_ = argv;
|
|
}
|
|
|
|
|
|
PluginsManager& OrthancPlugins::GetManager()
|
|
{
|
|
return pimpl_->manager_;
|
|
}
|
|
|
|
|
|
const PluginsManager& OrthancPlugins::GetManager() const
|
|
{
|
|
return pimpl_->manager_;
|
|
}
|
|
|
|
|
|
PluginsErrorDictionary& OrthancPlugins::GetErrorDictionary()
|
|
{
|
|
return pimpl_->dictionary_;
|
|
}
|
|
|
|
|
|
IWorklistRequestHandler* OrthancPlugins::ConstructWorklistRequestHandler()
|
|
{
|
|
if (HasWorklistHandler())
|
|
{
|
|
return new WorklistHandler(*this);
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::HasWorklistHandler()
|
|
{
|
|
boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_);
|
|
return pimpl_->worklistCallback_ != NULL;
|
|
}
|
|
|
|
|
|
IFindRequestHandler* OrthancPlugins::ConstructFindRequestHandler()
|
|
{
|
|
if (HasFindHandler())
|
|
{
|
|
return new FindHandler(*this);
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::HasFindHandler()
|
|
{
|
|
boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_);
|
|
return pimpl_->findCallback_ != NULL;
|
|
}
|
|
|
|
|
|
IMoveRequestHandler* OrthancPlugins::ConstructMoveRequestHandler()
|
|
{
|
|
if (HasMoveHandler())
|
|
{
|
|
return new MoveHandler(*this);
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::HasMoveHandler()
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
|
|
return pimpl_->moveCallbacks_.callback != NULL;
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::HasCustomImageDecoder()
|
|
{
|
|
boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
|
|
return !pimpl_->decodeImageCallbacks_.empty();
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::HasCustomTranscoder()
|
|
{
|
|
boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
|
|
return !pimpl_->transcoderCallbacks_.empty();
|
|
}
|
|
|
|
|
|
ImageAccessor* OrthancPlugins::Decode(const void* dicom,
|
|
size_t size,
|
|
unsigned int frame)
|
|
{
|
|
boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
|
|
|
|
for (PImpl::DecodeImageCallbacks::const_iterator
|
|
decoder = pimpl_->decodeImageCallbacks_.begin();
|
|
decoder != pimpl_->decodeImageCallbacks_.end(); ++decoder)
|
|
{
|
|
OrthancPluginImage* pluginImage = NULL;
|
|
if ((*decoder) (&pluginImage, dicom, size, frame) == OrthancPluginErrorCode_Success &&
|
|
pluginImage != NULL)
|
|
{
|
|
return reinterpret_cast<ImageAccessor*>(pluginImage);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::IsAllowed(HttpMethod method,
|
|
const char* uri,
|
|
const char* ip,
|
|
const char* username,
|
|
const HttpToolbox::Arguments& httpHeaders,
|
|
const HttpToolbox::GetArguments& getArguments)
|
|
{
|
|
OrthancPluginHttpMethod cMethod = Plugins::Convert(method);
|
|
|
|
std::vector<const char*> httpKeys(httpHeaders.size());
|
|
std::vector<const char*> httpValues(httpHeaders.size());
|
|
|
|
size_t pos = 0;
|
|
for (HttpToolbox::Arguments::const_iterator
|
|
it = httpHeaders.begin(); it != httpHeaders.end(); ++it, pos++)
|
|
{
|
|
httpKeys[pos] = it->first.c_str();
|
|
httpValues[pos] = it->second.c_str();
|
|
}
|
|
|
|
std::vector<const char*> getKeys(getArguments.size());
|
|
std::vector<const char*> getValues(getArguments.size());
|
|
|
|
for (size_t i = 0; i < getArguments.size(); i++)
|
|
{
|
|
getKeys[i] = getArguments[i].first.c_str();
|
|
getValues[i] = getArguments[i].second.c_str();
|
|
}
|
|
|
|
{
|
|
boost::shared_lock<boost::shared_mutex> lock(pimpl_->incomingHttpRequestFilterMutex_);
|
|
|
|
// Improved callback with support for GET arguments, since Orthanc 1.3.0
|
|
for (PImpl::IncomingHttpRequestFilters2::const_iterator
|
|
filter = pimpl_->incomingHttpRequestFilters2_.begin();
|
|
filter != pimpl_->incomingHttpRequestFilters2_.end(); ++filter)
|
|
{
|
|
int32_t allowed = (*filter) (cMethod, uri, ip,
|
|
httpKeys.size(),
|
|
httpKeys.empty() ? NULL : &httpKeys[0],
|
|
httpValues.empty() ? NULL : &httpValues[0],
|
|
getKeys.size(),
|
|
getKeys.empty() ? NULL : &getKeys[0],
|
|
getValues.empty() ? NULL : &getValues[0]);
|
|
|
|
if (allowed == 0)
|
|
{
|
|
return false;
|
|
}
|
|
else if (allowed != 1)
|
|
{
|
|
// The callback is only allowed to answer 0 or 1
|
|
throw OrthancException(ErrorCode_Plugin);
|
|
}
|
|
}
|
|
|
|
for (PImpl::IncomingHttpRequestFilters::const_iterator
|
|
filter = pimpl_->incomingHttpRequestFilters_.begin();
|
|
filter != pimpl_->incomingHttpRequestFilters_.end(); ++filter)
|
|
{
|
|
int32_t allowed = (*filter) (cMethod, uri, ip, httpKeys.size(),
|
|
httpKeys.empty() ? NULL : &httpKeys[0],
|
|
httpValues.empty() ? NULL : &httpValues[0]);
|
|
|
|
if (allowed == 0)
|
|
{
|
|
return false;
|
|
}
|
|
else if (allowed != 1)
|
|
{
|
|
// The callback is only allowed to answer 0 or 1
|
|
throw OrthancException(ErrorCode_Plugin);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
IJob* OrthancPlugins::UnserializeJob(const std::string& type,
|
|
const Json::Value& value)
|
|
{
|
|
const std::string serialized = value.toStyledString();
|
|
|
|
boost::mutex::scoped_lock lock(pimpl_->jobsUnserializersMutex_);
|
|
|
|
for (PImpl::JobsUnserializers::iterator
|
|
unserializer = pimpl_->jobsUnserializers_.begin();
|
|
unserializer != pimpl_->jobsUnserializers_.end(); ++unserializer)
|
|
{
|
|
OrthancPluginJob* job = (*unserializer) (type.c_str(), serialized.c_str());
|
|
if (job != NULL)
|
|
{
|
|
return reinterpret_cast<PluginsJob*>(job);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RefreshMetrics()
|
|
{
|
|
boost::mutex::scoped_lock lock(pimpl_->refreshMetricsMutex_);
|
|
|
|
for (PImpl::RefreshMetricsCallbacks::iterator
|
|
it = pimpl_->refreshMetricsCallbacks_.begin();
|
|
it != pimpl_->refreshMetricsCallbacks_.end(); ++it)
|
|
{
|
|
if (*it != NULL)
|
|
{
|
|
(*it) ();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class OrthancPlugins::HttpServerChunkedReader : public IHttpHandler::IChunkedRequestReader
|
|
{
|
|
private:
|
|
OrthancPluginServerChunkedRequestReader* reader_;
|
|
_OrthancPluginChunkedRestCallback parameters_;
|
|
PluginsErrorDictionary& errorDictionary_;
|
|
|
|
public:
|
|
HttpServerChunkedReader(OrthancPluginServerChunkedRequestReader* reader,
|
|
const _OrthancPluginChunkedRestCallback& parameters,
|
|
PluginsErrorDictionary& errorDictionary) :
|
|
reader_(reader),
|
|
parameters_(parameters),
|
|
errorDictionary_(errorDictionary)
|
|
{
|
|
assert(reader_ != NULL);
|
|
}
|
|
|
|
virtual ~HttpServerChunkedReader()
|
|
{
|
|
assert(reader_ != NULL);
|
|
parameters_.finalize(reader_);
|
|
}
|
|
|
|
virtual void AddBodyChunk(const void* data,
|
|
size_t size) ORTHANC_OVERRIDE
|
|
{
|
|
if (static_cast<uint32_t>(size) != size)
|
|
{
|
|
throw OrthancException(ErrorCode_NotEnoughMemory, ERROR_MESSAGE_64BIT);
|
|
}
|
|
|
|
assert(reader_ != NULL);
|
|
parameters_.addChunk(reader_, data, size);
|
|
}
|
|
|
|
virtual void Execute(HttpOutput& output) ORTHANC_OVERRIDE
|
|
{
|
|
assert(reader_ != NULL);
|
|
|
|
PImpl::PluginHttpOutput pluginOutput(output);
|
|
|
|
OrthancPluginErrorCode error = parameters_.execute(
|
|
reader_, reinterpret_cast<OrthancPluginRestOutput*>(&pluginOutput));
|
|
|
|
pluginOutput.Close(error, errorDictionary_);
|
|
}
|
|
};
|
|
|
|
|
|
bool OrthancPlugins::CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
|
|
RequestOrigin origin,
|
|
const char* remoteIp,
|
|
const char* username,
|
|
HttpMethod method,
|
|
const UriComponents& uri,
|
|
const HttpToolbox::Arguments& headers)
|
|
{
|
|
if (method != HttpMethod_Post &&
|
|
method != HttpMethod_Put)
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
|
|
RestCallbackMatcher matcher(uri);
|
|
|
|
PImpl::ChunkedRestCallback* callback = NULL;
|
|
|
|
// Loop over the callbacks registered by the plugins
|
|
boost::shared_lock<boost::shared_mutex> lock(pimpl_->restCallbackRegistrationMutex_);
|
|
for (PImpl::ChunkedRestCallbacks::const_iterator it = pimpl_->chunkedRestCallbacks_.begin();
|
|
it != pimpl_->chunkedRestCallbacks_.end(); ++it)
|
|
{
|
|
if (matcher.IsMatch((*it)->GetRegularExpression()))
|
|
{
|
|
callback = *it;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (callback == NULL)
|
|
{
|
|
// Callback not found
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
OrthancPluginServerChunkedRequestReaderFactory handler;
|
|
|
|
switch (method)
|
|
{
|
|
case HttpMethod_Post:
|
|
handler = callback->GetParameters().postHandler;
|
|
break;
|
|
|
|
case HttpMethod_Put:
|
|
handler = callback->GetParameters().putHandler;
|
|
break;
|
|
|
|
default:
|
|
handler = NULL;
|
|
break;
|
|
}
|
|
|
|
if (handler == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Delegating chunked HTTP request to plugin for URI: " << matcher.GetFlatUri();
|
|
|
|
HttpRequestConverter converter(matcher, method, headers);
|
|
converter.GetRequest().body = NULL;
|
|
converter.GetRequest().bodySize = 0;
|
|
|
|
OrthancPluginServerChunkedRequestReader* reader = NULL;
|
|
|
|
OrthancPluginErrorCode errorCode = handler(
|
|
&reader, matcher.GetFlatUri().c_str(), &converter.GetRequest());
|
|
|
|
if (errorCode != OrthancPluginErrorCode_Success)
|
|
{
|
|
throw OrthancException(static_cast<ErrorCode>(errorCode));
|
|
}
|
|
else if (reader == NULL)
|
|
{
|
|
// The plugin has not created a reader for chunked body
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
target.reset(new HttpServerChunkedReader(reader, callback->GetParameters(), GetErrorDictionary()));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
IStorageCommitmentFactory::ILookupHandler* OrthancPlugins::CreateStorageCommitment(
|
|
const std::string& jobId,
|
|
const std::string& transactionUid,
|
|
const std::vector<std::string>& sopClassUids,
|
|
const std::vector<std::string>& sopInstanceUids,
|
|
const std::string& remoteAet,
|
|
const std::string& calledAet)
|
|
{
|
|
boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_);
|
|
|
|
for (PImpl::StorageCommitmentScpCallbacks::iterator
|
|
it = pimpl_->storageCommitmentScpCallbacks_.begin();
|
|
it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it)
|
|
{
|
|
assert(*it != NULL);
|
|
IStorageCommitmentFactory::ILookupHandler* handler = (*it)->CreateStorageCommitment
|
|
(jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet);
|
|
|
|
if (handler != NULL)
|
|
{
|
|
return handler;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::TranscodeBuffer(std::string& target,
|
|
const void* buffer,
|
|
size_t size,
|
|
const std::set<DicomTransferSyntax>& allowedSyntaxes,
|
|
bool allowNewSopInstanceUid)
|
|
{
|
|
boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
|
|
|
|
if (pimpl_->transcoderCallbacks_.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::vector<const char*> uids;
|
|
uids.reserve(allowedSyntaxes.size());
|
|
for (std::set<DicomTransferSyntax>::const_iterator
|
|
it = allowedSyntaxes.begin(); it != allowedSyntaxes.end(); ++it)
|
|
{
|
|
uids.push_back(GetTransferSyntaxUid(*it));
|
|
}
|
|
|
|
for (PImpl::TranscoderCallbacks::const_iterator
|
|
transcoder = pimpl_->transcoderCallbacks_.begin();
|
|
transcoder != pimpl_->transcoderCallbacks_.end(); ++transcoder)
|
|
{
|
|
PluginMemoryBuffer32 a;
|
|
|
|
if ((*transcoder) (a.GetObject(), buffer, size, uids.empty() ? NULL : &uids[0],
|
|
static_cast<uint32_t>(uids.size()), allowNewSopInstanceUid) ==
|
|
OrthancPluginErrorCode_Success)
|
|
{
|
|
a.MoveToString(target);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool OrthancPlugins::IsValidAuthorizationToken(const std::string& token) const
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
|
|
return (pimpl_->authorizationTokens_.find(token) != pimpl_->authorizationTokens_.end());
|
|
}
|
|
|
|
|
|
unsigned int OrthancPlugins::GetMaxDatabaseRetries() const
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
|
|
return pimpl_->maxDatabaseRetries_;
|
|
}
|
|
|
|
|
|
void OrthancPlugins::RegisterWebDavCollections(HttpServer& target)
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
|
|
|
|
while (!pimpl_->webDavCollections_.empty())
|
|
{
|
|
WebDavCollection* collection = pimpl_->webDavCollections_.front();
|
|
assert(collection != NULL);
|
|
|
|
UriComponents components;
|
|
Toolbox::SplitUriComponents(components, collection->GetUri());
|
|
target.Register(components, collection);
|
|
|
|
pimpl_->webDavCollections_.pop_front();
|
|
}
|
|
}
|
|
}
|