Mercurial > hg > orthanc
changeset 1197:61b71ccac362 db-changes
integration mainline->db-changes
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 23 Oct 2014 13:19:18 +0200 |
parents | 669bb978d52e (diff) 97089aa85b5f (current diff) |
children | 1169528a9a5f |
files | NEWS |
diffstat | 24 files changed, 713 insertions(+), 117 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Thu Oct 23 13:14:58 2014 +0200 +++ b/CMakeLists.txt Thu Oct 23 13:19:18 2014 +0200 @@ -264,6 +264,7 @@ set(EMBEDDED_FILES PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql UPGRADE_DATABASE_3_TO_4 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade3To4.sql + UPGRADE_DATABASE_4_TO_5 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade4To5.sql CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json DICOM_CONFORMANCE_STATEMENT ${CMAKE_CURRENT_SOURCE_DIR}/Resources/DicomConformanceStatement.txt LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua
--- a/Core/DicomFormat/DicomTag.cpp Thu Oct 23 13:14:58 2014 +0200 +++ b/Core/DicomFormat/DicomTag.cpp Thu Oct 23 13:19:18 2014 +0200 @@ -244,4 +244,14 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } + + + bool DicomTag::IsIdentifier() const + { + return (*this == DICOM_TAG_PATIENT_ID || + *this == DICOM_TAG_STUDY_INSTANCE_UID || + *this == DICOM_TAG_ACCESSION_NUMBER || + *this == DICOM_TAG_SERIES_INSTANCE_UID || + *this == DICOM_TAG_SOP_INSTANCE_UID); + } }
--- a/Core/DicomFormat/DicomTag.h Thu Oct 23 13:14:58 2014 +0200 +++ b/Core/DicomFormat/DicomTag.h Thu Oct 23 13:19:18 2014 +0200 @@ -86,6 +86,8 @@ static void GetTagsForModule(std::set<DicomTag>& target, ResourceType module); + + bool IsIdentifier() const; }; // Aliases for the most useful tags
--- a/NEWS Thu Oct 23 13:14:58 2014 +0200 +++ b/NEWS Thu Oct 23 13:19:18 2014 +0200 @@ -1,7 +1,9 @@ Pending changes in the mainline =============================== +* Major speed-up thanks to a new database schema * Download ZIP + DICOMDIR from Orthanc Explorer +* Plugins can monitor changes through callbacks * Sample plugin framework to serve static resources * Fix issue 21 (Microsoft Visual Studio precompiled headers) * Fix issue 22 (Error decoding multi-frame instances)
--- a/OrthancServer/DatabaseWrapper.cpp Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/DatabaseWrapper.cpp Thu Oct 23 13:19:18 2014 +0200 @@ -92,6 +92,34 @@ } }; + class SignalResourceDeleted : public SQLite::IScalarFunction + { + private: + IServerIndexListener& listener_; + + public: + SignalResourceDeleted(IServerIndexListener& listener) : + listener_(listener) + { + } + + virtual const char* GetName() const + { + return "SignalResourceDeleted"; + } + + virtual unsigned int GetCardinality() const + { + return 2; + } + + virtual void Compute(SQLite::FunctionContext& context) + { + ResourceType type = static_cast<ResourceType>(context.GetIntValue(1)); + listener_.SignalChange(ChangeType_Deleted, type, context.GetStringValue(0)); + } + }; + class SignalRemainingAncestor : public SQLite::IScalarFunction { private: @@ -230,7 +258,7 @@ throw OrthancException(ErrorCode_InternalError); } - LogChange(changeType, id, type); + LogChange(changeType, id, type, publicId); return id; } @@ -511,18 +539,35 @@ } } + + static void SetMainDicomTagsInternal(SQLite::Statement& s, + int64_t id, + const DicomElement& element) + { + s.BindInt64(0, id); + s.BindInt(1, element.GetTag().GetGroup()); + s.BindInt(2, element.GetTag().GetElement()); + s.BindString(3, element.GetValue().AsString()); + s.Run(); + } + + void DatabaseWrapper::SetMainDicomTags(int64_t id, const DicomMap& tags) { DicomArray flattened(tags); for (size_t i = 0; i < flattened.GetSize(); i++) { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); - s.BindInt64(0, id); - s.BindInt(1, flattened.GetElement(i).GetTag().GetGroup()); - s.BindInt(2, flattened.GetElement(i).GetTag().GetElement()); - s.BindString(3, flattened.GetElement(i).GetValue().AsString()); - s.Run(); + if (flattened.GetElement(i).GetTag().IsIdentifier()) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)"); + SetMainDicomTagsInternal(s, id, flattened.GetElement(i)); + } + else + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); + SetMainDicomTagsInternal(s, id, flattened.GetElement(i)); + } } } @@ -539,6 +584,15 @@ s.ColumnInt(2), s.ColumnString(3)); } + + SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT * FROM DicomIdentifiers WHERE id=?"); + s2.BindInt64(0, id); + while (s2.Step()) + { + map.SetValue(s2.ColumnInt(1), + s2.ColumnInt(2), + s2.ColumnString(3)); + } } @@ -596,14 +650,21 @@ void DatabaseWrapper::LogChange(ChangeType changeType, int64_t internalId, ResourceType resourceType, - const boost::posix_time::ptime& date) + const std::string& publicId) { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); - s.BindInt(0, changeType); - s.BindInt64(1, internalId); - s.BindInt(2, resourceType); - s.BindString(3, boost::posix_time::to_iso_string(date)); - s.Run(); + if (changeType <= ChangeType_INTERNAL_LastLogged) + { + const boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); + s.BindInt(0, changeType); + s.BindInt64(1, internalId); + s.BindInt(2, resourceType); + s.BindString(3, boost::posix_time::to_iso_string(now)); + s.Run(); + } + + listener_.SignalChange(changeType, resourceType, publicId); } @@ -852,11 +913,12 @@ /** * History of the database versions: * - Version 3: from Orthanc 0.3.2 to Orthanc 0.7.2 (inclusive) - * - Version 4: from Orthanc 0.7.3 (inclusive) + * - Version 4: from Orthanc 0.7.3 to Orthanc 0.8.3 (inclusive) + * - Version 5: from Orthanc 0.8.4 (inclusive) **/ - // This version of Orthanc is only compatible with versions 3 of 4 of the DB schema - ok = (v == 3 || v == 4); + // This version of Orthanc is only compatible with versions 3, 4 and 5 of the DB schema + ok = (v == 3 || v == 4 || v == 5); if (v == 3) { @@ -866,6 +928,18 @@ db_.BeginTransaction(); db_.Execute(upgrade); db_.CommitTransaction(); + v = 4; + } + + if (v == 4) + { + LOG(WARNING) << "Upgrading database version from 4 to 5"; + std::string upgrade; + EmbeddedResources::GetFileResource(upgrade, EmbeddedResources::UPGRADE_DATABASE_4_TO_5); + db_.BeginTransaction(); + db_.Execute(upgrade); + db_.CommitTransaction(); + v = 5; } } catch (boost::bad_lexical_cast&) @@ -881,6 +955,7 @@ signalRemainingAncestor_ = new Internals::SignalRemainingAncestor; db_.Register(signalRemainingAncestor_); db_.Register(new Internals::SignalFileDeleted(listener_)); + db_.Register(new Internals::SignalResourceDeleted(listener_)); } uint64_t DatabaseWrapper::GetResourceCount(ResourceType resourceType) @@ -1010,12 +1085,17 @@ } - void DatabaseWrapper::LookupTagValue(std::list<int64_t>& result, - DicomTag tag, - const std::string& value) + void DatabaseWrapper::LookupIdentifier(std::list<int64_t>& result, + const DicomTag& tag, + const std::string& value) { + if (!tag.IsIdentifier()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT id FROM MainDicomTags WHERE tagGroup=? AND tagElement=? and value=?"); + "SELECT id FROM DicomIdentifiers WHERE tagGroup=? AND tagElement=? and value=?"); s.BindInt(0, tag.GetGroup()); s.BindInt(1, tag.GetElement()); @@ -1030,11 +1110,11 @@ } - void DatabaseWrapper::LookupTagValue(std::list<int64_t>& result, - const std::string& value) + void DatabaseWrapper::LookupIdentifier(std::list<int64_t>& result, + const std::string& value) { SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT id FROM MainDicomTags WHERE value=?"); + "SELECT id FROM DicomIdentifiers WHERE value=?"); s.BindString(0, value);
--- a/OrthancServer/DatabaseWrapper.h Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/DatabaseWrapper.h Thu Oct 23 13:19:18 2014 +0200 @@ -157,7 +157,7 @@ void LogChange(ChangeType changeType, int64_t internalId, ResourceType resourceType, - const boost::posix_time::ptime& date = boost::posix_time::second_clock::local_time()); + const std::string& publicId); void GetChanges(Json::Value& target, int64_t since, @@ -229,12 +229,12 @@ bool IsExistingResource(int64_t internalId); - void LookupTagValue(std::list<int64_t>& result, - DicomTag tag, - const std::string& value); + void LookupIdentifier(std::list<int64_t>& result, + const DicomTag& tag, + const std::string& value); - void LookupTagValue(std::list<int64_t>& result, - const std::string& value); + void LookupIdentifier(std::list<int64_t>& result, + const std::string& value); void GetAllMetadata(std::map<MetadataType, std::string>& result, int64_t id);
--- a/OrthancServer/IServerIndexListener.h Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/IServerIndexListener.h Thu Oct 23 13:19:18 2014 +0200 @@ -48,5 +48,9 @@ const std::string& publicId) = 0; virtual void SignalFileDeleted(const FileInfo& info) = 0; + + virtual void SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId) = 0; }; }
--- a/OrthancServer/OrthancFindRequestHandler.cpp Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Thu Oct 23 13:19:18 2014 +0200 @@ -320,7 +320,7 @@ << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")"; std::list<std::string> resources; - index_.LookupTagValue(resources, tag, value, level_); + index_.LookupIdentifier(resources, tag, value, level_); if (isFilterApplied_) {
--- a/OrthancServer/OrthancMoveRequestHandler.cpp Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Thu Oct 23 13:19:18 2014 +0200 @@ -101,7 +101,7 @@ } - bool OrthancMoveRequestHandler::LookupResource(std::string& publicId, + bool OrthancMoveRequestHandler::LookupIdentifier(std::string& publicId, DicomTag tag, const DicomMap& input) { @@ -113,7 +113,7 @@ std::string value = input.GetValue(tag).AsString(); std::list<std::string> ids; - context_.GetIndex().LookupTagValue(ids, tag, value); + context_.GetIndex().LookupIdentifier(ids, tag, value); if (ids.size() != 1) { @@ -155,19 +155,19 @@ switch (level) { case ResourceType_Patient: - ok = LookupResource(publicId, DICOM_TAG_PATIENT_ID, input); + ok = LookupIdentifier(publicId, DICOM_TAG_PATIENT_ID, input); break; case ResourceType_Study: - ok = LookupResource(publicId, DICOM_TAG_STUDY_INSTANCE_UID, input); + ok = LookupIdentifier(publicId, DICOM_TAG_STUDY_INSTANCE_UID, input); break; case ResourceType_Series: - ok = LookupResource(publicId, DICOM_TAG_SERIES_INSTANCE_UID, input); + ok = LookupIdentifier(publicId, DICOM_TAG_SERIES_INSTANCE_UID, input); break; case ResourceType_Instance: - ok = LookupResource(publicId, DICOM_TAG_SOP_INSTANCE_UID, input); + ok = LookupIdentifier(publicId, DICOM_TAG_SOP_INSTANCE_UID, input); break; default:
--- a/OrthancServer/OrthancMoveRequestHandler.h Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.h Thu Oct 23 13:19:18 2014 +0200 @@ -41,9 +41,9 @@ private: ServerContext& context_; - bool LookupResource(std::string& publicId, - DicomTag tag, - const DicomMap& input); + bool LookupIdentifier(std::string& publicId, + DicomTag tag, + const DicomMap& input); public: OrthancMoveRequestHandler(ServerContext& context) :
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Thu Oct 23 13:19:18 2014 +0200 @@ -794,7 +794,7 @@ std::string tag = call.GetPostBody(); Resources resources; - OrthancRestApi::GetIndex(call).LookupTagValue(resources, tag); + OrthancRestApi::GetIndex(call).LookupIdentifier(resources, tag); Json::Value result = Json::arrayValue;
--- a/OrthancServer/PrepareDatabase.sql Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/PrepareDatabase.sql Thu Oct 23 13:19:18 2014 +0200 @@ -18,6 +18,15 @@ PRIMARY KEY(id, tagGroup, tagElement) ); +-- The following table was added in Orthanc 0.8.5 (database v5) +CREATE TABLE DicomIdentifiers( + id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE, + tagGroup INTEGER, + tagElement INTEGER, + value TEXT, + PRIMARY KEY(id, tagGroup, tagElement) + ); + CREATE TABLE Metadata( id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE, type INTEGER, @@ -68,8 +77,14 @@ CREATE INDEX PatientRecyclingIndex ON PatientRecyclingOrder(patientId); CREATE INDEX MainDicomTagsIndex1 ON MainDicomTags(id); -CREATE INDEX MainDicomTagsIndex2 ON MainDicomTags(tagGroup, tagElement); -CREATE INDEX MainDicomTagsIndexValues ON MainDicomTags(value COLLATE BINARY); +-- The 2 following indexes were removed in Orthanc 0.8.5 (database v5), to speed up +-- CREATE INDEX MainDicomTagsIndex2 ON MainDicomTags(tagGroup, tagElement); +-- CREATE INDEX MainDicomTagsIndexValues ON MainDicomTags(value COLLATE BINARY); + +-- The 3 following indexes were added in Orthanc 0.8.5 (database v5) +CREATE INDEX DicomIdentifiersIndex1 ON DicomIdentifiers(id); +CREATE INDEX DicomIdentifiersIndex2 ON DicomIdentifiers(tagGroup, tagElement); +CREATE INDEX DicomIdentifiersIndexValues ON DicomIdentifiers(value COLLATE BINARY); CREATE INDEX ChangesIndex ON Changes(internalId); @@ -85,6 +100,7 @@ CREATE TRIGGER ResourceDeleted AFTER DELETE ON Resources BEGIN + SELECT SignalResourceDeleted(old.publicId, old.resourceType); -- New in Orthanc 0.8.5 (db v5) SELECT SignalRemainingAncestor(parent.publicId, parent.resourceType) FROM Resources AS parent WHERE internalId = old.parentId; END; @@ -107,4 +123,4 @@ -- Set the version of the database schema -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration -INSERT INTO GlobalProperties VALUES (1, "4"); +INSERT INTO GlobalProperties VALUES (1, "5");
--- a/OrthancServer/ServerContext.cpp Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/ServerContext.cpp Thu Oct 23 13:19:18 2014 +0200 @@ -371,7 +371,7 @@ } catch (OrthancException& e) { - LOG(ERROR) << "Error in OnStoredInstance callback (Lua): " << e.What(); + LOG(ERROR) << "Error in OnStoredInstance callback (plugins): " << e.What(); } } } @@ -520,4 +520,23 @@ { return index_.DeleteResource(target, uuid, expectedType); } + + + void ServerContext::SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId) + { + if (plugins_ != NULL) + { + try + { + plugins_->SignalChange(changeType, resourceType, publicId); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Error in OnChangeCallback (plugins): " << e.What(); + } + } + } + }
--- a/OrthancServer/ServerContext.h Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/ServerContext.h Thu Oct 23 13:19:18 2014 +0200 @@ -202,5 +202,9 @@ bool DeleteResource(Json::Value& target, const std::string& uuid, ResourceType expectedType); + + void SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId); }; }
--- a/OrthancServer/ServerEnumerations.cpp Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/ServerEnumerations.cpp Thu Oct 23 13:19:18 2014 +0200 @@ -231,6 +231,12 @@ case ChangeType_StableSeries: return "StableSeries"; + case ChangeType_Deleted: + return "Deleted"; + + case ChangeType_NewChildInstance: + return "NewChildInstance"; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); }
--- a/OrthancServer/ServerEnumerations.h Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/ServerEnumerations.h Thu Oct 23 13:19:18 2014 +0200 @@ -134,7 +134,13 @@ ChangeType_ModifiedPatient = 11, ChangeType_StablePatient = 12, ChangeType_StableStudy = 13, - ChangeType_StableSeries = 14 + ChangeType_StableSeries = 14, + + ChangeType_INTERNAL_LastLogged = 4095, + + // The changes below this point are not logged into the database + ChangeType_Deleted = 4096, + ChangeType_NewChildInstance = 4097 };
--- a/OrthancServer/ServerIndex.cpp Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/ServerIndex.cpp Thu Oct 23 13:19:18 2014 +0200 @@ -61,13 +61,58 @@ 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_; + } + }; + + struct ServerIndexChange + { + private: + ChangeType changeType_; + ResourceType resourceType_; + std::string publicId_; + + public: + ServerIndexChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId) : + changeType_(changeType), + resourceType_(resourceType), + publicId_(publicId) + { + } + + ChangeType GetChangeType() const + { + return changeType_; + } + + ResourceType GetResourceType() const + { + return resourceType_; + } + + const std::string& GetPublicId() const + { + return publicId_; + } }; ServerContext& context_; @@ -75,11 +120,21 @@ ResourceType remainingType_; std::string remainingPublicId_; 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 && @@ -87,11 +142,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() @@ -101,11 +160,21 @@ void CommitFilesToRemove() { - for (std::list<FileToRemove>::iterator + for (std::list<FileToRemove>::const_iterator it = pendingFilesToRemove_.begin(); it != pendingFilesToRemove_.end(); ++it) { - context_.RemoveFile(it->uuid_, it->type_); + context_.RemoveFile(it->GetUuid(), it->GetContentType()); + } + } + + void CommitChanges() + { + for (std::list<ServerIndexChange>::const_iterator + it = pendingChanges_.begin(); + it != pendingChanges_.end(); it++) + { + context_.SignalChange(it->GetChangeType(), it->GetResourceType(), it->GetPublicId()); } } @@ -137,6 +206,23 @@ sizeOfFilesToRemove_ += info.GetCompressedSize(); } + virtual void SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId) + { + LOG(INFO) << "Change related to resource " << publicId << " of type " + << EnumerationToString(resourceType) << ": " << EnumerationToString(changeType); + + if (insideTransaction_) + { + pendingChanges_.push_back(ServerIndexChange(changeType, resourceType, publicId)); + } + else + { + context_.SignalChange(changeType, resourceType, publicId); + } + } + bool HasRemainingLevel() const { return hasRemainingLevel_; @@ -171,9 +257,15 @@ { assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize()); - index_.listener_->Reset(); transaction_.reset(index_.db_->StartTransaction()); transaction_->Begin(); + + index_.listener_->StartTransaction(); + } + + ~Transaction() + { + index_.listener_->EndTransaction(); } void Commit(uint64_t sizeOfAddedFiles) @@ -194,22 +286,31 @@ assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize()); + // Send all the pending changes to the Orthanc plugins + index_.listener_->CommitChanges(); + isCommitted_ = true; } } }; - 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(); } @@ -218,6 +319,16 @@ { return (boost::posix_time::second_clock::local_time() - time_).total_seconds(); } + + ResourceType GetResourceType() const + { + return type_; + } + + const std::string& GetPublicId() const + { + return publicId_; + } }; @@ -226,7 +337,6 @@ ResourceType expectedType) { boost::mutex::scoped_lock lock(mutex_); - listener_->Reset(); Transaction t(*this); @@ -403,7 +513,6 @@ const MetadataMap& metadata) { boost::mutex::scoped_lock lock(mutex_); - listener_->Reset(); instanceMetadata.clear(); @@ -597,13 +706,13 @@ SeriesStatus seriesStatus = GetSeriesStatus(series); if (seriesStatus == SeriesStatus_Complete) { - db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series); + db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series, hasher.HashSeries()); } // Mark the parent resources of this instance as unstable - MarkAsUnstable(series, ResourceType_Series); - MarkAsUnstable(study, ResourceType_Study); - MarkAsUnstable(patient, ResourceType_Patient); + MarkAsUnstable(series, ResourceType_Series, hasher.HashSeries()); + MarkAsUnstable(study, ResourceType_Study, hasher.HashStudy()); + MarkAsUnstable(patient, ResourceType_Patient, hasher.HashPatient()); t.Commit(instanceSize); @@ -1386,7 +1495,7 @@ throw OrthancException(ErrorCode_UnknownResource); } - db_->LogChange(changeType, id, type); + db_->LogChange(changeType, id, type, publicId); transaction->Commit(); } @@ -1583,18 +1692,18 @@ // Ensure that the resource is still existing before logging the change 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->db_->LogChange(ChangeType_StablePatient, id, ResourceType_Patient, payload.GetPublicId()); break; - case Orthanc::ResourceType_Study: - that->db_->LogChange(ChangeType_StableStudy, id, ResourceType_Study); + case ResourceType_Study: + that->db_->LogChange(ChangeType_StableStudy, id, ResourceType_Study, payload.GetPublicId()); break; - case Orthanc::ResourceType_Series: - that->db_->LogChange(ChangeType_StableSeries, id, ResourceType_Series); + case ResourceType_Series: + that->db_->LogChange(ChangeType_StableSeries, id, ResourceType_Series, payload.GetPublicId()); break; default: @@ -1611,7 +1720,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. @@ -1619,23 +1729,26 @@ 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; + + db_->LogChange(ChangeType_NewChildInstance, id, 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) @@ -1648,16 +1761,16 @@ } - 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) @@ -1667,15 +1780,15 @@ } - void ServerIndex::LookupTagValue(std::list< std::pair<ResourceType, 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) @@ -1736,7 +1849,6 @@ FileContentType type) { boost::mutex::scoped_lock lock(mutex_); - listener_->Reset(); Transaction t(*this); @@ -1780,6 +1892,4 @@ return true; } - - }
--- a/OrthancServer/ServerIndex.h Thu Oct 23 13:14:58 2014 +0200 +++ b/OrthancServer/ServerIndex.h Thu Oct 23 13:19:18 2014 +0200 @@ -60,7 +60,7 @@ private: class Transaction; - struct UnstableResourcePayload; + class UnstableResourcePayload; bool done_; boost::mutex mutex_; @@ -92,7 +92,8 @@ void StandaloneRecycling(); void MarkAsUnstable(int64_t id, - Orthanc::ResourceType type); + Orthanc::ResourceType type, + const std::string& publicId); void GetStatisticsInternal(/* out */ uint64_t& compressedSize, /* out */ uint64_t& uncompressedSize, @@ -216,17 +217,17 @@ /* out */ unsigned int& countInstances, const std::string& publicId); - void LookupTagValue(std::list<std::string>& result, - DicomTag tag, - const std::string& value, - ResourceType type); + void LookupIdentifier(std::list<std::string>& result, + const DicomTag& tag, + const std::string& value, + ResourceType type); - void LookupTagValue(std::list<std::string>& result, - DicomTag tag, - const std::string& value); + void LookupIdentifier(std::list<std::string>& result, + const DicomTag& tag, + const std::string& value); - void LookupTagValue(std::list< std::pair<ResourceType, std::string> >& result, - const std::string& value); + void LookupIdentifier(std::list< std::pair<ResourceType, std::string> >& result, + const std::string& value); StoreStatus AddAttachment(const FileInfo& attachment, const std::string& publicId);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Upgrade4To5.sql Thu Oct 23 13:19:18 2014 +0200 @@ -0,0 +1,66 @@ +-- This SQLite script updates the version of the Orthanc database from 4 to 5. + + +-- Remove 2 indexes to speed up + +DROP INDEX MainDicomTagsIndex2; +DROP INDEX MainDicomTagsIndexValues; + + +-- Add a new table to index the DICOM identifiers + +CREATE TABLE DicomIdentifiers( + id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE, + tagGroup INTEGER, + tagElement INTEGER, + value TEXT, + PRIMARY KEY(id, tagGroup, tagElement) + ); + +CREATE INDEX DicomIdentifiersIndex1 ON DicomIdentifiers(id); +CREATE INDEX DicomIdentifiersIndex2 ON DicomIdentifiers(tagGroup, tagElement); +CREATE INDEX DicomIdentifiersIndexValues ON DicomIdentifiers(value COLLATE BINARY); + + +-- Migrate data from MainDicomTags to MainResourcesTags and MainInstancesTags + +INSERT INTO DicomIdentifiers SELECT * FROM MainDicomTags + WHERE ((tagGroup = 16 AND tagElement = 32) OR -- PatientID (0x0010, 0x0020) + (tagGroup = 32 AND tagElement = 13) OR -- StudyInstanceUID (0x0020, 0x000d) + (tagGroup = 8 AND tagElement = 80) OR -- AccessionNumber (0x0008, 0x0050) + (tagGroup = 32 AND tagElement = 14) OR -- SeriesInstanceUID (0x0020, 0x000e) + (tagGroup = 8 AND tagElement = 24)); -- SOPInstanceUID (0x0008, 0x0018) + +DELETE FROM MainDicomTags + WHERE ((tagGroup = 16 AND tagElement = 32) OR -- PatientID (0x0010, 0x0020) + (tagGroup = 32 AND tagElement = 13) OR -- StudyInstanceUID (0x0020, 0x000d) + (tagGroup = 8 AND tagElement = 80) OR -- AccessionNumber (0x0008, 0x0050) + (tagGroup = 32 AND tagElement = 14) OR -- SeriesInstanceUID (0x0020, 0x000e) + (tagGroup = 8 AND tagElement = 24)); -- SOPInstanceUID (0x0008, 0x0018) + + +-- Upgrade the "ResourceDeleted" trigger + +DROP TRIGGER ResourceDeleted; +DROP TRIGGER ResourceDeletedParentCleaning; + +CREATE TRIGGER ResourceDeleted +AFTER DELETE ON Resources +BEGIN + SELECT SignalResourceDeleted(old.publicId, old.resourceType); + SELECT SignalRemainingAncestor(parent.publicId, parent.resourceType) + FROM Resources AS parent WHERE internalId = old.parentId; +END; + +CREATE TRIGGER ResourceDeletedParentCleaning +AFTER DELETE ON Resources +FOR EACH ROW WHEN (SELECT COUNT(*) FROM Resources WHERE parentId = old.parentId) = 0 +BEGIN + DELETE FROM Resources WHERE internalId = old.parentId; +END; + + +-- Change the database version +-- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration + +UPDATE GlobalProperties SET value="5" WHERE property=1;
--- a/Plugins/Engine/OrthancPlugins.cpp Thu Oct 23 13:14:58 2014 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Thu Oct 23 13:19:18 2014 +0200 @@ -40,6 +40,7 @@ #include "../../OrthancServer/ServerToolbox.h" #include "../../OrthancServer/OrthancInitialization.h" +#include <boost/thread.hpp> #include <boost/regex.hpp> #include <glog/logging.h> @@ -84,13 +85,16 @@ typedef std::pair<boost::regex*, OrthancPluginRestCallback> RestCallback; typedef std::list<RestCallback> RestCallbacks; typedef std::list<OrthancPluginOnStoredInstanceCallback> OnStoredCallbacks; + typedef std::list<OrthancPluginOnChangeCallback> OnChangeCallbacks; ServerContext& context_; RestCallbacks restCallbacks_; OrthancRestApi* restApi_; OnStoredCallbacks onStoredCallbacks_; + OnChangeCallbacks onChangeCallbacks_; bool hasStorageArea_; _OrthancPluginRegisterStorageArea storageArea_; + boost::mutex callbackMutex_; PImpl(ServerContext& context) : context_(context), @@ -123,6 +127,86 @@ } + static OrthancPluginResourceType Convert(ResourceType type) + { + switch (type) + { + case ResourceType_Patient: + return OrthancPluginResourceType_Patient; + + case ResourceType_Study: + return OrthancPluginResourceType_Study; + + case ResourceType_Series: + return OrthancPluginResourceType_Series; + + case ResourceType_Instance: + return OrthancPluginResourceType_Instance; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + static OrthancPluginChangeType Convert(ChangeType type) + { + switch (type) + { + case ChangeType_AnonymizedPatient: + return OrthancPluginChangeType_AnonymizedPatient; + + case ChangeType_AnonymizedSeries: + return OrthancPluginChangeType_AnonymizedSeries; + + case ChangeType_AnonymizedStudy: + return OrthancPluginChangeType_AnonymizedStudy; + + case ChangeType_CompletedSeries: + return OrthancPluginChangeType_CompletedSeries; + + case ChangeType_Deleted: + return OrthancPluginChangeType_Deleted; + + case ChangeType_ModifiedPatient: + return OrthancPluginChangeType_ModifiedPatient; + + case ChangeType_ModifiedSeries: + return OrthancPluginChangeType_ModifiedSeries; + + case ChangeType_ModifiedStudy: + return OrthancPluginChangeType_ModifiedStudy; + + case ChangeType_NewChildInstance: + return OrthancPluginChangeType_NewChildInstance; + + case ChangeType_NewInstance: + return OrthancPluginChangeType_NewInstance; + + case ChangeType_NewPatient: + return OrthancPluginChangeType_NewPatient; + + case ChangeType_NewSeries: + return OrthancPluginChangeType_NewSeries; + + case ChangeType_NewStudy: + return OrthancPluginChangeType_NewStudy; + + case ChangeType_StablePatient: + return OrthancPluginChangeType_StablePatient; + + case ChangeType_StableSeries: + return OrthancPluginChangeType_StableSeries; + + case ChangeType_StableStudy: + return OrthancPluginChangeType_StableStudy; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + OrthancPlugins::OrthancPlugins(ServerContext& context) { pimpl_.reset(new PImpl(context)); @@ -257,9 +341,14 @@ } assert(callback != NULL); - int32_t error = callback(reinterpret_cast<OrthancPluginRestOutput*>(&output), - flatUri.c_str(), - &request); + int32_t error; + + { + boost::mutex::scoped_lock lock(pimpl_->callbackMutex_); + error = callback(reinterpret_cast<OrthancPluginRestOutput*>(&output), + flatUri.c_str(), + &request); + } if (error < 0) { @@ -279,8 +368,10 @@ void OrthancPlugins::SignalStoredInstance(DicomInstanceToStore& instance, - const std::string& instanceId) + const std::string& instanceId) { + boost::mutex::scoped_lock lock(pimpl_->callbackMutex_); + for (PImpl::OnStoredCallbacks::const_iterator callback = pimpl_->onStoredCallbacks_.begin(); callback != pimpl_->onStoredCallbacks_.end(); ++callback) @@ -292,6 +383,36 @@ + void OrthancPlugins::SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId) + { + OrthancPluginChangeType c; + OrthancPluginResourceType r; + + try + { + c = Convert(changeType); + r = Convert(resourceType); + } + catch (OrthancException&) + { + // This change type or resource type is not supported by the plugin SDK + return; + } + + boost::mutex::scoped_lock lock(pimpl_->callbackMutex_); + + for (PImpl::OnChangeCallbacks::const_iterator + callback = pimpl_->onChangeCallbacks_.begin(); + callback != pimpl_->onChangeCallbacks_.end(); ++callback) + { + (*callback) (c, r, publicId.c_str()); + } + } + + + static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target, const void* data, size_t size) @@ -353,6 +474,16 @@ } + void OrthancPlugins::RegisterOnChangeCallback(const void* parameters) + { + const _OrthancPluginOnChangeCallback& p = + *reinterpret_cast<const _OrthancPluginOnChangeCallback*>(parameters); + + LOG(INFO) << "Plugin has registered an OnChange callback"; + pimpl_->onChangeCallbacks_.push_back(p.callback); + } + + void OrthancPlugins::AnswerBuffer(const void* parameters) { @@ -618,7 +749,7 @@ } std::list<std::string> result; - pimpl_->context_.GetIndex().LookupTagValue(result, tag, p.argument, level); + pimpl_->context_.GetIndex().LookupIdentifier(result, tag, p.argument, level); if (result.size() == 1) { @@ -777,6 +908,10 @@ RegisterOnStoredInstanceCallback(parameters); return true; + case _OrthancPluginService_RegisterOnChangeCallback: + RegisterOnChangeCallback(parameters); + return true; + case _OrthancPluginService_AnswerBuffer: AnswerBuffer(parameters); return true;
--- a/Plugins/Engine/OrthancPlugins.h Thu Oct 23 13:14:58 2014 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Thu Oct 23 13:19:18 2014 +0200 @@ -53,6 +53,8 @@ void RegisterOnStoredInstanceCallback(const void* parameters); + void RegisterOnChangeCallback(const void* parameters); + void AnswerBuffer(const void* parameters); void Redirect(const void* parameters); @@ -95,6 +97,10 @@ virtual bool InvokeService(_OrthancPluginService service, const void* parameters); + void SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId); + void SignalStoredInstance(DicomInstanceToStore& instance, const std::string& instanceId);
--- a/Plugins/OrthancCPlugin/OrthancCPlugin.h Thu Oct 23 13:14:58 2014 +0200 +++ b/Plugins/OrthancCPlugin/OrthancCPlugin.h Thu Oct 23 13:19:18 2014 +0200 @@ -27,7 +27,8 @@ * The name and the version of a plugin is only used to prevent it * from being loaded twice. * - * + * The various callbacks are guaranteed to be executed in mutual + * exclusion since Orthanc 0.8.5. **/ @@ -88,7 +89,7 @@ #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 0 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 8 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 3 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 5 @@ -245,6 +246,7 @@ _OrthancPluginService_RegisterRestCallback = 1000, _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, _OrthancPluginService_RegisterStorageArea = 1002, + _OrthancPluginService_RegisterOnChangeCallback = 1003, /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -341,6 +343,44 @@ /** + * The supported type of DICOM resources. + **/ + typedef enum + { + OrthancPluginResourceType_Patient = 0, /*!< Patient */ + OrthancPluginResourceType_Study = 1, /*!< Study */ + OrthancPluginResourceType_Series = 2, /*!< Series */ + OrthancPluginResourceType_Instance = 3 /*!< Instance */ + } OrthancPluginResourceType; + + + + /** + * The supported type of changes that can happen to DICOM resources. + **/ + typedef enum + { + OrthancPluginChangeType_AnonymizedPatient = 0, /*!< Patient resulting from an anomyization */ + OrthancPluginChangeType_AnonymizedSeries = 1, /*!< Series resulting from an anonymization */ + OrthancPluginChangeType_AnonymizedStudy = 2, /*!< Study resulting from an anomyization */ + OrthancPluginChangeType_CompletedSeries = 3, /*!< Series is now complete */ + OrthancPluginChangeType_Deleted = 4, /*!< Deleted resource */ + OrthancPluginChangeType_ModifiedPatient = 5, /*!< Patient resulting from a modification */ + OrthancPluginChangeType_ModifiedSeries = 6, /*!< Series resulting from a modification */ + OrthancPluginChangeType_ModifiedStudy = 7, /*!< Study resulting from a modification */ + OrthancPluginChangeType_NewChildInstance = 8, /*!< A new instance was added to this resource */ + OrthancPluginChangeType_NewInstance = 9, /*!< New instance received */ + OrthancPluginChangeType_NewPatient = 10, /*!< New patient created */ + OrthancPluginChangeType_NewSeries = 11, /*!< New series created */ + OrthancPluginChangeType_NewStudy = 12, /*!< New study created */ + OrthancPluginChangeType_StablePatient = 13, /*!< Timeout: No new instance in this patient */ + OrthancPluginChangeType_StableSeries = 14, /*!< Timeout: No new instance in this series */ + OrthancPluginChangeType_StableStudy = 15 /*!< Timeout: No new instance in this study */ + } OrthancPluginChangeType; + + + + /** * @brief A memory buffer allocated by the core system of Orthanc. * * A memory buffer allocated by the core system of Orthanc. When the @@ -397,6 +437,16 @@ /** + * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. + **/ + typedef int32_t (*OrthancPluginOnChangeCallback) ( + OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId); + + + + /** * @brief Signature of a function to free dynamic memory. **/ typedef void (*OrthancPluginFree) (void* buffer); @@ -1642,6 +1692,33 @@ + typedef struct + { + OrthancPluginOnChangeCallback callback; + } _OrthancPluginOnChangeCallback; + + /** + * @brief Register a callback to monitor changes. + * + * This function registers a callback function that is called + * whenever a change happens to some DICOM resource. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( + OrthancPluginContext* context, + OrthancPluginOnChangeCallback callback) + { + _OrthancPluginOnChangeCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); + } + + + + #ifdef __cplusplus } #endif
--- a/Plugins/Samples/Basic/Plugin.c Thu Oct 23 13:14:58 2014 +0200 +++ b/Plugins/Samples/Basic/Plugin.c Thu Oct 23 13:19:18 2014 +0200 @@ -215,6 +215,7 @@ char buffer[256]; FILE* fp; char* json; + static int first = 1; sprintf(buffer, "Just received a DICOM instance of size %d and ID %s from AET %s", (int) OrthancPluginGetInstanceSize(context, instance), instanceId, @@ -228,7 +229,12 @@ fclose(fp); json = OrthancPluginGetInstanceSimplifiedJson(context, instance); - printf("[%s]\n", json); + if (first) + { + /* Only print the first DICOM instance */ + printf("[%s]\n", json); + first = 0; + } OrthancPluginFreeString(context, json); if (OrthancPluginHasInstanceMetadata(context, instance, "ReceptionDate")) @@ -244,6 +250,16 @@ } +ORTHANC_PLUGINS_API int32_t OnChangeCallback(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId) +{ + char info[1024]; + sprintf(info, "Change %d on resource %s of type %d", changeType, resourceId, resourceType); + OrthancPluginLogWarning(context, info); + return 0; +} + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c) { @@ -293,6 +309,8 @@ OrthancPluginRegisterOnStoredInstanceCallback(context, OnStoredCallback); + OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); + /* Make REST requests to the built-in Orthanc API */ OrthancPluginRestApiGet(context, &tmp, "/changes"); OrthancPluginFreeMemoryBuffer(context, &tmp);
--- a/UnitTestsSources/ServerIndexTests.cpp Thu Oct 23 13:14:58 2014 +0200 +++ b/UnitTestsSources/ServerIndexTests.cpp Thu Oct 23 13:19:18 2014 +0200 @@ -58,6 +58,7 @@ { public: std::vector<std::string> deletedFiles_; + std::vector<std::string> deletedResources_; std::string ancestorId_; ResourceType ancestorType_; @@ -79,7 +80,21 @@ const std::string fileUuid = info.GetUuid(); deletedFiles_.push_back(fileUuid); LOG(INFO) << "A file must be removed: " << fileUuid; - } + } + + virtual void SignalChange(ChangeType changeType, + ResourceType resourceType, + const std::string& publicId) + { + if (changeType == ChangeType_Deleted) + { + deletedResources_.push_back(publicId); + } + + LOG(INFO) << "Change related to resource " << publicId << " of type " + << EnumerationToString(resourceType) << ": " << EnumerationToString(changeType); + } + }; @@ -281,12 +296,14 @@ ASSERT_EQ(CompressionType_None, att.GetCompressionType()); ASSERT_EQ(0u, listener_->deletedFiles_.size()); + ASSERT_EQ(0u, listener_->deletedResources_.size()); ASSERT_EQ(7u, index_->GetTableRecordCount("Resources")); ASSERT_EQ(3u, index_->GetTableRecordCount("AttachedFiles")); ASSERT_EQ(1u, index_->GetTableRecordCount("Metadata")); ASSERT_EQ(1u, index_->GetTableRecordCount("MainDicomTags")); + index_->DeleteResource(a[0]); - + ASSERT_EQ(5u, listener_->deletedResources_.size()); ASSERT_EQ(2u, listener_->deletedFiles_.size()); ASSERT_FALSE(std::find(listener_->deletedFiles_.begin(), listener_->deletedFiles_.end(), @@ -300,6 +317,7 @@ ASSERT_EQ(1u, index_->GetTableRecordCount("AttachedFiles")); ASSERT_EQ(0u, index_->GetTableRecordCount("MainDicomTags")); index_->DeleteResource(a[5]); + ASSERT_EQ(7u, listener_->deletedResources_.size()); ASSERT_EQ(0u, index_->GetTableRecordCount("Resources")); ASSERT_EQ(0u, index_->GetTableRecordCount("AttachedFiles")); ASSERT_EQ(2u, index_->GetTableRecordCount("GlobalProperties")); @@ -395,9 +413,11 @@ ASSERT_EQ(10u, index_->GetTableRecordCount("PatientRecyclingOrder")); listener_->Reset(); + ASSERT_EQ(0u, listener_->deletedResources_.size()); index_->DeleteResource(patients[5]); index_->DeleteResource(patients[0]); + ASSERT_EQ(2u, listener_->deletedResources_.size()); ASSERT_EQ(8u, index_->GetTableRecordCount("Resources")); ASSERT_EQ(8u, index_->GetTableRecordCount("PatientRecyclingOrder")); @@ -408,20 +428,27 @@ int64_t p; ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); index_->DeleteResource(p); + ASSERT_EQ(3u, listener_->deletedResources_.size()); ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); index_->DeleteResource(p); + ASSERT_EQ(4u, listener_->deletedResources_.size()); ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); index_->DeleteResource(p); + ASSERT_EQ(5u, listener_->deletedResources_.size()); ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); index_->DeleteResource(p); + ASSERT_EQ(6u, listener_->deletedResources_.size()); ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]); index_->DeleteResource(p); index_->DeleteResource(patients[8]); + ASSERT_EQ(8u, listener_->deletedResources_.size()); ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]); index_->DeleteResource(p); + ASSERT_EQ(9u, listener_->deletedResources_.size()); ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]); index_->DeleteResource(p); ASSERT_FALSE(index_->SelectPatientToRecycle(p)); + ASSERT_EQ(10u, listener_->deletedResources_.size()); ASSERT_EQ(10u, listener_->deletedFiles_.size()); ASSERT_EQ(0u, index_->GetTableRecordCount("Resources")); @@ -477,16 +504,21 @@ // Unprotecting a patient puts it at the last position in the recycling queue int64_t p; + ASSERT_EQ(0u, listener_->deletedResources_.size()); ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]); index_->DeleteResource(p); + ASSERT_EQ(1u, listener_->deletedResources_.size()); ASSERT_TRUE(index_->SelectPatientToRecycle(p, patients[1])); ASSERT_EQ(p, patients[4]); ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); index_->DeleteResource(p); + ASSERT_EQ(2u, listener_->deletedResources_.size()); ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); index_->DeleteResource(p); + ASSERT_EQ(3u, listener_->deletedResources_.size()); ASSERT_FALSE(index_->SelectPatientToRecycle(p, patients[2])); ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); index_->DeleteResource(p); + ASSERT_EQ(4u, listener_->deletedResources_.size()); // "patients[3]" is still protected ASSERT_FALSE(index_->SelectPatientToRecycle(p)); @@ -500,6 +532,7 @@ ASSERT_TRUE(index_->SelectPatientToRecycle(p, patients[2])); ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); index_->DeleteResource(p); + ASSERT_EQ(5u, listener_->deletedResources_.size()); ASSERT_EQ(5u, listener_->deletedFiles_.size()); ASSERT_EQ(0u, index_->GetTableRecordCount("Resources")); @@ -518,7 +551,7 @@ -TEST_P(DatabaseWrapperTest, LookupTagValue) +TEST_P(DatabaseWrapperTest, LookupIdentifier) { int64_t a[] = { index_->CreateResource("a", ResourceType_Study), // 0 @@ -535,29 +568,29 @@ std::list<int64_t> s; - index_->LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "0"); + index_->LookupIdentifier(s, DICOM_TAG_STUDY_INSTANCE_UID, "0"); ASSERT_EQ(2u, s.size()); ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end()); ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end()); - index_->LookupTagValue(s, "0"); + index_->LookupIdentifier(s, "0"); ASSERT_EQ(3u, s.size()); ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end()); ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end()); ASSERT_TRUE(std::find(s.begin(), s.end(), a[3]) != s.end()); - index_->LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1"); + index_->LookupIdentifier(s, DICOM_TAG_STUDY_INSTANCE_UID, "1"); ASSERT_EQ(1u, s.size()); ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end()); - index_->LookupTagValue(s, "1"); + index_->LookupIdentifier(s, "1"); ASSERT_EQ(1u, s.size()); ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end()); /*{ std::list<std::string> s; - context.GetIndex().LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1.2.250.1.74.20130819132500.29000036381059"); + context.GetIndex().LookupIdentifier(s, DICOM_TAG_STUDY_INSTANCE_UID, "1.2.250.1.74.20130819132500.29000036381059"); for (std::list<std::string>::iterator i = s.begin(); i != s.end(); i++) { std::cout << "*** " << *i << std::endl;;