Mercurial > hg > orthanc
changeset 4587:888868a5dc4e db-changes
ServerIndex now uses StatelessDatabaseOperations
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Mar 2021 15:59:03 +0100 |
parents | 1d96fe7e054e |
children | 94147ce2f097 |
files | OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.h OrthancServer/Sources/ServerIndex.cpp OrthancServer/Sources/ServerIndex.h |
diffstat | 4 files changed, 144 insertions(+), 3586 deletions(-) [+] |
line wrap: on
line diff
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Tue Mar 09 18:24:59 2021 +0100 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Wed Mar 10 15:59:03 2021 +0100 @@ -668,8 +668,24 @@ StatelessDatabaseOperations::StatelessDatabaseOperations(IDatabaseWrapper& db) : db_(db), maxRetries_(10), - mainDicomTagsRegistry_(new MainDicomTagsRegistry) + mainDicomTagsRegistry_(new MainDicomTagsRegistry), + hasFlushToDisk_(db.HasFlushToDisk()) + { + } + + + void StatelessDatabaseOperations::FlushToDisk() { + boost::mutex::scoped_lock lock(databaseMutex_); + + try + { + db_.FlushToDisk(); + } + catch (OrthancException&) + { + LOG(ERROR) << "Cannot flush the SQLite database to the disk (is your filesystem full?)"; + } } @@ -2415,21 +2431,25 @@ } - void StatelessDatabaseOperations::LogChange(ChangeType changeType, + void StatelessDatabaseOperations::LogChange(int64_t internalId, + ChangeType changeType, const std::string& publicId, ResourceType level) { class Operations : public IReadWriteOperations { private: + int64_t internalId_; ChangeType changeType_; const std::string& publicId_; ResourceType level_; public: - Operations(ChangeType changeType, + Operations(int64_t internalId, + ChangeType changeType, const std::string& publicId, ResourceType level) : + internalId_(internalId), changeType_(changeType), publicId_(publicId), level_(level) @@ -2440,13 +2460,18 @@ { int64_t id; ResourceType type; - if (transaction.LookupResource(id, type, publicId_)) + if (transaction.LookupResource(id, type, publicId_) && + id == internalId_) { - // Make sure that the resource is still existing. Ignore if - // the resource has been deleted, because this function - // might e.g. be called from - // "StatelessDatabaseOperations::UnstableResourcesMonitorThread()" (for - // which a deleted resource not an error case) + /** + * Make sure that the resource is still existing, with the + * same internal ID, which indicates the absence of bouncing + * (if deleting then recreating the same resource). Don't + * throw an exception if the resource has been deleted, + * because this function might e.g. be called from + * "StatelessDatabaseOperations::UnstableResourcesMonitorThread()" + * (for which a deleted resource is *not* an error case). + **/ if (type == level_) { transaction.LogChange(id, changeType_, type, publicId_); @@ -2460,7 +2485,7 @@ } }; - Operations operations(changeType, publicId, level); + Operations operations(internalId, changeType, publicId, level); Apply(operations); }
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Tue Mar 09 18:24:59 2021 +0100 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Wed Mar 10 15:59:03 2021 +0100 @@ -404,11 +404,12 @@ class MainDicomTagsRegistry; class Transaction; - IDatabaseWrapper& db_; - boost::mutex databaseMutex_; // TODO - REMOVE + IDatabaseWrapper& db_; + boost::mutex databaseMutex_; // TODO - REMOVE std::unique_ptr<ITransactionContextFactory> factory_; - unsigned int maxRetries_; - std::unique_ptr<MainDicomTagsRegistry> mainDicomTagsRegistry_; + unsigned int maxRetries_; + std::unique_ptr<MainDicomTagsRegistry> mainDicomTagsRegistry_; + bool hasFlushToDisk_; void NormalizeLookup(std::vector<DatabaseConstraint>& target, const DatabaseLookup& source, @@ -433,6 +434,13 @@ return db_.GetDatabaseVersion(); } + void FlushToDisk(); + + bool HasFlushToDisk() const + { + return hasFlushToDisk_; + } + void Apply(IReadOnlyOperations& operations); void Apply(IReadWriteOperations& operations); @@ -568,7 +576,8 @@ void DeleteAttachment(const std::string& publicId, FileContentType type); - void LogChange(ChangeType changeType, + void LogChange(int64_t internalId, + ChangeType changeType, const std::string& publicId, ResourceType level);
--- a/OrthancServer/Sources/ServerIndex.cpp Tue Mar 09 18:24:59 2021 +0100 +++ b/OrthancServer/Sources/ServerIndex.cpp Wed Mar 10 15:59:03 2021 +0100 @@ -38,45 +38,19 @@ #define NOMINMAX #endif -#include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h" -#include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" -#include "../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h" #include "../../OrthancFramework/Sources/Logging.h" #include "../../OrthancFramework/Sources/Toolbox.h" -#include "Database/ResourcesContent.h" #include "OrthancConfiguration.h" -#include "Search/DatabaseLookup.h" -#include "Search/DicomTagConstraint.h" #include "ServerContext.h" #include "ServerIndexChange.h" #include "ServerToolbox.h" -#include <boost/lexical_cast.hpp> -#include <boost/tuple/tuple.hpp> -#include <stdio.h> -#include <stack> static const uint64_t MEGA_BYTES = 1024 * 1024; namespace Orthanc { - static void CopyListToVector(std::vector<std::string>& target, - const std::list<std::string>& source) - { - target.resize(source.size()); - - size_t pos = 0; - - for (std::list<std::string>::const_iterator - it = source.begin(); it != source.end(); ++it) - { - target[pos] = *it; - pos ++; - } - } - - class ServerIndex::Listener : public IDatabaseListener, public ServerIndex::ITransactionContext { @@ -127,8 +101,7 @@ public: explicit Listener(ServerContext& context) : context_(context), - insideTransaction_(false), - sizeOfAddedAttachments_(0) + insideTransaction_(false) { Reset(); assert(ResourceType_Patient < ResourceType_Study && @@ -283,6 +256,23 @@ { return context_.GetIndex().IsUnstableResource(id); } + + virtual void Commit() ORTHANC_OVERRIDE + { + // We can remove the files once the SQLite transaction has + // been successfully committed. Some files might have to be + // deleted because of recycling. + CommitFilesToRemove(); + + // Send all the pending changes to the Orthanc plugins + CommitChanges(); + } + + virtual int64_t GetCompressedSizeDelta() ORTHANC_OVERRIDE + { + return (static_cast<int64_t>(GetSizeOfAddedAttachments()) - + static_cast<int64_t>(GetSizeOfFilesToRemove())); + } }; @@ -298,6 +288,12 @@ Context(ServerIndex& index) : listener_(*index.listener_) { + listener_.StartTransaction(); + } + + ~Context() + { + listener_.EndTransaction(); } virtual bool IsUnstableResource(int64_t id) ORTHANC_OVERRIDE @@ -327,6 +323,16 @@ { listener_.SignalChange(change); } + + virtual void Commit() ORTHANC_OVERRIDE + { + listener_.Commit(); + } + + virtual int64_t GetCompressedSizeDelta() ORTHANC_OVERRIDE + { + return listener_.GetCompressedSizeDelta(); + } }; ServerIndex& index_; @@ -344,56 +350,6 @@ }; - class ServerIndex::Transaction : public boost::noncopyable - { - private: - ServerIndex& index_; - std::unique_ptr<IDatabaseWrapper::ITransaction> transaction_; - bool isCommitted_; - - public: - explicit Transaction(ServerIndex& index, - TransactionType type) : - index_(index), - isCommitted_(false) - { - transaction_.reset(index_.db_.StartTransaction(type)); - index_.listener_->StartTransaction(); - } - - ~Transaction() - { - index_.listener_->EndTransaction(); - - if (!isCommitted_) - { - transaction_->Rollback(); - } - } - - void Commit() - { - if (!isCommitted_) - { - int64_t delta = (static_cast<int64_t>(index_.listener_->GetSizeOfAddedAttachments()) - - static_cast<int64_t>(index_.listener_->GetSizeOfFilesToRemove())); - - transaction_->Commit(delta); - - // We can remove the files once the SQLite transaction has - // been successfully committed. Some files might have to be - // deleted because of recycling. - index_.listener_->CommitFilesToRemove(); - - // Send all the pending changes to the Orthanc plugins - index_.listener_->CommitChanges(); - - isCommitted_ = true; - } - } - }; - - class ServerIndex::UnstableResourcePayload { private: @@ -562,19 +518,7 @@ if (count >= countThreshold) { Logging::Flush(); - - { - boost::mutex::scoped_lock lock(that->databaseMutex_); - - try - { - that->db_.FlushToDisk(); - } - catch (OrthancException&) - { - LOG(ERROR) << "Cannot flush the SQLite database to the disk (is your filesystem full?)"; - } - } + that->FlushToDisk(); count = 0; } @@ -584,22 +528,6 @@ } - void ServerIndex::ReadWriteTransaction::LogChange(int64_t internalId, - ChangeType changeType, - ResourceType resourceType, - const std::string& publicId) - { - ServerIndexChange change(changeType, resourceType, publicId); - - if (changeType <= ChangeType_INTERNAL_LastLogged) - { - db_.LogChange(internalId, change); - } - - GetTransactionContext().SignalChange(change); - } - - bool ServerIndex::IsUnstableResource(int64_t id) { boost::mutex::scoped_lock lock(monitoringMutex_); @@ -609,16 +537,14 @@ ServerIndex::ServerIndex(ServerContext& context, IDatabaseWrapper& db, - unsigned int threadSleepGranularityMilliseconds) : + unsigned int threadSleepGranularityMilliseconds) : + StatelessDatabaseOperations(db), done_(false), - db_(db), maximumStorageSize_(0), - maximumPatients_(0), - mainDicomTagsRegistry_(new MainDicomTagsRegistry), - maxRetries_(10) + maximumPatients_(0) { listener_.reset(new Listener(context)); - db_.SetListener(*listener_); + db.SetListener(*listener_); SetTransactionContextFactory(new TransactionContextFactory(*this)); @@ -626,7 +552,7 @@ // execution of Orthanc StandaloneRecycling(maximumStorageSize_, maximumPatients_); - if (db.HasFlushToDisk()) + if (HasFlushToDisk()) { flushThread_ = boost::thread(FlushThread, this, threadSleepGranularityMilliseconds); } @@ -636,7 +562,6 @@ } - ServerIndex::~ServerIndex() { if (!done_) @@ -647,15 +572,13 @@ } - void ServerIndex::Stop() { if (!done_) { done_ = true; - if (db_.HasFlushToDisk() && - flushThread_.joinable()) + if (flushThread_.joinable()) { flushThread_.join(); } @@ -668,121 +591,6 @@ } - - SeriesStatus ServerIndex::ReadOnlyTransaction::GetSeriesStatus(int64_t id, - int64_t expectedNumberOfInstances) - { - std::list<std::string> values; - db_.GetChildrenMetadata(values, id, MetadataType_Instance_IndexInSeries); - - std::set<int64_t> instances; - - for (std::list<std::string>::const_iterator - it = values.begin(); it != values.end(); ++it) - { - int64_t index; - - try - { - index = boost::lexical_cast<int64_t>(*it); - } - catch (boost::bad_lexical_cast&) - { - return SeriesStatus_Unknown; - } - - if (!(index > 0 && index <= expectedNumberOfInstances)) - { - // Out-of-range instance index - return SeriesStatus_Inconsistent; - } - - if (instances.find(index) != instances.end()) - { - // Twice the same instance index - return SeriesStatus_Inconsistent; - } - - instances.insert(index); - } - - if (static_cast<int64_t>(instances.size()) == expectedNumberOfInstances) - { - return SeriesStatus_Complete; - } - else - { - return SeriesStatus_Missing; - } - } - - - void ServerIndex::ReadOnlyTransaction::MainDicomTagsToJson(Json::Value& target, - int64_t resourceId, - ResourceType resourceType) - { - DicomMap tags; - db_.GetMainDicomTags(tags, resourceId); - - if (resourceType == ResourceType_Study) - { - DicomMap t1, t2; - tags.ExtractStudyInformation(t1); - tags.ExtractPatientInformation(t2); - - target["MainDicomTags"] = Json::objectValue; - FromDcmtkBridge::ToJson(target["MainDicomTags"], t1, true); - - target["PatientMainDicomTags"] = Json::objectValue; - FromDcmtkBridge::ToJson(target["PatientMainDicomTags"], t2, true); - } - else - { - target["MainDicomTags"] = Json::objectValue; - FromDcmtkBridge::ToJson(target["MainDicomTags"], tags, true); - } - } - - - template <typename T> - static void FormatLog(Json::Value& target, - const std::list<T>& log, - const std::string& name, - bool done, - int64_t since, - bool hasLast, - int64_t last) - { - Json::Value items = Json::arrayValue; - for (typename std::list<T>::const_iterator - it = log.begin(); it != log.end(); ++it) - { - Json::Value item; - it->Format(item); - items.append(item); - } - - target = Json::objectValue; - target[name] = items; - target["Done"] = done; - - if (!hasLast) - { - // Best-effort guess of the last index in the sequence - if (log.empty()) - { - last = since; - } - else - { - last = log.back().GetSeq(); - } - } - - target["Last"] = static_cast<int>(last); - } - - void ServerIndex::SetMaximumPatientCount(unsigned int count) { { @@ -802,6 +610,7 @@ StandaloneRecycling(maximumStorageSize_, maximumPatients_); } + void ServerIndex::SetMaximumStorageSize(uint64_t size) { { @@ -844,39 +653,52 @@ // Check for stable resources each few seconds boost::this_thread::sleep(boost::posix_time::milliseconds(threadSleepGranularityMilliseconds)); - boost::mutex::scoped_lock lock(that->monitoringMutex_); - - while (!that->unstableResources_.IsEmpty() && - that->unstableResources_.GetOldestPayload().GetAge() > static_cast<unsigned int>(stableAge)) + for (;;) { - // This DICOM resource has not received any new instance for - // some time. It can be considered as stable. - - UnstableResourcePayload payload; - int64_t id = that->unstableResources_.RemoveOldest(payload); + UnstableResourcePayload stableResource; + int64_t stableId; + + { + boost::mutex::scoped_lock lock(that->monitoringMutex_); - // Ensure that the resource is still existing before logging the change - if (that->db_.IsExistingResource(id)) - { - switch (payload.GetResourceType()) + if (!that->unstableResources_.IsEmpty() && + that->unstableResources_.GetOldestPayload().GetAge() > static_cast<unsigned int>(stableAge)) + { + // This DICOM resource has not received any new instance for + // some time. It can be considered as stable. + stableId = that->unstableResources_.RemoveOldest(stableResource); + //LOG(TRACE) << "Stable resource: " << EnumerationToString(stableResource.GetResourceType()) << " " << stableId; + } + else { - case ResourceType_Patient: - that->LogChange(ChangeType_StablePatient, payload.GetPublicId(), ResourceType_Patient); - break; - - case ResourceType_Study: - that->LogChange(ChangeType_StableStudy, payload.GetPublicId(), ResourceType_Study); - break; + // No more stable DICOM resource, leave the internal loop + break; + } + } - case ResourceType_Series: - that->LogChange(ChangeType_StableSeries, payload.GetPublicId(), ResourceType_Series); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - //LOG(INFO) << "Stable resource: " << EnumerationToString(payload.type_) << " " << id; + /** + * WARNING: Don't protect the calls to "LogChange()" using + * "monitoringMutex_", as this could lead to deadlocks in + * other threads (typically, if "Store()" is being running in + * another thread, which leads to calls to "MarkAsUnstable()", + * which leads to two lockings of "monitoringMutex_"). + **/ + switch (stableResource.GetResourceType()) + { + case ResourceType_Patient: + that->LogChange(stableId, ChangeType_StablePatient, stableResource.GetPublicId(), ResourceType_Patient); + break; + + case ResourceType_Study: + that->LogChange(stableId, ChangeType_StableStudy, stableResource.GetPublicId(), ResourceType_Study); + break; + + case ResourceType_Series: + that->LogChange(stableId, ChangeType_StableSeries, stableResource.GetPublicId(), ResourceType_Series); + break; + + default: + throw OrthancException(ErrorCode_InternalError); } } } @@ -902,2298 +724,10 @@ } - - void ServerIndex::NormalizeLookup(std::vector<DatabaseConstraint>& target, - const DatabaseLookup& source, - ResourceType queryLevel) const - { - assert(mainDicomTagsRegistry_.get() != NULL); - - target.clear(); - target.reserve(source.GetConstraintsCount()); - - for (size_t i = 0; i < source.GetConstraintsCount(); i++) - { - ResourceType level; - DicomTagType type; - - mainDicomTagsRegistry_->LookupTag(level, type, source.GetConstraint(i).GetTag()); - - if (type == DicomTagType_Identifier || - type == DicomTagType_Main) - { - // Use the fact that patient-level tags are copied at the study level - if (level == ResourceType_Patient && - queryLevel != ResourceType_Patient) - { - level = ResourceType_Study; - } - - target.push_back(source.GetConstraint(i).ConvertToDatabaseConstraint(level, type)); - } - } - } - - - - - - /*** - ** PROTOTYPING FOR DB REFACTORING BELOW - ***/ - - namespace - { - /** - * Some handy templates to reduce the verbosity in the definitions - * of the internal classes. - **/ - - template <typename Operations, - typename Tuple> - class TupleOperationsWrapper : public ServerIndex::IReadOnlyOperations - { - protected: - Operations& operations_; - const Tuple& tuple_; - - public: - TupleOperationsWrapper(Operations& operations, - const Tuple& tuple) : - operations_(operations), - tuple_(tuple) - { - } - - virtual void Apply(ServerIndex::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE - { - operations_.ApplyTuple(transaction, tuple_); - } - }; - - - template <typename T1> - class ReadOnlyOperationsT1 : public boost::noncopyable - { - public: - typedef typename boost::tuple<T1> Tuple; - - virtual ~ReadOnlyOperationsT1() - { - } - - virtual void ApplyTuple(ServerIndex::ReadOnlyTransaction& transaction, - const Tuple& tuple) = 0; - - void Apply(ServerIndex& index, - T1 t1) - { - const Tuple tuple(t1); - TupleOperationsWrapper<ReadOnlyOperationsT1, Tuple> wrapper(*this, tuple); - index.Apply(wrapper); - } - }; - - - template <typename T1, - typename T2> - class ReadOnlyOperationsT2 : public boost::noncopyable - { - public: - typedef typename boost::tuple<T1, T2> Tuple; - - virtual ~ReadOnlyOperationsT2() - { - } - - virtual void ApplyTuple(ServerIndex::ReadOnlyTransaction& transaction, - const Tuple& tuple) = 0; - - void Apply(ServerIndex& index, - T1 t1, - T2 t2) - { - const Tuple tuple(t1, t2); - TupleOperationsWrapper<ReadOnlyOperationsT2, Tuple> wrapper(*this, tuple); - index.Apply(wrapper); - } - }; - - - template <typename T1, - typename T2, - typename T3> - class ReadOnlyOperationsT3 : public boost::noncopyable - { - public: - typedef typename boost::tuple<T1, T2, T3> Tuple; - - virtual ~ReadOnlyOperationsT3() - { - } - - virtual void ApplyTuple(ServerIndex::ReadOnlyTransaction& transaction, - const Tuple& tuple) = 0; - - void Apply(ServerIndex& index, - T1 t1, - T2 t2, - T3 t3) - { - const Tuple tuple(t1, t2, t3); - TupleOperationsWrapper<ReadOnlyOperationsT3, Tuple> wrapper(*this, tuple); - index.Apply(wrapper); - } - }; - - - template <typename T1, - typename T2, - typename T3, - typename T4> - class ReadOnlyOperationsT4 : public boost::noncopyable - { - public: - typedef typename boost::tuple<T1, T2, T3, T4> Tuple; - - virtual ~ReadOnlyOperationsT4() - { - } - - virtual void ApplyTuple(ServerIndex::ReadOnlyTransaction& transaction, - const Tuple& tuple) = 0; - - void Apply(ServerIndex& index, - T1 t1, - T2 t2, - T3 t3, - T4 t4) - { - const Tuple tuple(t1, t2, t3, t4); - TupleOperationsWrapper<ReadOnlyOperationsT4, Tuple> wrapper(*this, tuple); - index.Apply(wrapper); - } - }; - - - template <typename T1, - typename T2, - typename T3, - typename T4, - typename T5> - class ReadOnlyOperationsT5 : public boost::noncopyable - { - public: - typedef typename boost::tuple<T1, T2, T3, T4, T5> Tuple; - - virtual ~ReadOnlyOperationsT5() - { - } - - virtual void ApplyTuple(ServerIndex::ReadOnlyTransaction& transaction, - const Tuple& tuple) = 0; - - void Apply(ServerIndex& index, - T1 t1, - T2 t2, - T3 t3, - T4 t4, - T5 t5) - { - const Tuple tuple(t1, t2, t3, t4, t5); - TupleOperationsWrapper<ReadOnlyOperationsT5, Tuple> wrapper(*this, tuple); - index.Apply(wrapper); - } - }; - - - template <typename T1, - typename T2, - typename T3, - typename T4, - typename T5, - typename T6> - class ReadOnlyOperationsT6 : public boost::noncopyable - { - public: - typedef typename boost::tuple<T1, T2, T3, T4, T5, T6> Tuple; - - virtual ~ReadOnlyOperationsT6() - { - } - - virtual void ApplyTuple(ServerIndex::ReadOnlyTransaction& transaction, - const Tuple& tuple) = 0; - - void Apply(ServerIndex& index, - T1 t1, - T2 t2, - T3 t3, - T4 t4, - T5 t5, - T6 t6) - { - const Tuple tuple(t1, t2, t3, t4, t5, t6); - TupleOperationsWrapper<ReadOnlyOperationsT6, Tuple> wrapper(*this, tuple); - index.Apply(wrapper); - } - }; - } - - - void ServerIndex::ApplyInternal(IReadOnlyOperations* readOperations, - IReadWriteOperations* writeOperations) - { - if ((readOperations == NULL && writeOperations == NULL) || - (readOperations != NULL && writeOperations != NULL)) - { - throw OrthancException(ErrorCode_InternalError); - } - - if (factory_.get() == NULL) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, "No transaction context was provided"); - } - - unsigned int count = 0; - - for (;;) - { - try - { - std::unique_ptr<ITransactionContext> context(factory_->Create()); - - if (context.get() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - boost::mutex::scoped_lock lock(databaseMutex_); // TODO - REMOVE - - if (readOperations != NULL) - { - /** - * IMPORTANT: In Orthanc <= 1.9.1, there was no transaction - * in this case. This was OK because of the presence of the - * global mutex protecting the database. - **/ - - Transaction transaction(*this, TransactionType_ReadOnly); // TODO - Only if not "TransactionType_Implicit" - { - ReadOnlyTransaction t(db_, *context); - readOperations->Apply(t); - } - transaction.Commit(); - } - else - { - assert(writeOperations != NULL); - - Transaction transaction(*this, TransactionType_ReadWrite); - { - ReadWriteTransaction t(db_, *context); - writeOperations->Apply(t); - } - transaction.Commit(); - } - - return; // Success - } - catch (OrthancException& e) - { - if (e.GetErrorCode() == ErrorCode_DatabaseCannotSerialize) - { - if (count == maxRetries_) - { - throw; - } - else - { - count++; - boost::this_thread::sleep(boost::posix_time::milliseconds(100 * count)); - } - } - else if (e.GetErrorCode() == ErrorCode_DatabaseUnavailable) - { - if (count == maxRetries_) - { - throw; - } - else - { - count++; - boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); - } - } - else - { - throw; - } - } - } - } - - - void ServerIndex::SetTransactionContextFactory(ITransactionContextFactory* factory) - { - if (factory == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - else if (factory_.get() != NULL) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - factory_.reset(factory); - } - } - - - void ServerIndex::Apply(IReadOnlyOperations& operations) - { - ApplyInternal(&operations, NULL); - } - - - void ServerIndex::Apply(IReadWriteOperations& operations) - { - ApplyInternal(NULL, &operations); - } - - - bool ServerIndex::ExpandResource(Json::Value& target, - const std::string& publicId, - ResourceType level) - { - class Operations : public ReadOnlyOperationsT4<bool&, Json::Value&, const std::string&, ResourceType> - { - private: - static bool LookupStringMetadata(std::string& result, - const std::map<MetadataType, std::string>& metadata, - MetadataType type) - { - std::map<MetadataType, std::string>::const_iterator found = metadata.find(type); - - if (found == metadata.end()) - { - return false; - } - else - { - result = found->second; - return true; - } - } - - - static bool LookupIntegerMetadata(int64_t& result, - const std::map<MetadataType, std::string>& metadata, - MetadataType type) - { - std::string s; - if (!LookupStringMetadata(s, metadata, type)) - { - return false; - } - - try - { - result = boost::lexical_cast<int64_t>(s); - return true; - } - catch (boost::bad_lexical_cast&) - { - return false; - } - } - - - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // Lookup for the requested resource - int64_t internalId; // unused - ResourceType type; - std::string parent; - if (!transaction.LookupResourceAndParent(internalId, type, parent, tuple.get<2>()) || - type != tuple.get<3>()) - { - tuple.get<0>() = false; - } - else - { - Json::Value& target = tuple.get<1>(); - target = Json::objectValue; - - // Set information about the parent resource (if it exists) - if (type == ResourceType_Patient) - { - if (!parent.empty()) - { - throw OrthancException(ErrorCode_DatabasePlugin); - } - } - else - { - if (parent.empty()) - { - throw OrthancException(ErrorCode_DatabasePlugin); - } - - switch (type) - { - case ResourceType_Study: - target["ParentPatient"] = parent; - break; - - case ResourceType_Series: - target["ParentStudy"] = parent; - break; - - case ResourceType_Instance: - target["ParentSeries"] = parent; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - // List the children resources - std::list<std::string> children; - transaction.GetChildrenPublicId(children, internalId); - - if (type != ResourceType_Instance) - { - Json::Value c = Json::arrayValue; - - for (std::list<std::string>::const_iterator - it = children.begin(); it != children.end(); ++it) - { - c.append(*it); - } - - switch (type) - { - case ResourceType_Patient: - target["Studies"] = c; - break; - - case ResourceType_Study: - target["Series"] = c; - break; - - case ResourceType_Series: - target["Instances"] = c; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - // Extract the metadata - std::map<MetadataType, std::string> metadata; - transaction.GetAllMetadata(metadata, internalId); - - // Set the resource type - switch (type) - { - case ResourceType_Patient: - target["Type"] = "Patient"; - break; - - case ResourceType_Study: - target["Type"] = "Study"; - break; - - case ResourceType_Series: - { - target["Type"] = "Series"; - - int64_t i; - if (LookupIntegerMetadata(i, metadata, MetadataType_Series_ExpectedNumberOfInstances)) - { - target["ExpectedNumberOfInstances"] = static_cast<int>(i); - target["Status"] = EnumerationToString(transaction.GetSeriesStatus(internalId, i)); - } - else - { - target["ExpectedNumberOfInstances"] = Json::nullValue; - target["Status"] = EnumerationToString(SeriesStatus_Unknown); - } - - break; - } - - case ResourceType_Instance: - { - target["Type"] = "Instance"; - - FileInfo attachment; - if (!transaction.LookupAttachment(attachment, internalId, FileContentType_Dicom)) - { - throw OrthancException(ErrorCode_InternalError); - } - - target["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize()); - target["FileUuid"] = attachment.GetUuid(); - - int64_t i; - if (LookupIntegerMetadata(i, metadata, MetadataType_Instance_IndexInSeries)) - { - target["IndexInSeries"] = static_cast<int>(i); - } - else - { - target["IndexInSeries"] = Json::nullValue; - } - - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - - // Record the remaining information - target["ID"] = tuple.get<2>(); - transaction.MainDicomTagsToJson(target, internalId, type); - - std::string tmp; - - if (LookupStringMetadata(tmp, metadata, MetadataType_AnonymizedFrom)) - { - target["AnonymizedFrom"] = tmp; - } - - if (LookupStringMetadata(tmp, metadata, MetadataType_ModifiedFrom)) - { - target["ModifiedFrom"] = tmp; - } - - if (type == ResourceType_Patient || - type == ResourceType_Study || - type == ResourceType_Series) - { - target["IsStable"] = !transaction.GetTransactionContext().IsUnstableResource(internalId); - - if (LookupStringMetadata(tmp, metadata, MetadataType_LastUpdate)) - { - target["LastUpdate"] = tmp; - } - } - - tuple.get<0>() = true; - } - } - }; - - bool found; - Operations operations; - operations.Apply(*this, found, target, publicId, level); - return found; - } - - - void ServerIndex::GetAllMetadata(std::map<MetadataType, std::string>& target, - const std::string& publicId, - ResourceType level) - { - class Operations : public ReadOnlyOperationsT3<std::map<MetadataType, std::string>&, const std::string&, ResourceType> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - ResourceType type; - int64_t id; - if (!transaction.LookupResource(id, type, tuple.get<1>()) || - tuple.get<2>() != type) - { - throw OrthancException(ErrorCode_UnknownResource); - } - else - { - transaction.GetAllMetadata(tuple.get<0>(), id); - } - } - }; - - Operations operations; - operations.Apply(*this, target, publicId, level); - } - - - bool ServerIndex::LookupAttachment(FileInfo& attachment, - const std::string& instancePublicId, - FileContentType contentType) - { - class Operations : public ReadOnlyOperationsT4<bool&, FileInfo&, const std::string&, FileContentType> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - int64_t internalId; - ResourceType type; - if (!transaction.LookupResource(internalId, type, tuple.get<2>())) - { - throw OrthancException(ErrorCode_UnknownResource); - } - else if (transaction.LookupAttachment(tuple.get<1>(), internalId, tuple.get<3>())) - { - assert(tuple.get<1>().GetContentType() == tuple.get<3>()); - tuple.get<0>() = true; - } - else - { - tuple.get<0>() = false; - } - } - }; - - bool found; - Operations operations; - operations.Apply(*this, found, attachment, instancePublicId, contentType); - return found; - } - - - void ServerIndex::GetAllUuids(std::list<std::string>& target, - ResourceType resourceType) - { - class Operations : public ReadOnlyOperationsT2<std::list<std::string>&, ResourceType> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // TODO - CANDIDATE FOR "TransactionType_Implicit" - transaction.GetAllPublicIds(tuple.get<0>(), tuple.get<1>()); - } - }; - - Operations operations; - operations.Apply(*this, target, resourceType); - } - - - void ServerIndex::GetAllUuids(std::list<std::string>& target, - ResourceType resourceType, - size_t since, - size_t limit) - { - if (limit == 0) - { - target.clear(); - } - else - { - class Operations : public ReadOnlyOperationsT4<std::list<std::string>&, ResourceType, size_t, size_t> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // TODO - CANDIDATE FOR "TransactionType_Implicit" - transaction.GetAllPublicIds(tuple.get<0>(), tuple.get<1>(), tuple.get<2>(), tuple.get<3>()); - } - }; - - Operations operations; - operations.Apply(*this, target, resourceType, since, limit); - } - } - - - void ServerIndex::GetGlobalStatistics(/* out */ uint64_t& diskSize, - /* out */ uint64_t& uncompressedSize, - /* out */ uint64_t& countPatients, - /* out */ uint64_t& countStudies, - /* out */ uint64_t& countSeries, - /* out */ uint64_t& countInstances) - { - class Operations : public ReadOnlyOperationsT6<uint64_t&, uint64_t&, uint64_t&, uint64_t&, uint64_t&, uint64_t&> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - tuple.get<0>() = transaction.GetTotalCompressedSize(); - tuple.get<1>() = transaction.GetTotalUncompressedSize(); - tuple.get<2>() = transaction.GetResourceCount(ResourceType_Patient); - tuple.get<3>() = transaction.GetResourceCount(ResourceType_Study); - tuple.get<4>() = transaction.GetResourceCount(ResourceType_Series); - tuple.get<5>() = transaction.GetResourceCount(ResourceType_Instance); - } - }; - - Operations operations; - operations.Apply(*this, diskSize, uncompressedSize, countPatients, - countStudies, countSeries, countInstances); - } - - - void ServerIndex::GetChanges(Json::Value& target, - int64_t since, - unsigned int maxResults) - { - class Operations : public ReadOnlyOperationsT3<Json::Value&, int64_t, unsigned int> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // NB: In Orthanc <= 1.3.2, a transaction was missing, as - // "GetLastChange()" involves calls to "GetPublicId()" - - std::list<ServerIndexChange> changes; - bool done; - bool hasLast = false; - int64_t last = 0; - - transaction.GetChanges(changes, done, tuple.get<1>(), tuple.get<2>()); - if (changes.empty()) - { - last = transaction.GetLastChangeIndex(); - hasLast = true; - } - - FormatLog(tuple.get<0>(), changes, "Changes", done, tuple.get<1>(), hasLast, last); - } - }; - - Operations operations; - operations.Apply(*this, target, since, maxResults); - } - - - void ServerIndex::GetLastChange(Json::Value& target) - { - class Operations : public ReadOnlyOperationsT1<Json::Value&> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // NB: In Orthanc <= 1.3.2, a transaction was missing, as - // "GetLastChange()" involves calls to "GetPublicId()" - - std::list<ServerIndexChange> changes; - bool hasLast = false; - int64_t last = 0; - - transaction.GetLastChange(changes); - if (changes.empty()) - { - last = transaction.GetLastChangeIndex(); - hasLast = true; - } - - FormatLog(tuple.get<0>(), changes, "Changes", true, 0, hasLast, last); - } - }; - - Operations operations; - operations.Apply(*this, target); - } - - - void ServerIndex::GetExportedResources(Json::Value& target, - int64_t since, - unsigned int maxResults) - { - class Operations : public ReadOnlyOperationsT3<Json::Value&, int64_t, unsigned int> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // TODO - CANDIDATE FOR "TransactionType_Implicit" - - std::list<ExportedResource> exported; - bool done; - transaction.GetExportedResources(exported, done, tuple.get<1>(), tuple.get<2>()); - FormatLog(tuple.get<0>(), exported, "Exports", done, tuple.get<1>(), false, -1); - } - }; - - Operations operations; - operations.Apply(*this, target, since, maxResults); - } - - - void ServerIndex::GetLastExportedResource(Json::Value& target) - { - class Operations : public ReadOnlyOperationsT1<Json::Value&> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // TODO - CANDIDATE FOR "TransactionType_Implicit" - - std::list<ExportedResource> exported; - transaction.GetLastExportedResource(exported); - FormatLog(tuple.get<0>(), exported, "Exports", true, 0, false, -1); - } - }; - - Operations operations; - operations.Apply(*this, target); - } - - - bool ServerIndex::IsProtectedPatient(const std::string& publicId) - { - class Operations : public ReadOnlyOperationsT2<bool&, const std::string&> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // Lookup for the requested resource - int64_t id; - ResourceType type; - if (!transaction.LookupResource(id, type, tuple.get<1>()) || - type != ResourceType_Patient) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - tuple.get<0>() = transaction.IsProtectedPatient(id); - } - } - }; - - bool isProtected; - Operations operations; - operations.Apply(*this, isProtected, publicId); - return isProtected; - } - - - void ServerIndex::GetChildren(std::list<std::string>& result, - const std::string& publicId) - { - class Operations : public ReadOnlyOperationsT2<std::list<std::string>&, const std::string&> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - ResourceType type; - int64_t resource; - if (!transaction.LookupResource(resource, type, tuple.get<1>())) - { - throw OrthancException(ErrorCode_UnknownResource); - } - else if (type == ResourceType_Instance) - { - // An instance cannot have a child - throw OrthancException(ErrorCode_BadParameterType); - } - else - { - std::list<int64_t> tmp; - transaction.GetChildrenInternalId(tmp, resource); - - tuple.get<0>().clear(); - - for (std::list<int64_t>::const_iterator - it = tmp.begin(); it != tmp.end(); ++it) - { - tuple.get<0>().push_back(transaction.GetPublicId(*it)); - } - } - } - }; - - Operations operations; - operations.Apply(*this, result, publicId); - } - - - void ServerIndex::GetChildInstances(std::list<std::string>& result, - const std::string& publicId) - { - class Operations : public ReadOnlyOperationsT2<std::list<std::string>&, const std::string&> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - tuple.get<0>().clear(); - - ResourceType type; - int64_t top; - if (!transaction.LookupResource(top, type, tuple.get<1>())) - { - throw OrthancException(ErrorCode_UnknownResource); - } - else if (type == ResourceType_Instance) - { - // The resource is already an instance: Do not go down the hierarchy - tuple.get<0>().push_back(tuple.get<1>()); - } - else - { - std::stack<int64_t> toExplore; - toExplore.push(top); - - std::list<int64_t> tmp; - while (!toExplore.empty()) - { - // Get the internal ID of the current resource - int64_t resource = toExplore.top(); - toExplore.pop(); - - // TODO - This could be optimized by seeing how many - // levels "type == transaction.GetResourceType(top)" is - // above the "instances level" - if (transaction.GetResourceType(resource) == ResourceType_Instance) - { - tuple.get<0>().push_back(transaction.GetPublicId(resource)); - } - else - { - // Tag all the children of this resource as to be explored - transaction.GetChildrenInternalId(tmp, resource); - for (std::list<int64_t>::const_iterator - it = tmp.begin(); it != tmp.end(); ++it) - { - toExplore.push(*it); - } - } - } - } - } - }; - - Operations operations; - operations.Apply(*this, result, publicId); - } - - - bool ServerIndex::LookupMetadata(std::string& target, - const std::string& publicId, - ResourceType expectedType, - MetadataType type) - { - class Operations : public ReadOnlyOperationsT5<bool&, std::string&, const std::string&, ResourceType, MetadataType> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - ResourceType rtype; - int64_t id; - if (!transaction.LookupResource(id, rtype, tuple.get<2>()) || - rtype != tuple.get<3>()) - { - throw OrthancException(ErrorCode_UnknownResource); - } - else - { - tuple.get<0>() = transaction.LookupMetadata(tuple.get<1>(), id, tuple.get<4>()); - } - } - }; - - bool found; - Operations operations; - operations.Apply(*this, found, target, publicId, expectedType, type); - return found; - } - - - void ServerIndex::ListAvailableAttachments(std::set<FileContentType>& target, - const std::string& publicId, - ResourceType expectedType) - { - class Operations : public ReadOnlyOperationsT3<std::set<FileContentType>&, const std::string&, ResourceType> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - ResourceType type; - int64_t id; - if (!transaction.LookupResource(id, type, tuple.get<1>()) || - tuple.get<2>() != type) - { - throw OrthancException(ErrorCode_UnknownResource); - } - else - { - transaction.ListAvailableAttachments(tuple.get<0>(), id); - } - } - }; - - Operations operations; - operations.Apply(*this, target, publicId, expectedType); - } - - - bool ServerIndex::LookupParent(std::string& target, - const std::string& publicId) - { - class Operations : public ReadOnlyOperationsT3<bool&, std::string&, const std::string&> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - ResourceType type; - int64_t id; - if (!transaction.LookupResource(id, type, tuple.get<2>())) - { - throw OrthancException(ErrorCode_UnknownResource); - } - else - { - int64_t parentId; - if (transaction.LookupParent(parentId, id)) - { - tuple.get<1>() = transaction.GetPublicId(parentId); - tuple.get<0>() = true; - } - else - { - tuple.get<0>() = false; - } - } - } - }; - - bool found; - Operations operations; - operations.Apply(*this, found, target, publicId); - return found; - } - - - void ServerIndex::GetResourceStatistics(/* out */ ResourceType& type, - /* out */ uint64_t& diskSize, - /* out */ uint64_t& uncompressedSize, - /* out */ unsigned int& countStudies, - /* out */ unsigned int& countSeries, - /* out */ unsigned int& countInstances, - /* out */ uint64_t& dicomDiskSize, - /* out */ uint64_t& dicomUncompressedSize, - const std::string& publicId) - { - class Operations : public IReadOnlyOperations - { - private: - ResourceType& type_; - uint64_t& diskSize_; - uint64_t& uncompressedSize_; - unsigned int& countStudies_; - unsigned int& countSeries_; - unsigned int& countInstances_; - uint64_t& dicomDiskSize_; - uint64_t& dicomUncompressedSize_; - const std::string& publicId_; - - public: - explicit Operations(ResourceType& type, - uint64_t& diskSize, - uint64_t& uncompressedSize, - unsigned int& countStudies, - unsigned int& countSeries, - unsigned int& countInstances, - uint64_t& dicomDiskSize, - uint64_t& dicomUncompressedSize, - const std::string& publicId) : - type_(type), - diskSize_(diskSize), - uncompressedSize_(uncompressedSize), - countStudies_(countStudies), - countSeries_(countSeries), - countInstances_(countInstances), - dicomDiskSize_(dicomDiskSize), - dicomUncompressedSize_(dicomUncompressedSize), - publicId_(publicId) - { - } - - virtual void Apply(ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE - { - int64_t top; - if (!transaction.LookupResource(top, type_, publicId_)) - { - throw OrthancException(ErrorCode_UnknownResource); - } - else - { - countInstances_ = 0; - countSeries_ = 0; - countStudies_ = 0; - diskSize_ = 0; - uncompressedSize_ = 0; - dicomDiskSize_ = 0; - dicomUncompressedSize_ = 0; - - std::stack<int64_t> toExplore; - toExplore.push(top); - - while (!toExplore.empty()) - { - // Get the internal ID of the current resource - int64_t resource = toExplore.top(); - toExplore.pop(); - - ResourceType thisType = transaction.GetResourceType(resource); - - std::set<FileContentType> f; - transaction.ListAvailableAttachments(f, resource); - - for (std::set<FileContentType>::const_iterator - it = f.begin(); it != f.end(); ++it) - { - FileInfo attachment; - if (transaction.LookupAttachment(attachment, resource, *it)) - { - if (attachment.GetContentType() == FileContentType_Dicom) - { - dicomDiskSize_ += attachment.GetCompressedSize(); - dicomUncompressedSize_ += attachment.GetUncompressedSize(); - } - - diskSize_ += attachment.GetCompressedSize(); - uncompressedSize_ += attachment.GetUncompressedSize(); - } - } - - if (thisType == ResourceType_Instance) - { - countInstances_++; - } - else - { - switch (thisType) - { - case ResourceType_Study: - countStudies_++; - break; - - case ResourceType_Series: - countSeries_++; - break; - - default: - break; - } - - // Tag all the children of this resource as to be explored - std::list<int64_t> tmp; - transaction.GetChildrenInternalId(tmp, resource); - for (std::list<int64_t>::const_iterator - it = tmp.begin(); it != tmp.end(); ++it) - { - toExplore.push(*it); - } - } - } - - if (countStudies_ == 0) - { - countStudies_ = 1; - } - - if (countSeries_ == 0) - { - countSeries_ = 1; - } - } - } - }; - - Operations operations(type, diskSize, uncompressedSize, countStudies, countSeries, - countInstances, dicomDiskSize, dicomUncompressedSize, publicId); - Apply(operations); - } - - - void ServerIndex::LookupIdentifierExact(std::vector<std::string>& result, - ResourceType level, - const DicomTag& tag, - const std::string& value) - { - assert((level == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) || - (level == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) || - (level == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) || - (level == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) || - (level == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID)); - - result.clear(); - - DicomTagConstraint c(tag, ConstraintType_Equal, value, true, true); - - std::vector<DatabaseConstraint> query; - query.push_back(c.ConvertToDatabaseConstraint(level, DicomTagType_Identifier)); - - - class Operations : public IReadOnlyOperations - { - private: - std::vector<std::string>& result_; - const std::vector<DatabaseConstraint>& query_; - ResourceType level_; - - public: - Operations(std::vector<std::string>& result, - const std::vector<DatabaseConstraint>& query, - ResourceType level) : - result_(result), - query_(query), - level_(level) - { - } - - virtual void Apply(ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE - { - // TODO - CANDIDATE FOR "TransactionType_Implicit" - std::list<std::string> tmp; - transaction.ApplyLookupResources(tmp, NULL, query_, level_, 0); - CopyListToVector(result_, tmp); - } - }; - - Operations operations(result, query, level); - Apply(operations); - } - - - bool ServerIndex::LookupGlobalProperty(std::string& value, - GlobalProperty property) - { - class Operations : public ReadOnlyOperationsT3<bool&, std::string&, GlobalProperty> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // TODO - CANDIDATE FOR "TransactionType_Implicit" - tuple.get<0>() = transaction.LookupGlobalProperty(tuple.get<1>(), tuple.get<2>()); - } - }; - - bool found; - Operations operations; - operations.Apply(*this, found, value, property); - return found; - } - - - std::string ServerIndex::GetGlobalProperty(GlobalProperty property, - const std::string& defaultValue) - { - std::string s; - if (LookupGlobalProperty(s, property)) - { - return s; - } - else - { - return defaultValue; - } - } - - - bool ServerIndex::GetMainDicomTags(DicomMap& result, - const std::string& publicId, - ResourceType expectedType, - ResourceType levelOfInterest) - { - // Yes, the following test could be shortened, but we wish to make it as clear as possible - if (!(expectedType == ResourceType_Patient && levelOfInterest == ResourceType_Patient) && - !(expectedType == ResourceType_Study && levelOfInterest == ResourceType_Patient) && - !(expectedType == ResourceType_Study && levelOfInterest == ResourceType_Study) && - !(expectedType == ResourceType_Series && levelOfInterest == ResourceType_Series) && - !(expectedType == ResourceType_Instance && levelOfInterest == ResourceType_Instance)) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - - class Operations : public ReadOnlyOperationsT5<bool&, DicomMap&, const std::string&, ResourceType, ResourceType> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // Lookup for the requested resource - int64_t id; - ResourceType type; - if (!transaction.LookupResource(id, type, tuple.get<2>()) || - type != tuple.get<3>()) - { - tuple.get<0>() = false; - } - else if (type == ResourceType_Study) - { - DicomMap tmp; - transaction.GetMainDicomTags(tmp, id); - - switch (tuple.get<4>()) - { - case ResourceType_Patient: - tmp.ExtractPatientInformation(tuple.get<1>()); - tuple.get<0>() = true; - break; - - case ResourceType_Study: - tmp.ExtractStudyInformation(tuple.get<1>()); - tuple.get<0>() = true; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - else - { - transaction.GetMainDicomTags(tuple.get<1>(), id); - tuple.get<0>() = true; - } - } - }; - - result.Clear(); - - bool found; - Operations operations; - operations.Apply(*this, found, result, publicId, expectedType, levelOfInterest); - return found; - } - - - bool ServerIndex::GetAllMainDicomTags(DicomMap& result, - const std::string& instancePublicId) - { - class Operations : public ReadOnlyOperationsT3<bool&, DicomMap&, const std::string&> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // Lookup for the requested resource - int64_t instance; - ResourceType type; - if (!transaction.LookupResource(instance, type, tuple.get<2>()) || - type != ResourceType_Instance) - { - tuple.get<0>() = false; - } - else - { - DicomMap tmp; - - transaction.GetMainDicomTags(tmp, instance); - tuple.get<1>().Merge(tmp); - - int64_t series; - if (!transaction.LookupParent(series, instance)) - { - throw OrthancException(ErrorCode_InternalError); - } - - tmp.Clear(); - transaction.GetMainDicomTags(tmp, series); - tuple.get<1>().Merge(tmp); - - int64_t study; - if (!transaction.LookupParent(study, series)) - { - throw OrthancException(ErrorCode_InternalError); - } - - tmp.Clear(); - transaction.GetMainDicomTags(tmp, study); - tuple.get<1>().Merge(tmp); - -#ifndef NDEBUG - { - // Sanity test to check that all the main DICOM tags from the - // patient level are copied at the study level - - int64_t patient; - if (!transaction.LookupParent(patient, study)) - { - throw OrthancException(ErrorCode_InternalError); - } - - tmp.Clear(); - transaction.GetMainDicomTags(tmp, study); - - std::set<DicomTag> patientTags; - tmp.GetTags(patientTags); - - for (std::set<DicomTag>::const_iterator - it = patientTags.begin(); it != patientTags.end(); ++it) - { - assert(tuple.get<1>().HasTag(*it)); - } - } -#endif - - tuple.get<0>() = true; - } - } - }; - - result.Clear(); - - bool found; - Operations operations; - operations.Apply(*this, found, result, instancePublicId); - return found; - } - - - bool ServerIndex::LookupResourceType(ResourceType& type, - const std::string& publicId) - { - class Operations : public ReadOnlyOperationsT3<bool&, ResourceType&, const std::string&> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // TODO - CANDIDATE FOR "TransactionType_Implicit" - int64_t id; - tuple.get<0>() = transaction.LookupResource(id, tuple.get<1>(), tuple.get<2>()); - } - }; - - bool found; - Operations operations; - operations.Apply(*this, found, type, publicId); - return found; - } - - - bool ServerIndex::LookupParent(std::string& target, - const std::string& publicId, - ResourceType parentType) - { - class Operations : public ReadOnlyOperationsT4<bool&, std::string&, const std::string&, ResourceType> - { - public: - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - ResourceType type; - int64_t id; - if (!transaction.LookupResource(id, type, tuple.get<2>())) - { - throw OrthancException(ErrorCode_UnknownResource); - } - - while (type != tuple.get<3>()) - { - int64_t parentId; - - if (type == ResourceType_Patient || // Cannot further go up in hierarchy - !transaction.LookupParent(parentId, id)) - { - tuple.get<0>() = false; - return; - } - - id = parentId; - type = GetParentResourceType(type); - } - - tuple.get<0>() = true; - tuple.get<1>() = transaction.GetPublicId(id); - } - }; - - bool found; - Operations operations; - operations.Apply(*this, found, target, publicId, parentType); - return found; - } - - - void ServerIndex::ApplyLookupResources(std::vector<std::string>& resourcesId, - std::vector<std::string>* instancesId, - const DatabaseLookup& lookup, - ResourceType queryLevel, - size_t limit) - { - class Operations : public ReadOnlyOperationsT4<bool, const std::vector<DatabaseConstraint>&, ResourceType, size_t> - { - private: - std::list<std::string> resourcesList_; - std::list<std::string> instancesList_; - - public: - const std::list<std::string>& GetResourcesList() const - { - return resourcesList_; - } - - const std::list<std::string>& GetInstancesList() const - { - return instancesList_; - } - - virtual void ApplyTuple(ReadOnlyTransaction& transaction, - const Tuple& tuple) ORTHANC_OVERRIDE - { - // TODO - CANDIDATE FOR "TransactionType_Implicit" - if (tuple.get<0>()) - { - transaction.ApplyLookupResources(resourcesList_, &instancesList_, tuple.get<1>(), tuple.get<2>(), tuple.get<3>()); - } - else - { - transaction.ApplyLookupResources(resourcesList_, NULL, tuple.get<1>(), tuple.get<2>(), tuple.get<3>()); - } - } - }; - - - std::vector<DatabaseConstraint> normalized; - NormalizeLookup(normalized, lookup, queryLevel); - - Operations operations; - operations.Apply(*this, (instancesId != NULL), normalized, queryLevel, limit); - - CopyListToVector(resourcesId, operations.GetResourcesList()); - - if (instancesId != NULL) - { - CopyListToVector(*instancesId, operations.GetInstancesList()); - } - } - - - bool ServerIndex::DeleteResource(Json::Value& target, - const std::string& uuid, - ResourceType expectedType) - { - class Operations : public IReadWriteOperations - { - private: - bool found_; - Json::Value& target_; - const std::string& uuid_; - ResourceType expectedType_; - - public: - Operations(Json::Value& target, - const std::string& uuid, - ResourceType expectedType) : - found_(false), - target_(target), - uuid_(uuid), - expectedType_(expectedType) - { - } - - bool IsFound() const - { - return found_; - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - int64_t id; - ResourceType type; - if (!transaction.LookupResource(id, type, uuid_) || - expectedType_ != type) - { - found_ = false; - } - else - { - found_ = true; - transaction.DeleteResource(id); - - std::string remainingPublicId; - ResourceType remainingLevel; - if (transaction.GetTransactionContext().LookupRemainingLevel(remainingPublicId, remainingLevel)) - { - target_["RemainingAncestor"] = Json::Value(Json::objectValue); - target_["RemainingAncestor"]["Path"] = GetBasePath(remainingLevel, remainingPublicId); - target_["RemainingAncestor"]["Type"] = EnumerationToString(remainingLevel); - target_["RemainingAncestor"]["ID"] = remainingPublicId; - } - else - { - target_["RemainingAncestor"] = Json::nullValue; - } - } - } - }; - - Operations operations(target, uuid, expectedType); - Apply(operations); - return operations.IsFound(); - } - - - void ServerIndex::LogExportedResource(const std::string& publicId, - const std::string& remoteModality) - { - class Operations : public IReadWriteOperations - { - private: - const std::string& publicId_; - const std::string& remoteModality_; - - public: - Operations(const std::string& publicId, - const std::string& remoteModality) : - publicId_(publicId), - remoteModality_(remoteModality) - { - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - int64_t id; - ResourceType type; - if (!transaction.LookupResource(id, type, publicId_)) - { - throw OrthancException(ErrorCode_InexistentItem); - } - - std::string patientId; - std::string studyInstanceUid; - std::string seriesInstanceUid; - std::string sopInstanceUid; - - int64_t currentId = id; - ResourceType currentType = type; - - // Iteratively go up inside the patient/study/series/instance hierarchy - bool done = false; - while (!done) - { - DicomMap map; - transaction.GetMainDicomTags(map, currentId); - - switch (currentType) - { - case ResourceType_Patient: - if (map.HasTag(DICOM_TAG_PATIENT_ID)) - { - patientId = map.GetValue(DICOM_TAG_PATIENT_ID).GetContent(); - } - done = true; - break; - - case ResourceType_Study: - if (map.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) - { - studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent(); - } - currentType = ResourceType_Patient; - break; - - case ResourceType_Series: - if (map.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) - { - seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent(); - } - currentType = ResourceType_Study; - break; - - case ResourceType_Instance: - if (map.HasTag(DICOM_TAG_SOP_INSTANCE_UID)) - { - sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).GetContent(); - } - currentType = ResourceType_Series; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - // If we have not reached the Patient level, find the parent of - // the current resource - if (!done) - { - bool ok = transaction.LookupParent(currentId, currentId); - (void) ok; // Remove warning about unused variable in release builds - assert(ok); - } - } - - ExportedResource resource(-1, - type, - publicId_, - remoteModality_, - SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */), - patientId, - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid); - - transaction.LogExportedResource(resource); - } - }; - - Operations operations(publicId, remoteModality); - Apply(operations); - } - - - void ServerIndex::SetProtectedPatient(const std::string& publicId, - bool isProtected) - { - class Operations : public IReadWriteOperations - { - private: - const std::string& publicId_; - bool isProtected_; - - public: - Operations(const std::string& publicId, - bool isProtected) : - publicId_(publicId), - isProtected_(isProtected) - { - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - // Lookup for the requested resource - int64_t id; - ResourceType type; - if (!transaction.LookupResource(id, type, publicId_) || - type != ResourceType_Patient) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - transaction.SetProtectedPatient(id, isProtected_); - } - } - }; - - Operations operations(publicId, isProtected); - Apply(operations); - - if (isProtected) - { - LOG(INFO) << "Patient " << publicId << " has been protected"; - } - else - { - LOG(INFO) << "Patient " << publicId << " has been unprotected"; - } - } - - - void ServerIndex::SetMetadata(const std::string& publicId, - MetadataType type, - const std::string& value) - { - class Operations : public IReadWriteOperations - { - private: - const std::string& publicId_; - MetadataType type_; - const std::string& value_; - - public: - Operations(const std::string& publicId, - MetadataType type, - const std::string& value) : - publicId_(publicId), - type_(type), - value_(value) - { - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - ResourceType rtype; - int64_t id; - if (!transaction.LookupResource(id, rtype, publicId_)) - { - throw OrthancException(ErrorCode_UnknownResource); - } - else - { - transaction.SetMetadata(id, type_, value_); - - if (IsUserMetadata(type_)) - { - transaction.LogChange(id, ChangeType_UpdatedMetadata, rtype, publicId_); - } - } - } - }; - - Operations operations(publicId, type, value); - Apply(operations); - } - - - void ServerIndex::DeleteMetadata(const std::string& publicId, - MetadataType type) - { - class Operations : public IReadWriteOperations - { - private: - const std::string& publicId_; - MetadataType type_; - - public: - Operations(const std::string& publicId, - MetadataType type) : - publicId_(publicId), - type_(type) - { - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - ResourceType rtype; - int64_t id; - if (!transaction.LookupResource(id, rtype, publicId_)) - { - throw OrthancException(ErrorCode_UnknownResource); - } - else - { - transaction.DeleteMetadata(id, type_); - - if (IsUserMetadata(type_)) - { - transaction.LogChange(id, ChangeType_UpdatedMetadata, rtype, publicId_); - } - } - } - }; - - Operations operations(publicId, type); - Apply(operations); - } - - - uint64_t ServerIndex::IncrementGlobalSequence(GlobalProperty sequence) - { - class Operations : public IReadWriteOperations - { - private: - uint64_t newValue_; - GlobalProperty sequence_; - - public: - explicit Operations(GlobalProperty sequence) : - newValue_(0), // Dummy initialization - sequence_(sequence) - { - } - - uint64_t GetNewValue() const - { - return newValue_; - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - std::string oldString; - - if (transaction.LookupGlobalProperty(oldString, sequence_)) - { - uint64_t oldValue; - - try - { - oldValue = boost::lexical_cast<uint64_t>(oldString); - } - catch (boost::bad_lexical_cast&) - { - LOG(ERROR) << "Cannot read the global sequence " - << boost::lexical_cast<std::string>(sequence_) << ", resetting it"; - oldValue = 0; - } - - newValue_ = oldValue + 1; - } - else - { - // Initialize the sequence at "1" - newValue_ = 1; - } - - transaction.SetGlobalProperty(sequence_, boost::lexical_cast<std::string>(newValue_)); - } - }; - - Operations operations(sequence); - Apply(operations); - assert(operations.GetNewValue() != 0); - return operations.GetNewValue(); - } - - - void ServerIndex::DeleteChanges() - { - class Operations : public IReadWriteOperations - { - public: - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - transaction.ClearChanges(); - } - }; - - Operations operations; - Apply(operations); - } - - - void ServerIndex::DeleteExportedResources() - { - class Operations : public IReadWriteOperations - { - public: - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - transaction.ClearExportedResources(); - } - }; - - Operations operations; - Apply(operations); - } - - - void ServerIndex::SetGlobalProperty(GlobalProperty property, - const std::string& value) - { - class Operations : public IReadWriteOperations - { - private: - GlobalProperty property_; - const std::string& value_; - - public: - Operations(GlobalProperty property, - const std::string& value) : - property_(property), - value_(value) - { - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - transaction.SetGlobalProperty(property_, value_); - } - }; - - Operations operations(property, value); - Apply(operations); - } - - - void ServerIndex::DeleteAttachment(const std::string& publicId, - FileContentType type) - { - class Operations : public IReadWriteOperations - { - private: - const std::string& publicId_; - FileContentType type_; - - public: - Operations(const std::string& publicId, - FileContentType type) : - publicId_(publicId), - type_(type) - { - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - ResourceType rtype; - int64_t id; - if (!transaction.LookupResource(id, rtype, publicId_)) - { - throw OrthancException(ErrorCode_UnknownResource); - } - else - { - transaction.DeleteAttachment(id, type_); - - if (IsUserContentType(type_)) - { - transaction.LogChange(id, ChangeType_UpdatedAttachment, rtype, publicId_); - } - } - } - }; - - Operations operations(publicId, type); - Apply(operations); - } - - - void ServerIndex::LogChange(ChangeType changeType, - const std::string& publicId, - ResourceType level) - { - class Operations : public IReadWriteOperations - { - private: - ChangeType changeType_; - const std::string& publicId_; - ResourceType level_; - - public: - Operations(ChangeType changeType, - const std::string& publicId, - ResourceType level) : - changeType_(changeType), - publicId_(publicId), - level_(level) - { - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - int64_t id; - ResourceType type; - if (transaction.LookupResource(id, type, publicId_)) - { - // Make sure that the resource is still existing. Ignore if - // the resource has been deleted, because this function - // might e.g. be called from - // "ServerIndex::UnstableResourcesMonitorThread()" (for - // which a deleted resource not an error case) - if (type == level_) - { - transaction.LogChange(id, changeType_, type, publicId_); - } - else - { - // Consistency check - throw OrthancException(ErrorCode_UnknownResource); - } - } - } - }; - - Operations operations(changeType, publicId, level); - Apply(operations); - } - - - void ServerIndex::ReconstructInstance(const ParsedDicomFile& dicom) - { - class Operations : public IReadWriteOperations - { - private: - DicomMap summary_; - std::unique_ptr<DicomInstanceHasher> hasher_; - bool hasTransferSyntax_; - DicomTransferSyntax transferSyntax_; - - public: - Operations(const ParsedDicomFile& dicom) - { - OrthancConfiguration::DefaultExtractDicomSummary(summary_, dicom); - hasher_.reset(new DicomInstanceHasher(summary_)); - hasTransferSyntax_ = dicom.LookupTransferSyntax(transferSyntax_); - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - int64_t patient = -1, study = -1, series = -1, instance = -1; - - ResourceType type1, type2, type3, type4; - if (!transaction.LookupResource(patient, type1, hasher_->HashPatient()) || - !transaction.LookupResource(study, type2, hasher_->HashStudy()) || - !transaction.LookupResource(series, type3, hasher_->HashSeries()) || - !transaction.LookupResource(instance, type4, hasher_->HashInstance()) || - type1 != ResourceType_Patient || - type2 != ResourceType_Study || - type3 != ResourceType_Series || - type4 != ResourceType_Instance || - patient == -1 || - study == -1 || - series == -1 || - instance == -1) - { - throw OrthancException(ErrorCode_InternalError); - } - - transaction.ClearMainDicomTags(patient); - transaction.ClearMainDicomTags(study); - transaction.ClearMainDicomTags(series); - transaction.ClearMainDicomTags(instance); - - { - ResourcesContent content; - content.AddResource(patient, ResourceType_Patient, summary_); - content.AddResource(study, ResourceType_Study, summary_); - content.AddResource(series, ResourceType_Series, summary_); - content.AddResource(instance, ResourceType_Instance, summary_); - transaction.SetResourcesContent(content); - } - - if (hasTransferSyntax_) - { - transaction.SetMetadata(instance, MetadataType_Instance_TransferSyntax, GetTransferSyntaxUid(transferSyntax_)); - } - - const DicomValue* value; - if ((value = summary_.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL && - !value->IsNull() && - !value->IsBinary()) - { - transaction.SetMetadata(instance, MetadataType_Instance_SopClassUid, value->GetContent()); - } - } - }; - - Operations operations(dicom); - Apply(operations); - } - - - static bool IsRecyclingNeeded(IDatabaseWrapper& db, - uint64_t maximumStorageSize, - unsigned int maximumPatients, - uint64_t addedInstanceSize) - { - if (maximumStorageSize != 0) - { - if (maximumStorageSize < addedInstanceSize) - { - throw OrthancException(ErrorCode_FullStorage, "Cannot store an instance of size " + - boost::lexical_cast<std::string>(addedInstanceSize) + - " bytes in a storage area limited to " + - boost::lexical_cast<std::string>(maximumStorageSize)); - } - - if (db.IsDiskSizeAbove(maximumStorageSize - addedInstanceSize)) - { - return true; - } - } - - if (maximumPatients != 0) - { - uint64_t patientCount = db.GetResourceCount(ResourceType_Patient); - if (patientCount > maximumPatients) - { - return true; - } - } - - return false; - } - - - void ServerIndex::ReadWriteTransaction::Recycle(uint64_t maximumStorageSize, - unsigned int maximumPatients, - uint64_t addedInstanceSize, - const std::string& newPatientId) - { - // TODO - Performance: Avoid calls to "IsRecyclingNeeded()" - - if (IsRecyclingNeeded(db_, maximumStorageSize, maximumPatients, addedInstanceSize)) - { - // Check whether other DICOM instances from this patient are - // already stored - int64_t patientToAvoid; - bool hasPatientToAvoid; - - if (newPatientId.empty()) - { - hasPatientToAvoid = false; - } - else - { - ResourceType type; - hasPatientToAvoid = db_.LookupResource(patientToAvoid, type, newPatientId); - if (type != ResourceType_Patient) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - // Iteratively select patient to remove until there is enough - // space in the DICOM store - int64_t patientToRecycle; - while (true) - { - // If other instances of this patient are already in the store, - // we must avoid to recycle them - bool ok = (hasPatientToAvoid ? - db_.SelectPatientToRecycle(patientToRecycle, patientToAvoid) : - db_.SelectPatientToRecycle(patientToRecycle)); - - if (!ok) - { - throw OrthancException(ErrorCode_FullStorage); - } - - LOG(TRACE) << "Recycling one patient"; - db_.DeleteResource(patientToRecycle); - - if (!IsRecyclingNeeded(db_, maximumStorageSize, maximumPatients, addedInstanceSize)) - { - // OK, we're done - return; - } - } - } - } - - - void ServerIndex::StandaloneRecycling(uint64_t maximumStorageSize, - unsigned int maximumPatientCount) - { - class Operations : public IReadWriteOperations - { - private: - uint64_t maximumStorageSize_; - unsigned int maximumPatientCount_; - - public: - Operations(uint64_t maximumStorageSize, - unsigned int maximumPatientCount) : - maximumStorageSize_(maximumStorageSize), - maximumPatientCount_(maximumPatientCount) - { - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - transaction.Recycle(maximumStorageSize_, maximumPatientCount_, 0, ""); - } - }; - - Operations operations(maximumStorageSize, maximumPatientCount); - Apply(operations); - } - - StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata, const DicomMap& dicomSummary, - const Attachments& attachments, - const MetadataMap& metadata, + const ServerIndex::Attachments& attachments, + const ServerIndex::MetadataMap& metadata, const DicomInstanceOrigin& origin, bool overwrite, bool hasTransferSyntax, @@ -3201,404 +735,6 @@ bool hasPixelDataOffset, uint64_t pixelDataOffset) { - class Operations : public IReadWriteOperations - { - private: - StoreStatus storeStatus_; - std::map<MetadataType, std::string>& instanceMetadata_; - const DicomMap& dicomSummary_; - const Attachments& attachments_; - const MetadataMap& metadata_; - const DicomInstanceOrigin& origin_; - bool overwrite_; - bool hasTransferSyntax_; - DicomTransferSyntax transferSyntax_; - bool hasPixelDataOffset_; - uint64_t pixelDataOffset_; - uint64_t maximumStorageSize_; - unsigned int maximumPatientCount_; - - // Auto-computed fields - bool hasExpectedInstances_; - int64_t expectedInstances_; - std::string hashPatient_; - std::string hashStudy_; - std::string hashSeries_; - std::string hashInstance_; - - - static void SetInstanceMetadata(ResourcesContent& content, - std::map<MetadataType, std::string>& instanceMetadata, - int64_t instance, - MetadataType metadata, - const std::string& value) - { - content.AddMetadata(instance, metadata, value); - instanceMetadata[metadata] = value; - } - - - static bool ComputeExpectedNumberOfInstances(int64_t& target, - const DicomMap& dicomSummary) - { - try - { - const DicomValue* value; - const DicomValue* value2; - - if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL && - !value->IsNull() && - !value->IsBinary() && - (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS)) != NULL && - !value2->IsNull() && - !value2->IsBinary()) - { - // Patch for series with temporal positions thanks to Will Ryder - int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->GetContent()); - int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->GetContent()); - target = imagesInAcquisition * countTemporalPositions; - return (target > 0); - } - - else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL && - !value->IsNull() && - !value->IsBinary() && - (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TIME_SLICES)) != NULL && - !value2->IsBinary() && - !value2->IsNull()) - { - // Support of Cardio-PET images - int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->GetContent()); - int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->GetContent()); - target = numberOfSlices * numberOfTimeSlices; - return (target > 0); - } - - else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL && - !value->IsNull() && - !value->IsBinary()) - { - target = boost::lexical_cast<int64_t>(value->GetContent()); - return (target > 0); - } - } - catch (OrthancException&) - { - } - catch (boost::bad_lexical_cast&) - { - } - - return false; - } - - public: - Operations(std::map<MetadataType, std::string>& instanceMetadata, - const DicomMap& dicomSummary, - const Attachments& attachments, - const MetadataMap& metadata, - const DicomInstanceOrigin& origin, - bool overwrite, - bool hasTransferSyntax, - DicomTransferSyntax transferSyntax, - bool hasPixelDataOffset, - uint64_t pixelDataOffset, - uint64_t maximumStorageSize, - unsigned int maximumPatientCount) : - storeStatus_(StoreStatus_Failure), - instanceMetadata_(instanceMetadata), - dicomSummary_(dicomSummary), - attachments_(attachments), - metadata_(metadata), - origin_(origin), - overwrite_(overwrite), - hasTransferSyntax_(hasTransferSyntax), - transferSyntax_(transferSyntax), - hasPixelDataOffset_(hasPixelDataOffset), - pixelDataOffset_(pixelDataOffset), - maximumStorageSize_(maximumStorageSize), - maximumPatientCount_(maximumPatientCount) - { - hasExpectedInstances_ = ComputeExpectedNumberOfInstances(expectedInstances_, dicomSummary); - - instanceMetadata_.clear(); - - DicomInstanceHasher hasher(dicomSummary); - hashPatient_ = hasher.HashPatient(); - hashStudy_ = hasher.HashStudy(); - hashSeries_ = hasher.HashSeries(); - hashInstance_ = hasher.HashInstance(); - } - - StoreStatus GetStoreStatus() const - { - return storeStatus_; - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - try - { - IDatabaseWrapper::CreateInstanceResult status; - int64_t instanceId; - - // Check whether this instance is already stored - if (!transaction.CreateInstance(status, instanceId, hashPatient_, - hashStudy_, hashSeries_, hashInstance_)) - { - // The instance already exists - - if (overwrite_) - { - // Overwrite the old instance - LOG(INFO) << "Overwriting instance: " << hashInstance_; - transaction.DeleteResource(instanceId); - - // Re-create the instance, now that the old one is removed - if (!transaction.CreateInstance(status, instanceId, hashPatient_, - hashStudy_, hashSeries_, hashInstance_)) - { - throw OrthancException(ErrorCode_InternalError); - } - } - else - { - // Do nothing if the instance already exists and overwriting is disabled - transaction.GetAllMetadata(instanceMetadata_, instanceId); - storeStatus_ = StoreStatus_AlreadyStored; - return; - } - } - - - // Warn about the creation of new resources. The order must be - // from instance to patient. - - // NB: In theory, could be sped up by grouping the underlying - // calls to "transaction.LogChange()". However, this would only have an - // impact when new patient/study/series get created, which - // occurs far less often that creating new instances. The - // positive impact looks marginal in practice. - transaction.LogChange(instanceId, ChangeType_NewInstance, ResourceType_Instance, hashInstance_); - - if (status.isNewSeries_) - { - transaction.LogChange(status.seriesId_, ChangeType_NewSeries, ResourceType_Series, hashSeries_); - } - - if (status.isNewStudy_) - { - transaction.LogChange(status.studyId_, ChangeType_NewStudy, ResourceType_Study, hashStudy_); - } - - if (status.isNewPatient_) - { - transaction.LogChange(status.patientId_, ChangeType_NewPatient, ResourceType_Patient, hashPatient_); - } - - - // Ensure there is enough room in the storage for the new instance - uint64_t instanceSize = 0; - for (Attachments::const_iterator it = attachments_.begin(); - it != attachments_.end(); ++it) - { - instanceSize += it->GetCompressedSize(); - } - - transaction.Recycle(maximumStorageSize_, maximumPatientCount_, - instanceSize, hashPatient_ /* don't consider the current patient for recycling */); - - - // Attach the files to the newly created instance - for (Attachments::const_iterator it = attachments_.begin(); - it != attachments_.end(); ++it) - { - transaction.AddAttachment(instanceId, *it); - } - - - { - ResourcesContent content; - - // Populate the tags of the newly-created resources - - content.AddResource(instanceId, ResourceType_Instance, dicomSummary_); - - if (status.isNewSeries_) - { - content.AddResource(status.seriesId_, ResourceType_Series, dicomSummary_); - } - - if (status.isNewStudy_) - { - content.AddResource(status.studyId_, ResourceType_Study, dicomSummary_); - } - - if (status.isNewPatient_) - { - content.AddResource(status.patientId_, ResourceType_Patient, dicomSummary_); - } - - - // Attach the user-specified metadata - - for (MetadataMap::const_iterator - it = metadata_.begin(); it != metadata_.end(); ++it) - { - switch (it->first.first) - { - case ResourceType_Patient: - content.AddMetadata(status.patientId_, it->first.second, it->second); - break; - - case ResourceType_Study: - content.AddMetadata(status.studyId_, it->first.second, it->second); - break; - - case ResourceType_Series: - content.AddMetadata(status.seriesId_, it->first.second, it->second); - break; - - case ResourceType_Instance: - SetInstanceMetadata(content, instanceMetadata_, instanceId, - it->first.second, it->second); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - // Attach the auto-computed metadata for the patient/study/series levels - std::string now = SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */); - content.AddMetadata(status.seriesId_, MetadataType_LastUpdate, now); - content.AddMetadata(status.studyId_, MetadataType_LastUpdate, now); - content.AddMetadata(status.patientId_, MetadataType_LastUpdate, now); - - if (status.isNewSeries_) - { - if (hasExpectedInstances_) - { - content.AddMetadata(status.seriesId_, MetadataType_Series_ExpectedNumberOfInstances, - boost::lexical_cast<std::string>(expectedInstances_)); - } - - // New in Orthanc 1.9.0 - content.AddMetadata(status.seriesId_, MetadataType_RemoteAet, - origin_.GetRemoteAetC()); - } - - - // Attach the auto-computed metadata for the instance level, - // reflecting these additions into the input metadata map - SetInstanceMetadata(content, instanceMetadata_, instanceId, - MetadataType_Instance_ReceptionDate, now); - SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_RemoteAet, - origin_.GetRemoteAetC()); - SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_Instance_Origin, - EnumerationToString(origin_.GetRequestOrigin())); - - - if (hasTransferSyntax_) - { - // New in Orthanc 1.2.0 - SetInstanceMetadata(content, instanceMetadata_, instanceId, - MetadataType_Instance_TransferSyntax, - GetTransferSyntaxUid(transferSyntax_)); - } - - { - std::string s; - - if (origin_.LookupRemoteIp(s)) - { - // New in Orthanc 1.4.0 - SetInstanceMetadata(content, instanceMetadata_, instanceId, - MetadataType_Instance_RemoteIp, s); - } - - if (origin_.LookupCalledAet(s)) - { - // New in Orthanc 1.4.0 - SetInstanceMetadata(content, instanceMetadata_, instanceId, - MetadataType_Instance_CalledAet, s); - } - - if (origin_.LookupHttpUsername(s)) - { - // New in Orthanc 1.4.0 - SetInstanceMetadata(content, instanceMetadata_, instanceId, - MetadataType_Instance_HttpUsername, s); - } - } - - if (hasPixelDataOffset_) - { - // New in Orthanc 1.9.1 - SetInstanceMetadata(content, instanceMetadata_, instanceId, - MetadataType_Instance_PixelDataOffset, - boost::lexical_cast<std::string>(pixelDataOffset_)); - } - - const DicomValue* value; - if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL && - !value->IsNull() && - !value->IsBinary()) - { - SetInstanceMetadata(content, instanceMetadata_, instanceId, - MetadataType_Instance_SopClassUid, value->GetContent()); - } - - - if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL || - (value = dicomSummary_.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL) - { - if (!value->IsNull() && - !value->IsBinary()) - { - SetInstanceMetadata(content, instanceMetadata_, instanceId, - MetadataType_Instance_IndexInSeries, Toolbox::StripSpaces(value->GetContent())); - } - } - - - transaction.SetResourcesContent(content); - } - - - // Check whether the series of this new instance is now completed - int64_t expectedNumberOfInstances; - if (ComputeExpectedNumberOfInstances(expectedNumberOfInstances, dicomSummary_)) - { - SeriesStatus seriesStatus = transaction.GetSeriesStatus(status.seriesId_, expectedNumberOfInstances); - if (seriesStatus == SeriesStatus_Complete) - { - transaction.LogChange(status.seriesId_, ChangeType_CompletedSeries, ResourceType_Series, hashSeries_); - } - } - - transaction.LogChange(status.seriesId_, ChangeType_NewChildInstance, ResourceType_Series, hashSeries_); - transaction.LogChange(status.studyId_, ChangeType_NewChildInstance, ResourceType_Study, hashStudy_); - transaction.LogChange(status.patientId_, ChangeType_NewChildInstance, ResourceType_Patient, hashPatient_); - - // Mark the parent resources of this instance as unstable - transaction.GetTransactionContext().MarkAsUnstable(status.seriesId_, ResourceType_Series, hashSeries_); - transaction.GetTransactionContext().MarkAsUnstable(status.studyId_, ResourceType_Study, hashStudy_); - transaction.GetTransactionContext().MarkAsUnstable(status.patientId_, ResourceType_Patient, hashPatient_); - transaction.GetTransactionContext().SignalAttachmentsAdded(instanceSize); - - storeStatus_ = StoreStatus_Success; - } - catch (OrthancException& e) - { - LOG(ERROR) << "EXCEPTION [" << e.What() << "]"; - storeStatus_ = StoreStatus_Failure; - } - } - }; - - uint64_t maximumStorageSize; unsigned int maximumPatients; @@ -3608,94 +744,15 @@ maximumPatients = maximumPatients_; } - Operations operations(instanceMetadata, dicomSummary, attachments, metadata, origin, - overwrite, hasTransferSyntax, transferSyntax, hasPixelDataOffset, - pixelDataOffset, maximumStorageSize, maximumPatients); - Apply(operations); - return operations.GetStoreStatus(); + return StatelessDatabaseOperations::Store( + instanceMetadata, dicomSummary, attachments, metadata, origin, overwrite, hasTransferSyntax, + transferSyntax, hasPixelDataOffset, pixelDataOffset, maximumStorageSize, maximumPatients); } - + StoreStatus ServerIndex::AddAttachment(const FileInfo& attachment, const std::string& publicId) { - class Operations : public IReadWriteOperations - { - private: - StoreStatus status_; - const FileInfo& attachment_; - const std::string& publicId_; - uint64_t maximumStorageSize_; - unsigned int maximumPatientCount_; - - public: - Operations(const FileInfo& attachment, - const std::string& publicId, - uint64_t maximumStorageSize, - unsigned int maximumPatientCount) : - status_(StoreStatus_Failure), - attachment_(attachment), - publicId_(publicId), - maximumStorageSize_(maximumStorageSize), - maximumPatientCount_(maximumPatientCount) - { - } - - StoreStatus GetStatus() const - { - return status_; - } - - virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE - { - ResourceType resourceType; - int64_t resourceId; - if (!transaction.LookupResource(resourceId, resourceType, publicId_)) - { - status_ = StoreStatus_Failure; // Inexistent resource - } - else - { - // Remove possible previous attachment - transaction.DeleteAttachment(resourceId, attachment_.GetContentType()); - - // Locate the patient of the target resource - int64_t patientId = resourceId; - for (;;) - { - int64_t parent; - if (transaction.LookupParent(parent, patientId)) - { - // We have not reached the patient level yet - patientId = parent; - } - else - { - // We have reached the patient level - break; - } - } - - // Possibly apply the recycling mechanism while preserving this patient - assert(transaction.GetResourceType(patientId) == ResourceType_Patient); - transaction.Recycle(maximumStorageSize_, maximumPatientCount_, - attachment_.GetCompressedSize(), transaction.GetPublicId(patientId)); - - transaction.AddAttachment(resourceId, attachment_); - - if (IsUserContentType(attachment_.GetContentType())) - { - transaction.LogChange(resourceId, ChangeType_UpdatedAttachment, resourceType, publicId_); - } - - transaction.GetTransactionContext().SignalAttachmentsAdded(attachment_.GetCompressedSize()); - - status_ = StoreStatus_Success; - } - } - }; - - uint64_t maximumStorageSize; unsigned int maximumPatients; @@ -3705,8 +762,7 @@ maximumPatients = maximumPatients_; } - Operations operations(attachment, publicId, maximumStorageSize, maximumPatients); - Apply(operations); - return operations.GetStatus(); + return StatelessDatabaseOperations::AddAttachment( + attachment, publicId, maximumStorageSize, maximumPatients); } }
--- a/OrthancServer/Sources/ServerIndex.h Tue Mar 09 18:24:59 2021 +0100 +++ b/OrthancServer/Sources/ServerIndex.h Wed Mar 10 15:59:03 2021 +0100 @@ -33,47 +33,32 @@ #pragma once +#include "Database/StatelessDatabaseOperations.h" #include "../../OrthancFramework/Sources/Cache/LeastRecentlyUsedIndex.h" -#include "../../OrthancFramework/Sources/DicomFormat/DicomMap.h" - -#include "Database/IDatabaseWrapper.h" -#include "DicomInstanceOrigin.h" #include <boost/thread.hpp> -#include <boost/noncopyable.hpp> namespace Orthanc { - class DatabaseLookup; - class ParsedDicomFile; class ServerContext; - - class ServerIndex : public boost::noncopyable + + class ServerIndex : public StatelessDatabaseOperations { - public: - typedef std::list<FileInfo> Attachments; - typedef std::map<std::pair<ResourceType, MetadataType>, std::string> MetadataMap; - private: class TransactionContextFactory; class Listener; - class Transaction; class UnstableResourcePayload; - class MainDicomTagsRegistry; bool done_; boost::mutex monitoringMutex_; - boost::mutex databaseMutex_; // TODO - REMOVE boost::thread flushThread_; boost::thread unstableResourcesMonitorThread_; std::unique_ptr<Listener> listener_; - IDatabaseWrapper& db_; LeastRecentlyUsedIndex<int64_t, UnstableResourcePayload> unstableResources_; uint64_t maximumStorageSize_; unsigned int maximumPatients_; - std::unique_ptr<MainDicomTagsRegistry> mainDicomTagsRegistry_; static void FlushThread(ServerIndex* that, unsigned int threadSleep); @@ -81,17 +66,10 @@ static void UnstableResourcesMonitorThread(ServerIndex* that, unsigned int threadSleep); - void StandaloneRecycling(uint64_t maximumStorageSize, - unsigned int maximumPatientCount); - void MarkAsUnstable(int64_t id, Orthanc::ResourceType type, const std::string& publicId); - void NormalizeLookup(std::vector<DatabaseConstraint>& target, - const DatabaseLookup& source, - ResourceType level) const; - bool IsUnstableResource(int64_t id); public: @@ -109,516 +87,6 @@ // "count == 0" means no limit on the number of patients void SetMaximumPatientCount(unsigned int count); - // It is assumed that "GetDatabaseVersion()" can run out of a - // database transaction - unsigned int GetDatabaseVersion() - { - return db_.GetDatabaseVersion(); - } - - - - /*** - ** PROTOTYPING FOR DB REFACTORING BELOW - ***/ - - public: - class ITransactionContext : public boost::noncopyable - { - public: - virtual ~ITransactionContext() - { - } - - virtual bool IsUnstableResource(int64_t id) = 0; - - virtual bool LookupRemainingLevel(std::string& remainingPublicId /* out */, - ResourceType& remainingLevel /* out */) = 0; - - virtual void MarkAsUnstable(int64_t id, - Orthanc::ResourceType type, - const std::string& publicId) = 0; - - virtual void SignalAttachmentsAdded(uint64_t compressedSize) = 0; - - virtual void SignalChange(const ServerIndexChange& change) = 0; - }; - - - class ITransactionContextFactory : public boost::noncopyable - { - public: - virtual ~ITransactionContextFactory() - { - } - - virtual ITransactionContext* Create() = 0; - }; - - - class ReadOnlyTransaction : public boost::noncopyable - { - private: - ITransactionContext& context_; - - protected: - IDatabaseWrapper& db_; - - public: - explicit ReadOnlyTransaction(IDatabaseWrapper& db, - ITransactionContext& context) : - context_(context), - db_(db) - { - } - - ITransactionContext& GetTransactionContext() - { - return context_; - } - - /** - * Higher-level constructions - **/ - - SeriesStatus GetSeriesStatus(int64_t id, - int64_t expectedNumberOfInstances); - - void MainDicomTagsToJson(Json::Value& result, - int64_t resourceId, - ResourceType resourceType); - - /** - * Read-only methods from "IDatabaseWrapper" - **/ - - void ApplyLookupResources(std::list<std::string>& resourcesId, - std::list<std::string>* instancesId, // Can be NULL if not needed - const std::vector<DatabaseConstraint>& lookup, - ResourceType queryLevel, - size_t limit) - { - return db_.ApplyLookupResources(resourcesId, instancesId, lookup, queryLevel, limit); - } - - void GetAllMetadata(std::map<MetadataType, std::string>& target, - int64_t id) - { - db_.GetAllMetadata(target, id); - } - - void GetAllPublicIds(std::list<std::string>& target, - ResourceType resourceType) - { - return db_.GetAllPublicIds(target, resourceType); - } - - void GetAllPublicIds(std::list<std::string>& target, - ResourceType resourceType, - size_t since, - size_t limit) - { - return db_.GetAllPublicIds(target, resourceType, since, limit); - } - - void GetChanges(std::list<ServerIndexChange>& target /*out*/, - bool& done /*out*/, - int64_t since, - uint32_t maxResults) - { - db_.GetChanges(target, done, since, maxResults); - } - - void GetChildrenInternalId(std::list<int64_t>& target, - int64_t id) - { - db_.GetChildrenInternalId(target, id); - } - - void GetChildrenPublicId(std::list<std::string>& target, - int64_t id) - { - db_.GetChildrenPublicId(target, id); - } - - void GetExportedResources(std::list<ExportedResource>& target /*out*/, - bool& done /*out*/, - int64_t since, - uint32_t maxResults) - { - return db_.GetExportedResources(target, done, since, maxResults); - } - - void GetLastChange(std::list<ServerIndexChange>& target /*out*/) - { - db_.GetLastChange(target); - } - - void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) - { - return db_.GetLastExportedResource(target); - } - - int64_t GetLastChangeIndex() - { - return db_.GetLastChangeIndex(); - } - - void GetMainDicomTags(DicomMap& map, - int64_t id) - { - db_.GetMainDicomTags(map, id); - } - - std::string GetPublicId(int64_t resourceId) - { - return db_.GetPublicId(resourceId); - } - - uint64_t GetResourceCount(ResourceType resourceType) - { - return db_.GetResourceCount(resourceType); - } - - ResourceType GetResourceType(int64_t resourceId) - { - return db_.GetResourceType(resourceId); - } - - uint64_t GetTotalCompressedSize() - { - return db_.GetTotalCompressedSize(); - } - - uint64_t GetTotalUncompressedSize() - { - return db_.GetTotalUncompressedSize(); - } - - bool IsProtectedPatient(int64_t internalId) - { - return db_.IsProtectedPatient(internalId); - } - - void ListAvailableAttachments(std::set<FileContentType>& target, - int64_t id) - { - db_.ListAvailableAttachments(target, id); - } - - bool LookupAttachment(FileInfo& attachment, - int64_t id, - FileContentType contentType) - { - return db_.LookupAttachment(attachment, id, contentType); - } - - bool LookupGlobalProperty(std::string& target, - GlobalProperty property) - { - return db_.LookupGlobalProperty(target, property); - } - - bool LookupMetadata(std::string& target, - int64_t id, - MetadataType type) - { - return db_.LookupMetadata(target, id, type); - } - - bool LookupParent(int64_t& parentId, - int64_t resourceId) - { - return db_.LookupParent(parentId, resourceId); - } - - bool LookupResource(int64_t& id, - ResourceType& type, - const std::string& publicId) - { - return db_.LookupResource(id, type, publicId); - } - - bool LookupResourceAndParent(int64_t& id, - ResourceType& type, - std::string& parentPublicId, - const std::string& publicId) - { - return db_.LookupResourceAndParent(id, type, parentPublicId, publicId); - } - }; - - - class ReadWriteTransaction : public ReadOnlyTransaction - { - public: - ReadWriteTransaction(IDatabaseWrapper& db, - ITransactionContext& context) : - ReadOnlyTransaction(db, context) - { - } - - void AddAttachment(int64_t id, - const FileInfo& attachment) - { - db_.AddAttachment(id, attachment); - } - - void ClearChanges() - { - db_.ClearChanges(); - } - - void ClearExportedResources() - { - db_.ClearExportedResources(); - } - - void ClearMainDicomTags(int64_t id) - { - return db_.ClearMainDicomTags(id); - } - - bool CreateInstance(IDatabaseWrapper::CreateInstanceResult& result, /* out */ - int64_t& instanceId, /* out */ - const std::string& patient, - const std::string& study, - const std::string& series, - const std::string& instance) - { - return db_.CreateInstance(result, instanceId, patient, study, series, instance); - } - - void DeleteAttachment(int64_t id, - FileContentType attachment) - { - return db_.DeleteAttachment(id, attachment); - } - - void DeleteMetadata(int64_t id, - MetadataType type) - { - db_.DeleteMetadata(id, type); - } - - void DeleteResource(int64_t id) - { - db_.DeleteResource(id); - } - - void LogChange(int64_t internalId, - ChangeType changeType, - ResourceType resourceType, - const std::string& publicId); - - void LogExportedResource(const ExportedResource& resource) - { - db_.LogExportedResource(resource); - } - - void SetGlobalProperty(GlobalProperty property, - const std::string& value) - { - db_.SetGlobalProperty(property, value); - } - - void SetMetadata(int64_t id, - MetadataType type, - const std::string& value) - { - return db_.SetMetadata(id, type, value); - } - - void SetProtectedPatient(int64_t internalId, - bool isProtected) - { - db_.SetProtectedPatient(internalId, isProtected); - } - - void SetResourcesContent(const ResourcesContent& content) - { - db_.SetResourcesContent(content); - } - - void Recycle(uint64_t maximumStorageSize, - unsigned int maximumPatients, - uint64_t addedInstanceSize, - const std::string& newPatientId); - }; - - - class IReadOnlyOperations : public boost::noncopyable - { - public: - virtual ~IReadOnlyOperations() - { - } - - virtual void Apply(ReadOnlyTransaction& transaction) = 0; - }; - - - class IReadWriteOperations : public boost::noncopyable - { - public: - virtual ~IReadWriteOperations() - { - } - - virtual void Apply(ReadWriteTransaction& transaction) = 0; - }; - - private: - void ApplyInternal(IReadOnlyOperations* readOperations, - IReadWriteOperations* writeOperations); - - std::unique_ptr<ITransactionContextFactory> factory_; - unsigned int maxRetries_; - - public: - void SetTransactionContextFactory(ITransactionContextFactory* factory /* takes ownership */); - - void Apply(IReadOnlyOperations& operations); - - void Apply(IReadWriteOperations& operations); - - bool ExpandResource(Json::Value& target, - const std::string& publicId, - ResourceType level); - - void GetAllMetadata(std::map<MetadataType, std::string>& target, - const std::string& publicId, - ResourceType level); - - void GetAllUuids(std::list<std::string>& target, - ResourceType resourceType); - - void GetAllUuids(std::list<std::string>& target, - ResourceType resourceType, - size_t since, - size_t limit); - - void GetGlobalStatistics(/* out */ uint64_t& diskSize, - /* out */ uint64_t& uncompressedSize, - /* out */ uint64_t& countPatients, - /* out */ uint64_t& countStudies, - /* out */ uint64_t& countSeries, - /* out */ uint64_t& countInstances); - - bool LookupAttachment(FileInfo& attachment, - const std::string& instancePublicId, - FileContentType contentType); - - void GetChanges(Json::Value& target, - int64_t since, - unsigned int maxResults); - - void GetLastChange(Json::Value& target); - - void GetExportedResources(Json::Value& target, - int64_t since, - unsigned int maxResults); - - void GetLastExportedResource(Json::Value& target); - - bool IsProtectedPatient(const std::string& publicId); - - void GetChildren(std::list<std::string>& result, - const std::string& publicId); - - void GetChildInstances(std::list<std::string>& result, - const std::string& publicId); - - bool LookupMetadata(std::string& target, - const std::string& publicId, - ResourceType expectedType, - MetadataType type); - - void ListAvailableAttachments(std::set<FileContentType>& target, - const std::string& publicId, - ResourceType expectedType); - - bool LookupParent(std::string& target, - const std::string& publicId); - - void GetResourceStatistics(/* out */ ResourceType& type, - /* out */ uint64_t& diskSize, - /* out */ uint64_t& uncompressedSize, - /* out */ unsigned int& countStudies, - /* out */ unsigned int& countSeries, - /* out */ unsigned int& countInstances, - /* out */ uint64_t& dicomDiskSize, - /* out */ uint64_t& dicomUncompressedSize, - const std::string& publicId); - - void LookupIdentifierExact(std::vector<std::string>& result, - ResourceType level, - const DicomTag& tag, - const std::string& value); - - bool LookupGlobalProperty(std::string& value, - GlobalProperty property); - - std::string GetGlobalProperty(GlobalProperty property, - const std::string& defaultValue); - - bool GetMainDicomTags(DicomMap& result, - const std::string& publicId, - ResourceType expectedType, - ResourceType levelOfInterest); - - // Only applicable at the instance level - bool GetAllMainDicomTags(DicomMap& result, - const std::string& instancePublicId); - - bool LookupResourceType(ResourceType& type, - const std::string& publicId); - - bool LookupParent(std::string& target, - const std::string& publicId, - ResourceType parentType); - - void ApplyLookupResources(std::vector<std::string>& resourcesId, - std::vector<std::string>* instancesId, // Can be NULL if not needed - const DatabaseLookup& lookup, - ResourceType queryLevel, - size_t limit); - - bool DeleteResource(Json::Value& target /* out */, - const std::string& uuid, - ResourceType expectedType); - - void LogExportedResource(const std::string& publicId, - const std::string& remoteModality); - - void SetProtectedPatient(const std::string& publicId, - bool isProtected); - - void SetMetadata(const std::string& publicId, - MetadataType type, - const std::string& value); - - void DeleteMetadata(const std::string& publicId, - MetadataType type); - - uint64_t IncrementGlobalSequence(GlobalProperty sequence); - - void DeleteChanges(); - - void DeleteExportedResources(); - - void SetGlobalProperty(GlobalProperty property, - const std::string& value); - - void DeleteAttachment(const std::string& publicId, - FileContentType type); - - void LogChange(ChangeType changeType, - const std::string& publicId, - ResourceType level); - - void ReconstructInstance(const ParsedDicomFile& dicom); - StoreStatus Store(std::map<MetadataType, std::string>& instanceMetadata, const DicomMap& dicomSummary, const Attachments& attachments,