# HG changeset patch # User Sebastien Jodogne # Date 1354881387 -3600 # Node ID 2354560daf2f42cfb600247856e12f958c327812 # Parent f1fb9d1aa444027f46676f97597d5cf434628dad primitives for recycling patients diff -r f1fb9d1aa444 -r 2354560daf2f OrthancServer/DatabaseWrapper.cpp --- a/OrthancServer/DatabaseWrapper.cpp Wed Dec 05 13:56:53 2012 +0100 +++ b/OrthancServer/DatabaseWrapper.cpp Fri Dec 07 12:56:27 2012 +0100 @@ -743,9 +743,9 @@ LOG(INFO) << "Version of the Orthanc database: " << version; unsigned int v = boost::lexical_cast(version); - // This version of Orthanc is only compatible with version 2 of - // the DB schema (since Orthanc 0.3.1) - ok = (v == 2); + // This version of Orthanc is only compatible with version 3 of + // the DB schema (since Orthanc 0.3.2) + ok = (v == 3); } catch (boost::bad_lexical_cast&) { @@ -777,4 +777,50 @@ return c; } + + bool DatabaseWrapper::SelectPatientToRecycle(int64_t& internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1"); + + if (!s.Step()) + { + // No patient remaining or all the patients are protected + return false; + } + else + { + internalId = s.ColumnInt(0); + return true; + } + } + + bool DatabaseWrapper::IsProtectedPatient(int64_t internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?"); + s.BindInt(0, internalId); + return !s.Step(); + } + + void DatabaseWrapper::SetProtectedPatient(int64_t internalId, + bool isProtected) + { + if (isProtected) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); + s.BindInt(0, internalId); + s.Run(); + } + else if (IsProtectedPatient(internalId)) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); + s.BindInt(0, internalId); + s.Run(); + } + else + { + // Nothing to do: The patient is already unprotected + } + } } diff -r f1fb9d1aa444 -r 2354560daf2f OrthancServer/DatabaseWrapper.h --- a/OrthancServer/DatabaseWrapper.h Wed Dec 05 13:56:53 2012 +0100 +++ b/OrthancServer/DatabaseWrapper.h Fri Dec 07 12:56:27 2012 +0100 @@ -179,6 +179,13 @@ void GetAllPublicIds(Json::Value& target, ResourceType resourceType); + bool SelectPatientToRecycle(int64_t& internalId); + + bool IsProtectedPatient(int64_t internalId); + + void SetProtectedPatient(int64_t internalId, + bool isProtected); + DatabaseWrapper(const std::string& path, IServerIndexListener& listener); diff -r f1fb9d1aa444 -r 2354560daf2f OrthancServer/PrepareDatabase.sql --- a/OrthancServer/PrepareDatabase.sql Wed Dec 05 13:56:53 2012 +0100 +++ b/OrthancServer/PrepareDatabase.sql Fri Dec 07 12:56:27 2012 +0100 @@ -55,9 +55,15 @@ date TEXT ); +CREATE TABLE PatientRecyclingOrder( + seq INTEGER PRIMARY KEY AUTOINCREMENT, + patientId INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE + ); + CREATE INDEX ChildrenIndex ON Resources(parentId); CREATE INDEX PublicIndex ON Resources(publicId); CREATE INDEX ResourceTypeIndex ON Resources(resourceType); +CREATE INDEX PatientRecyclingIndex ON PatientRecyclingOrder(patientId); CREATE INDEX MainDicomTagsIndex1 ON MainDicomTags(id); CREATE INDEX MainDicomTagsIndex2 ON MainDicomTags(tagGroup, tagElement); @@ -86,6 +92,14 @@ DELETE FROM Resources WHERE internalId = old.parentId; END; +CREATE TRIGGER PatientAdded +AFTER INSERT ON Resources +FOR EACH ROW WHEN new.resourceType = 1 -- "1" corresponds to "ResourceType_Patient" in C++ +BEGIN + INSERT INTO PatientRecyclingOrder VALUES (NULL, new.internalId); +END; + + -- Set the version of the database schema -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration -INSERT INTO GlobalProperties VALUES (1, "2"); +INSERT INTO GlobalProperties VALUES (1, "3"); diff -r f1fb9d1aa444 -r 2354560daf2f UnitTests/ServerIndex.cpp --- a/UnitTests/ServerIndex.cpp Wed Dec 05 13:56:53 2012 +0100 +++ b/UnitTests/ServerIndex.cpp Fri Dec 07 12:56:27 2012 +0100 @@ -1,6 +1,7 @@ #include "gtest/gtest.h" #include "../OrthancServer/DatabaseWrapper.h" +#include "../Core/Toolbox.h" #include #include @@ -12,7 +13,7 @@ class ServerIndexListener : public IServerIndexListener { public: - std::set deletedFiles_; + std::vector deletedFiles_; std::string ancestorId_; ResourceType ancestorType_; @@ -31,7 +32,7 @@ virtual void SignalFileDeleted(const std::string& fileUuid) { - deletedFiles_.insert(fileUuid); + deletedFiles_.push_back(fileUuid); LOG(INFO) << "A file must be removed: " << fileUuid; } }; @@ -170,8 +171,12 @@ index.DeleteResource(a[0]); ASSERT_EQ(2u, listener.deletedFiles_.size()); - ASSERT_FALSE(listener.deletedFiles_.find("my json file") == listener.deletedFiles_.end()); - ASSERT_FALSE(listener.deletedFiles_.find("my dicom file") == listener.deletedFiles_.end()); + ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), + listener.deletedFiles_.end(), + "my json file") == listener.deletedFiles_.end()); + ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), + listener.deletedFiles_.end(), + "my dicom file") == listener.deletedFiles_.end()); ASSERT_EQ(2u, index.GetTableRecordCount("Resources")); ASSERT_EQ(0u, index.GetTableRecordCount("Metadata")); @@ -183,7 +188,9 @@ ASSERT_EQ(2u, index.GetTableRecordCount("GlobalProperties")); ASSERT_EQ(3u, listener.deletedFiles_.size()); - ASSERT_FALSE(listener.deletedFiles_.find("world") == listener.deletedFiles_.end()); + ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), + listener.deletedFiles_.end(), + "world") == listener.deletedFiles_.end()); } @@ -256,3 +263,132 @@ index.DeleteResource(a[6]); ASSERT_EQ("", listener.ancestorId_); // No more ancestor } + + +TEST(DatabaseWrapper, PatientRecycling) +{ + ServerIndexListener listener; + DatabaseWrapper index(listener); + + std::vector patients; + for (int i = 0; i < 10; i++) + { + std::string p = "Patient " + boost::lexical_cast(i); + patients.push_back(index.CreateResource(p, ResourceType_Patient)); + index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10)); + ASSERT_FALSE(index.IsProtectedPatient(patients[i])); + } + + ASSERT_EQ(10u, index.GetTableRecordCount("Resources")); + ASSERT_EQ(10u, index.GetTableRecordCount("PatientRecyclingOrder")); + + listener.Reset(); + + index.DeleteResource(patients[5]); + index.DeleteResource(patients[0]); + ASSERT_EQ(8u, index.GetTableRecordCount("Resources")); + ASSERT_EQ(8u, index.GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_EQ(2u, listener.deletedFiles_.size()); + ASSERT_EQ("Patient 5", listener.deletedFiles_[0]); + ASSERT_EQ("Patient 0", listener.deletedFiles_[1]); + + int64_t p; + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); + index.DeleteResource(p); + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); + index.DeleteResource(p); + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); + index.DeleteResource(p); + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); + index.DeleteResource(p); + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]); + index.DeleteResource(p); + index.DeleteResource(patients[8]); + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]); + index.DeleteResource(p); + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]); + index.DeleteResource(p); + ASSERT_FALSE(index.SelectPatientToRecycle(p)); + + ASSERT_EQ(10u, listener.deletedFiles_.size()); + ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); +} + + +TEST(DatabaseWrapper, PatientProtection) +{ + Toolbox::RemoveFile("hi.sqlite"); + ServerIndexListener listener; + DatabaseWrapper index("hi.sqlite", listener); + + std::vector patients; + for (int i = 0; i < 5; i++) + { + std::string p = "Patient " + boost::lexical_cast(i); + patients.push_back(index.CreateResource(p, ResourceType_Patient)); + index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10)); + ASSERT_FALSE(index.IsProtectedPatient(patients[i])); + } + + ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); + ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_FALSE(index.IsProtectedPatient(patients[2])); + index.SetProtectedPatient(patients[2], true); + ASSERT_TRUE(index.IsProtectedPatient(patients[2])); + ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); + ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); + + index.SetProtectedPatient(patients[2], true); + ASSERT_TRUE(index.IsProtectedPatient(patients[2])); + ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); + index.SetProtectedPatient(patients[2], false); + ASSERT_FALSE(index.IsProtectedPatient(patients[2])); + ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); + index.SetProtectedPatient(patients[2], false); + ASSERT_FALSE(index.IsProtectedPatient(patients[2])); + ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); + ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); + index.SetProtectedPatient(patients[2], true); + ASSERT_TRUE(index.IsProtectedPatient(patients[2])); + ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); + index.SetProtectedPatient(patients[2], false); + ASSERT_FALSE(index.IsProtectedPatient(patients[2])); + ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); + index.SetProtectedPatient(patients[3], true); + ASSERT_TRUE(index.IsProtectedPatient(patients[3])); + ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); + ASSERT_EQ(0u, listener.deletedFiles_.size()); + + // Unprotecting a patient puts it at the last position in the recycling queue + int64_t p; + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]); + index.DeleteResource(p); + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); + index.DeleteResource(p); + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); + index.DeleteResource(p); + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); + index.DeleteResource(p); + // "patients[3]" is still protected + ASSERT_FALSE(index.SelectPatientToRecycle(p)); + + ASSERT_EQ(4u, listener.deletedFiles_.size()); + ASSERT_EQ(1u, index.GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); + + index.SetProtectedPatient(patients[3], false); + ASSERT_EQ(1u, index.GetTableRecordCount("PatientRecyclingOrder")); + ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); + index.DeleteResource(p); + + ASSERT_EQ(5u, listener.deletedFiles_.size()); + ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); +}