1732 lines
55 KiB
C++
1732 lines
55 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 "OrthancPluginDatabase.h"
|
|
|
|
#if ORTHANC_ENABLE_PLUGINS != 1
|
|
# error The plugin support is disabled
|
|
#endif
|
|
|
|
|
|
#include "../../../OrthancFramework/Sources/Logging.h"
|
|
#include "../../../OrthancFramework/Sources/OrthancException.h"
|
|
#include "../../Sources/Database/Compatibility/ICreateInstance.h"
|
|
#include "../../Sources/Database/Compatibility/IGetChildrenMetadata.h"
|
|
#include "../../Sources/Database/Compatibility/ILookupResourceAndParent.h"
|
|
#include "../../Sources/Database/Compatibility/ILookupResources.h"
|
|
#include "../../Sources/Database/Compatibility/ISetResourcesContent.h"
|
|
#include "../../Sources/Database/VoidDatabaseListener.h"
|
|
#include "PluginsEnumerations.h"
|
|
|
|
#include <cassert>
|
|
|
|
namespace Orthanc
|
|
{
|
|
class OrthancPluginDatabase::Transaction :
|
|
public BaseCompatibilityTransaction,
|
|
public Compatibility::ICreateInstance,
|
|
public Compatibility::IGetChildrenMetadata,
|
|
public Compatibility::ILookupResources,
|
|
public Compatibility::ILookupResourceAndParent,
|
|
public Compatibility::ISetResourcesContent
|
|
{
|
|
private:
|
|
typedef std::pair<int64_t, ResourceType> AnswerResource;
|
|
typedef std::map<MetadataType, std::string> AnswerMetadata;
|
|
|
|
OrthancPluginDatabase& that_;
|
|
boost::recursive_mutex::scoped_lock lock_;
|
|
IDatabaseListener& listener_;
|
|
_OrthancPluginDatabaseAnswerType type_;
|
|
|
|
std::list<std::string> answerStrings_;
|
|
std::list<int32_t> answerInt32_;
|
|
std::list<int64_t> answerInt64_;
|
|
std::list<AnswerResource> answerResources_;
|
|
std::list<FileInfo> answerAttachments_;
|
|
|
|
DicomMap* answerDicomMap_;
|
|
std::list<ServerIndexChange>* answerChanges_;
|
|
std::list<ExportedResource>* answerExportedResources_;
|
|
bool* answerDone_;
|
|
bool answerDoneIgnored_;
|
|
std::list<std::string>* answerMatchingResources_;
|
|
std::list<std::string>* answerMatchingInstances_;
|
|
AnswerMetadata* answerMetadata_;
|
|
|
|
void CheckSuccess(OrthancPluginErrorCode code) const
|
|
{
|
|
that_.CheckSuccess(code);
|
|
}
|
|
|
|
|
|
static FileInfo Convert(const OrthancPluginAttachment& attachment)
|
|
{
|
|
return FileInfo(attachment.uuid,
|
|
static_cast<FileContentType>(attachment.contentType),
|
|
attachment.uncompressedSize,
|
|
attachment.uncompressedHash,
|
|
static_cast<CompressionType>(attachment.compressionType),
|
|
attachment.compressedSize,
|
|
attachment.compressedHash);
|
|
}
|
|
|
|
|
|
void ResetAnswers()
|
|
{
|
|
type_ = _OrthancPluginDatabaseAnswerType_None;
|
|
|
|
answerDicomMap_ = NULL;
|
|
answerChanges_ = NULL;
|
|
answerExportedResources_ = NULL;
|
|
answerDone_ = NULL;
|
|
answerMatchingResources_ = NULL;
|
|
answerMatchingInstances_ = NULL;
|
|
answerMetadata_ = NULL;
|
|
}
|
|
|
|
|
|
void ForwardAnswers(std::list<int64_t>& target)
|
|
{
|
|
if (type_ != _OrthancPluginDatabaseAnswerType_None &&
|
|
type_ != _OrthancPluginDatabaseAnswerType_Int64)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
target.clear();
|
|
|
|
if (type_ == _OrthancPluginDatabaseAnswerType_Int64)
|
|
{
|
|
for (std::list<int64_t>::const_iterator
|
|
it = answerInt64_.begin(); it != answerInt64_.end(); ++it)
|
|
{
|
|
target.push_back(*it);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ForwardAnswers(std::list<std::string>& target)
|
|
{
|
|
if (type_ != _OrthancPluginDatabaseAnswerType_None &&
|
|
type_ != _OrthancPluginDatabaseAnswerType_String)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
target.clear();
|
|
|
|
if (type_ == _OrthancPluginDatabaseAnswerType_String)
|
|
{
|
|
for (std::list<std::string>::const_iterator
|
|
it = answerStrings_.begin(); it != answerStrings_.end(); ++it)
|
|
{
|
|
target.push_back(*it);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool ForwardSingleAnswer(std::string& target)
|
|
{
|
|
if (type_ == _OrthancPluginDatabaseAnswerType_None)
|
|
{
|
|
return false;
|
|
}
|
|
else if (type_ == _OrthancPluginDatabaseAnswerType_String &&
|
|
answerStrings_.size() == 1)
|
|
{
|
|
target = answerStrings_.front();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
}
|
|
|
|
|
|
bool ForwardSingleAnswer(int64_t& target)
|
|
{
|
|
if (type_ == _OrthancPluginDatabaseAnswerType_None)
|
|
{
|
|
return false;
|
|
}
|
|
else if (type_ == _OrthancPluginDatabaseAnswerType_Int64 &&
|
|
answerInt64_.size() == 1)
|
|
{
|
|
target = answerInt64_.front();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
}
|
|
|
|
|
|
void ProcessEvent(const _OrthancPluginDatabaseAnswer& answer)
|
|
{
|
|
switch (answer.type)
|
|
{
|
|
case _OrthancPluginDatabaseAnswerType_DeletedAttachment:
|
|
{
|
|
const OrthancPluginAttachment& attachment =
|
|
*reinterpret_cast<const OrthancPluginAttachment*>(answer.valueGeneric);
|
|
listener_.SignalAttachmentDeleted(Convert(attachment));
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginDatabaseAnswerType_RemainingAncestor:
|
|
{
|
|
ResourceType type = Plugins::Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32));
|
|
listener_.SignalRemainingAncestor(type, answer.valueString);
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginDatabaseAnswerType_DeletedResource:
|
|
{
|
|
ResourceType type = Plugins::Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32));
|
|
listener_.SignalResourceDeleted(type, answer.valueString);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
}
|
|
|
|
|
|
public:
|
|
explicit Transaction(OrthancPluginDatabase& that,
|
|
IDatabaseListener& listener) :
|
|
that_(that),
|
|
lock_(that.mutex_),
|
|
listener_(listener),
|
|
type_(_OrthancPluginDatabaseAnswerType_None),
|
|
answerDoneIgnored_(false)
|
|
{
|
|
if (that_.activeTransaction_ != NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError);
|
|
}
|
|
|
|
that_.activeTransaction_ = this;
|
|
|
|
ResetAnswers();
|
|
}
|
|
|
|
virtual ~Transaction()
|
|
{
|
|
assert(that_.activeTransaction_ != NULL);
|
|
that_.activeTransaction_ = NULL;
|
|
}
|
|
|
|
IDatabaseListener& GetDatabaseListener() const
|
|
{
|
|
return listener_;
|
|
}
|
|
|
|
void Begin()
|
|
{
|
|
CheckSuccess(that_.backend_.startTransaction(that_.payload_));
|
|
}
|
|
|
|
virtual void Rollback() ORTHANC_OVERRIDE
|
|
{
|
|
CheckSuccess(that_.backend_.rollbackTransaction(that_.payload_));
|
|
}
|
|
|
|
virtual void Commit(int64_t diskSizeDelta) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.fastGetTotalSize_)
|
|
{
|
|
CheckSuccess(that_.backend_.commitTransaction(that_.payload_));
|
|
}
|
|
else
|
|
{
|
|
if (static_cast<int64_t>(that_.currentDiskSize_) + diskSizeDelta < 0)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
uint64_t newDiskSize = (that_.currentDiskSize_ + diskSizeDelta);
|
|
|
|
assert(newDiskSize == GetTotalCompressedSize());
|
|
|
|
CheckSuccess(that_.backend_.commitTransaction(that_.payload_));
|
|
|
|
// The transaction has succeeded, we can commit the new disk size
|
|
that_.currentDiskSize_ = newDiskSize;
|
|
}
|
|
}
|
|
|
|
|
|
void AnswerReceived(const _OrthancPluginDatabaseAnswer& answer)
|
|
{
|
|
if (answer.type == _OrthancPluginDatabaseAnswerType_None)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
if (answer.type == _OrthancPluginDatabaseAnswerType_DeletedAttachment ||
|
|
answer.type == _OrthancPluginDatabaseAnswerType_DeletedResource ||
|
|
answer.type == _OrthancPluginDatabaseAnswerType_RemainingAncestor)
|
|
{
|
|
ProcessEvent(answer);
|
|
return;
|
|
}
|
|
|
|
if (type_ == _OrthancPluginDatabaseAnswerType_None)
|
|
{
|
|
type_ = answer.type;
|
|
|
|
switch (type_)
|
|
{
|
|
case _OrthancPluginDatabaseAnswerType_Int32:
|
|
answerInt32_.clear();
|
|
break;
|
|
|
|
case _OrthancPluginDatabaseAnswerType_Int64:
|
|
answerInt64_.clear();
|
|
break;
|
|
|
|
case _OrthancPluginDatabaseAnswerType_Resource:
|
|
answerResources_.clear();
|
|
break;
|
|
|
|
case _OrthancPluginDatabaseAnswerType_Attachment:
|
|
answerAttachments_.clear();
|
|
break;
|
|
|
|
case _OrthancPluginDatabaseAnswerType_String:
|
|
answerStrings_.clear();
|
|
break;
|
|
|
|
case _OrthancPluginDatabaseAnswerType_DicomTag:
|
|
assert(answerDicomMap_ != NULL);
|
|
answerDicomMap_->Clear();
|
|
break;
|
|
|
|
case _OrthancPluginDatabaseAnswerType_Change:
|
|
assert(answerChanges_ != NULL);
|
|
answerChanges_->clear();
|
|
break;
|
|
|
|
case _OrthancPluginDatabaseAnswerType_ExportedResource:
|
|
assert(answerExportedResources_ != NULL);
|
|
answerExportedResources_->clear();
|
|
break;
|
|
|
|
case _OrthancPluginDatabaseAnswerType_MatchingResource:
|
|
assert(answerMatchingResources_ != NULL);
|
|
answerMatchingResources_->clear();
|
|
|
|
if (answerMatchingInstances_ != NULL)
|
|
{
|
|
answerMatchingInstances_->clear();
|
|
}
|
|
|
|
break;
|
|
|
|
case _OrthancPluginDatabaseAnswerType_Metadata:
|
|
assert(answerMetadata_ != NULL);
|
|
answerMetadata_->clear();
|
|
break;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_DatabasePlugin,
|
|
"Unhandled type of answer for custom index plugin: " +
|
|
boost::lexical_cast<std::string>(answer.type));
|
|
}
|
|
}
|
|
else if (type_ != answer.type)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin,
|
|
"Error in the plugin protocol: Cannot change the answer type");
|
|
}
|
|
|
|
switch (answer.type)
|
|
{
|
|
case _OrthancPluginDatabaseAnswerType_Int32:
|
|
{
|
|
answerInt32_.push_back(answer.valueInt32);
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginDatabaseAnswerType_Int64:
|
|
{
|
|
answerInt64_.push_back(answer.valueInt64);
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginDatabaseAnswerType_Resource:
|
|
{
|
|
OrthancPluginResourceType type = static_cast<OrthancPluginResourceType>(answer.valueInt32);
|
|
answerResources_.push_back(std::make_pair(answer.valueInt64, Plugins::Convert(type)));
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginDatabaseAnswerType_Attachment:
|
|
{
|
|
const OrthancPluginAttachment& attachment =
|
|
*reinterpret_cast<const OrthancPluginAttachment*>(answer.valueGeneric);
|
|
|
|
answerAttachments_.push_back(Convert(attachment));
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginDatabaseAnswerType_DicomTag:
|
|
{
|
|
const OrthancPluginDicomTag& tag = *reinterpret_cast<const OrthancPluginDicomTag*>(answer.valueGeneric);
|
|
assert(answerDicomMap_ != NULL);
|
|
answerDicomMap_->SetValue(tag.group, tag.element, std::string(tag.value), false);
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginDatabaseAnswerType_String:
|
|
{
|
|
if (answer.valueString == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
if (type_ == _OrthancPluginDatabaseAnswerType_None)
|
|
{
|
|
type_ = _OrthancPluginDatabaseAnswerType_String;
|
|
answerStrings_.clear();
|
|
}
|
|
else if (type_ != _OrthancPluginDatabaseAnswerType_String)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
answerStrings_.push_back(std::string(answer.valueString));
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginDatabaseAnswerType_Change:
|
|
{
|
|
assert(answerDone_ != NULL);
|
|
if (answer.valueUint32 == 1)
|
|
{
|
|
*answerDone_ = true;
|
|
}
|
|
else if (*answerDone_)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
else
|
|
{
|
|
const OrthancPluginChange& change =
|
|
*reinterpret_cast<const OrthancPluginChange*>(answer.valueGeneric);
|
|
assert(answerChanges_ != NULL);
|
|
answerChanges_->push_back
|
|
(ServerIndexChange(change.seq,
|
|
static_cast<ChangeType>(change.changeType),
|
|
Plugins::Convert(change.resourceType),
|
|
change.publicId,
|
|
change.date));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginDatabaseAnswerType_ExportedResource:
|
|
{
|
|
assert(answerDone_ != NULL);
|
|
if (answer.valueUint32 == 1)
|
|
{
|
|
*answerDone_ = true;
|
|
}
|
|
else if (*answerDone_)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
else
|
|
{
|
|
const OrthancPluginExportedResource& exported =
|
|
*reinterpret_cast<const OrthancPluginExportedResource*>(answer.valueGeneric);
|
|
assert(answerExportedResources_ != NULL);
|
|
answerExportedResources_->push_back
|
|
(ExportedResource(exported.seq,
|
|
Plugins::Convert(exported.resourceType),
|
|
exported.publicId,
|
|
exported.modality,
|
|
exported.date,
|
|
exported.patientId,
|
|
exported.studyInstanceUid,
|
|
exported.seriesInstanceUid,
|
|
exported.sopInstanceUid));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginDatabaseAnswerType_MatchingResource:
|
|
{
|
|
const OrthancPluginMatchingResource& match =
|
|
*reinterpret_cast<const OrthancPluginMatchingResource*>(answer.valueGeneric);
|
|
|
|
if (match.resourceId == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
assert(answerMatchingResources_ != NULL);
|
|
answerMatchingResources_->push_back(match.resourceId);
|
|
|
|
if (answerMatchingInstances_ != NULL)
|
|
{
|
|
if (match.someInstanceId == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
answerMatchingInstances_->push_back(match.someInstanceId);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case _OrthancPluginDatabaseAnswerType_Metadata:
|
|
{
|
|
const OrthancPluginResourcesContentMetadata& metadata =
|
|
*reinterpret_cast<const OrthancPluginResourcesContentMetadata*>(answer.valueGeneric);
|
|
|
|
MetadataType type = static_cast<MetadataType>(metadata.metadata);
|
|
|
|
if (metadata.value == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
assert(answerMetadata_ != NULL &&
|
|
answerMetadata_->find(type) == answerMetadata_->end());
|
|
(*answerMetadata_) [type] = metadata.value;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_DatabasePlugin,
|
|
"Unhandled type of answer for custom index plugin: " +
|
|
boost::lexical_cast<std::string>(answer.type));
|
|
}
|
|
}
|
|
|
|
|
|
// From the "ILookupResources" interface
|
|
virtual void LookupIdentifier(std::list<int64_t>& result,
|
|
ResourceType level,
|
|
const DicomTag& tag,
|
|
Compatibility::IdentifierConstraintType type,
|
|
const std::string& value) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.lookupIdentifier3 == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin,
|
|
"The database plugin does not implement the mandatory LookupIdentifier3() extension");
|
|
}
|
|
|
|
OrthancPluginDicomTag tmp;
|
|
tmp.group = tag.GetGroup();
|
|
tmp.element = tag.GetElement();
|
|
tmp.value = value.c_str();
|
|
|
|
ResetAnswers();
|
|
CheckSuccess(that_.extensions_.lookupIdentifier3(that_.GetContext(), that_.payload_, Plugins::Convert(level),
|
|
&tmp, Compatibility::Convert(type)));
|
|
ForwardAnswers(result);
|
|
}
|
|
|
|
|
|
virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
|
|
std::list<std::string>* instancesId,
|
|
const DatabaseDicomTagConstraints& lookup,
|
|
ResourceType queryLevel,
|
|
const std::set<std::string>& labels,
|
|
LabelsConstraint labelsConstraint,
|
|
uint32_t limit) ORTHANC_OVERRIDE
|
|
{
|
|
if (!labels.empty())
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // "HasLabelsSupport()" has returned "false"
|
|
}
|
|
|
|
if (that_.extensions_.lookupResources == NULL)
|
|
{
|
|
// Fallback to compatibility mode
|
|
ILookupResources::Apply
|
|
(*this, *this, resourcesId, instancesId, lookup, queryLevel, limit);
|
|
}
|
|
else
|
|
{
|
|
std::vector<OrthancPluginDatabaseConstraint> constraints;
|
|
std::vector< std::vector<const char*> > constraintsValues;
|
|
|
|
constraints.resize(lookup.GetSize());
|
|
constraintsValues.resize(lookup.GetSize());
|
|
|
|
for (size_t i = 0; i < lookup.GetSize(); i++)
|
|
{
|
|
lookup.GetConstraint(i).EncodeForPlugins(constraints[i], constraintsValues[i]);
|
|
}
|
|
|
|
ResetAnswers();
|
|
answerMatchingResources_ = &resourcesId;
|
|
answerMatchingInstances_ = instancesId;
|
|
|
|
CheckSuccess(that_.extensions_.lookupResources(that_.GetContext(), that_.payload_, lookup.GetSize(),
|
|
(lookup.IsEmpty() ? NULL : &constraints[0]),
|
|
Plugins::Convert(queryLevel),
|
|
limit, (instancesId == NULL ? 0 : 1)));
|
|
}
|
|
}
|
|
|
|
|
|
virtual bool CreateInstance(IDatabaseWrapper::CreateInstanceResult& result,
|
|
int64_t& instanceId,
|
|
const std::string& patient,
|
|
const std::string& study,
|
|
const std::string& series,
|
|
const std::string& instance) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.createInstance == NULL)
|
|
{
|
|
// Fallback to compatibility mode
|
|
return ICreateInstance::Apply
|
|
(*this, result, instanceId, patient, study, series, instance);
|
|
}
|
|
else
|
|
{
|
|
OrthancPluginCreateInstanceResult output;
|
|
memset(&output, 0, sizeof(output));
|
|
|
|
CheckSuccess(that_.extensions_.createInstance(&output, that_.payload_, patient.c_str(),
|
|
study.c_str(), series.c_str(), instance.c_str()));
|
|
|
|
instanceId = output.instanceId;
|
|
|
|
if (output.isNewInstance)
|
|
{
|
|
result.isNewPatient_ = output.isNewPatient;
|
|
result.isNewStudy_ = output.isNewStudy;
|
|
result.isNewSeries_ = output.isNewSeries;
|
|
result.patientId_ = output.patientId;
|
|
result.studyId_ = output.studyId;
|
|
result.seriesId_ = output.seriesId;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
virtual void AddAttachment(int64_t id,
|
|
const FileInfo& attachment,
|
|
int64_t revision) ORTHANC_OVERRIDE
|
|
{
|
|
// "revision" is not used, as it was added in Orthanc 1.9.2
|
|
OrthancPluginAttachment tmp;
|
|
tmp.uuid = attachment.GetUuid().c_str();
|
|
tmp.contentType = static_cast<int32_t>(attachment.GetContentType());
|
|
tmp.uncompressedSize = attachment.GetUncompressedSize();
|
|
tmp.uncompressedHash = attachment.GetUncompressedMD5().c_str();
|
|
tmp.compressionType = static_cast<int32_t>(attachment.GetCompressionType());
|
|
tmp.compressedSize = attachment.GetCompressedSize();
|
|
tmp.compressedHash = attachment.GetCompressedMD5().c_str();
|
|
|
|
CheckSuccess(that_.backend_.addAttachment(that_.payload_, id, &tmp));
|
|
}
|
|
|
|
|
|
// From the "ICreateInstance" interface
|
|
virtual void AttachChild(int64_t parent,
|
|
int64_t child) ORTHANC_OVERRIDE
|
|
{
|
|
CheckSuccess(that_.backend_.attachChild(that_.payload_, parent, child));
|
|
}
|
|
|
|
|
|
virtual void ClearChanges() ORTHANC_OVERRIDE
|
|
{
|
|
CheckSuccess(that_.backend_.clearChanges(that_.payload_));
|
|
}
|
|
|
|
|
|
virtual void ClearExportedResources() ORTHANC_OVERRIDE
|
|
{
|
|
CheckSuccess(that_.backend_.clearExportedResources(that_.payload_));
|
|
}
|
|
|
|
|
|
virtual void ClearMainDicomTags(int64_t id) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.clearMainDicomTags == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin,
|
|
"Your custom index plugin does not implement the mandatory ClearMainDicomTags() extension");
|
|
}
|
|
|
|
CheckSuccess(that_.extensions_.clearMainDicomTags(that_.payload_, id));
|
|
}
|
|
|
|
|
|
// From the "ICreateInstance" interface
|
|
virtual int64_t CreateResource(const std::string& publicId,
|
|
ResourceType type) ORTHANC_OVERRIDE
|
|
{
|
|
int64_t id;
|
|
CheckSuccess(that_.backend_.createResource(&id, that_.payload_, publicId.c_str(), Plugins::Convert(type)));
|
|
return id;
|
|
}
|
|
|
|
|
|
virtual void DeleteAttachment(int64_t id,
|
|
FileContentType attachment) ORTHANC_OVERRIDE
|
|
{
|
|
CheckSuccess(that_.backend_.deleteAttachment(that_.payload_, id, static_cast<int32_t>(attachment)));
|
|
}
|
|
|
|
|
|
virtual void DeleteMetadata(int64_t id,
|
|
MetadataType type) ORTHANC_OVERRIDE
|
|
{
|
|
CheckSuccess(that_.backend_.deleteMetadata(that_.payload_, id, static_cast<int32_t>(type)));
|
|
}
|
|
|
|
|
|
virtual void DeleteResource(int64_t id) ORTHANC_OVERRIDE
|
|
{
|
|
CheckSuccess(that_.backend_.deleteResource(that_.payload_, id));
|
|
}
|
|
|
|
|
|
// From the "ILookupResources" interface
|
|
void GetAllInternalIds(std::list<int64_t>& target,
|
|
ResourceType resourceType) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.getAllInternalIds == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin,
|
|
"The database plugin does not implement the mandatory GetAllInternalIds() extension");
|
|
}
|
|
|
|
ResetAnswers();
|
|
CheckSuccess(that_.extensions_.getAllInternalIds(that_.GetContext(), that_.payload_, Plugins::Convert(resourceType)));
|
|
ForwardAnswers(target);
|
|
}
|
|
|
|
|
|
|
|
virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
|
|
int64_t id) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.getAllMetadata == NULL)
|
|
{
|
|
// Fallback implementation if extension is missing
|
|
target.clear();
|
|
|
|
ResetAnswers();
|
|
CheckSuccess(that_.backend_.listAvailableMetadata(that_.GetContext(), that_.payload_, id));
|
|
|
|
if (type_ != _OrthancPluginDatabaseAnswerType_None &&
|
|
type_ != _OrthancPluginDatabaseAnswerType_Int32)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
target.clear();
|
|
|
|
if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
|
|
{
|
|
for (std::list<int32_t>::const_iterator
|
|
it = answerInt32_.begin(); it != answerInt32_.end(); ++it)
|
|
{
|
|
MetadataType type = static_cast<MetadataType>(*it);
|
|
|
|
std::string value;
|
|
int64_t revision; // Ignored
|
|
if (LookupMetadata(value, revision, id, type))
|
|
{
|
|
target[type] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ResetAnswers();
|
|
|
|
answerMetadata_ = ⌖
|
|
target.clear();
|
|
|
|
CheckSuccess(that_.extensions_.getAllMetadata(that_.GetContext(), that_.payload_, id));
|
|
|
|
if (type_ != _OrthancPluginDatabaseAnswerType_None &&
|
|
type_ != _OrthancPluginDatabaseAnswerType_Metadata)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
virtual void GetAllPublicIds(std::list<std::string>& target,
|
|
ResourceType resourceType) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
CheckSuccess(that_.backend_.getAllPublicIds(that_.GetContext(), that_.payload_, Plugins::Convert(resourceType)));
|
|
ForwardAnswers(target);
|
|
}
|
|
|
|
|
|
virtual void GetAllPublicIdsCompatibility(std::list<std::string>& target,
|
|
ResourceType resourceType,
|
|
int64_t since,
|
|
uint32_t limit) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.getAllPublicIdsWithLimit != NULL)
|
|
{
|
|
// This extension is available since Orthanc 0.9.4
|
|
ResetAnswers();
|
|
CheckSuccess(that_.extensions_.getAllPublicIdsWithLimit
|
|
(that_.GetContext(), that_.payload_, Plugins::Convert(resourceType), since, limit));
|
|
ForwardAnswers(target);
|
|
}
|
|
else
|
|
{
|
|
// The extension is not available in the database plugin, use a
|
|
// fallback implementation
|
|
target.clear();
|
|
|
|
if (limit == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::list<std::string> tmp;
|
|
GetAllPublicIds(tmp, resourceType);
|
|
|
|
if (tmp.size() <= static_cast<size_t>(since))
|
|
{
|
|
// Not enough results => empty answer
|
|
return;
|
|
}
|
|
|
|
std::list<std::string>::iterator current = tmp.begin();
|
|
std::advance(current, since);
|
|
|
|
while (limit > 0 && current != tmp.end())
|
|
{
|
|
target.push_back(*current);
|
|
--limit;
|
|
++current;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
|
|
bool& done /*out*/,
|
|
int64_t since,
|
|
uint32_t limit) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
answerChanges_ = ⌖
|
|
answerDone_ = &done;
|
|
done = false;
|
|
|
|
CheckSuccess(that_.backend_.getChanges(that_.GetContext(), that_.payload_, since, limit));
|
|
}
|
|
|
|
|
|
virtual void GetChildrenInternalId(std::list<int64_t>& target,
|
|
int64_t id) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
CheckSuccess(that_.backend_.getChildrenInternalId(that_.GetContext(), that_.payload_, id));
|
|
ForwardAnswers(target);
|
|
}
|
|
|
|
|
|
virtual void GetChildrenMetadata(std::list<std::string>& target,
|
|
int64_t resourceId,
|
|
MetadataType metadata) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.getChildrenMetadata == NULL)
|
|
{
|
|
IGetChildrenMetadata::Apply(*this, target, resourceId, metadata);
|
|
}
|
|
else
|
|
{
|
|
ResetAnswers();
|
|
CheckSuccess(that_.extensions_.getChildrenMetadata
|
|
(that_.GetContext(), that_.payload_, resourceId, static_cast<int32_t>(metadata)));
|
|
ForwardAnswers(target);
|
|
}
|
|
}
|
|
|
|
|
|
virtual void GetChildrenPublicId(std::list<std::string>& target,
|
|
int64_t id) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
CheckSuccess(that_.backend_.getChildrenPublicId(that_.GetContext(), that_.payload_, id));
|
|
ForwardAnswers(target);
|
|
}
|
|
|
|
|
|
virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
|
|
bool& done /*out*/,
|
|
int64_t since,
|
|
uint32_t limit) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
answerExportedResources_ = ⌖
|
|
answerDone_ = &done;
|
|
done = false;
|
|
|
|
CheckSuccess(that_.backend_.getExportedResources(that_.GetContext(), that_.payload_, since, limit));
|
|
}
|
|
|
|
|
|
virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) ORTHANC_OVERRIDE
|
|
{
|
|
answerDoneIgnored_ = false;
|
|
|
|
ResetAnswers();
|
|
answerChanges_ = ⌖
|
|
answerDone_ = &answerDoneIgnored_;
|
|
|
|
CheckSuccess(that_.backend_.getLastChange(that_.GetContext(), that_.payload_));
|
|
}
|
|
|
|
|
|
int64_t GetLastChangeIndex() ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.getLastChangeIndex == NULL)
|
|
{
|
|
// This was the default behavior in Orthanc <= 1.5.1
|
|
// https://groups.google.com/d/msg/orthanc-users/QhzB6vxYeZ0/YxabgqpfBAAJ
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
int64_t result = 0;
|
|
CheckSuccess(that_.extensions_.getLastChangeIndex(&result, that_.payload_));
|
|
return result;
|
|
}
|
|
}
|
|
|
|
|
|
virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) ORTHANC_OVERRIDE
|
|
{
|
|
answerDoneIgnored_ = false;
|
|
|
|
ResetAnswers();
|
|
answerExportedResources_ = ⌖
|
|
answerDone_ = &answerDoneIgnored_;
|
|
|
|
CheckSuccess(that_.backend_.getLastExportedResource(that_.GetContext(), that_.payload_));
|
|
}
|
|
|
|
|
|
virtual void GetMainDicomTags(DicomMap& map,
|
|
int64_t id) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
answerDicomMap_ = ↦
|
|
|
|
CheckSuccess(that_.backend_.getMainDicomTags(that_.GetContext(), that_.payload_, id));
|
|
}
|
|
|
|
|
|
virtual std::string GetPublicId(int64_t resourceId) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
std::string s;
|
|
|
|
CheckSuccess(that_.backend_.getPublicId(that_.GetContext(), that_.payload_, resourceId));
|
|
|
|
if (!ForwardSingleAnswer(s))
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
virtual uint64_t GetResourcesCount(ResourceType resourceType) ORTHANC_OVERRIDE
|
|
{
|
|
uint64_t count;
|
|
CheckSuccess(that_.backend_.getResourceCount(&count, that_.payload_, Plugins::Convert(resourceType)));
|
|
return count;
|
|
}
|
|
|
|
|
|
virtual ResourceType GetResourceType(int64_t resourceId) ORTHANC_OVERRIDE
|
|
{
|
|
OrthancPluginResourceType type;
|
|
CheckSuccess(that_.backend_.getResourceType(&type, that_.payload_, resourceId));
|
|
return Plugins::Convert(type);
|
|
}
|
|
|
|
|
|
virtual uint64_t GetTotalCompressedSize() ORTHANC_OVERRIDE
|
|
{
|
|
uint64_t size;
|
|
CheckSuccess(that_.backend_.getTotalCompressedSize(&size, that_.payload_));
|
|
return size;
|
|
}
|
|
|
|
|
|
virtual uint64_t GetTotalUncompressedSize() ORTHANC_OVERRIDE
|
|
{
|
|
uint64_t size;
|
|
CheckSuccess(that_.backend_.getTotalUncompressedSize(&size, that_.payload_));
|
|
return size;
|
|
}
|
|
|
|
|
|
virtual bool IsDiskSizeAbove(uint64_t threshold) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.fastGetTotalSize_)
|
|
{
|
|
return GetTotalCompressedSize() > threshold;
|
|
}
|
|
else
|
|
{
|
|
assert(GetTotalCompressedSize() == that_.currentDiskSize_);
|
|
return that_.currentDiskSize_ > threshold;
|
|
}
|
|
}
|
|
|
|
|
|
virtual bool IsProtectedPatient(int64_t internalId) ORTHANC_OVERRIDE
|
|
{
|
|
int32_t isProtected;
|
|
CheckSuccess(that_.backend_.isProtectedPatient(&isProtected, that_.payload_, internalId));
|
|
return (isProtected != 0);
|
|
}
|
|
|
|
|
|
virtual void ListAvailableAttachments(std::set<FileContentType>& target,
|
|
int64_t id) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
|
|
CheckSuccess(that_.backend_.listAvailableAttachments(that_.GetContext(), that_.payload_, id));
|
|
|
|
if (type_ != _OrthancPluginDatabaseAnswerType_None &&
|
|
type_ != _OrthancPluginDatabaseAnswerType_Int32)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
target.clear();
|
|
|
|
if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
|
|
{
|
|
for (std::list<int32_t>::const_iterator
|
|
it = answerInt32_.begin(); it != answerInt32_.end(); ++it)
|
|
{
|
|
target.insert(static_cast<FileContentType>(*it));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
virtual void LogChange(ChangeType changeType,
|
|
ResourceType resourceType,
|
|
int64_t internalId,
|
|
const std::string& publicId,
|
|
const std::string& date) ORTHANC_OVERRIDE
|
|
{
|
|
OrthancPluginChange tmp;
|
|
tmp.seq = -1; // Unused (it is attributed by the database engine)
|
|
tmp.changeType = static_cast<int32_t>(changeType);
|
|
tmp.resourceType = Plugins::Convert(resourceType);
|
|
tmp.publicId = publicId.c_str();
|
|
tmp.date = date.c_str();
|
|
|
|
CheckSuccess(that_.backend_.logChange(that_.payload_, &tmp));
|
|
}
|
|
|
|
|
|
virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE
|
|
{
|
|
OrthancPluginExportedResource tmp;
|
|
tmp.seq = resource.GetSeq();
|
|
tmp.resourceType = Plugins::Convert(resource.GetResourceType());
|
|
tmp.publicId = resource.GetPublicId().c_str();
|
|
tmp.modality = resource.GetModality().c_str();
|
|
tmp.date = resource.GetDate().c_str();
|
|
tmp.patientId = resource.GetPatientId().c_str();
|
|
tmp.studyInstanceUid = resource.GetStudyInstanceUid().c_str();
|
|
tmp.seriesInstanceUid = resource.GetSeriesInstanceUid().c_str();
|
|
tmp.sopInstanceUid = resource.GetSopInstanceUid().c_str();
|
|
|
|
CheckSuccess(that_.backend_.logExportedResource(that_.payload_, &tmp));
|
|
}
|
|
|
|
|
|
virtual bool LookupAttachment(FileInfo& attachment,
|
|
int64_t& revision,
|
|
int64_t id,
|
|
FileContentType contentType) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
|
|
CheckSuccess(that_.backend_.lookupAttachment
|
|
(that_.GetContext(), that_.payload_, id, static_cast<int32_t>(contentType)));
|
|
|
|
revision = 0; // Dummy value, as revisions were added in Orthanc 1.9.2
|
|
|
|
if (type_ == _OrthancPluginDatabaseAnswerType_None)
|
|
{
|
|
return false;
|
|
}
|
|
else if (type_ == _OrthancPluginDatabaseAnswerType_Attachment &&
|
|
answerAttachments_.size() == 1)
|
|
{
|
|
attachment = answerAttachments_.front();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
}
|
|
|
|
|
|
virtual bool LookupGlobalProperty(std::string& target,
|
|
GlobalProperty property,
|
|
bool shared) ORTHANC_OVERRIDE
|
|
{
|
|
// "shared" is unused, as database plugins using Orthanc SDK <=
|
|
// 1.9.1 are not compatible with multiple readers/writers
|
|
|
|
ResetAnswers();
|
|
|
|
CheckSuccess(that_.backend_.lookupGlobalProperty
|
|
(that_.GetContext(), that_.payload_, static_cast<int32_t>(property)));
|
|
|
|
return ForwardSingleAnswer(target);
|
|
}
|
|
|
|
|
|
// From the "ILookupResources" interface
|
|
virtual void LookupIdentifierRange(std::list<int64_t>& result,
|
|
ResourceType level,
|
|
const DicomTag& tag,
|
|
const std::string& start,
|
|
const std::string& end) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.lookupIdentifierRange == NULL)
|
|
{
|
|
// Default implementation, for plugins using Orthanc SDK <= 1.3.2
|
|
|
|
LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_GreaterOrEqual, start);
|
|
|
|
std::list<int64_t> b;
|
|
LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_SmallerOrEqual, end);
|
|
|
|
result.splice(result.end(), b);
|
|
}
|
|
else
|
|
{
|
|
ResetAnswers();
|
|
CheckSuccess(that_.extensions_.lookupIdentifierRange(that_.GetContext(), that_.payload_, Plugins::Convert(level),
|
|
tag.GetGroup(), tag.GetElement(),
|
|
start.c_str(), end.c_str()));
|
|
ForwardAnswers(result);
|
|
}
|
|
}
|
|
|
|
|
|
virtual bool LookupMetadata(std::string& target,
|
|
int64_t& revision,
|
|
int64_t id,
|
|
MetadataType type) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
CheckSuccess(that_.backend_.lookupMetadata(that_.GetContext(), that_.payload_, id, static_cast<int32_t>(type)));
|
|
revision = 0; // Dummy value, as revisions were added in Orthanc 1.9.2
|
|
return ForwardSingleAnswer(target);
|
|
}
|
|
|
|
|
|
virtual bool LookupParent(int64_t& parentId,
|
|
int64_t resourceId) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
CheckSuccess(that_.backend_.lookupParent(that_.GetContext(), that_.payload_, resourceId));
|
|
return ForwardSingleAnswer(parentId);
|
|
}
|
|
|
|
|
|
virtual bool LookupResource(int64_t& id,
|
|
ResourceType& type,
|
|
const std::string& publicId) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
|
|
CheckSuccess(that_.backend_.lookupResource(that_.GetContext(), that_.payload_, publicId.c_str()));
|
|
|
|
if (type_ == _OrthancPluginDatabaseAnswerType_None)
|
|
{
|
|
return false;
|
|
}
|
|
else if (type_ == _OrthancPluginDatabaseAnswerType_Resource &&
|
|
answerResources_.size() == 1)
|
|
{
|
|
id = answerResources_.front().first;
|
|
type = answerResources_.front().second;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
}
|
|
|
|
|
|
virtual bool LookupResourceAndParent(int64_t& id,
|
|
ResourceType& type,
|
|
std::string& parentPublicId,
|
|
const std::string& publicId) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.lookupResourceAndParent == NULL)
|
|
{
|
|
return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId);
|
|
}
|
|
else
|
|
{
|
|
std::list<std::string> parent;
|
|
|
|
uint8_t isExisting;
|
|
OrthancPluginResourceType pluginType = OrthancPluginResourceType_Patient;
|
|
|
|
ResetAnswers();
|
|
CheckSuccess(that_.extensions_.lookupResourceAndParent
|
|
(that_.GetContext(), &isExisting, &id, &pluginType, that_.payload_, publicId.c_str()));
|
|
ForwardAnswers(parent);
|
|
|
|
if (isExisting)
|
|
{
|
|
type = Plugins::Convert(pluginType);
|
|
|
|
if (parent.empty())
|
|
{
|
|
if (type != ResourceType_Patient)
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
}
|
|
else if (parent.size() == 1)
|
|
{
|
|
if ((type != ResourceType_Study &&
|
|
type != ResourceType_Series &&
|
|
type != ResourceType_Instance) ||
|
|
parent.front().empty())
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
parentPublicId = parent.front();
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_DatabasePlugin);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
virtual bool SelectPatientToRecycle(int64_t& internalId) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
CheckSuccess(that_.backend_.selectPatientToRecycle(that_.GetContext(), that_.payload_));
|
|
return ForwardSingleAnswer(internalId);
|
|
}
|
|
|
|
|
|
virtual bool SelectPatientToRecycle(int64_t& internalId,
|
|
int64_t patientIdToAvoid) ORTHANC_OVERRIDE
|
|
{
|
|
ResetAnswers();
|
|
CheckSuccess(that_.backend_.selectPatientToRecycle2(that_.GetContext(), that_.payload_, patientIdToAvoid));
|
|
return ForwardSingleAnswer(internalId);
|
|
}
|
|
|
|
|
|
virtual void SetGlobalProperty(GlobalProperty property,
|
|
bool shared,
|
|
const std::string& value) ORTHANC_OVERRIDE
|
|
{
|
|
// "shared" is unused, as database plugins using Orthanc SDK <=
|
|
// 1.9.1 are not compatible with multiple readers/writers
|
|
|
|
CheckSuccess(that_.backend_.setGlobalProperty
|
|
(that_.payload_, static_cast<int32_t>(property), value.c_str()));
|
|
}
|
|
|
|
|
|
// From the "ISetResourcesContent" interface
|
|
virtual void SetIdentifierTag(int64_t id,
|
|
const DicomTag& tag,
|
|
const std::string& value) ORTHANC_OVERRIDE
|
|
{
|
|
OrthancPluginDicomTag tmp;
|
|
tmp.group = tag.GetGroup();
|
|
tmp.element = tag.GetElement();
|
|
tmp.value = value.c_str();
|
|
|
|
CheckSuccess(that_.backend_.setIdentifierTag(that_.payload_, id, &tmp));
|
|
}
|
|
|
|
|
|
// From the "ISetResourcesContent" interface
|
|
virtual void SetMainDicomTag(int64_t id,
|
|
const DicomTag& tag,
|
|
const std::string& value) ORTHANC_OVERRIDE
|
|
{
|
|
OrthancPluginDicomTag tmp;
|
|
tmp.group = tag.GetGroup();
|
|
tmp.element = tag.GetElement();
|
|
tmp.value = value.c_str();
|
|
|
|
CheckSuccess(that_.backend_.setMainDicomTag(that_.payload_, id, &tmp));
|
|
}
|
|
|
|
|
|
virtual void SetMetadata(int64_t id,
|
|
MetadataType type,
|
|
const std::string& value,
|
|
int64_t revision) ORTHANC_OVERRIDE
|
|
{
|
|
// "revision" is not used, as it was added in Orthanc 1.9.2
|
|
CheckSuccess(that_.backend_.setMetadata
|
|
(that_.payload_, id, static_cast<int32_t>(type), value.c_str()));
|
|
}
|
|
|
|
|
|
virtual void SetProtectedPatient(int64_t internalId,
|
|
bool isProtected) ORTHANC_OVERRIDE
|
|
{
|
|
CheckSuccess(that_.backend_.setProtectedPatient(that_.payload_, internalId, isProtected));
|
|
}
|
|
|
|
|
|
// From the "ISetResourcesContent" interface
|
|
virtual void SetResourcesContent(const Orthanc::ResourcesContent& content) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.setResourcesContent == NULL)
|
|
{
|
|
ISetResourcesContent::Apply(*this, content);
|
|
}
|
|
else
|
|
{
|
|
std::vector<OrthancPluginResourcesContentTags> identifierTags;
|
|
std::vector<OrthancPluginResourcesContentTags> mainDicomTags;
|
|
std::vector<OrthancPluginResourcesContentMetadata> metadata;
|
|
|
|
identifierTags.reserve(content.GetListTags().size());
|
|
mainDicomTags.reserve(content.GetListTags().size());
|
|
metadata.reserve(content.GetListMetadata().size());
|
|
|
|
for (ResourcesContent::ListTags::const_iterator
|
|
it = content.GetListTags().begin(); it != content.GetListTags().end(); ++it)
|
|
{
|
|
OrthancPluginResourcesContentTags tmp;
|
|
tmp.resource = it->GetResourceId();
|
|
tmp.group = it->GetTag().GetGroup();
|
|
tmp.element = it->GetTag().GetElement();
|
|
tmp.value = it->GetValue().c_str();
|
|
|
|
if (it->IsIdentifier())
|
|
{
|
|
identifierTags.push_back(tmp);
|
|
}
|
|
else
|
|
{
|
|
mainDicomTags.push_back(tmp);
|
|
}
|
|
}
|
|
|
|
for (ResourcesContent::ListMetadata::const_iterator
|
|
it = content.GetListMetadata().begin(); it != content.GetListMetadata().end(); ++it)
|
|
{
|
|
OrthancPluginResourcesContentMetadata tmp;
|
|
tmp.resource = it->GetResourceId();
|
|
tmp.metadata = it->GetType();
|
|
tmp.value = it->GetValue().c_str();
|
|
metadata.push_back(tmp);
|
|
}
|
|
|
|
assert(identifierTags.size() + mainDicomTags.size() == content.GetListTags().size() &&
|
|
metadata.size() == content.GetListMetadata().size());
|
|
|
|
CheckSuccess(that_.extensions_.setResourcesContent(
|
|
that_.payload_,
|
|
identifierTags.size(),
|
|
(identifierTags.empty() ? NULL : &identifierTags[0]),
|
|
mainDicomTags.size(),
|
|
(mainDicomTags.empty() ? NULL : &mainDicomTags[0]),
|
|
metadata.size(),
|
|
(metadata.empty() ? NULL : &metadata[0])));
|
|
}
|
|
}
|
|
|
|
|
|
// From the "ICreateInstance" interface
|
|
virtual void TagMostRecentPatient(int64_t patient) ORTHANC_OVERRIDE
|
|
{
|
|
if (that_.extensions_.tagMostRecentPatient != NULL)
|
|
{
|
|
CheckSuccess(that_.extensions_.tagMostRecentPatient(that_.payload_, patient));
|
|
}
|
|
}
|
|
|
|
|
|
virtual void AddLabel(int64_t resource,
|
|
const std::string& label) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // Not supported
|
|
}
|
|
|
|
|
|
virtual void RemoveLabel(int64_t resource,
|
|
const std::string& label) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // Not supported
|
|
}
|
|
|
|
|
|
virtual void ListLabels(std::set<std::string>& target,
|
|
int64_t resource) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // Not supported
|
|
}
|
|
|
|
|
|
virtual void ListAllLabels(std::set<std::string>& target) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // Not supported
|
|
}
|
|
|
|
virtual void StoreKeyValue(const std::string& storeId,
|
|
const std::string& key,
|
|
const void* value,
|
|
size_t valueSize) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // Not supported
|
|
}
|
|
|
|
virtual void DeleteKeyValue(const std::string& storeId,
|
|
const std::string& key) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // Not supported
|
|
}
|
|
|
|
virtual bool GetKeyValue(std::string& value,
|
|
const std::string& storeId,
|
|
const std::string& key) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // Not supported
|
|
}
|
|
|
|
virtual void ListKeysValues(std::list<std::string>& keys,
|
|
std::list<std::string>& values,
|
|
const std::string& storeId,
|
|
bool first,
|
|
const std::string& from,
|
|
uint64_t limit) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // Not supported
|
|
}
|
|
|
|
virtual void EnqueueValue(const std::string& queueId,
|
|
const void* value,
|
|
size_t valueSize) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // Not supported
|
|
}
|
|
|
|
virtual bool DequeueValue(std::string& value,
|
|
const std::string& queueId,
|
|
QueueOrigin origin) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // Not supported
|
|
}
|
|
|
|
virtual uint64_t GetQueueSize(const std::string& queueId) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError); // Not supported
|
|
}
|
|
|
|
virtual void GetAttachmentCustomData(std::string& customData,
|
|
const std::string& attachmentUuid) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_NotImplemented); // Not supported
|
|
}
|
|
|
|
virtual void SetAttachmentCustomData(const std::string& attachmentUuid,
|
|
const void* customData,
|
|
size_t customDataSize) ORTHANC_OVERRIDE
|
|
{
|
|
throw OrthancException(ErrorCode_NotImplemented); // Not supported
|
|
}
|
|
};
|
|
|
|
|
|
void OrthancPluginDatabase::CheckSuccess(OrthancPluginErrorCode code)
|
|
{
|
|
if (code != OrthancPluginErrorCode_Success)
|
|
{
|
|
errorDictionary_.LogError(code, true);
|
|
throw OrthancException(static_cast<ErrorCode>(code));
|
|
}
|
|
}
|
|
|
|
|
|
OrthancPluginDatabase::OrthancPluginDatabase(SharedLibrary& library,
|
|
PluginsErrorDictionary& errorDictionary,
|
|
const OrthancPluginDatabaseBackend& backend,
|
|
const OrthancPluginDatabaseExtensions* extensions,
|
|
size_t extensionsSize,
|
|
void *payload) :
|
|
library_(library),
|
|
errorDictionary_(errorDictionary),
|
|
backend_(backend),
|
|
payload_(payload),
|
|
activeTransaction_(NULL),
|
|
fastGetTotalSize_(false),
|
|
currentDiskSize_(0)
|
|
{
|
|
static const char* const MISSING = " Missing extension in database index plugin: ";
|
|
|
|
memset(&extensions_, 0, sizeof(extensions_));
|
|
|
|
size_t size = sizeof(extensions_);
|
|
if (extensionsSize < size)
|
|
{
|
|
size = extensionsSize; // Not all the extensions are available
|
|
}
|
|
|
|
memcpy(&extensions_, extensions, size);
|
|
|
|
bool isOptimal = true;
|
|
|
|
if (extensions_.lookupResources == NULL)
|
|
{
|
|
CLOG(INFO, PLUGINS) << MISSING << "LookupIdentifierRange()";
|
|
isOptimal = false;
|
|
}
|
|
|
|
if (extensions_.createInstance == NULL)
|
|
{
|
|
CLOG(INFO, PLUGINS) << MISSING << "CreateInstance()";
|
|
isOptimal = false;
|
|
}
|
|
|
|
if (extensions_.setResourcesContent == NULL)
|
|
{
|
|
CLOG(INFO, PLUGINS) << MISSING << "SetResourcesContent()";
|
|
isOptimal = false;
|
|
}
|
|
|
|
if (extensions_.getChildrenMetadata == NULL)
|
|
{
|
|
CLOG(INFO, PLUGINS) << MISSING << "GetChildrenMetadata()";
|
|
isOptimal = false;
|
|
}
|
|
|
|
if (extensions_.getAllMetadata == NULL)
|
|
{
|
|
CLOG(INFO, PLUGINS) << MISSING << "GetAllMetadata()";
|
|
isOptimal = false;
|
|
}
|
|
|
|
if (extensions_.lookupResourceAndParent == NULL)
|
|
{
|
|
CLOG(INFO, PLUGINS) << MISSING << "LookupResourceAndParent()";
|
|
isOptimal = false;
|
|
}
|
|
|
|
if (isOptimal)
|
|
{
|
|
CLOG(INFO, PLUGINS) << "The performance of the database index plugin "
|
|
<< "is optimal for this version of Orthanc";
|
|
}
|
|
else
|
|
{
|
|
LOG(WARNING) << "Performance warning in the database index: "
|
|
<< "Some extensions are missing in the plugin";
|
|
}
|
|
|
|
if (extensions_.getLastChangeIndex == NULL)
|
|
{
|
|
LOG(WARNING) << "The database extension GetLastChangeIndex() is missing";
|
|
}
|
|
|
|
if (extensions_.tagMostRecentPatient == NULL)
|
|
{
|
|
LOG(WARNING) << "The database extension TagMostRecentPatient() is missing "
|
|
<< "(affected by issue 58)";
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPluginDatabase::Open()
|
|
{
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(mutex_);
|
|
CheckSuccess(backend_.open(payload_));
|
|
}
|
|
|
|
VoidDatabaseListener listener;
|
|
|
|
{
|
|
Transaction transaction(*this, listener);
|
|
transaction.Begin();
|
|
|
|
std::string tmp;
|
|
fastGetTotalSize_ =
|
|
(transaction.LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast, true /* unused in old databases */) &&
|
|
tmp == "1");
|
|
|
|
if (fastGetTotalSize_)
|
|
{
|
|
currentDiskSize_ = 0; // Unused
|
|
}
|
|
else
|
|
{
|
|
// This is the case of database plugins using Orthanc SDK <= 1.5.2
|
|
LOG(WARNING) << "Your database index plugin is not compatible with multiple Orthanc writers";
|
|
currentDiskSize_ = transaction.GetTotalCompressedSize();
|
|
}
|
|
|
|
transaction.Commit(0);
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPluginDatabase::Close()
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(mutex_);
|
|
CheckSuccess(backend_.close(payload_));
|
|
}
|
|
|
|
|
|
IDatabaseWrapper::ITransaction* OrthancPluginDatabase::StartTransaction(TransactionType type,
|
|
IDatabaseListener& listener)
|
|
{
|
|
// TODO - Take advantage of "type"
|
|
|
|
std::unique_ptr<Transaction> transaction(new Transaction(*this, listener));
|
|
transaction->Begin();
|
|
return transaction.release();
|
|
}
|
|
|
|
|
|
unsigned int OrthancPluginDatabase::GetDatabaseVersion()
|
|
{
|
|
if (extensions_.getDatabaseVersion != NULL)
|
|
{
|
|
uint32_t version;
|
|
CheckSuccess(extensions_.getDatabaseVersion(&version, payload_));
|
|
return version;
|
|
}
|
|
else
|
|
{
|
|
// Before adding the "GetDatabaseVersion()" extension in plugins
|
|
// (OrthancPostgreSQL <= 1.2), the only supported DB schema was
|
|
// version 5.
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPluginDatabase::Upgrade(unsigned int targetVersion,
|
|
IPluginStorageArea& storageArea)
|
|
{
|
|
VoidDatabaseListener listener;
|
|
|
|
if (extensions_.upgradeDatabase != NULL)
|
|
{
|
|
Transaction transaction(*this, listener);
|
|
transaction.Begin();
|
|
|
|
OrthancPluginErrorCode code = extensions_.upgradeDatabase(
|
|
payload_, targetVersion,
|
|
reinterpret_cast<OrthancPluginStorageArea*>(&storageArea));
|
|
|
|
if (code == OrthancPluginErrorCode_Success)
|
|
{
|
|
transaction.Commit(0);
|
|
}
|
|
else
|
|
{
|
|
transaction.Rollback();
|
|
errorDictionary_.LogError(code, true);
|
|
throw OrthancException(static_cast<ErrorCode>(code));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void OrthancPluginDatabase::AnswerReceived(const _OrthancPluginDatabaseAnswer& answer)
|
|
{
|
|
boost::recursive_mutex::scoped_lock lock(mutex_);
|
|
|
|
if (activeTransaction_ != NULL)
|
|
{
|
|
activeTransaction_->AnswerReceived(answer);
|
|
}
|
|
else
|
|
{
|
|
LOG(WARNING) << "Received an answer from the database index plugin, but not transaction is active";
|
|
}
|
|
}
|
|
|
|
uint64_t OrthancPluginDatabase::MeasureLatency()
|
|
{
|
|
throw OrthancException(ErrorCode_NotImplemented);
|
|
}
|
|
}
|