Mercurial > hg > orthanc
diff OrthancServer/ServerIndex.cpp @ 1364:111e23bb4904 query-retrieve
integration mainline->query-retrieve
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 21 May 2015 16:58:30 +0200 |
parents | c2c28dd17e87 216db29c5aa9 |
children | b22ba8c5edbe |
line wrap: on
line diff
--- a/OrthancServer/ServerIndex.cpp Wed Jun 25 15:34:40 2014 +0200 +++ b/OrthancServer/ServerIndex.cpp Thu May 21 16:58:30 2015 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -37,12 +37,12 @@ #define NOMINMAX #endif +#include "ServerIndexChange.h" #include "EmbeddedResources.h" #include "OrthancInitialization.h" #include "../Core/Toolbox.h" #include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomArray.h" -#include "../Core/SQLite/Transaction.h" #include "FromDcmtkBridge.h" #include "ServerContext.h" @@ -59,16 +59,49 @@ class ServerIndexListener : public IServerIndexListener { private: + struct FileToRemove + { + private: + std::string uuid_; + FileContentType type_; + + public: + FileToRemove(const FileInfo& info) : uuid_(info.GetUuid()), + type_(info.GetContentType()) + { + } + + const std::string& GetUuid() const + { + return uuid_; + } + + FileContentType GetContentType() const + { + return type_; + } + }; + ServerContext& context_; bool hasRemainingLevel_; ResourceType remainingType_; std::string remainingPublicId_; - std::list<std::string> pendingFilesToRemove_; + std::list<FileToRemove> pendingFilesToRemove_; + std::list<ServerIndexChange> pendingChanges_; uint64_t sizeOfFilesToRemove_; + bool insideTransaction_; + + void Reset() + { + sizeOfFilesToRemove_ = 0; + hasRemainingLevel_ = false; + pendingFilesToRemove_.clear(); + pendingChanges_.clear(); + } public: - ServerIndexListener(ServerContext& context) : - context_(context) + ServerIndexListener(ServerContext& context) : context_(context), + insideTransaction_(false) { Reset(); assert(ResourceType_Patient < ResourceType_Study && @@ -76,11 +109,15 @@ ResourceType_Series < ResourceType_Instance); } - void Reset() + void StartTransaction() { - sizeOfFilesToRemove_ = 0; - hasRemainingLevel_ = false; - pendingFilesToRemove_.clear(); + Reset(); + insideTransaction_ = true; + } + + void EndTransaction() + { + insideTransaction_ = false; } uint64_t GetSizeOfFilesToRemove() @@ -90,18 +127,28 @@ void CommitFilesToRemove() { - for (std::list<std::string>::iterator + for (std::list<FileToRemove>::const_iterator it = pendingFilesToRemove_.begin(); it != pendingFilesToRemove_.end(); ++it) { - context_.RemoveFile(*it); + context_.RemoveFile(it->GetUuid(), it->GetContentType()); + } + } + + void CommitChanges() + { + for (std::list<ServerIndexChange>::const_iterator + it = pendingChanges_.begin(); + it != pendingChanges_.end(); ++it) + { + context_.SignalChange(*it); } } virtual void SignalRemainingAncestor(ResourceType parentType, const std::string& publicId) { - LOG(INFO) << "Remaining ancestor \"" << publicId << "\" (" << parentType << ")"; + VLOG(1) << "Remaining ancestor \"" << publicId << "\" (" << parentType << ")"; if (hasRemainingLevel_) { @@ -122,10 +169,26 @@ virtual void SignalFileDeleted(const FileInfo& info) { assert(Toolbox::IsUuid(info.GetUuid())); - pendingFilesToRemove_.push_back(info.GetUuid()); + pendingFilesToRemove_.push_back(FileToRemove(info)); sizeOfFilesToRemove_ += info.GetCompressedSize(); } + virtual void SignalChange(const ServerIndexChange& change) + { + VLOG(1) << "Change related to resource " << change.GetPublicId() << " of type " + << EnumerationToString(change.GetResourceType()) << ": " + << EnumerationToString(change.GetChangeType()); + + if (insideTransaction_) + { + pendingChanges_.push_back(change); + } + else + { + context_.SignalChange(change); + } + } + bool HasRemainingLevel() const { return hasRemainingLevel_; @@ -150,7 +213,7 @@ { private: ServerIndex& index_; - std::auto_ptr<SQLite::Transaction> transaction_; + std::auto_ptr<SQLite::ITransaction> transaction_; bool isCommitted_; public: @@ -158,11 +221,22 @@ index_(index), isCommitted_(false) { - assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize()); + transaction_.reset(index_.db_.StartTransaction()); + transaction_->Begin(); + + assert(index_.currentStorageSize_ == index_.db_.GetTotalCompressedSize()); + + index_.listener_->StartTransaction(); + } - index_.listener_->Reset(); - transaction_.reset(index_.db_->StartTransaction()); - transaction_->Begin(); + ~Transaction() + { + index_.listener_->EndTransaction(); + + if (!isCommitted_) + { + transaction_->Rollback(); + } } void Commit(uint64_t sizeOfAddedFiles) @@ -181,7 +255,8 @@ assert(index_.currentStorageSize_ >= index_.listener_->GetSizeOfFilesToRemove()); index_.currentStorageSize_ -= index_.listener_->GetSizeOfFilesToRemove(); - assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize()); + // Send all the pending changes to the Orthanc plugins + index_.listener_->CommitChanges(); isCommitted_ = true; } @@ -189,16 +264,22 @@ }; - struct ServerIndex::UnstableResourcePayload + class ServerIndex::UnstableResourcePayload { - Orthanc::ResourceType type_; + private: + ResourceType type_; + std::string publicId_; boost::posix_time::ptime time_; - UnstableResourcePayload() : type_(Orthanc::ResourceType_Instance) + public: + UnstableResourcePayload() : type_(ResourceType_Instance) { } - UnstableResourcePayload(Orthanc::ResourceType type) : type_(type) + UnstableResourcePayload(Orthanc::ResourceType type, + const std::string& publicId) : + type_(type), + publicId_(publicId) { time_ = boost::posix_time::second_clock::local_time(); } @@ -207,6 +288,16 @@ { return (boost::posix_time::second_clock::local_time() - time_).total_seconds(); } + + ResourceType GetResourceType() const + { + return type_; + } + + const std::string& GetPublicId() const + { + return publicId_; + } }; @@ -215,19 +306,18 @@ ResourceType expectedType) { boost::mutex::scoped_lock lock(mutex_); - listener_->Reset(); Transaction t(*this); int64_t id; ResourceType type; - if (!db_->LookupResource(uuid, id, type) || + if (!db_.LookupResource(id, type, uuid) || expectedType != type) { return false; } - db_->DeleteResource(id); + db_.DeleteResource(id); if (listener_->HasRemainingLevel()) { @@ -252,18 +342,22 @@ void ServerIndex::FlushThread(ServerIndex* that) { - unsigned int sleep; + // By default, wait for 10 seconds before flushing + unsigned int sleep = 10; try { boost::mutex::scoped_lock lock(that->mutex_); - std::string sleepString = that->db_->GetGlobalProperty(GlobalProperty_FlushSleep); - sleep = boost::lexical_cast<unsigned int>(sleepString); + std::string sleepString; + + if (that->db_.LookupGlobalProperty(sleepString, GlobalProperty_FlushSleep) && + Toolbox::IsInteger(sleepString)) + { + sleep = boost::lexical_cast<unsigned int>(sleepString); + } } catch (boost::bad_lexical_cast&) { - // By default, wait for 10 seconds before flushing - sleep = 10; } LOG(INFO) << "Starting the database flushing thread (sleep = " << sleep << ")"; @@ -280,7 +374,7 @@ } boost::mutex::scoped_lock lock(that->mutex_); - that->db_->FlushToDisk(); + that->db_.FlushToDisk(); count = 0; } @@ -288,7 +382,7 @@ } - static void ComputeExpectedNumberOfInstances(DatabaseWrapper& db, + static void ComputeExpectedNumberOfInstances(IDatabaseWrapper& db, int64_t series, const DicomMap& dicomSummary) { @@ -328,40 +422,147 @@ } + + + bool ServerIndex::GetMetadataAsInteger(int64_t& result, + int64_t id, + MetadataType type) + { + std::string s; + if (!db_.LookupMetadata(s, id, type)) + { + return false; + } + + try + { + result = boost::lexical_cast<int64_t>(s); + return true; + } + catch (boost::bad_lexical_cast&) + { + return false; + } + } + + + void ServerIndex::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); + } + + assert(listener_.get() != NULL); + listener_->SignalChange(change); + } + + + uint64_t ServerIndex::IncrementGlobalSequenceInternal(GlobalProperty property) + { + std::string oldValue; + + if (db_.LookupGlobalProperty(oldValue, property)) + { + uint64_t oldNumber; + + try + { + oldNumber = boost::lexical_cast<uint64_t>(oldValue); + db_.SetGlobalProperty(property, boost::lexical_cast<std::string>(oldNumber + 1)); + return oldNumber + 1; + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_InternalError); + } + } + else + { + // Initialize the sequence at "1" + db_.SetGlobalProperty(property, "1"); + return 1; + } + } + + + + void ServerIndex::SetMainDicomTags(int64_t resource, + const DicomMap& tags) + { + DicomArray flattened(tags); + for (size_t i = 0; i < flattened.GetSize(); i++) + { + const DicomElement& element = flattened.GetElement(i); + db_.SetMainDicomTag(resource, element.GetTag(), element.GetValue().AsString()); + } + } + + + int64_t ServerIndex::CreateResource(const std::string& publicId, + ResourceType type) + { + int64_t id = db_.CreateResource(publicId, type); + + ChangeType changeType; + switch (type) + { + case ResourceType_Patient: + changeType = ChangeType_NewPatient; + break; + + case ResourceType_Study: + changeType = ChangeType_NewStudy; + break; + + case ResourceType_Series: + changeType = ChangeType_NewSeries; + break; + + case ResourceType_Instance: + changeType = ChangeType_NewInstance; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + ServerIndexChange change(changeType, type, publicId); + db_.LogChange(id, change); + + assert(listener_.get() != NULL); + listener_->SignalChange(change); + + return id; + } + + ServerIndex::ServerIndex(ServerContext& context, - const std::string& dbPath) : + IDatabaseWrapper& db) : done_(false), + db_(db), maximumStorageSize_(0), maximumPatients_(0) { listener_.reset(new Internals::ServerIndexListener(context)); - - if (dbPath == ":memory:") - { - db_.reset(new DatabaseWrapper(*listener_)); - } - else - { - boost::filesystem::path p = dbPath; + db_.SetListener(*listener_); - try - { - boost::filesystem::create_directories(p); - } - catch (boost::filesystem::filesystem_error) - { - } - - db_.reset(new DatabaseWrapper(p.string() + "/index", *listener_)); - } - - currentStorageSize_ = db_->GetTotalCompressedSize(); + currentStorageSize_ = db_.GetTotalCompressedSize(); // Initial recycling if the parameters have changed since the last // execution of Orthanc StandaloneRecycling(); - flushThread_ = boost::thread(FlushThread, this); + if (db.HasFlushToDisk()) + { + flushThread_ = boost::thread(FlushThread, this); + } + unstableResourcesMonitorThread_ = boost::thread(UnstableResourcesMonitorThread, this); } @@ -370,7 +571,8 @@ { done_ = true; - if (flushThread_.joinable()) + if (db_.HasFlushToDisk() && + flushThread_.joinable()) { flushThread_.join(); } @@ -382,12 +584,15 @@ } - StoreStatus ServerIndex::Store(const DicomMap& dicomSummary, + StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata, + const DicomMap& dicomSummary, const Attachments& attachments, - const std::string& remoteAet) + const std::string& remoteAet, + const MetadataMap& metadata) { boost::mutex::scoped_lock lock(mutex_); - listener_->Reset(); + + instanceMetadata.clear(); DicomInstanceHasher hasher(dicomSummary); @@ -399,9 +604,10 @@ { ResourceType type; int64_t tmp; - if (db_->LookupResource(hasher.HashInstance(), tmp, type)) + if (db_.LookupResource(tmp, type, hasher.HashInstance())) { assert(type == ResourceType_Instance); + db_.GetAllMetadata(instanceMetadata, tmp); return StoreStatus_AlreadyStored; } } @@ -417,11 +623,11 @@ Recycle(instanceSize, hasher.HashPatient()); // Create the instance - int64_t instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance); + int64_t instance = CreateResource(hasher.HashInstance(), ResourceType_Instance); DicomMap dicom; dicomSummary.ExtractInstanceInformation(dicom); - db_->SetMainDicomTags(instance, dicom); + SetMainDicomTags(instance, dicom); // Detect up to which level the patient/study/series/instance // hierarchy must be created @@ -433,26 +639,26 @@ { ResourceType dummy; - if (db_->LookupResource(hasher.HashSeries(), series, dummy)) + if (db_.LookupResource(series, dummy, hasher.HashSeries())) { assert(dummy == ResourceType_Series); // The patient, the study and the series already exist - bool ok = (db_->LookupResource(hasher.HashPatient(), patient, dummy) && - db_->LookupResource(hasher.HashStudy(), study, dummy)); + bool ok = (db_.LookupResource(patient, dummy, hasher.HashPatient()) && + db_.LookupResource(study, dummy, hasher.HashStudy())); assert(ok); } - else if (db_->LookupResource(hasher.HashStudy(), study, dummy)) + else if (db_.LookupResource(study, dummy, hasher.HashStudy())) { assert(dummy == ResourceType_Study); // New series: The patient and the study already exist isNewSeries = true; - bool ok = db_->LookupResource(hasher.HashPatient(), patient, dummy); + bool ok = db_.LookupResource(patient, dummy, hasher.HashPatient()); assert(ok); } - else if (db_->LookupResource(hasher.HashPatient(), patient, dummy)) + else if (db_.LookupResource(patient, dummy, hasher.HashPatient())) { assert(dummy == ResourceType_Patient); @@ -472,38 +678,38 @@ // Create the series if needed if (isNewSeries) { - series = db_->CreateResource(hasher.HashSeries(), ResourceType_Series); + series = CreateResource(hasher.HashSeries(), ResourceType_Series); dicomSummary.ExtractSeriesInformation(dicom); - db_->SetMainDicomTags(series, dicom); + SetMainDicomTags(series, dicom); } // Create the study if needed if (isNewStudy) { - study = db_->CreateResource(hasher.HashStudy(), ResourceType_Study); + study = CreateResource(hasher.HashStudy(), ResourceType_Study); dicomSummary.ExtractStudyInformation(dicom); - db_->SetMainDicomTags(study, dicom); + SetMainDicomTags(study, dicom); } // Create the patient if needed if (isNewPatient) { - patient = db_->CreateResource(hasher.HashPatient(), ResourceType_Patient); + patient = CreateResource(hasher.HashPatient(), ResourceType_Patient); dicomSummary.ExtractPatientInformation(dicom); - db_->SetMainDicomTags(patient, dicom); + SetMainDicomTags(patient, dicom); } // Create the parent-to-child links - db_->AttachChild(series, instance); + db_.AttachChild(series, instance); if (isNewSeries) { - db_->AttachChild(study, series); + db_.AttachChild(study, series); } if (isNewStudy) { - db_->AttachChild(patient, study); + db_.AttachChild(patient, study); } // Sanity checks @@ -516,40 +722,75 @@ for (Attachments::const_iterator it = attachments.begin(); it != attachments.end(); ++it) { - db_->AddAttachment(instance, *it); + db_.AddAttachment(instance, *it); } - // Attach the metadata + // Attach the user-specified metadata + for (MetadataMap::const_iterator + it = metadata.begin(); it != metadata.end(); ++it) + { + switch (it->first.first) + { + case ResourceType_Patient: + db_.SetMetadata(patient, it->first.second, it->second); + break; + + case ResourceType_Study: + db_.SetMetadata(study, it->first.second, it->second); + break; + + case ResourceType_Series: + db_.SetMetadata(series, it->first.second, it->second); + break; + + case ResourceType_Instance: + db_.SetMetadata(instance, it->first.second, it->second); + instanceMetadata[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 = Toolbox::GetNowIsoString(); - db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, now); - db_->SetMetadata(series, MetadataType_LastUpdate, now); - db_->SetMetadata(study, MetadataType_LastUpdate, now); - db_->SetMetadata(patient, MetadataType_LastUpdate, now); - db_->SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet); + db_.SetMetadata(series, MetadataType_LastUpdate, now); + db_.SetMetadata(study, MetadataType_LastUpdate, now); + db_.SetMetadata(patient, MetadataType_LastUpdate, now); + + // Attach the auto-computed metadata for the instance level, + // reflecting these additions into the input metadata map + db_.SetMetadata(instance, MetadataType_Instance_ReceptionDate, now); + instanceMetadata[MetadataType_Instance_ReceptionDate] = now; + + db_.SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet); + instanceMetadata[MetadataType_Instance_RemoteAet] = remoteAet; const DicomValue* value; if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL || (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL) { - db_->SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString()); - } - - if (isNewSeries) - { - ComputeExpectedNumberOfInstances(*db_, series, dicomSummary); + db_.SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString()); + instanceMetadata[MetadataType_Instance_IndexInSeries] = value->AsString(); } // Check whether the series of this new instance is now completed + if (isNewSeries) + { + ComputeExpectedNumberOfInstances(db_, series, dicomSummary); + } + SeriesStatus seriesStatus = GetSeriesStatus(series); if (seriesStatus == SeriesStatus_Complete) { - db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series); + LogChange(series, ChangeType_CompletedSeries, ResourceType_Series, hasher.HashSeries()); } // Mark the parent resources of this instance as unstable - MarkAsUnstable(patient, ResourceType_Patient); - MarkAsUnstable(study, ResourceType_Study); - MarkAsUnstable(series, ResourceType_Series); + MarkAsUnstable(series, ResourceType_Series, hasher.HashSeries()); + MarkAsUnstable(study, ResourceType_Study, hasher.HashStudy()); + MarkAsUnstable(patient, ResourceType_Patient, hasher.HashPatient()); t.Commit(instanceSize); @@ -557,8 +798,7 @@ } catch (OrthancException& e) { - LOG(ERROR) << "EXCEPTION [" << e.What() << "]" - << " (SQLite status: " << db_->GetErrorMessage() << ")"; + LOG(ERROR) << "EXCEPTION [" << e.What() << "]"; } return StoreStatus_Failure; @@ -571,17 +811,17 @@ target = Json::objectValue; uint64_t cs = currentStorageSize_; - assert(cs == db_->GetTotalCompressedSize()); - uint64_t us = db_->GetTotalUncompressedSize(); + assert(cs == db_.GetTotalCompressedSize()); + uint64_t us = db_.GetTotalUncompressedSize(); target["TotalDiskSize"] = boost::lexical_cast<std::string>(cs); target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us); - target["TotalDiskSizeMB"] = boost::lexical_cast<unsigned int>(cs / MEGA_BYTES); - target["TotalUncompressedSizeMB"] = boost::lexical_cast<unsigned int>(us / MEGA_BYTES); + target["TotalDiskSizeMB"] = static_cast<unsigned int>(cs / MEGA_BYTES); + target["TotalUncompressedSizeMB"] = static_cast<unsigned int>(us / MEGA_BYTES); - target["CountPatients"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Patient)); - target["CountStudies"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Study)); - target["CountSeries"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Series)); - target["CountInstances"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Instance)); + target["CountPatients"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Patient)); + target["CountStudies"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Study)); + target["CountSeries"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Series)); + target["CountInstances"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Instance)); } @@ -589,34 +829,23 @@ SeriesStatus ServerIndex::GetSeriesStatus(int64_t id) { // Get the expected number of instances in this series (from the metadata) - std::string s = db_->GetMetadata(id, MetadataType_Series_ExpectedNumberOfInstances); - - size_t expected; - try - { - expected = boost::lexical_cast<size_t>(s); - } - catch (boost::bad_lexical_cast&) + int64_t expected; + if (!GetMetadataAsInteger(expected, id, MetadataType_Series_ExpectedNumberOfInstances)) { return SeriesStatus_Unknown; } // Loop over the instances of this series std::list<int64_t> children; - db_->GetChildrenInternalId(children, id); + db_.GetChildrenInternalId(children, id); - std::set<size_t> instances; + std::set<int64_t> instances; for (std::list<int64_t>::const_iterator it = children.begin(); it != children.end(); ++it) { // Get the index of this instance in the series - s = db_->GetMetadata(*it, MetadataType_Instance_IndexInSeries); - size_t index; - try - { - index = boost::lexical_cast<size_t>(s); - } - catch (boost::bad_lexical_cast&) + int64_t index; + if (!GetMetadataAsInteger(index, *it, MetadataType_Instance_IndexInSeries)) { return SeriesStatus_Unknown; } @@ -636,7 +865,7 @@ instances.insert(index); } - if (instances.size() == expected) + if (static_cast<int64_t>(instances.size()) == expected) { return SeriesStatus_Complete; } @@ -652,7 +881,7 @@ int64_t resourceId) { DicomMap tags; - db_->GetMainDicomTags(tags, resourceId); + db_.GetMainDicomTags(tags, resourceId); target["MainDicomTags"] = Json::objectValue; FromDcmtkBridge::ToJson(target["MainDicomTags"], tags); } @@ -668,7 +897,7 @@ // Lookup for the requested resource int64_t id; ResourceType type; - if (!db_->LookupResource(publicId, id, type) || + if (!db_.LookupResource(id, type, publicId) || type != expectedType) { return false; @@ -678,12 +907,12 @@ if (type != ResourceType_Patient) { int64_t parentId; - if (!db_->LookupParent(parentId, id)) + if (!db_.LookupParent(parentId, id)) { throw OrthancException(ErrorCode_InternalError); } - std::string parent = db_->GetPublicId(parentId); + std::string parent = db_.GetPublicId(parentId); switch (type) { @@ -706,7 +935,7 @@ // List the children resources std::list<std::string> children; - db_->GetChildrenPublicId(children, id); + db_.GetChildrenPublicId(children, id); if (type != ResourceType_Instance) { @@ -753,9 +982,9 @@ result["Type"] = "Series"; result["Status"] = EnumerationToString(GetSeriesStatus(id)); - int i; - if (db_->GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances)) - result["ExpectedNumberOfInstances"] = i; + int64_t i; + if (GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances)) + result["ExpectedNumberOfInstances"] = static_cast<int>(i); else result["ExpectedNumberOfInstances"] = Json::nullValue; @@ -767,7 +996,7 @@ result["Type"] = "Instance"; FileInfo attachment; - if (!db_->LookupAttachment(attachment, id, FileContentType_Dicom)) + if (!db_.LookupAttachment(attachment, id, FileContentType_Dicom)) { throw OrthancException(ErrorCode_InternalError); } @@ -775,9 +1004,9 @@ result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize()); result["FileUuid"] = attachment.GetUuid(); - int i; - if (db_->GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries)) - result["IndexInSeries"] = i; + int64_t i; + if (GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries)) + result["IndexInSeries"] = static_cast<int>(i); else result["IndexInSeries"] = Json::nullValue; @@ -794,19 +1023,26 @@ std::string tmp; - tmp = db_->GetMetadata(id, MetadataType_AnonymizedFrom); - if (tmp.size() != 0) + if (db_.LookupMetadata(tmp, id, MetadataType_AnonymizedFrom)) + { result["AnonymizedFrom"] = tmp; + } - tmp = db_->GetMetadata(id, MetadataType_ModifiedFrom); - if (tmp.size() != 0) + if (db_.LookupMetadata(tmp, id, MetadataType_ModifiedFrom)) + { result["ModifiedFrom"] = tmp; + } if (type == ResourceType_Patient || type == ResourceType_Study || type == ResourceType_Series) { result["IsStable"] = !unstableResources_.Contains(id); + + if (db_.LookupMetadata(tmp, id, MetadataType_LastUpdate)) + { + result["LastUpdate"] = tmp; + } } return true; @@ -821,12 +1057,12 @@ int64_t id; ResourceType type; - if (!db_->LookupResource(instanceUuid, id, type)) + if (!db_.LookupResource(id, type, instanceUuid)) { - throw OrthancException(ErrorCode_InternalError); + throw OrthancException(ErrorCode_UnknownResource); } - if (db_->LookupAttachment(attachment, id, contentType)) + if (db_.LookupAttachment(attachment, id, contentType)) { assert(attachment.GetContentType() == contentType); return true; @@ -839,38 +1075,77 @@ - void ServerIndex::GetAllUuids(Json::Value& target, + void ServerIndex::GetAllUuids(std::list<std::string>& target, ResourceType resourceType) { boost::mutex::scoped_lock lock(mutex_); - db_->GetAllPublicIds(target, resourceType); + db_.GetAllPublicIds(target, resourceType); } - bool ServerIndex::GetChanges(Json::Value& target, + template <typename T> + static void FormatLog(Json::Value& target, + const std::list<T>& log, + const std::string& name, + bool done, + int64_t since) + { + 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; + + int64_t last = (log.empty() ? since : log.back().GetSeq()); + target["Last"] = static_cast<int>(last); + } + + + void ServerIndex::GetChanges(Json::Value& target, int64_t since, unsigned int maxResults) { - boost::mutex::scoped_lock lock(mutex_); - db_->GetChanges(target, since, maxResults); - return true; + std::list<ServerIndexChange> changes; + bool done; + + { + boost::mutex::scoped_lock lock(mutex_); + db_.GetChanges(changes, done, since, maxResults); + } + + FormatLog(target, changes, "Changes", done, since); } - bool ServerIndex::GetLastChange(Json::Value& target) + + void ServerIndex::GetLastChange(Json::Value& target) { - boost::mutex::scoped_lock lock(mutex_); - db_->GetLastChange(target); - return true; + std::list<ServerIndexChange> changes; + + { + boost::mutex::scoped_lock lock(mutex_); + db_.GetLastChange(changes); + } + + FormatLog(target, changes, "Changes", true, 0); } + void ServerIndex::LogExportedResource(const std::string& publicId, const std::string& remoteModality) { boost::mutex::scoped_lock lock(mutex_); + Transaction transaction(*this); int64_t id; ResourceType type; - if (!db_->LookupResource(publicId, id, type)) + if (!db_.LookupResource(id, type, publicId)) { throw OrthancException(ErrorCode_InternalError); } @@ -888,7 +1163,7 @@ while (!done) { DicomMap map; - db_->GetMainDicomTags(map, currentId); + db_.GetMainDicomTags(map, currentId); switch (currentType) { @@ -920,36 +1195,52 @@ // the current resource if (!done) { - bool ok = db_->LookupParent(currentId, currentId); + bool ok = db_.LookupParent(currentId, currentId); assert(ok); } } - // No need for a SQLite::Transaction here, as we only insert 1 record - db_->LogExportedResource(type, - publicId, - remoteModality, - patientId, - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid); + ExportedResource resource(-1, + type, + publicId, + remoteModality, + Toolbox::GetNowIsoString(), + patientId, + studyInstanceUid, + seriesInstanceUid, + sopInstanceUid); + + db_.LogExportedResource(resource); + transaction.Commit(0); } - bool ServerIndex::GetExportedResources(Json::Value& target, + void ServerIndex::GetExportedResources(Json::Value& target, int64_t since, unsigned int maxResults) { - boost::mutex::scoped_lock lock(mutex_); - db_->GetExportedResources(target, since, maxResults); - return true; + std::list<ExportedResource> exported; + bool done; + + { + boost::mutex::scoped_lock lock(mutex_); + db_.GetExportedResources(exported, done, since, maxResults); + } + + FormatLog(target, exported, "Exports", done, since); } - bool ServerIndex::GetLastExportedResource(Json::Value& target) + + void ServerIndex::GetLastExportedResource(Json::Value& target) { - boost::mutex::scoped_lock lock(mutex_); - db_->GetLastExportedResource(target); - return true; + std::list<ExportedResource> exported; + + { + boost::mutex::scoped_lock lock(mutex_); + db_.GetLastExportedResource(exported); + } + + FormatLog(target, exported, "Exports", true, 0); } @@ -958,7 +1249,7 @@ if (maximumStorageSize_ != 0) { uint64_t currentSize = currentStorageSize_ - listener_->GetSizeOfFilesToRemove(); - assert(db_->GetTotalCompressedSize() == currentSize); + assert(db_.GetTotalCompressedSize() == currentSize); if (currentSize + instanceSize > maximumStorageSize_) { @@ -968,7 +1259,7 @@ if (maximumPatients_ != 0) { - uint64_t patientCount = db_->GetResourceCount(ResourceType_Patient); + uint64_t patientCount = db_.GetResourceCount(ResourceType_Patient); if (patientCount > maximumPatients_) { return true; @@ -991,7 +1282,7 @@ // already stored int64_t patientToAvoid; ResourceType type; - bool hasPatientToAvoid = db_->LookupResource(newPatientId, patientToAvoid, type); + bool hasPatientToAvoid = db_.LookupResource(patientToAvoid, type, newPatientId); if (hasPatientToAvoid && type != ResourceType_Patient) { @@ -1006,16 +1297,16 @@ // 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); + db_.SelectPatientToRecycle(patientToRecycle, patientToAvoid) : + db_.SelectPatientToRecycle(patientToRecycle); if (!ok) { throw OrthancException(ErrorCode_FullStorage); } - LOG(INFO) << "Recycling one patient"; - db_->DeleteResource(patientToRecycle); + VLOG(1) << "Recycling one patient"; + db_.DeleteResource(patientToRecycle); if (!IsRecyclingNeeded(instanceSize)) { @@ -1075,13 +1366,13 @@ // Lookup for the requested resource int64_t id; ResourceType type; - if (!db_->LookupResource(publicId, id, type) || + if (!db_.LookupResource(id, type, publicId) || type != ResourceType_Patient) { throw OrthancException(ErrorCode_ParameterOutOfRange); } - return db_->IsProtectedPatient(id); + return db_.IsProtectedPatient(id); } @@ -1089,18 +1380,19 @@ bool isProtected) { boost::mutex::scoped_lock lock(mutex_); + Transaction transaction(*this); // Lookup for the requested resource int64_t id; ResourceType type; - if (!db_->LookupResource(publicId, id, type) || + if (!db_.LookupResource(id, type, publicId) || type != ResourceType_Patient) { throw OrthancException(ErrorCode_ParameterOutOfRange); } - // No need for a SQLite::Transaction here, as we only make 1 write to the DB - db_->SetProtectedPatient(id, isProtected); + db_.SetProtectedPatient(id, isProtected); + transaction.Commit(0); if (isProtected) LOG(INFO) << "Patient " << publicId << " has been protected"; @@ -1118,7 +1410,7 @@ ResourceType type; int64_t resource; - if (!db_->LookupResource(publicId, resource, type)) + if (!db_.LookupResource(resource, type, publicId)) { throw OrthancException(ErrorCode_UnknownResource); } @@ -1130,12 +1422,12 @@ } std::list<int64_t> tmp; - db_->GetChildrenInternalId(tmp, resource); + db_.GetChildrenInternalId(tmp, resource); for (std::list<int64_t>::const_iterator it = tmp.begin(); it != tmp.end(); ++it) { - result.push_back(db_->GetPublicId(*it)); + result.push_back(db_.GetPublicId(*it)); } } @@ -1149,7 +1441,7 @@ ResourceType type; int64_t top; - if (!db_->LookupResource(publicId, top, type)) + if (!db_.LookupResource(top, type, publicId)) { throw OrthancException(ErrorCode_UnknownResource); } @@ -1172,14 +1464,14 @@ int64_t resource = toExplore.top(); toExplore.pop(); - if (db_->GetResourceType(resource) == ResourceType_Instance) + if (db_.GetResourceType(resource) == ResourceType_Instance) { - result.push_back(db_->GetPublicId(resource)); + result.push_back(db_.GetPublicId(resource)); } else { // Tag all the children of this resource as to be explored - db_->GetChildrenInternalId(tmp, resource); + db_.GetChildrenInternalId(tmp, resource); for (std::list<int64_t>::const_iterator it = tmp.begin(); it != tmp.end(); ++it) { @@ -1198,12 +1490,12 @@ ResourceType rtype; int64_t id; - if (!db_->LookupResource(publicId, id, rtype)) + if (!db_.LookupResource(id, rtype, publicId)) { throw OrthancException(ErrorCode_UnknownResource); } - db_->SetMetadata(id, type, value); + db_.SetMetadata(id, type, value); } @@ -1214,12 +1506,12 @@ ResourceType rtype; int64_t id; - if (!db_->LookupResource(publicId, id, rtype)) + if (!db_.LookupResource(id, rtype, publicId)) { throw OrthancException(ErrorCode_UnknownResource); } - db_->DeleteMetadata(id, type); + db_.DeleteMetadata(id, type); } @@ -1231,12 +1523,12 @@ ResourceType rtype; int64_t id; - if (!db_->LookupResource(publicId, id, rtype)) + if (!db_.LookupResource(id, rtype, publicId)) { throw OrthancException(ErrorCode_UnknownResource); } - return db_->LookupMetadata(target, id, type); + return db_.LookupMetadata(target, id, type); } @@ -1247,12 +1539,12 @@ ResourceType rtype; int64_t id; - if (!db_->LookupResource(publicId, id, rtype)) + if (!db_.LookupResource(id, rtype, publicId)) { throw OrthancException(ErrorCode_UnknownResource); } - db_->ListAvailableMetadata(target, id); + db_.ListAvailableMetadata(target, id); } @@ -1264,13 +1556,13 @@ ResourceType type; int64_t id; - if (!db_->LookupResource(publicId, id, type) || + if (!db_.LookupResource(id, type, publicId) || expectedType != type) { throw OrthancException(ErrorCode_UnknownResource); } - db_->ListAvailableAttachments(target, id); + db_.ListAvailableAttachments(target, id); } @@ -1281,15 +1573,15 @@ ResourceType type; int64_t id; - if (!db_->LookupResource(publicId, id, type)) + if (!db_.LookupResource(id, type, publicId)) { throw OrthancException(ErrorCode_UnknownResource); } int64_t parentId; - if (db_->LookupParent(parentId, id)) + if (db_.LookupParent(parentId, id)) { - target = db_->GetPublicId(parentId); + target = db_.GetPublicId(parentId); return true; } else @@ -1302,12 +1594,10 @@ uint64_t ServerIndex::IncrementGlobalSequence(GlobalProperty sequence) { boost::mutex::scoped_lock lock(mutex_); - - std::auto_ptr<SQLite::Transaction> transaction(db_->StartTransaction()); + Transaction transaction(*this); - transaction->Begin(); - uint64_t seq = db_->IncrementGlobalSequence(sequence); - transaction->Commit(); + uint64_t seq = IncrementGlobalSequenceInternal(sequence); + transaction.Commit(0); return seq; } @@ -1318,32 +1608,30 @@ const std::string& publicId) { boost::mutex::scoped_lock lock(mutex_); - std::auto_ptr<SQLite::Transaction> transaction(db_->StartTransaction()); - transaction->Begin(); + Transaction transaction(*this); int64_t id; ResourceType type; - if (!db_->LookupResource(publicId, id, type)) + if (!db_.LookupResource(id, type, publicId)) { throw OrthancException(ErrorCode_UnknownResource); } - db_->LogChange(changeType, id, type); - - transaction->Commit(); + LogChange(id, changeType, type, publicId); + transaction.Commit(0); } void ServerIndex::DeleteChanges() { boost::mutex::scoped_lock lock(mutex_); - db_->ClearTable("Changes"); + db_.ClearChanges(); } void ServerIndex::DeleteExportedResources() { boost::mutex::scoped_lock lock(mutex_); - db_->ClearTable("ExportedResources"); + db_.ClearExportedResources(); } @@ -1370,16 +1658,16 @@ int64_t resource = toExplore.top(); toExplore.pop(); - ResourceType thisType = db_->GetResourceType(resource); + ResourceType thisType = db_.GetResourceType(resource); std::list<FileContentType> f; - db_->ListAvailableAttachments(f, resource); + db_.ListAvailableAttachments(f, resource); for (std::list<FileContentType>::const_iterator it = f.begin(); it != f.end(); ++it) { FileInfo attachment; - if (db_->LookupAttachment(attachment, resource, *it)) + if (db_.LookupAttachment(attachment, resource, *it)) { compressedSize += attachment.GetCompressedSize(); uncompressedSize += attachment.GetUncompressedSize(); @@ -1408,7 +1696,7 @@ // Tag all the children of this resource as to be explored std::list<int64_t> tmp; - db_->GetChildrenInternalId(tmp, resource); + db_.GetChildrenInternalId(tmp, resource); for (std::list<int64_t>::const_iterator it = tmp.begin(); it != tmp.end(); ++it) { @@ -1437,7 +1725,7 @@ ResourceType type; int64_t top; - if (!db_->LookupResource(publicId, top, type)) + if (!db_.LookupResource(top, type, publicId)) { throw OrthancException(ErrorCode_UnknownResource); } @@ -1452,9 +1740,9 @@ target = Json::objectValue; target["DiskSize"] = boost::lexical_cast<std::string>(compressedSize); - target["DiskSizeMB"] = boost::lexical_cast<unsigned int>(compressedSize / MEGA_BYTES); + target["DiskSizeMB"] = static_cast<unsigned int>(compressedSize / MEGA_BYTES); target["UncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize); - target["UncompressedSizeMB"] = boost::lexical_cast<unsigned int>(uncompressedSize / MEGA_BYTES); + target["UncompressedSizeMB"] = static_cast<unsigned int>(uncompressedSize / MEGA_BYTES); switch (type) { @@ -1486,7 +1774,7 @@ ResourceType type; int64_t top; - if (!db_->LookupResource(publicId, top, type)) + if (!db_.LookupResource(top, type, publicId)) { throw OrthancException(ErrorCode_UnknownResource); } @@ -1523,20 +1811,20 @@ int64_t id = that->unstableResources_.RemoveOldest(payload); // Ensure that the resource is still existing before logging the change - if (that->db_->IsExistingResource(id)) + if (that->db_.IsExistingResource(id)) { - switch (payload.type_) + switch (payload.GetResourceType()) { - case Orthanc::ResourceType_Patient: - that->db_->LogChange(ChangeType_StablePatient, id, ResourceType_Patient); + case ResourceType_Patient: + that->LogChange(id, ChangeType_StablePatient, ResourceType_Patient, payload.GetPublicId()); break; - case Orthanc::ResourceType_Study: - that->db_->LogChange(ChangeType_StableStudy, id, ResourceType_Study); + case ResourceType_Study: + that->LogChange(id, ChangeType_StableStudy, ResourceType_Study, payload.GetPublicId()); break; - case Orthanc::ResourceType_Series: - that->db_->LogChange(ChangeType_StableSeries, id, ResourceType_Series); + case ResourceType_Series: + that->LogChange(id, ChangeType_StableSeries, ResourceType_Series, payload.GetPublicId()); break; default: @@ -1553,7 +1841,8 @@ void ServerIndex::MarkAsUnstable(int64_t id, - Orthanc::ResourceType type) + Orthanc::ResourceType type, + const std::string& publicId) { // WARNING: Before calling this method, "mutex_" must be locked. @@ -1561,68 +1850,72 @@ type == Orthanc::ResourceType_Study || type == Orthanc::ResourceType_Series); - unstableResources_.AddOrMakeMostRecent(id, type); + UnstableResourcePayload payload(type, publicId); + unstableResources_.AddOrMakeMostRecent(id, payload); //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id; + + LogChange(id, ChangeType_NewChildInstance, type, publicId); } - void ServerIndex::LookupTagValue(std::list<std::string>& result, - DicomTag tag, - const std::string& value, - ResourceType type) + void ServerIndex::LookupIdentifier(std::list<std::string>& result, + const DicomTag& tag, + const std::string& value, + ResourceType type) { result.clear(); boost::mutex::scoped_lock lock(mutex_); std::list<int64_t> id; - db_->LookupTagValue(id, tag, value); + db_.LookupIdentifier(id, tag, value); for (std::list<int64_t>::const_iterator it = id.begin(); it != id.end(); ++it) { - if (db_->GetResourceType(*it) == type) + if (db_.GetResourceType(*it) == type) { - result.push_back(db_->GetPublicId(*it)); + result.push_back(db_.GetPublicId(*it)); } } } - void ServerIndex::LookupTagValue(std::list<std::string>& result, - DicomTag tag, - const std::string& value) + void ServerIndex::LookupIdentifier(std::list<std::string>& result, + const DicomTag& tag, + const std::string& value) { result.clear(); boost::mutex::scoped_lock lock(mutex_); std::list<int64_t> id; - db_->LookupTagValue(id, tag, value); + db_.LookupIdentifier(id, tag, value); for (std::list<int64_t>::const_iterator it = id.begin(); it != id.end(); ++it) { - result.push_back(db_->GetPublicId(*it)); + result.push_back(db_.GetPublicId(*it)); } } - void ServerIndex::LookupTagValue(std::list<std::string>& result, - const std::string& value) + void ServerIndex::LookupIdentifier(std::list< std::pair<ResourceType, std::string> >& result, + const std::string& value) { result.clear(); boost::mutex::scoped_lock lock(mutex_); std::list<int64_t> id; - db_->LookupTagValue(id, value); + db_.LookupIdentifier(id, value); for (std::list<int64_t>::const_iterator it = id.begin(); it != id.end(); ++it) { - result.push_back(db_->GetPublicId(*it)); + result.push_back(std::make_pair(db_.GetResourceType(*it), + db_.GetPublicId(*it))); } } @@ -1636,20 +1929,20 @@ ResourceType resourceType; int64_t resourceId; - if (!db_->LookupResource(publicId, resourceId, resourceType)) + if (!db_.LookupResource(resourceId, resourceType, publicId)) { return StoreStatus_Failure; // Inexistent resource } // Remove possible previous attachment - db_->DeleteAttachment(resourceId, attachment.GetContentType()); + db_.DeleteAttachment(resourceId, attachment.GetContentType()); // Locate the patient of the target resource int64_t patientId = resourceId; for (;;) { int64_t parent; - if (db_->LookupParent(parent, patientId)) + if (db_.LookupParent(parent, patientId)) { // We have not reached the patient level yet patientId = parent; @@ -1662,10 +1955,10 @@ } // Possibly apply the recycling mechanism while preserving this patient - assert(db_->GetResourceType(patientId) == ResourceType_Patient); - Recycle(attachment.GetCompressedSize(), db_->GetPublicId(patientId)); + assert(db_.GetResourceType(patientId) == ResourceType_Patient); + Recycle(attachment.GetCompressedSize(), db_.GetPublicId(patientId)); - db_->AddAttachment(resourceId, attachment); + db_.AddAttachment(resourceId, attachment); t.Commit(attachment.GetCompressedSize()); @@ -1677,21 +1970,101 @@ FileContentType type) { boost::mutex::scoped_lock lock(mutex_); - listener_->Reset(); - Transaction t(*this); ResourceType rtype; int64_t id; - if (!db_->LookupResource(publicId, id, rtype)) + if (!db_.LookupResource(id, rtype, publicId)) { throw OrthancException(ErrorCode_UnknownResource); } - db_->DeleteAttachment(id, type); + db_.DeleteAttachment(id, type); t.Commit(0); } + bool ServerIndex::GetMetadata(Json::Value& target, + const std::string& publicId) + { + boost::mutex::scoped_lock lock(mutex_); + + target = Json::objectValue; + + ResourceType type; + int64_t id; + if (!db_.LookupResource(id, type, publicId)) + { + return false; + } + + std::list<MetadataType> metadata; + db_.ListAvailableMetadata(metadata, id); + + for (std::list<MetadataType>::const_iterator + it = metadata.begin(); it != metadata.end(); ++it) + { + std::string key = EnumerationToString(*it); + + std::string value; + if (!db_.LookupMetadata(value, id, *it)) + { + value.clear(); + } + + target[key] = value; + } + + return true; + } + + + void ServerIndex::SetGlobalProperty(GlobalProperty property, + const std::string& value) + { + boost::mutex::scoped_lock lock(mutex_); + db_.SetGlobalProperty(property, value); + } + + + std::string ServerIndex::GetGlobalProperty(GlobalProperty property, + const std::string& defaultValue) + { + boost::mutex::scoped_lock lock(mutex_); + + std::string value; + if (db_.LookupGlobalProperty(value, property)) + { + return value; + } + else + { + return defaultValue; + } + } + + + bool ServerIndex::GetMainDicomTags(DicomMap& result, + const std::string& publicId, + ResourceType expectedType) + { + result.Clear(); + + boost::mutex::scoped_lock lock(mutex_); + + // Lookup for the requested resource + int64_t id; + ResourceType type; + if (!db_.LookupResource(id, type, publicId) || + type != expectedType) + { + return false; + } + else + { + db_.GetMainDicomTags(result, id); + return true; + } + } }