# HG changeset patch # User Sebastien Jodogne # Date 1444747323 -7200 # Node ID 77d3e3a13c107ef672ae955c93f25798221f91e6 # Parent f5ddbd9239dd1911dbb2e7f1244ce39a01febfcb# Parent d2268c7a7ede5648744dac8dfab7f36956d96670 integration db-changes->mainline diff -r f5ddbd9239dd -r 77d3e3a13c10 CMakeLists.txt --- a/CMakeLists.txt Fri Oct 09 17:20:26 2015 +0200 +++ b/CMakeLists.txt Tue Oct 13 16:42:03 2015 +0200 @@ -10,8 +10,9 @@ # * Orthanc 0.3.1 = version 2 # * Orthanc 0.4.0 -> Orthanc 0.7.2 = version 3 # * Orthanc 0.7.3 -> Orthanc 0.8.4 = version 4 -# * Orthanc 0.8.5 -> mainline = version 5 -set(ORTHANC_DATABASE_VERSION 5) +# * Orthanc 0.8.5 -> Orthanc 0.9.4 = version 5 +# * Orthanc 0.9.5 -> mainline = version 6 +set(ORTHANC_DATABASE_VERSION 6) ##################################################################### @@ -145,21 +146,28 @@ set(ORTHANC_SERVER_SOURCES + OrthancServer/DatabaseWrapper.cpp + OrthancServer/DatabaseWrapperBase.cpp + OrthancServer/DicomDirWriter.cpp + OrthancServer/DicomFindQuery.cpp + OrthancServer/DicomModification.cpp OrthancServer/DicomProtocol/DicomFindAnswers.cpp OrthancServer/DicomProtocol/DicomServer.cpp OrthancServer/DicomProtocol/DicomUserConnection.cpp OrthancServer/DicomProtocol/RemoteModalityParameters.cpp OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp - OrthancServer/DicomModification.cpp + OrthancServer/ExportedResource.cpp OrthancServer/FromDcmtkBridge.cpp - OrthancServer/ParsedDicomFile.cpp - OrthancServer/DicomDirWriter.cpp OrthancServer/Internals/CommandDispatcher.cpp + OrthancServer/Internals/DicomImageDecoder.cpp OrthancServer/Internals/FindScp.cpp OrthancServer/Internals/MoveScp.cpp OrthancServer/Internals/StoreScp.cpp - OrthancServer/Internals/DicomImageDecoder.cpp + OrthancServer/LuaScripting.cpp + OrthancServer/OrthancFindRequestHandler.cpp + OrthancServer/OrthancHttpHandler.cpp OrthancServer/OrthancInitialization.cpp + OrthancServer/OrthancMoveRequestHandler.cpp OrthancServer/OrthancPeerParameters.cpp OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/OrthancRestApi/OrthancRestApi.cpp @@ -168,20 +176,15 @@ OrthancServer/OrthancRestApi/OrthancRestModalities.cpp OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/OrthancRestApi/OrthancRestSystem.cpp - OrthancServer/ServerIndex.cpp - OrthancServer/ToDcmtkBridge.cpp - OrthancServer/DatabaseWrapper.cpp + OrthancServer/ParsedDicomFile.cpp + OrthancServer/QueryRetrieveHandler.cpp + OrthancServer/ResourceFinder.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerEnumerations.cpp + OrthancServer/ServerIndex.cpp OrthancServer/ServerToolbox.cpp - OrthancServer/OrthancFindRequestHandler.cpp - OrthancServer/OrthancMoveRequestHandler.cpp - OrthancServer/ExportedResource.cpp - OrthancServer/ResourceFinder.cpp - OrthancServer/DicomFindQuery.cpp - OrthancServer/QueryRetrieveHandler.cpp - OrthancServer/LuaScripting.cpp - OrthancServer/OrthancHttpHandler.cpp + OrthancServer/SliceOrdering.cpp + OrthancServer/ToDcmtkBridge.cpp # From "lua-scripting" branch OrthancServer/DicomInstanceToStore.cpp diff -r f5ddbd9239dd -r 77d3e3a13c10 Core/DicomFormat/DicomMap.cpp --- a/Core/DicomFormat/DicomMap.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/Core/DicomFormat/DicomMap.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -83,7 +83,8 @@ DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, DICOM_TAG_NUMBER_OF_SLICES, DICOM_TAG_NUMBER_OF_TIME_SLICES, - DICOM_TAG_SERIES_INSTANCE_UID + DICOM_TAG_SERIES_INSTANCE_UID, + DICOM_TAG_IMAGE_ORIENTATION_PATIENT // New in db v6 }; static DicomTag instanceTags[] = @@ -95,7 +96,8 @@ DICOM_TAG_INSTANCE_NUMBER, DICOM_TAG_NUMBER_OF_FRAMES, DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, - DICOM_TAG_SOP_INSTANCE_UID + DICOM_TAG_SOP_INSTANCE_UID, + DICOM_TAG_IMAGE_POSITION_PATIENT // New in db v6 }; diff -r f5ddbd9239dd -r 77d3e3a13c10 Core/DicomFormat/DicomTag.h --- a/Core/DicomFormat/DicomTag.h Fri Oct 09 17:20:26 2015 +0200 +++ b/Core/DicomFormat/DicomTag.h Tue Oct 13 16:42:03 2015 +0200 @@ -134,6 +134,8 @@ static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103); static const DicomTag DICOM_TAG_PLANAR_CONFIGURATION(0x0028, 0x0006); static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004); + static const DicomTag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037); + static const DicomTag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032); // Tags related to date and time static const DicomTag DICOM_TAG_ACQUISITION_DATE(0x0008, 0x0022); diff -r f5ddbd9239dd -r 77d3e3a13c10 Core/Enumerations.cpp --- a/Core/Enumerations.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/Core/Enumerations.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -44,11 +44,6 @@ // "Resources/GenerateErrorCodes.py" const char* EnumerationToString(ErrorCode error) { - if (error >= ErrorCode_START_PLUGINS) - { - return "Error encountered within some plugin"; - } - switch (error) { case ErrorCode_InternalError: @@ -318,8 +313,21 @@ case ErrorCode_DatabaseNotInitialized: return "Plugin trying to call the database during its initialization"; + case ErrorCode_SslDisabled: + return "Orthanc has been built without SSL support"; + + case ErrorCode_CannotOrderSlices: + return "Unable to order the slices of the series"; + default: - return "Unknown error code"; + if (error >= ErrorCode_START_PLUGINS) + { + return "Error encountered within some plugin"; + } + else + { + return "Unknown error code"; + } } } diff -r f5ddbd9239dd -r 77d3e3a13c10 Core/Enumerations.h --- a/Core/Enumerations.h Fri Oct 09 17:20:26 2015 +0200 +++ b/Core/Enumerations.h Tue Oct 13 16:42:03 2015 +0200 @@ -134,6 +134,8 @@ ErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, ErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, ErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, + ErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, + ErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, ErrorCode_START_PLUGINS = 1000000 }; diff -r f5ddbd9239dd -r 77d3e3a13c10 Core/HttpServer/MongooseServer.cpp --- a/Core/HttpServer/MongooseServer.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/Core/HttpServer/MongooseServer.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -931,7 +931,7 @@ #if ORTHANC_SSL_ENABLED == 0 if (enabled) { - throw OrthancException("Orthanc has been built without SSL support"); + throw OrthancException(ErrorCode_SslDisabled); } else { diff -r f5ddbd9239dd -r 77d3e3a13c10 Core/SQLite/Connection.h --- a/Core/SQLite/Connection.h Fri Oct 09 17:20:26 2015 +0200 +++ b/Core/SQLite/Connection.h Tue Oct 13 16:42:03 2015 +0200 @@ -46,7 +46,7 @@ struct sqlite3; struct sqlite3_stmt; -#define SQLITE_FROM_HERE SQLite::StatementId(__FILE__, __LINE__) +#define SQLITE_FROM_HERE ::Orthanc::SQLite::StatementId(__FILE__, __LINE__) namespace Orthanc { diff -r f5ddbd9239dd -r 77d3e3a13c10 NEWS --- a/NEWS Fri Oct 09 17:20:26 2015 +0200 +++ b/NEWS Tue Oct 13 16:42:03 2015 +0200 @@ -5,6 +5,7 @@ * "/tools/create-dicom": Support of binary tags encoded using data URI scheme * "/tools/create-dicom": Support of hierarchical structures (creation of sequences) * "/modify" can insert/modify sequences +* "/series/.../ordered-slices" to order the slices of a 2D+t or 3D image * New URIs for attachments: ".../compress", ".../uncompress" and ".../is-compressed" Plugins diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancExplorer/libs/images/ajax-loader.gif diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/DatabaseWrapper.cpp --- a/OrthancServer/DatabaseWrapper.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/DatabaseWrapper.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -37,6 +37,7 @@ #include "../Core/Logging.h" #include "../Core/Uuid.h" #include "EmbeddedResources.h" +#include "ServerToolbox.h" #include #include @@ -186,130 +187,6 @@ } - - void DatabaseWrapper::SetGlobalProperty(GlobalProperty property, - const std::string& value) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)"); - s.BindInt(0, property); - s.BindString(1, value); - s.Run(); - } - - bool DatabaseWrapper::LookupGlobalProperty(std::string& target, - GlobalProperty property) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT value FROM GlobalProperties WHERE property=?"); - s.BindInt(0, property); - - if (!s.Step()) - { - return false; - } - else - { - target = s.ColumnString(0); - return true; - } - } - - int64_t DatabaseWrapper::CreateResource(const std::string& publicId, - ResourceType type) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)"); - s.BindInt(0, type); - s.BindString(1, publicId); - s.Run(); - return db_.GetLastInsertRowId(); - } - - bool DatabaseWrapper::LookupResource(int64_t& id, - ResourceType& type, - const std::string& publicId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT internalId, resourceType FROM Resources WHERE publicId=?"); - s.BindString(0, publicId); - - if (!s.Step()) - { - return false; - } - else - { - id = s.ColumnInt(0); - type = static_cast(s.ColumnInt(1)); - - // Check whether there is a single resource with this public id - assert(!s.Step()); - - return true; - } - } - - bool DatabaseWrapper::LookupParent(int64_t& parentId, - int64_t resourceId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT parentId FROM Resources WHERE internalId=?"); - s.BindInt64(0, resourceId); - - if (!s.Step()) - { - throw OrthancException(ErrorCode_UnknownResource); - } - - if (s.ColumnIsNull(0)) - { - return false; - } - else - { - parentId = s.ColumnInt(0); - return true; - } - } - - std::string DatabaseWrapper::GetPublicId(int64_t resourceId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT publicId FROM Resources WHERE internalId=?"); - s.BindInt64(0, resourceId); - - if (!s.Step()) - { - throw OrthancException(ErrorCode_UnknownResource); - } - - return s.ColumnString(0); - } - - - ResourceType DatabaseWrapper::GetResourceType(int64_t resourceId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT resourceType FROM Resources WHERE internalId=?"); - s.BindInt64(0, resourceId); - - if (!s.Step()) - { - throw OrthancException(ErrorCode_UnknownResource); - } - - return static_cast(s.ColumnInt(0)); - } - - - void DatabaseWrapper::AttachChild(int64_t parent, - int64_t child) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?"); - s.BindInt64(0, parent); - s.BindInt64(1, child); - s.Run(); - } - void DatabaseWrapper::GetChildren(std::list& childrenPublicIds, int64_t id) @@ -341,183 +218,6 @@ } } - void DatabaseWrapper::SetMetadata(int64_t id, - MetadataType type, - const std::string& value) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)"); - s.BindInt64(0, id); - s.BindInt(1, type); - s.BindString(2, value); - s.Run(); - } - - void DatabaseWrapper::DeleteMetadata(int64_t id, - MetadataType type) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?"); - s.BindInt64(0, id); - s.BindInt(1, type); - s.Run(); - } - - bool DatabaseWrapper::LookupMetadata(std::string& target, - int64_t id, - MetadataType type) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT value FROM Metadata WHERE id=? AND type=?"); - s.BindInt64(0, id); - s.BindInt(1, type); - - if (!s.Step()) - { - return false; - } - else - { - target = s.ColumnString(0); - return true; - } - } - - void DatabaseWrapper::ListAvailableMetadata(std::list& target, - int64_t id) - { - target.clear(); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?"); - s.BindInt64(0, id); - - while (s.Step()) - { - target.push_back(static_cast(s.ColumnInt(0))); - } - } - - - void DatabaseWrapper::AddAttachment(int64_t id, - const FileInfo& attachment) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); - s.BindInt64(0, id); - s.BindInt(1, attachment.GetContentType()); - s.BindString(2, attachment.GetUuid()); - s.BindInt64(3, attachment.GetCompressedSize()); - s.BindInt64(4, attachment.GetUncompressedSize()); - s.BindInt(5, attachment.GetCompressionType()); - s.BindString(6, attachment.GetUncompressedMD5()); - s.BindString(7, attachment.GetCompressedMD5()); - s.Run(); - } - - - void DatabaseWrapper::DeleteAttachment(int64_t id, - FileContentType attachment) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?"); - s.BindInt64(0, id); - s.BindInt(1, attachment); - s.Run(); - } - - - - void DatabaseWrapper::ListAvailableAttachments(std::list& target, - int64_t id) - { - target.clear(); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT fileType FROM AttachedFiles WHERE id=?"); - s.BindInt64(0, id); - - while (s.Step()) - { - target.push_back(static_cast(s.ColumnInt(0))); - } - } - - bool DatabaseWrapper::LookupAttachment(FileInfo& attachment, - int64_t id, - FileContentType contentType) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?"); - s.BindInt64(0, id); - s.BindInt(1, contentType); - - if (!s.Step()) - { - return false; - } - else - { - attachment = FileInfo(s.ColumnString(0), - contentType, - s.ColumnInt64(1), - s.ColumnString(4), - static_cast(s.ColumnInt(2)), - s.ColumnInt64(3), - s.ColumnString(5)); - return true; - } - } - - - static void SetMainDicomTagsInternal(SQLite::Statement& s, - int64_t id, - const DicomTag& tag, - const std::string& value) - { - s.BindInt64(0, id); - s.BindInt(1, tag.GetGroup()); - s.BindInt(2, tag.GetElement()); - s.BindString(3, value); - s.Run(); - } - - - void DatabaseWrapper::SetMainDicomTag(int64_t id, - const DicomTag& tag, - const std::string& value) - { - if (tag.IsIdentifier()) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)"); - SetMainDicomTagsInternal(s, id, tag, value); - } - else - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); - SetMainDicomTagsInternal(s, id, tag, value); - } - } - - void DatabaseWrapper::GetMainDicomTags(DicomMap& map, - int64_t id) - { - map.Clear(); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?"); - s.BindInt64(0, id); - while (s.Step()) - { - map.SetValue(s.ColumnInt(1), - 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)); - } - } - bool DatabaseWrapper::GetParentPublicId(std::string& target, int64_t id) @@ -538,164 +238,6 @@ } - void DatabaseWrapper::GetChildrenPublicId(std::list& target, - int64_t id) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " - "WHERE a.parentId = b.internalId AND b.internalId = ?"); - s.BindInt64(0, id); - - target.clear(); - - while (s.Step()) - { - target.push_back(s.ColumnString(0)); - } - } - - - void DatabaseWrapper::GetChildrenInternalId(std::list& target, - int64_t id) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b " - "WHERE a.parentId = b.internalId AND b.internalId = ?"); - s.BindInt64(0, id); - - target.clear(); - - while (s.Step()) - { - target.push_back(s.ColumnInt64(0)); - } - } - - - void DatabaseWrapper::LogChange(int64_t internalId, - const ServerIndexChange& change) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); - s.BindInt(0, change.GetChangeType()); - s.BindInt64(1, internalId); - s.BindInt(2, change.GetResourceType()); - s.BindString(3, change.GetDate()); - s.Run(); - } - - - void DatabaseWrapper::GetChangesInternal(std::list& target, - bool& done, - SQLite::Statement& s, - uint32_t maxResults) - { - target.clear(); - - while (target.size() < maxResults && s.Step()) - { - int64_t seq = s.ColumnInt64(0); - ChangeType changeType = static_cast(s.ColumnInt(1)); - ResourceType resourceType = static_cast(s.ColumnInt(3)); - const std::string& date = s.ColumnString(4); - - int64_t internalId = s.ColumnInt64(2); - std::string publicId = GetPublicId(internalId); - - target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date)); - } - - done = !(target.size() == maxResults && s.Step()); - } - - - void DatabaseWrapper::GetChanges(std::list& target, - bool& done, - int64_t since, - uint32_t maxResults) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?"); - s.BindInt64(0, since); - s.BindInt(1, maxResults + 1); - GetChangesInternal(target, done, s, maxResults); - } - - void DatabaseWrapper::GetLastChange(std::list& target) - { - bool done; // Ignored - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1"); - GetChangesInternal(target, done, s, 1); - } - - - void DatabaseWrapper::LogExportedResource(const ExportedResource& resource) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)"); - - s.BindInt(0, resource.GetResourceType()); - s.BindString(1, resource.GetPublicId()); - s.BindString(2, resource.GetModality()); - s.BindString(3, resource.GetPatientId()); - s.BindString(4, resource.GetStudyInstanceUid()); - s.BindString(5, resource.GetSeriesInstanceUid()); - s.BindString(6, resource.GetSopInstanceUid()); - s.BindString(7, resource.GetDate()); - s.Run(); - } - - - void DatabaseWrapper::GetExportedResourcesInternal(std::list& target, - bool& done, - SQLite::Statement& s, - uint32_t maxResults) - { - target.clear(); - - while (target.size() < maxResults && s.Step()) - { - int64_t seq = s.ColumnInt64(0); - ResourceType resourceType = static_cast(s.ColumnInt(1)); - std::string publicId = s.ColumnString(2); - - ExportedResource resource(seq, - resourceType, - publicId, - s.ColumnString(3), // modality - s.ColumnString(8), // date - s.ColumnString(4), // patient ID - s.ColumnString(5), // study instance UID - s.ColumnString(6), // series instance UID - s.ColumnString(7)); // sop instance UID - - target.push_back(resource); - } - - done = !(target.size() == maxResults && s.Step()); - } - - - void DatabaseWrapper::GetExportedResources(std::list& target, - bool& done, - int64_t since, - uint32_t maxResults) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?"); - s.BindInt64(0, since); - s.BindInt(1, maxResults + 1); - GetExportedResourcesInternal(target, done, s, maxResults); - } - - - void DatabaseWrapper::GetLastExportedResource(std::list& target) - { - bool done; // Ignored - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1"); - GetExportedResourcesInternal(target, done, s, 1); - } - - - - int64_t DatabaseWrapper::GetTableRecordCount(const std::string& table) { char buf[128]; @@ -714,68 +256,14 @@ } - uint64_t DatabaseWrapper::GetTotalCompressedSize() - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles"); - s.Run(); - return static_cast(s.ColumnInt64(0)); - } - - - uint64_t DatabaseWrapper::GetTotalUncompressedSize() + DatabaseWrapper::DatabaseWrapper(const std::string& path) : listener_(NULL), base_(db_) { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles"); - s.Run(); - return static_cast(s.ColumnInt64(0)); - } - - void DatabaseWrapper::GetAllPublicIds(std::list& target, - ResourceType resourceType) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?"); - s.BindInt(0, resourceType); - - target.clear(); - while (s.Step()) - { - target.push_back(s.ColumnString(0)); - } + db_.Open(path); } - void DatabaseWrapper::GetAllPublicIds(std::list& target, - ResourceType resourceType, - size_t since, - size_t limit) - { - if (limit == 0) - { - target.clear(); - return; - } - - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=? LIMIT ? OFFSET ?"); - s.BindInt(0, resourceType); - s.BindInt64(1, limit); - s.BindInt64(2, since); - - target.clear(); - while (s.Step()) - { - target.push_back(s.ColumnString(0)); - } - } - - - DatabaseWrapper::DatabaseWrapper(const std::string& path) : listener_(NULL) - { - db_.Open(path); - Open(); - } - - DatabaseWrapper::DatabaseWrapper() : listener_(NULL) + DatabaseWrapper::DatabaseWrapper() : listener_(NULL), base_(db_) { db_.OpenInMemory(); - Open(); } void DatabaseWrapper::Open() @@ -839,15 +327,17 @@ void DatabaseWrapper::Upgrade(unsigned int targetVersion, IStorageArea& storageArea) { - if (targetVersion != 5) + if (targetVersion != 6) { throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } - // This version of Orthanc is only compatible with versions 3, 4 and 5 of the DB schema + // This version of Orthanc is only compatible with versions 3, 4, + // 5 and 6 of the DB schema if (version_ != 3 && version_ != 4 && - version_ != 5) + version_ != 5 && + version_ != 6) { throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } @@ -865,6 +355,18 @@ ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_4_TO_5); version_ = 5; } + + if (version_ == 5) + { + LOG(WARNING) << "Upgrading database version from 5 to 6"; + // No change in the DB schema, the step from version 5 to 6 only + // consists in reconstructing the main DICOM tags information. + db_.BeginTransaction(); + Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Study); + Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Series); + db_.CommitTransaction(); + version_ = 6; + } } @@ -875,90 +377,6 @@ db_.Register(new Internals::SignalResourceDeleted(listener)); } - uint64_t DatabaseWrapper::GetResourceCount(ResourceType resourceType) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT COUNT(*) FROM Resources WHERE resourceType=?"); - s.BindInt(0, resourceType); - - if (!s.Step()) - { - throw OrthancException(ErrorCode_InternalError); - } - - int64_t c = s.ColumnInt(0); - assert(!s.Step()); - - 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::SelectPatientToRecycle(int64_t& internalId, - int64_t patientIdToAvoid) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT patientId FROM PatientRecyclingOrder " - "WHERE patientId != ? ORDER BY seq ASC LIMIT 1"); - s.BindInt64(0, patientIdToAvoid); - - 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.BindInt64(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.BindInt64(0, internalId); - s.Run(); - } - else if (IsProtectedPatient(internalId)) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); - s.BindInt64(0, internalId); - s.Run(); - } - else - { - // Nothing to do: The patient is already unprotected - } - } - - void DatabaseWrapper::ClearTable(const std::string& tableName) { @@ -966,54 +384,89 @@ } - bool DatabaseWrapper::IsExistingResource(int64_t internalId) + bool DatabaseWrapper::LookupParent(int64_t& parentId, + int64_t resourceId) { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT * FROM Resources WHERE internalId=?"); - s.BindInt64(0, internalId); - return s.Step(); + bool found; + ErrorCode error = base_.LookupParent(found, parentId, resourceId); + + if (error != ErrorCode_Success) + { + throw OrthancException(error); + } + else + { + return found; + } + } + + + ResourceType DatabaseWrapper::GetResourceType(int64_t resourceId) + { + ResourceType result; + ErrorCode code = base_.GetResourceType(result, resourceId); + + if (code == ErrorCode_Success) + { + return result; + } + else + { + throw OrthancException(code); + } } - void DatabaseWrapper::LookupIdentifier(std::list& target, - const DicomTag& tag, - const std::string& value) + std::string DatabaseWrapper::GetPublicId(int64_t resourceId) + { + std::string id; + + if (base_.GetPublicId(id, resourceId)) + { + return id; + } + else + { + throw OrthancException(ErrorCode_UnknownResource); + } + } + + + void DatabaseWrapper::GetChanges(std::list& target /*out*/, + bool& done /*out*/, + int64_t since, + uint32_t maxResults) + { + ErrorCode code = base_.GetChanges(target, done, since, maxResults); + + if (code != ErrorCode_Success) + { + throw OrthancException(code); + } + } + + + void DatabaseWrapper::GetLastChange(std::list& target /*out*/) + { + ErrorCode code = base_.GetLastChange(target); + + if (code != ErrorCode_Success) + { + throw OrthancException(code); + } + } + + + void DatabaseWrapper::LookupIdentifier(std::list& target, + const DicomTag& tag, + const std::string& value) { if (!tag.IsIdentifier()) { throw OrthancException(ErrorCode_ParameterOutOfRange); } - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT id FROM DicomIdentifiers WHERE tagGroup=? AND tagElement=? and value=?"); - - s.BindInt(0, tag.GetGroup()); - s.BindInt(1, tag.GetElement()); - s.BindString(2, value); - - target.clear(); - - while (s.Step()) - { - target.push_back(s.ColumnInt64(0)); - } - } - - - void DatabaseWrapper::LookupIdentifier(std::list& target, - const std::string& value) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT id FROM DicomIdentifiers WHERE value=?"); - - s.BindString(0, value); - - target.clear(); - - while (s.Step()) - { - target.push_back(s.ColumnInt64(0)); - } + base_.LookupIdentifier(target, tag, value); } diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/DatabaseWrapper.h --- a/OrthancServer/DatabaseWrapper.h Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/DatabaseWrapper.h Tue Oct 13 16:42:03 2015 +0200 @@ -36,6 +36,7 @@ #include "../Core/SQLite/Connection.h" #include "../Core/SQLite/Transaction.h" +#include "DatabaseWrapperBase.h" namespace Orthanc { @@ -54,21 +55,10 @@ private: IDatabaseListener* listener_; SQLite::Connection db_; + DatabaseWrapperBase base_; Internals::SignalRemainingAncestor* signalRemainingAncestor_; unsigned int version_; - void Open(); - - void GetChangesInternal(std::list& target, - bool& done, - SQLite::Statement& s, - uint32_t maxResults); - - void GetExportedResourcesInternal(std::list& target, - bool& done, - SQLite::Statement& s, - uint32_t maxResults); - void ClearTable(const std::string& tableName); public: @@ -76,20 +66,39 @@ DatabaseWrapper(); + virtual void Open(); + + virtual void Close() + { + db_.Close(); + } + virtual void SetListener(IDatabaseListener& listener); virtual void SetGlobalProperty(GlobalProperty property, - const std::string& value); + const std::string& value) + { + base_.SetGlobalProperty(property, value); + } virtual bool LookupGlobalProperty(std::string& target, - GlobalProperty property); + GlobalProperty property) + { + return base_.LookupGlobalProperty(target, property); + } virtual int64_t CreateResource(const std::string& publicId, - ResourceType type); + ResourceType type) + { + return base_.CreateResource(publicId, type); + } virtual bool LookupResource(int64_t& id, ResourceType& type, - const std::string& publicId); + const std::string& publicId) + { + return base_.LookupResource(id, type, publicId); + } virtual bool LookupParent(int64_t& parentId, int64_t resourceId); @@ -99,52 +108,99 @@ virtual ResourceType GetResourceType(int64_t resourceId); virtual void AttachChild(int64_t parent, - int64_t child); + int64_t child) + { + base_.AttachChild(parent, child); + } virtual void DeleteResource(int64_t id); virtual void SetMetadata(int64_t id, MetadataType type, - const std::string& value); + const std::string& value) + { + base_.SetMetadata(id, type, value); + } virtual void DeleteMetadata(int64_t id, - MetadataType type); + MetadataType type) + { + base_.DeleteMetadata(id, type); + } virtual bool LookupMetadata(std::string& target, int64_t id, - MetadataType type); + MetadataType type) + { + return base_.LookupMetadata(target, id, type); + } virtual void ListAvailableMetadata(std::list& target, - int64_t id); + int64_t id) + { + base_.ListAvailableMetadata(target, id); + } virtual void AddAttachment(int64_t id, - const FileInfo& attachment); + const FileInfo& attachment) + { + base_.AddAttachment(id, attachment); + } virtual void DeleteAttachment(int64_t id, - FileContentType attachment); + FileContentType attachment) + { + base_.DeleteAttachment(id, attachment); + } virtual void ListAvailableAttachments(std::list& target, - int64_t id); + int64_t id) + { + return base_.ListAvailableAttachments(target, id); + } virtual bool LookupAttachment(FileInfo& attachment, int64_t id, - FileContentType contentType); + FileContentType contentType) + { + return base_.LookupAttachment(attachment, id, contentType); + } + + virtual void ClearMainDicomTags(int64_t id) + { + base_.ClearMainDicomTags(id); + } virtual void SetMainDicomTag(int64_t id, const DicomTag& tag, - const std::string& value); + const std::string& value) + { + base_.SetMainDicomTag(id, tag, value); + } virtual void GetMainDicomTags(DicomMap& map, - int64_t id); + int64_t id) + { + base_.GetMainDicomTags(map, id); + } virtual void GetChildrenPublicId(std::list& target, - int64_t id); + int64_t id) + { + base_.GetChildrenPublicId(target, id); + } virtual void GetChildrenInternalId(std::list& target, - int64_t id); + int64_t id) + { + base_.GetChildrenInternalId(target, id); + } virtual void LogChange(int64_t internalId, - const ServerIndexChange& change); + const ServerIndexChange& change) + { + base_.LogChange(internalId, change); + } virtual void GetChanges(std::list& target /*out*/, bool& done /*out*/, @@ -153,38 +209,74 @@ virtual void GetLastChange(std::list& target /*out*/); - virtual void LogExportedResource(const ExportedResource& resource); + virtual void LogExportedResource(const ExportedResource& resource) + { + base_.LogExportedResource(resource); + } virtual void GetExportedResources(std::list& target /*out*/, bool& done /*out*/, int64_t since, - uint32_t maxResults); + uint32_t maxResults) + { + base_.GetExportedResources(target, done, since, maxResults); + } - virtual void GetLastExportedResource(std::list& target /*out*/); + virtual void GetLastExportedResource(std::list& target /*out*/) + { + base_.GetLastExportedResource(target); + } - virtual uint64_t GetTotalCompressedSize(); + virtual uint64_t GetTotalCompressedSize() + { + return base_.GetTotalCompressedSize(); + } - virtual uint64_t GetTotalUncompressedSize(); + virtual uint64_t GetTotalUncompressedSize() + { + return base_.GetTotalUncompressedSize(); + } - virtual uint64_t GetResourceCount(ResourceType resourceType); + virtual uint64_t GetResourceCount(ResourceType resourceType) + { + return base_.GetResourceCount(resourceType); + } virtual void GetAllPublicIds(std::list& target, - ResourceType resourceType); + ResourceType resourceType) + { + base_.GetAllPublicIds(target, resourceType); + } virtual void GetAllPublicIds(std::list& target, ResourceType resourceType, size_t since, - size_t limit); + size_t limit) + { + base_.GetAllPublicIds(target, resourceType, since, limit); + } - virtual bool SelectPatientToRecycle(int64_t& internalId); + virtual bool SelectPatientToRecycle(int64_t& internalId) + { + return base_.SelectPatientToRecycle(internalId); + } virtual bool SelectPatientToRecycle(int64_t& internalId, - int64_t patientIdToAvoid); + int64_t patientIdToAvoid) + { + return base_.SelectPatientToRecycle(internalId, patientIdToAvoid); + } - virtual bool IsProtectedPatient(int64_t internalId); + virtual bool IsProtectedPatient(int64_t internalId) + { + return base_.IsProtectedPatient(internalId); + } virtual void SetProtectedPatient(int64_t internalId, - bool isProtected); + bool isProtected) + { + base_.SetProtectedPatient(internalId, isProtected); + } virtual SQLite::ITransaction* StartTransaction() { @@ -211,14 +303,20 @@ ClearTable("ExportedResources"); } - virtual bool IsExistingResource(int64_t internalId); + virtual bool IsExistingResource(int64_t internalId) + { + return base_.IsExistingResource(internalId); + } virtual void LookupIdentifier(std::list& target, const DicomTag& tag, const std::string& value); virtual void LookupIdentifier(std::list& target, - const std::string& value); + const std::string& value) + { + base_.LookupIdentifier(target, value); + } virtual void GetAllMetadata(std::map& target, int64_t id); diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/DatabaseWrapperBase.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DatabaseWrapperBase.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -0,0 +1,715 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * 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 + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "PrecompiledHeadersServer.h" +#include "DatabaseWrapperBase.h" + +#include + +namespace Orthanc +{ + void DatabaseWrapperBase::SetGlobalProperty(GlobalProperty property, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)"); + s.BindInt(0, property); + s.BindString(1, value); + s.Run(); + } + + bool DatabaseWrapperBase::LookupGlobalProperty(std::string& target, + GlobalProperty property) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT value FROM GlobalProperties WHERE property=?"); + s.BindInt(0, property); + + if (!s.Step()) + { + return false; + } + else + { + target = s.ColumnString(0); + return true; + } + } + + int64_t DatabaseWrapperBase::CreateResource(const std::string& publicId, + ResourceType type) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)"); + s.BindInt(0, type); + s.BindString(1, publicId); + s.Run(); + return db_.GetLastInsertRowId(); + } + + bool DatabaseWrapperBase::LookupResource(int64_t& id, + ResourceType& type, + const std::string& publicId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT internalId, resourceType FROM Resources WHERE publicId=?"); + s.BindString(0, publicId); + + if (!s.Step()) + { + return false; + } + else + { + id = s.ColumnInt(0); + type = static_cast(s.ColumnInt(1)); + + // Check whether there is a single resource with this public id + assert(!s.Step()); + + return true; + } + } + + ErrorCode DatabaseWrapperBase::LookupParent(bool& found, + int64_t& parentId, + int64_t resourceId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT parentId FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); + + if (!s.Step()) + { + return ErrorCode_UnknownResource; + } + + if (s.ColumnIsNull(0)) + { + found = false; + } + else + { + found = true; + parentId = s.ColumnInt(0); + } + + return ErrorCode_Success; + } + + bool DatabaseWrapperBase::GetPublicId(std::string& result, + int64_t resourceId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT publicId FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); + + if (!s.Step()) + { + return false; + } + else + { + result = s.ColumnString(0); + return true; + } + } + + + ErrorCode DatabaseWrapperBase::GetResourceType(ResourceType& result, + int64_t resourceId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT resourceType FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); + + if (s.Step()) + { + result = static_cast(s.ColumnInt(0)); + return ErrorCode_Success; + } + else + { + return ErrorCode_UnknownResource; + } + } + + + void DatabaseWrapperBase::AttachChild(int64_t parent, + int64_t child) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?"); + s.BindInt64(0, parent); + s.BindInt64(1, child); + s.Run(); + } + + + void DatabaseWrapperBase::SetMetadata(int64_t id, + MetadataType type, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, type); + s.BindString(2, value); + s.Run(); + } + + void DatabaseWrapperBase::DeleteMetadata(int64_t id, + MetadataType type) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?"); + s.BindInt64(0, id); + s.BindInt(1, type); + s.Run(); + } + + bool DatabaseWrapperBase::LookupMetadata(std::string& target, + int64_t id, + MetadataType type) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT value FROM Metadata WHERE id=? AND type=?"); + s.BindInt64(0, id); + s.BindInt(1, type); + + if (!s.Step()) + { + return false; + } + else + { + target = s.ColumnString(0); + return true; + } + } + + void DatabaseWrapperBase::ListAvailableMetadata(std::list& target, + int64_t id) + { + target.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?"); + s.BindInt64(0, id); + + while (s.Step()) + { + target.push_back(static_cast(s.ColumnInt(0))); + } + } + + + void DatabaseWrapperBase::AddAttachment(int64_t id, + const FileInfo& attachment) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, attachment.GetContentType()); + s.BindString(2, attachment.GetUuid()); + s.BindInt64(3, attachment.GetCompressedSize()); + s.BindInt64(4, attachment.GetUncompressedSize()); + s.BindInt(5, attachment.GetCompressionType()); + s.BindString(6, attachment.GetUncompressedMD5()); + s.BindString(7, attachment.GetCompressedMD5()); + s.Run(); + } + + + void DatabaseWrapperBase::DeleteAttachment(int64_t id, + FileContentType attachment) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?"); + s.BindInt64(0, id); + s.BindInt(1, attachment); + s.Run(); + } + + + + void DatabaseWrapperBase::ListAvailableAttachments(std::list& target, + int64_t id) + { + target.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT fileType FROM AttachedFiles WHERE id=?"); + s.BindInt64(0, id); + + while (s.Step()) + { + target.push_back(static_cast(s.ColumnInt(0))); + } + } + + bool DatabaseWrapperBase::LookupAttachment(FileInfo& attachment, + int64_t id, + FileContentType contentType) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?"); + s.BindInt64(0, id); + s.BindInt(1, contentType); + + if (!s.Step()) + { + return false; + } + else + { + attachment = FileInfo(s.ColumnString(0), + contentType, + s.ColumnInt64(1), + s.ColumnString(4), + static_cast(s.ColumnInt(2)), + s.ColumnInt64(3), + s.ColumnString(5)); + return true; + } + } + + + void DatabaseWrapperBase::ClearMainDicomTags(int64_t id) + { + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?"); + s.BindInt64(0, id); + s.Run(); + } + + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?"); + s.BindInt64(0, id); + s.Run(); + } + } + + + static void SetMainDicomTagsInternal(SQLite::Statement& s, + int64_t id, + const DicomTag& tag, + const std::string& value) + { + s.BindInt64(0, id); + s.BindInt(1, tag.GetGroup()); + s.BindInt(2, tag.GetElement()); + s.BindString(3, value); + s.Run(); + } + + + void DatabaseWrapperBase::SetMainDicomTag(int64_t id, + const DicomTag& tag, + const std::string& value) + { + if (tag.IsIdentifier()) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)"); + SetMainDicomTagsInternal(s, id, tag, value); + } + else + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); + SetMainDicomTagsInternal(s, id, tag, value); + } + } + + void DatabaseWrapperBase::GetMainDicomTags(DicomMap& map, + int64_t id) + { + map.Clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?"); + s.BindInt64(0, id); + while (s.Step()) + { + map.SetValue(s.ColumnInt(1), + 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)); + } + } + + + + void DatabaseWrapperBase::GetChildrenPublicId(std::list& target, + int64_t id) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " + "WHERE a.parentId = b.internalId AND b.internalId = ?"); + s.BindInt64(0, id); + + target.clear(); + + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } + } + + + void DatabaseWrapperBase::GetChildrenInternalId(std::list& target, + int64_t id) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b " + "WHERE a.parentId = b.internalId AND b.internalId = ?"); + s.BindInt64(0, id); + + target.clear(); + + while (s.Step()) + { + target.push_back(s.ColumnInt64(0)); + } + } + + + void DatabaseWrapperBase::LogChange(int64_t internalId, + const ServerIndexChange& change) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); + s.BindInt(0, change.GetChangeType()); + s.BindInt64(1, internalId); + s.BindInt(2, change.GetResourceType()); + s.BindString(3, change.GetDate()); + s.Run(); + } + + + ErrorCode DatabaseWrapperBase::GetChangesInternal(std::list& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults) + { + target.clear(); + + while (target.size() < maxResults && s.Step()) + { + int64_t seq = s.ColumnInt64(0); + ChangeType changeType = static_cast(s.ColumnInt(1)); + ResourceType resourceType = static_cast(s.ColumnInt(3)); + const std::string& date = s.ColumnString(4); + + int64_t internalId = s.ColumnInt64(2); + std::string publicId; + if (!GetPublicId(publicId, internalId)) + { + return ErrorCode_UnknownResource; + } + + target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date)); + } + + done = !(target.size() == maxResults && s.Step()); + return ErrorCode_Success; + } + + + ErrorCode DatabaseWrapperBase::GetChanges(std::list& target, + bool& done, + int64_t since, + uint32_t maxResults) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?"); + s.BindInt64(0, since); + s.BindInt(1, maxResults + 1); + return GetChangesInternal(target, done, s, maxResults); + } + + ErrorCode DatabaseWrapperBase::GetLastChange(std::list& target) + { + bool done; // Ignored + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1"); + return GetChangesInternal(target, done, s, 1); + } + + + void DatabaseWrapperBase::LogExportedResource(const ExportedResource& resource) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)"); + + s.BindInt(0, resource.GetResourceType()); + s.BindString(1, resource.GetPublicId()); + s.BindString(2, resource.GetModality()); + s.BindString(3, resource.GetPatientId()); + s.BindString(4, resource.GetStudyInstanceUid()); + s.BindString(5, resource.GetSeriesInstanceUid()); + s.BindString(6, resource.GetSopInstanceUid()); + s.BindString(7, resource.GetDate()); + s.Run(); + } + + + void DatabaseWrapperBase::GetExportedResourcesInternal(std::list& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults) + { + target.clear(); + + while (target.size() < maxResults && s.Step()) + { + int64_t seq = s.ColumnInt64(0); + ResourceType resourceType = static_cast(s.ColumnInt(1)); + std::string publicId = s.ColumnString(2); + + ExportedResource resource(seq, + resourceType, + publicId, + s.ColumnString(3), // modality + s.ColumnString(8), // date + s.ColumnString(4), // patient ID + s.ColumnString(5), // study instance UID + s.ColumnString(6), // series instance UID + s.ColumnString(7)); // sop instance UID + + target.push_back(resource); + } + + done = !(target.size() == maxResults && s.Step()); + } + + + void DatabaseWrapperBase::GetExportedResources(std::list& target, + bool& done, + int64_t since, + uint32_t maxResults) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?"); + s.BindInt64(0, since); + s.BindInt(1, maxResults + 1); + GetExportedResourcesInternal(target, done, s, maxResults); + } + + + void DatabaseWrapperBase::GetLastExportedResource(std::list& target) + { + bool done; // Ignored + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1"); + GetExportedResourcesInternal(target, done, s, 1); + } + + + + uint64_t DatabaseWrapperBase::GetTotalCompressedSize() + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles"); + s.Run(); + return static_cast(s.ColumnInt64(0)); + } + + + uint64_t DatabaseWrapperBase::GetTotalUncompressedSize() + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles"); + s.Run(); + return static_cast(s.ColumnInt64(0)); + } + + void DatabaseWrapperBase::GetAllPublicIds(std::list& target, + ResourceType resourceType) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?"); + s.BindInt(0, resourceType); + + target.clear(); + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } + } + + void DatabaseWrapperBase::GetAllPublicIds(std::list& target, + ResourceType resourceType, + size_t since, + size_t limit) + { + if (limit == 0) + { + target.clear(); + return; + } + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=? LIMIT ? OFFSET ?"); + s.BindInt(0, resourceType); + s.BindInt64(1, limit); + s.BindInt64(2, since); + + target.clear(); + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } + } + + + uint64_t DatabaseWrapperBase::GetResourceCount(ResourceType resourceType) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT COUNT(*) FROM Resources WHERE resourceType=?"); + s.BindInt(0, resourceType); + + if (!s.Step()) + { + return 0; + } + else + { + int64_t c = s.ColumnInt(0); + assert(!s.Step()); + return c; + } + } + + + bool DatabaseWrapperBase::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 DatabaseWrapperBase::SelectPatientToRecycle(int64_t& internalId, + int64_t patientIdToAvoid) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT patientId FROM PatientRecyclingOrder " + "WHERE patientId != ? ORDER BY seq ASC LIMIT 1"); + s.BindInt64(0, patientIdToAvoid); + + if (!s.Step()) + { + // No patient remaining or all the patients are protected + return false; + } + else + { + internalId = s.ColumnInt(0); + return true; + } + } + + bool DatabaseWrapperBase::IsProtectedPatient(int64_t internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?"); + s.BindInt64(0, internalId); + return !s.Step(); + } + + void DatabaseWrapperBase::SetProtectedPatient(int64_t internalId, + bool isProtected) + { + if (isProtected) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); + s.BindInt64(0, internalId); + s.Run(); + } + else if (IsProtectedPatient(internalId)) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); + s.BindInt64(0, internalId); + s.Run(); + } + else + { + // Nothing to do: The patient is already unprotected + } + } + + + + bool DatabaseWrapperBase::IsExistingResource(int64_t internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM Resources WHERE internalId=?"); + s.BindInt64(0, internalId); + return s.Step(); + } + + + void DatabaseWrapperBase::LookupIdentifier(std::list& target, + const DicomTag& tag, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT id FROM DicomIdentifiers WHERE tagGroup=? AND tagElement=? and value=?"); + + s.BindInt(0, tag.GetGroup()); + s.BindInt(1, tag.GetElement()); + s.BindString(2, value); + + target.clear(); + + while (s.Step()) + { + target.push_back(s.ColumnInt64(0)); + } + } + + + void DatabaseWrapperBase::LookupIdentifier(std::list& target, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT id FROM DicomIdentifiers WHERE value=?"); + + s.BindString(0, value); + + target.clear(); + + while (s.Step()) + { + target.push_back(s.ColumnInt64(0)); + } + } +} diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/DatabaseWrapperBase.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DatabaseWrapperBase.h Tue Oct 13 16:42:03 2015 +0200 @@ -0,0 +1,197 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * 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 + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Core/DicomFormat/DicomMap.h" +#include "../Core/DicomFormat/DicomTag.h" +#include "../Core/Enumerations.h" +#include "../Core/FileStorage/FileInfo.h" +#include "../Core/SQLite/Connection.h" +#include "../OrthancServer/ExportedResource.h" +#include "../OrthancServer/ServerIndexChange.h" +#include "ServerEnumerations.h" + +#include + + +namespace Orthanc +{ + /** + * This class is shared between the Orthanc core and the sample + * database plugin whose code is in + * "../Plugins/Samples/DatabasePlugin". + **/ + class DatabaseWrapperBase + { + private: + SQLite::Connection& db_; + + ErrorCode GetChangesInternal(std::list& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults); + + void GetExportedResourcesInternal(std::list& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults); + + public: + DatabaseWrapperBase(SQLite::Connection& db) : db_(db) + { + } + + void SetGlobalProperty(GlobalProperty property, + const std::string& value); + + bool LookupGlobalProperty(std::string& target, + GlobalProperty property); + + int64_t CreateResource(const std::string& publicId, + ResourceType type); + + bool LookupResource(int64_t& id, + ResourceType& type, + const std::string& publicId); + + ErrorCode LookupParent(bool& found, + int64_t& parentId, + int64_t resourceId); + + bool GetPublicId(std::string& result, + int64_t resourceId); + + ErrorCode GetResourceType(ResourceType& result, + int64_t resourceId); + + void AttachChild(int64_t parent, + int64_t child); + + void SetMetadata(int64_t id, + MetadataType type, + const std::string& value); + + void DeleteMetadata(int64_t id, + MetadataType type); + + bool LookupMetadata(std::string& target, + int64_t id, + MetadataType type); + + void ListAvailableMetadata(std::list& target, + int64_t id); + + void AddAttachment(int64_t id, + const FileInfo& attachment); + + void DeleteAttachment(int64_t id, + FileContentType attachment); + + void ListAvailableAttachments(std::list& target, + int64_t id); + + bool LookupAttachment(FileInfo& attachment, + int64_t id, + FileContentType contentType); + + + void ClearMainDicomTags(int64_t id); + + + void SetMainDicomTag(int64_t id, + const DicomTag& tag, + const std::string& value); + + void GetMainDicomTags(DicomMap& map, + int64_t id); + + void GetChildrenPublicId(std::list& target, + int64_t id); + + void GetChildrenInternalId(std::list& target, + int64_t id); + + void LogChange(int64_t internalId, + const ServerIndexChange& change); + + ErrorCode GetChanges(std::list& target, + bool& done, + int64_t since, + uint32_t maxResults); + + ErrorCode GetLastChange(std::list& target); + + void LogExportedResource(const ExportedResource& resource); + + void GetExportedResources(std::list& target, + bool& done, + int64_t since, + uint32_t maxResults); + + void GetLastExportedResource(std::list& target); + + uint64_t GetTotalCompressedSize(); + + uint64_t GetTotalUncompressedSize(); + + void GetAllPublicIds(std::list& target, + ResourceType resourceType); + + void GetAllPublicIds(std::list& target, + ResourceType resourceType, + size_t since, + size_t limit); + + uint64_t GetResourceCount(ResourceType resourceType); + + bool SelectPatientToRecycle(int64_t& internalId); + + bool SelectPatientToRecycle(int64_t& internalId, + int64_t patientIdToAvoid); + + bool IsProtectedPatient(int64_t internalId); + + void SetProtectedPatient(int64_t internalId, + bool isProtected); + + bool IsExistingResource(int64_t internalId); + + void LookupIdentifier(std::list& target, + const DicomTag& tag, + const std::string& value); + + void LookupIdentifier(std::list& target, + const std::string& value); + }; +} + diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/DicomProtocol/DicomServer.cpp --- a/OrthancServer/DicomProtocol/DicomServer.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/DicomProtocol/DicomServer.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -281,9 +281,8 @@ throw OrthancException(ErrorCode_DicomPortInUse); } + continue_ = true; pimpl_->workers_.reset(new RunnableWorkersPool(4)); // Use 4 workers - TODO as a parameter? - - continue_ = true; pimpl_->thread_ = boost::thread(ServerThread, this); } diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/DicomProtocol/DicomServer.h --- a/OrthancServer/DicomProtocol/DicomServer.h Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/DicomProtocol/DicomServer.h Tue Oct 13 16:42:03 2015 +0200 @@ -40,6 +40,7 @@ #include #include + namespace Orthanc { class DicomServer : public boost::noncopyable diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/IDatabaseWrapper.h --- a/OrthancServer/IDatabaseWrapper.h Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/IDatabaseWrapper.h Tue Oct 13 16:42:03 2015 +0200 @@ -51,6 +51,10 @@ { } + virtual void Open() = 0; + + virtual void Close() = 0; + virtual void AddAttachment(int64_t id, const FileInfo& attachment) = 0; @@ -168,6 +172,8 @@ virtual void SetGlobalProperty(GlobalProperty property, const std::string& value) = 0; + virtual void ClearMainDicomTags(int64_t id) = 0; + virtual void SetMainDicomTag(int64_t id, const DicomTag& tag, const std::string& value) = 0; diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/Internals/StoreScp.cpp --- a/OrthancServer/Internals/StoreScp.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/Internals/StoreScp.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -212,7 +212,7 @@ if (e.GetErrorCode() == ErrorCode_InexistentTag) { - LogMissingRequiredTag(summary); + Toolbox::LogMissingRequiredTag(summary); } else { diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -39,6 +39,7 @@ #include "../ResourceFinder.h" #include "../DicomFindQuery.h" #include "../ServerContext.h" +#include "../SliceOrdering.h" namespace Orthanc @@ -208,7 +209,7 @@ context.ReadJson(full, publicId); Json::Value simplified; - SimplifyTags(simplified, full); + Toolbox::SimplifyTags(simplified, full); call.GetOutput().AnswerJson(simplified); } else @@ -792,7 +793,7 @@ if (simplify) { Json::Value simplified; - SimplifyTags(simplified, sharedTags); + Toolbox::SimplifyTags(simplified, sharedTags); call.GetOutput().AnswerJson(simplified); } else @@ -859,7 +860,7 @@ if (simplify) { Json::Value simplified; - SimplifyTags(simplified, result); + Toolbox::SimplifyTags(simplified, result); call.GetOutput().AnswerJson(simplified); } else @@ -1030,7 +1031,7 @@ if (simplify) { Json::Value simplified; - SimplifyTags(simplified, full); + Toolbox::SimplifyTags(simplified, full); result[*it] = simplified; } else @@ -1093,6 +1094,19 @@ } + static void OrderSlices(RestApiGetCall& call) + { + const std::string id = call.GetUriComponent("id", ""); + + ServerIndex& index = OrthancRestApi::GetIndex(call); + SliceOrdering ordering(index, id); + + Json::Value result; + ordering.Format(result); + call.GetOutput().AnswerJson(result); + } + + void OrthancRestApi::RegisterResources() { Register("/instances", ListResources); @@ -1187,5 +1201,7 @@ Register("/series/{id}/instances-tags", GetChildInstancesTags); Register("/instances/{id}/content/*", GetRawContent); + + Register("/series/{id}/ordered-slices", OrderSlices); } } diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/OrthancRestApi/OrthancRestSystem.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -53,9 +53,7 @@ { Json::Value result = Json::objectValue; - std::string dbVersion = OrthancRestApi::GetIndex(call).GetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "0"); - - result["DatabaseVersion"] = boost::lexical_cast(dbVersion); + result["DatabaseVersion"] = OrthancRestApi::GetIndex(call).GetDatabaseVersion(); result["DicomAet"] = Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC"); result["DicomPort"] = Configuration::GetGlobalIntegerParameter("DicomPort", 4242); result["HttpPort"] = Configuration::GetGlobalIntegerParameter("HttpPort", 8042); diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/ParsedDicomFile.cpp diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/PrepareDatabase.sql --- a/OrthancServer/PrepareDatabase.sql Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/PrepareDatabase.sql Tue Oct 13 16:42:03 2015 +0200 @@ -123,4 +123,4 @@ -- Set the version of the database schema -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration -INSERT INTO GlobalProperties VALUES (1, "5"); +INSERT INTO GlobalProperties VALUES (1, "6"); diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/ResourceFinder.cpp --- a/OrthancServer/ResourceFinder.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/ResourceFinder.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -37,8 +37,6 @@ #include "FromDcmtkBridge.h" #include "ServerContext.h" -#include - namespace Orthanc { class ResourceFinder::CandidateResources @@ -190,9 +188,21 @@ } - void RestrictMainDicomTags(const IQuery& query) + void RestrictMainDicomTags(const IQuery& query, + bool filterPatientTagsAtStudyLevel) { - if (!query.HasMainDicomTagsFilter(level_)) + if (filterPatientTagsAtStudyLevel && + level_ == ResourceType_Patient) + { + return; + } + + bool hasTagsAtThisLevel = query.HasMainDicomTagsFilter(level_); + bool hasTagsAtPatientLevel = (filterPatientTagsAtStudyLevel && + level_ == ResourceType_Study && + query.HasMainDicomTagsFilter(ResourceType_Patient)); + + if (!hasTagsAtThisLevel && !hasTagsAtPatientLevel) { return; } @@ -207,13 +217,22 @@ it = resources.begin(); it != resources.end(); ++it) { DicomMap mainTags; - if (index_.GetMainDicomTags(mainTags, *it, level_)) + + if (hasTagsAtThisLevel && + (!index_.GetMainDicomTags(mainTags, *it, level_, level_) || + !query.FilterMainDicomTags(*it, level_, mainTags))) { - if (query.FilterMainDicomTags(*it, level_, mainTags)) - { - filtered_.insert(*it); - } + continue; } + + if (hasTagsAtPatientLevel && + (!index_.GetMainDicomTags(mainTags, *it, ResourceType_Study, ResourceType_Patient) || + !query.FilterMainDicomTags(*it, ResourceType_Patient, mainTags))) + { + continue; + } + + filtered_.insert(*it); } } }; @@ -266,7 +285,14 @@ throw OrthancException(ErrorCode_InternalError); } - candidates.RestrictMainDicomTags(query); + if (query.GetLevel() == ResourceType_Patient) + { + candidates.RestrictMainDicomTags(query, false); + } + else + { + candidates.RestrictMainDicomTags(query, true); + } } diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/ServerContext.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -189,7 +189,7 @@ resultPublicId = hasher.HashInstance(); Json::Value simplifiedTags; - SimplifyTags(simplifiedTags, dicom.GetJson()); + Toolbox::SimplifyTags(simplifiedTags, dicom.GetJson()); // Test if the instance must be filtered out bool accepted = true; @@ -299,7 +299,7 @@ { if (e.GetErrorCode() == ErrorCode_InexistentTag) { - LogMissingRequiredTag(dicom.GetSummary()); + Toolbox::LogMissingRequiredTag(dicom.GetSummary()); } throw; diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/ServerEnumerations.h Tue Oct 13 16:42:03 2015 +0200 @@ -116,7 +116,7 @@ enum GlobalProperty { - GlobalProperty_DatabaseSchemaVersion = 1, + GlobalProperty_DatabaseSchemaVersion = 1, // Unused in the Orthanc core as of Orthanc 0.9.5 GlobalProperty_FlushSleep = 2, GlobalProperty_AnonymizationSequence = 3 }; diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/ServerIndex.cpp --- a/OrthancServer/ServerIndex.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/ServerIndex.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -40,6 +40,7 @@ #include "ServerIndexChange.h" #include "EmbeddedResources.h" #include "OrthancInitialization.h" +#include "ServerToolbox.h" #include "../Core/Toolbox.h" #include "../Core/Logging.h" #include "../Core/Uuid.h" @@ -490,18 +491,6 @@ - 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) { @@ -638,10 +627,7 @@ // Create the instance int64_t instance = CreateResource(hasher.HashInstance(), ResourceType_Instance); - - DicomMap dicom; - dicomSummary.ExtractInstanceInformation(dicom); - SetMainDicomTags(instance, dicom); + Toolbox::SetMainDicomTags(db_, instance, ResourceType_Instance, dicomSummary, true); // Detect up to which level the patient/study/series/instance // hierarchy must be created @@ -693,24 +679,22 @@ if (isNewSeries) { series = CreateResource(hasher.HashSeries(), ResourceType_Series); - dicomSummary.ExtractSeriesInformation(dicom); - SetMainDicomTags(series, dicom); + Toolbox::SetMainDicomTags(db_, series, ResourceType_Series, dicomSummary, true); } // Create the study if needed if (isNewStudy) { study = CreateResource(hasher.HashStudy(), ResourceType_Study); - dicomSummary.ExtractStudyInformation(dicom); - SetMainDicomTags(study, dicom); + Toolbox::SetMainDicomTags(db_, study, ResourceType_Study, dicomSummary, true); + Toolbox::SetMainDicomTags(db_, study, ResourceType_Patient, dicomSummary, false); // New in version 0.9.5 (db v6) } // Create the patient if needed if (isNewPatient) { patient = CreateResource(hasher.HashPatient(), ResourceType_Patient); - dicomSummary.ExtractPatientInformation(dicom); - SetMainDicomTags(patient, dicom); + Toolbox::SetMainDicomTags(db_, patient, ResourceType_Patient, dicomSummary, true); } // Create the parent-to-child links @@ -890,14 +874,55 @@ } + static std::string GetPatientIdOfStudy(IDatabaseWrapper& db, + int64_t resourceId) + { + int64_t patient; + if (!db.LookupParent(patient, resourceId)) + { + throw OrthancException(ErrorCode_InternalError); + } + + DicomMap tags; + db.GetMainDicomTags(tags, patient); + + if (tags.HasTag(DICOM_TAG_PATIENT_ID)) + { + return tags.GetValue(DICOM_TAG_PATIENT_ID).AsString(); + } + else + { + return ""; + } + } + void ServerIndex::MainDicomTagsToJson(Json::Value& target, - int64_t resourceId) + int64_t resourceId, + ResourceType resourceType) { DicomMap tags; db_.GetMainDicomTags(tags, resourceId); - target["MainDicomTags"] = Json::objectValue; - FromDcmtkBridge::ToJson(target["MainDicomTags"], tags, true); + + if (resourceType == ResourceType_Study) + { + DicomMap t1, t2; + tags.ExtractStudyInformation(t1); + tags.ExtractPatientInformation(t2); + + target["MainDicomTags"] = Json::objectValue; + FromDcmtkBridge::ToJson(target["MainDicomTags"], t1, true); + + target["PatientMainDicomTags"] = Json::objectValue; + FromDcmtkBridge::ToJson(target["PatientMainDicomTags"], t2, true); + + target["PatientMainDicomTags"]["PatientID"] = GetPatientIdOfStudy(db_, resourceId); + } + else + { + target["MainDicomTags"] = Json::objectValue; + FromDcmtkBridge::ToJson(target["MainDicomTags"], tags, true); + } } bool ServerIndex::LookupResource(Json::Value& result, @@ -1033,7 +1058,7 @@ // Record the remaining information result["ID"] = publicId; - MainDicomTagsToJson(result, id); + MainDicomTagsToJson(result, id, type); std::string tmp; @@ -2077,8 +2102,19 @@ bool ServerIndex::GetMainDicomTags(DicomMap& result, const std::string& publicId, - ResourceType expectedType) + ResourceType expectedType, + ResourceType levelOfInterest) { + // Yes, the following test could be shortened, but we wish to make it as clear as possible + if (!(expectedType == ResourceType_Patient && levelOfInterest == ResourceType_Patient) && + !(expectedType == ResourceType_Study && levelOfInterest == ResourceType_Patient) && + !(expectedType == ResourceType_Study && levelOfInterest == ResourceType_Study) && + !(expectedType == ResourceType_Series && levelOfInterest == ResourceType_Series) && + !(expectedType == ResourceType_Instance && levelOfInterest == ResourceType_Instance)) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + result.Clear(); boost::mutex::scoped_lock lock(mutex_); @@ -2091,6 +2127,27 @@ { return false; } + + if (type == ResourceType_Study) + { + DicomMap tmp; + db_.GetMainDicomTags(tmp, id); + + switch (levelOfInterest) + { + case ResourceType_Patient: + tmp.ExtractPatientInformation(result); + result.SetValue(DICOM_TAG_PATIENT_ID, GetPatientIdOfStudy(db_, id)); + return true; + + case ResourceType_Study: + tmp.ExtractStudyInformation(result); + return true; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } else { db_.GetMainDicomTags(result, id); @@ -2108,4 +2165,10 @@ return db_.LookupResource(id, type, publicId); } + + unsigned int ServerIndex::GetDatabaseVersion() + { + boost::mutex::scoped_lock lock(mutex_); + return db_.GetDatabaseVersion(); + } } diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/ServerIndex.h --- a/OrthancServer/ServerIndex.h Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/ServerIndex.h Tue Oct 13 16:42:03 2015 +0200 @@ -76,7 +76,8 @@ static void UnstableResourcesMonitorThread(ServerIndex* that); void MainDicomTagsToJson(Json::Value& result, - int64_t resourceId); + int64_t resourceId, + ResourceType resourceType); SeriesStatus GetSeriesStatus(int64_t id); @@ -110,9 +111,6 @@ uint64_t IncrementGlobalSequenceInternal(GlobalProperty property); - void SetMainDicomTags(int64_t resource, - const DicomMap& tags); - int64_t CreateResource(const std::string& publicId, ResourceType type); @@ -263,9 +261,12 @@ bool GetMainDicomTags(DicomMap& result, const std::string& publicId, - ResourceType expectedType); + ResourceType expectedType, + ResourceType levelOfInterest); bool LookupResourceType(ResourceType& type, const std::string& publicId); + + unsigned int GetDatabaseVersion(); }; } diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/ServerToolbox.cpp --- a/OrthancServer/ServerToolbox.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/ServerToolbox.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -33,122 +33,298 @@ #include "PrecompiledHeadersServer.h" #include "ServerToolbox.h" +#include "../Core/DicomFormat/DicomArray.h" +#include "../Core/FileStorage/StorageAccessor.h" #include "../Core/Logging.h" #include "../Core/OrthancException.h" +#include "ParsedDicomFile.h" #include namespace Orthanc { - void SimplifyTags(Json::Value& target, - const Json::Value& source) + namespace Toolbox { - assert(source.isObject()); + void SimplifyTags(Json::Value& target, + const Json::Value& source) + { + assert(source.isObject()); - target = Json::objectValue; - Json::Value::Members members = source.getMemberNames(); + target = Json::objectValue; + Json::Value::Members members = source.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& v = source[members[i]]; + const std::string& name = v["Name"].asString(); + const std::string& type = v["Type"].asString(); - for (size_t i = 0; i < members.size(); i++) - { - const Json::Value& v = source[members[i]]; - const std::string& name = v["Name"].asString(); - const std::string& type = v["Type"].asString(); + if (type == "String") + { + target[name] = v["Value"].asString(); + } + else if (type == "TooLong" || + type == "Null") + { + target[name] = Json::nullValue; + } + else if (type == "Sequence") + { + const Json::Value& array = v["Value"]; + assert(array.isArray()); - if (type == "String") - { - target[name] = v["Value"].asString(); + Json::Value children = Json::arrayValue; + for (Json::Value::ArrayIndex i = 0; i < array.size(); i++) + { + Json::Value c; + SimplifyTags(c, array[i]); + children.append(c); + } + + target[name] = children; + } + else + { + assert(0); + } } - else if (type == "TooLong" || - type == "Null") + } + + + void LogMissingRequiredTag(const DicomMap& summary) + { + std::string s, t; + + if (summary.HasTag(DICOM_TAG_PATIENT_ID)) { - target[name] = Json::nullValue; + if (t.size() > 0) + t += ", "; + t += "PatientID=" + summary.GetValue(DICOM_TAG_PATIENT_ID).AsString(); } - else if (type == "Sequence") + else { - const Json::Value& array = v["Value"]; - assert(array.isArray()); + if (s.size() > 0) + s += ", "; + s += "PatientID"; + } - Json::Value children = Json::arrayValue; - for (Json::Value::ArrayIndex i = 0; i < array.size(); i++) - { - Json::Value c; - SimplifyTags(c, array[i]); - children.append(c); - } + if (summary.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "StudyInstanceUID=" + summary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString(); + } + else + { + if (s.size() > 0) + s += ", "; + s += "StudyInstanceUID"; + } - target[name] = children; + if (summary.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "SeriesInstanceUID=" + summary.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString(); + } + else + { + if (s.size() > 0) + s += ", "; + s += "SeriesInstanceUID"; + } + + if (summary.HasTag(DICOM_TAG_SOP_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "SOPInstanceUID=" + summary.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString(); + } + else + { + if (s.size() > 0) + s += ", "; + s += "SOPInstanceUID"; + } + + if (t.size() == 0) + { + LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)"; } else { - assert(0); + LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t; + } + } + + + void SetMainDicomTags(IDatabaseWrapper& database, + int64_t resource, + ResourceType level, + const DicomMap& dicomSummary, + bool includeIdentifiers) + { + // WARNING: The database should be locked with a transaction! + + DicomMap tags; + + switch (level) + { + case ResourceType_Patient: + dicomSummary.ExtractPatientInformation(tags); + break; + + case ResourceType_Study: + dicomSummary.ExtractStudyInformation(tags); + break; + + case ResourceType_Series: + dicomSummary.ExtractSeriesInformation(tags); + break; + + case ResourceType_Instance: + dicomSummary.ExtractInstanceInformation(tags); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + DicomArray flattened(tags); + for (size_t i = 0; i < flattened.GetSize(); i++) + { + const DicomElement& element = flattened.GetElement(i); + + if (includeIdentifiers || + !element.GetTag().IsIdentifier()) + { + database.SetMainDicomTag(resource, element.GetTag(), element.GetValue().AsString()); + } + } + } + + + bool FindOneChildInstance(int64_t& result, + IDatabaseWrapper& database, + int64_t resource, + ResourceType type) + { + for (;;) + { + if (type == ResourceType_Instance) + { + result = resource; + return true; + } + + std::list children; + database.GetChildrenInternalId(children, resource); + if (children.empty()) + { + return false; + } + + resource = children.front(); + type = GetChildResourceType(type); + } + } + + + void ReconstructMainDicomTags(IDatabaseWrapper& database, + IStorageArea& storageArea, + ResourceType level) + { + // WARNING: The database should be locked with a transaction! + + const char* plural = NULL; + + switch (level) + { + case ResourceType_Patient: + plural = "patients"; + break; + + case ResourceType_Study: + plural = "studies"; + break; + + case ResourceType_Series: + plural = "series"; + break; + + case ResourceType_Instance: + plural = "instances"; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + LOG(WARNING) << "Upgrade: Reconstructing the main DICOM tags of all the " << plural << "..."; + + std::list resources; + database.GetAllPublicIds(resources, level); + + for (std::list::const_iterator + it = resources.begin(); it != resources.end(); it++) + { + // Locate the resource and one of its child instances + int64_t resource, instance; + ResourceType tmp; + + if (!database.LookupResource(resource, tmp, *it) || + tmp != level || + !FindOneChildInstance(instance, database, resource, level)) + { + throw OrthancException(ErrorCode_InternalError); + } + + // Get the DICOM file attached to some instances in the resource + FileInfo attachment; + if (!database.LookupAttachment(attachment, instance, FileContentType_Dicom)) + { + throw OrthancException(ErrorCode_InternalError); + } + + // Read and parse the content of the DICOM file + StorageAccessor accessor(storageArea); + + std::string content; + accessor.Read(content, attachment); + + ParsedDicomFile dicom(content); + + // Update the tags of this resource + DicomMap dicomSummary; + dicom.Convert(dicomSummary); + + database.ClearMainDicomTags(resource); + + switch (level) + { + case ResourceType_Patient: + Toolbox::SetMainDicomTags(database, resource, ResourceType_Patient, dicomSummary, true); + break; + + case ResourceType_Study: + Toolbox::SetMainDicomTags(database, resource, ResourceType_Study, dicomSummary, true); + + // Duplicate the patient tags at the study level (new in Orthanc 0.9.5 - db v6) + Toolbox::SetMainDicomTags(database, resource, ResourceType_Patient, dicomSummary, false); + break; + + case ResourceType_Series: + Toolbox::SetMainDicomTags(database, resource, ResourceType_Series, dicomSummary, true); + break; + + case ResourceType_Instance: + Toolbox::SetMainDicomTags(database, resource, ResourceType_Instance, dicomSummary, true); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } } } } - - - void LogMissingRequiredTag(const DicomMap& summary) - { - std::string s, t; - - if (summary.HasTag(DICOM_TAG_PATIENT_ID)) - { - if (t.size() > 0) - t += ", "; - t += "PatientID=" + summary.GetValue(DICOM_TAG_PATIENT_ID).AsString(); - } - else - { - if (s.size() > 0) - s += ", "; - s += "PatientID"; - } - - if (summary.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) - { - if (t.size() > 0) - t += ", "; - t += "StudyInstanceUID=" + summary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString(); - } - else - { - if (s.size() > 0) - s += ", "; - s += "StudyInstanceUID"; - } - - if (summary.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) - { - if (t.size() > 0) - t += ", "; - t += "SeriesInstanceUID=" + summary.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString(); - } - else - { - if (s.size() > 0) - s += ", "; - s += "SeriesInstanceUID"; - } - - if (summary.HasTag(DICOM_TAG_SOP_INSTANCE_UID)) - { - if (t.size() > 0) - t += ", "; - t += "SOPInstanceUID=" + summary.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString(); - } - else - { - if (s.size() > 0) - s += ", "; - s += "SOPInstanceUID"; - } - - if (t.size() == 0) - { - LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)"; - } - else - { - LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t; - } - } } diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/ServerToolbox.h --- a/OrthancServer/ServerToolbox.h Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/ServerToolbox.h Tue Oct 13 16:42:03 2015 +0200 @@ -33,13 +33,32 @@ #pragma once #include "../Core/DicomFormat/DicomMap.h" +#include "IDatabaseWrapper.h" #include namespace Orthanc { - void SimplifyTags(Json::Value& target, - const Json::Value& source); + namespace Toolbox + { + void SimplifyTags(Json::Value& target, + const Json::Value& source); + + void LogMissingRequiredTag(const DicomMap& summary); - void LogMissingRequiredTag(const DicomMap& summary); + void SetMainDicomTags(IDatabaseWrapper& database, + int64_t resource, + ResourceType level, + const DicomMap& dicomSummary, + bool includeIdentifiers); + + bool FindOneChildInstance(int64_t& result, + IDatabaseWrapper& database, + int64_t resource, + ResourceType type); + + void ReconstructMainDicomTags(IDatabaseWrapper& database, + IStorageArea& storageArea, + ResourceType level); + } } diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/SliceOrdering.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/SliceOrdering.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -0,0 +1,409 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * 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 + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "PrecompiledHeadersServer.h" +#include "SliceOrdering.h" + +#include "../Core/Logging.h" +#include "../Core/Toolbox.h" +#include "ServerEnumerations.h" + +#include +#include +#include + + +namespace Orthanc +{ + static bool TokenizeVector(std::vector& result, + const std::string& value, + unsigned int expectedSize) + { + std::vector tokens; + Toolbox::TokenizeString(tokens, value, '\\'); + + if (tokens.size() != expectedSize) + { + return false; + } + + result.resize(tokens.size()); + + for (size_t i = 0; i < tokens.size(); i++) + { + try + { + result[i] = boost::lexical_cast(tokens[i]); + } + catch (boost::bad_lexical_cast&) + { + return false; + } + } + + return true; + } + + + static bool TokenizeVector(std::vector& result, + const DicomMap& map, + const DicomTag& tag, + unsigned int expectedSize) + { + const DicomValue* value = map.TestAndGetValue(tag); + + if (value == NULL || + value->IsNull()) + { + return false; + } + else + { + return TokenizeVector(result, value->AsString(), expectedSize); + } + } + + + struct SliceOrdering::Instance : public boost::noncopyable + { + private: + std::string instanceId_; + bool hasPosition_; + Vector position_; + bool hasIndexInSeries_; + size_t indexInSeries_; + unsigned int framesCount_; + + public: + Instance(ServerIndex& index, + const std::string& instanceId) : + instanceId_(instanceId), + framesCount_(1) + { + DicomMap instance; + if (!index.GetMainDicomTags(instance, instanceId, ResourceType_Instance, ResourceType_Instance)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + const DicomValue* frames = instance.TestAndGetValue(DICOM_TAG_NUMBER_OF_FRAMES); + if (frames != NULL && + !frames->IsNull()) + { + try + { + framesCount_ = boost::lexical_cast(frames->AsString()); + } + catch (boost::bad_lexical_cast&) + { + } + } + + std::vector tmp; + hasPosition_ = TokenizeVector(tmp, instance, DICOM_TAG_IMAGE_POSITION_PATIENT, 3); + + if (hasPosition_) + { + position_[0] = tmp[0]; + position_[1] = tmp[1]; + position_[2] = tmp[2]; + } + + std::string s; + hasIndexInSeries_ = false; + + try + { + if (index.LookupMetadata(s, instanceId, MetadataType_Instance_IndexInSeries)) + { + indexInSeries_ = boost::lexical_cast(s); + hasIndexInSeries_ = true; + } + } + catch (boost::bad_lexical_cast&) + { + } + } + + const std::string& GetIdentifier() const + { + return instanceId_; + } + + bool HasPosition() const + { + return hasPosition_; + } + + float ComputeRelativePosition(const Vector& normal) const + { + assert(HasPosition()); + return (normal[0] * position_[0] + + normal[1] * position_[1] + + normal[2] * position_[2]); + } + + bool HasIndexInSeries() const + { + return hasIndexInSeries_; + } + + size_t GetIndexInSeries() const + { + assert(HasIndexInSeries()); + return indexInSeries_; + } + + unsigned int GetFramesCount() const + { + return framesCount_; + } + }; + + + class SliceOrdering::PositionComparator + { + private: + const Vector& normal_; + + public: + PositionComparator(const Vector& normal) : normal_(normal) + { + } + + int operator() (const Instance* a, + const Instance* b) const + { + return a->ComputeRelativePosition(normal_) < b->ComputeRelativePosition(normal_); + } + }; + + + bool SliceOrdering::IndexInSeriesComparator(const SliceOrdering::Instance* a, + const SliceOrdering::Instance* b) + { + return a->GetIndexInSeries() < b->GetIndexInSeries(); + } + + + void SliceOrdering::ComputeNormal() + { + DicomMap series; + if (!index_.GetMainDicomTags(series, seriesId_, ResourceType_Series, ResourceType_Series)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + std::vector cosines; + hasNormal_ = TokenizeVector(cosines, series, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, 6); + + if (hasNormal_) + { + normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4]; + normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5]; + normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3]; + } + } + + + void SliceOrdering::CreateInstances() + { + std::list instancesId; + index_.GetChildren(instancesId, seriesId_); + + instances_.reserve(instancesId.size()); + for (std::list::const_iterator + it = instancesId.begin(); it != instancesId.end(); ++it) + { + instances_.push_back(new Instance(index_, *it)); + } + } + + + bool SliceOrdering::SortUsingPositions() + { + if (instances_.size() <= 1) + { + // One single instance: It is sorted by default + return true; + } + + if (!hasNormal_) + { + return false; + } + + for (size_t i = 0; i < instances_.size(); i++) + { + assert(instances_[i] != NULL); + if (!instances_[i]->HasPosition()) + { + return false; + } + } + + PositionComparator comparator(normal_); + std::sort(instances_.begin(), instances_.end(), comparator); + + float a = instances_.front()->ComputeRelativePosition(normal_); + float b = instances_.back()->ComputeRelativePosition(normal_); + + if (std::fabs(b - a) <= 10.0f * std::numeric_limits::epsilon()) + { + // Not enough difference between the minimum and maximum + // positions along the normal of the volume + return false; + } + else + { + // This is a 3D volume + isVolume_ = true; + return true; + } + } + + + bool SliceOrdering::SortUsingIndexInSeries() + { + if (instances_.size() <= 1) + { + // One single instance: It is sorted by default + return true; + } + + for (size_t i = 0; i < instances_.size(); i++) + { + assert(instances_[i] != NULL); + if (!instances_[i]->HasIndexInSeries()) + { + return false; + } + } + + std::sort(instances_.begin(), instances_.end(), IndexInSeriesComparator); + + for (size_t i = 1; i < instances_.size(); i++) + { + if (instances_[i - 1]->GetIndexInSeries() == instances_[i]->GetIndexInSeries()) + { + // The current "IndexInSeries" occurs 2 times: Not a proper ordering + return false; + } + } + + return true; + } + + + SliceOrdering::SliceOrdering(ServerIndex& index, + const std::string& seriesId) : + index_(index), + seriesId_(seriesId), + isVolume_(false) + { + ComputeNormal(); + CreateInstances(); + + if (!SortUsingPositions() && + !SortUsingIndexInSeries()) + { + LOG(ERROR) << "Unable to order the slices of the series " << seriesId; + throw OrthancException(ErrorCode_CannotOrderSlices); + } + } + + + SliceOrdering::~SliceOrdering() + { + for (std::vector::iterator + it = instances_.begin(); it != instances_.end(); ++it) + { + if (*it != NULL) + { + delete *it; + } + } + } + + + const std::string& SliceOrdering::GetInstanceId(size_t index) const + { + if (index >= instances_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return instances_[index]->GetIdentifier(); + } + } + + + unsigned int SliceOrdering::GetFramesCount(size_t index) const + { + if (index >= instances_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return instances_[index]->GetFramesCount(); + } + } + + + void SliceOrdering::Format(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = (isVolume_ ? "Volume" : "Sequence"); + + Json::Value tmp = Json::arrayValue; + for (size_t i = 0; i < GetInstancesCount(); i++) + { + tmp.append(GetBasePath(ResourceType_Instance, GetInstanceId(i)) + "/file"); + } + + result["Dicom"] = tmp; + + tmp.clear(); + for (size_t i = 0; i < GetInstancesCount(); i++) + { + std::string base = GetBasePath(ResourceType_Instance, GetInstanceId(i)); + for (size_t j = 0; j < GetFramesCount(i); j++) + { + tmp.append(base + "/frames/" + boost::lexical_cast(j)); + } + } + + result["Slices"] = tmp; + } +} diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/SliceOrdering.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/SliceOrdering.h Tue Oct 13 16:42:03 2015 +0200 @@ -0,0 +1,82 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * 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 + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ServerIndex.h" + +namespace Orthanc +{ + class SliceOrdering + { + private: + typedef float Vector[3]; + + struct Instance; + struct PositionComparator; + + ServerIndex& index_; + std::string seriesId_; + bool hasNormal_; + Vector normal_; + std::vector instances_; + bool isVolume_; + + static bool IndexInSeriesComparator(const SliceOrdering::Instance* a, + const SliceOrdering::Instance* b); + + void ComputeNormal(); + + void CreateInstances(); + + bool SortUsingPositions(); + + bool SortUsingIndexInSeries(); + + public: + SliceOrdering(ServerIndex& index, + const std::string& seriesId); + + ~SliceOrdering(); + + size_t GetInstancesCount() const + { + return instances_.size(); + } + + const std::string& GetInstanceId(size_t index) const; + + unsigned int GetFramesCount(size_t index) const; + + void Format(Json::Value& result) const; + }; +} diff -r f5ddbd9239dd -r 77d3e3a13c10 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/OrthancServer/main.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -572,13 +572,28 @@ dicomServer.Start(); LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber(); - bool restart = StartHttpServer(context, restApi, plugins); + bool restart; + ErrorCode error = ErrorCode_Success; + + try + { + restart = StartHttpServer(context, restApi, plugins); + } + catch (OrthancException& e) + { + error = e.GetErrorCode(); + } dicomServer.Stop(); LOG(WARNING) << " DICOM server has stopped"; serverFactory.Done(); + if (error != ErrorCode_Success) + { + throw OrthancException(error); + } + return restart; } @@ -617,7 +632,7 @@ IStorageArea& storageArea, bool allowDatabaseUpgrade) { - // Upgrade the database, if needed + // Upgrade the schema of the database, if needed unsigned int currentVersion = database.GetDatabaseVersion(); if (currentVersion == ORTHANC_DATABASE_VERSION) { @@ -626,20 +641,20 @@ if (currentVersion > ORTHANC_DATABASE_VERSION) { - LOG(ERROR) << "The version of the database (" << currentVersion + LOG(ERROR) << "The version of the database schema (" << currentVersion << ") is too recent for this version of Orthanc. Please upgrade Orthanc."; return false; } if (!allowDatabaseUpgrade) { - LOG(ERROR) << "The database must be upgraded from version " + LOG(ERROR) << "The database schema must be upgraded from version " << currentVersion << " to " << ORTHANC_DATABASE_VERSION << ": Please run Orthanc with the \"--upgrade\" command-line option"; return false; } - LOG(WARNING) << "Upgrading the database from version " + LOG(WARNING) << "Upgrading the database from schema version " << currentVersion << " to " << ORTHANC_DATABASE_VERSION; database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea); @@ -647,7 +662,7 @@ currentVersion = database.GetDatabaseVersion(); if (ORTHANC_DATABASE_VERSION != currentVersion) { - LOG(ERROR) << "The database was not properly updated, it is still at version " << currentVersion; + LOG(ERROR) << "The database schema was not properly upgraded, it is still at version " << currentVersion; throw OrthancException(ErrorCode_InternalError); } @@ -657,14 +672,8 @@ static bool ConfigureServerContext(IDatabaseWrapper& database, IStorageArea& storageArea, - OrthancPlugins *plugins, - bool allowDatabaseUpgrade) + OrthancPlugins *plugins) { - if (!UpgradeDatabase(database, storageArea, allowDatabaseUpgrade)) - { - return false; - } - ServerContext context(database, storageArea); HttpClient::SetDefaultTimeout(Configuration::GetGlobalIntegerParameter("HttpTimeout", 0)); @@ -700,7 +709,18 @@ } #endif - bool restart = ConfigureHttpHandler(context, plugins); + bool restart; + ErrorCode error = ErrorCode_Success; + + try + { + restart = ConfigureHttpHandler(context, plugins); + } + catch (OrthancException& e) + { + error = e.GetErrorCode(); + } + context.Stop(); #if ORTHANC_PLUGINS_ENABLED == 1 @@ -710,10 +730,35 @@ } #endif + if (error != ErrorCode_Success) + { + throw OrthancException(error); + } + return restart; } +static bool ConfigureDatabase(IDatabaseWrapper& database, + IStorageArea& storageArea, + OrthancPlugins *plugins, + bool allowDatabaseUpgrade) +{ + database.Open(); + + if (!UpgradeDatabase(database, storageArea, allowDatabaseUpgrade)) + { + return false; + } + + bool success = ConfigureServerContext(database, storageArea, plugins); + + database.Close(); + + return success; +} + + static bool ConfigurePlugins(int argc, char* argv[], bool allowDatabaseUpgrade) @@ -751,14 +796,14 @@ assert(database != NULL); assert(storage.get() != NULL); - return ConfigureServerContext(*database, *storage, &plugins, allowDatabaseUpgrade); + return ConfigureDatabase(*database, *storage, &plugins, allowDatabaseUpgrade); #elif ORTHANC_PLUGINS_ENABLED == 0 // The plugins are disabled databasePtr.reset(Configuration::CreateDatabaseWrapper()); storage.reset(Configuration::CreateStorageArea()); - return ConfigureServerContext(*databasePtr, *storage, NULL, allowDatabaseUpgrade); + return ConfigureDatabase(*databasePtr, *storage, NULL, allowDatabaseUpgrade); #else # error The macro ORTHANC_PLUGINS_ENABLED must be set to 0 or 1 diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Engine/OrthancPluginDatabase.cpp --- a/Plugins/Engine/OrthancPluginDatabase.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Engine/OrthancPluginDatabase.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -46,50 +46,6 @@ namespace Orthanc { - 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_InternalError); - } - } - - - static ResourceType Convert(OrthancPluginResourceType type) - { - switch (type) - { - case OrthancPluginResourceType_Patient: - return ResourceType_Patient; - - case OrthancPluginResourceType_Study: - return ResourceType_Study; - - case OrthancPluginResourceType_Series: - return ResourceType_Series; - - case OrthancPluginResourceType_Instance: - return ResourceType_Instance; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - static FileInfo Convert(const OrthancPluginAttachment& attachment) { return FileInfo(attachment.uuid, @@ -102,6 +58,16 @@ } + void OrthancPluginDatabase::CheckSuccess(OrthancPluginErrorCode code) + { + if (code != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast(code)); + } + } + + void OrthancPluginDatabase::ResetAnswers() { type_ = _OrthancPluginDatabaseAnswerType_None; @@ -234,50 +200,26 @@ tmp.compressedSize = attachment.GetCompressedSize(); tmp.compressedHash = attachment.GetCompressedMD5().c_str(); - OrthancPluginErrorCode error = backend_.addAttachment(payload_, id, &tmp); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.addAttachment(payload_, id, &tmp)); } void OrthancPluginDatabase::AttachChild(int64_t parent, int64_t child) { - OrthancPluginErrorCode error = backend_.attachChild(payload_, parent, child); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.attachChild(payload_, parent, child)); } void OrthancPluginDatabase::ClearChanges() { - OrthancPluginErrorCode error = backend_.clearChanges(payload_); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.clearChanges(payload_)); } void OrthancPluginDatabase::ClearExportedResources() { - OrthancPluginErrorCode error = backend_.clearExportedResources(payload_); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.clearExportedResources(payload_)); } @@ -285,15 +227,7 @@ ResourceType type) { int64_t id; - - OrthancPluginErrorCode error = backend_.createResource(&id, payload_, publicId.c_str(), Convert(type)); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.createResource(&id, payload_, publicId.c_str(), Plugins::Convert(type))); return id; } @@ -301,38 +235,20 @@ void OrthancPluginDatabase::DeleteAttachment(int64_t id, FileContentType attachment) { - OrthancPluginErrorCode error = backend_.deleteAttachment(payload_, id, static_cast(attachment)); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.deleteAttachment(payload_, id, static_cast(attachment))); } void OrthancPluginDatabase::DeleteMetadata(int64_t id, MetadataType type) { - OrthancPluginErrorCode error = backend_.deleteMetadata(payload_, id, static_cast(type)); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.deleteMetadata(payload_, id, static_cast(type))); } void OrthancPluginDatabase::DeleteResource(int64_t id) { - OrthancPluginErrorCode error = backend_.deleteResource(payload_, id); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.deleteResource(payload_, id)); } @@ -362,15 +278,7 @@ ResourceType resourceType) { ResetAnswers(); - - OrthancPluginErrorCode error = backend_.getAllPublicIds(GetContext(), payload_, Convert(resourceType)); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.getAllPublicIds(GetContext(), payload_, Plugins::Convert(resourceType))); ForwardAnswers(target); } @@ -384,16 +292,8 @@ { // This extension is available since Orthanc 0.9.4 ResetAnswers(); - - OrthancPluginErrorCode error = extensions_.getAllPublicIdsWithLimit - (GetContext(), payload_, Convert(resourceType), since, limit); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(extensions_.getAllPublicIdsWithLimit + (GetContext(), payload_, Plugins::Convert(resourceType), since, limit)); ForwardAnswers(target); } else @@ -440,13 +340,7 @@ answerDone_ = &done; done = false; - OrthancPluginErrorCode error = backend_.getChanges(GetContext(), payload_, since, maxResults); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.getChanges(GetContext(), payload_, since, maxResults)); } @@ -454,15 +348,7 @@ int64_t id) { ResetAnswers(); - - OrthancPluginErrorCode error = backend_.getChildrenInternalId(GetContext(), payload_, id); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.getChildrenInternalId(GetContext(), payload_, id)); ForwardAnswers(target); } @@ -471,15 +357,7 @@ int64_t id) { ResetAnswers(); - - OrthancPluginErrorCode error = backend_.getChildrenPublicId(GetContext(), payload_, id); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.getChildrenPublicId(GetContext(), payload_, id)); ForwardAnswers(target); } @@ -494,13 +372,7 @@ answerDone_ = &done; done = false; - OrthancPluginErrorCode error = backend_.getExportedResources(GetContext(), payload_, since, maxResults); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.getExportedResources(GetContext(), payload_, since, maxResults)); } @@ -512,13 +384,7 @@ answerChanges_ = ⌖ answerDone_ = &ignored; - OrthancPluginErrorCode error = backend_.getLastChange(GetContext(), payload_); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.getLastChange(GetContext(), payload_)); } @@ -530,13 +396,7 @@ answerExportedResources_ = ⌖ answerDone_ = &ignored; - OrthancPluginErrorCode error = backend_.getLastExportedResource(GetContext(), payload_); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.getLastExportedResource(GetContext(), payload_)); } @@ -546,13 +406,7 @@ ResetAnswers(); answerDicomMap_ = ↦ - OrthancPluginErrorCode error = backend_.getMainDicomTags(GetContext(), payload_, id); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.getMainDicomTags(GetContext(), payload_, id)); } @@ -561,14 +415,8 @@ ResetAnswers(); std::string s; - OrthancPluginErrorCode error = backend_.getPublicId(GetContext(), payload_, resourceId); + CheckSuccess(backend_.getPublicId(GetContext(), payload_, resourceId)); - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - if (!ForwardSingleAnswer(s)) { throw OrthancException(ErrorCode_DatabasePlugin); @@ -581,15 +429,7 @@ uint64_t OrthancPluginDatabase::GetResourceCount(ResourceType resourceType) { uint64_t count; - - OrthancPluginErrorCode error = backend_.getResourceCount(&count, payload_, Convert(resourceType)); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.getResourceCount(&count, payload_, Plugins::Convert(resourceType))); return count; } @@ -597,31 +437,15 @@ ResourceType OrthancPluginDatabase::GetResourceType(int64_t resourceId) { OrthancPluginResourceType type; - - OrthancPluginErrorCode error = backend_.getResourceType(&type, payload_, resourceId); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - - return Convert(type); + CheckSuccess(backend_.getResourceType(&type, payload_, resourceId)); + return Plugins::Convert(type); } uint64_t OrthancPluginDatabase::GetTotalCompressedSize() { uint64_t size; - - OrthancPluginErrorCode error = backend_.getTotalCompressedSize(&size, payload_); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.getTotalCompressedSize(&size, payload_)); return size; } @@ -629,15 +453,7 @@ uint64_t OrthancPluginDatabase::GetTotalUncompressedSize() { uint64_t size; - - OrthancPluginErrorCode error = backend_.getTotalUncompressedSize(&size, payload_); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.getTotalUncompressedSize(&size, payload_)); return size; } @@ -645,15 +461,7 @@ bool OrthancPluginDatabase::IsExistingResource(int64_t internalId) { int32_t existing; - - OrthancPluginErrorCode error = backend_.isExistingResource(&existing, payload_, internalId); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.isExistingResource(&existing, payload_, internalId)); return (existing != 0); } @@ -661,15 +469,7 @@ bool OrthancPluginDatabase::IsProtectedPatient(int64_t internalId) { int32_t isProtected; - - OrthancPluginErrorCode error = backend_.isProtectedPatient(&isProtected, payload_, internalId); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.isProtectedPatient(&isProtected, payload_, internalId)); return (isProtected != 0); } @@ -678,14 +478,7 @@ int64_t id) { ResetAnswers(); - - OrthancPluginErrorCode error = backend_.listAvailableMetadata(GetContext(), payload_, id); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.listAvailableMetadata(GetContext(), payload_, id)); if (type_ != _OrthancPluginDatabaseAnswerType_None && type_ != _OrthancPluginDatabaseAnswerType_Int32) @@ -711,13 +504,7 @@ { ResetAnswers(); - OrthancPluginErrorCode error = backend_.listAvailableAttachments(GetContext(), payload_, id); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.listAvailableAttachments(GetContext(), payload_, id)); if (type_ != _OrthancPluginDatabaseAnswerType_None && type_ != _OrthancPluginDatabaseAnswerType_Int32) @@ -744,17 +531,11 @@ OrthancPluginChange tmp; tmp.seq = change.GetSeq(); tmp.changeType = static_cast(change.GetChangeType()); - tmp.resourceType = Convert(change.GetResourceType()); + tmp.resourceType = Plugins::Convert(change.GetResourceType()); tmp.publicId = change.GetPublicId().c_str(); tmp.date = change.GetDate().c_str(); - OrthancPluginErrorCode error = backend_.logChange(payload_, &tmp); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.logChange(payload_, &tmp)); } @@ -762,7 +543,7 @@ { OrthancPluginExportedResource tmp; tmp.seq = resource.GetSeq(); - tmp.resourceType = Convert(resource.GetResourceType()); + tmp.resourceType = Plugins::Convert(resource.GetResourceType()); tmp.publicId = resource.GetPublicId().c_str(); tmp.modality = resource.GetModality().c_str(); tmp.date = resource.GetDate().c_str(); @@ -771,13 +552,7 @@ tmp.seriesInstanceUid = resource.GetSeriesInstanceUid().c_str(); tmp.sopInstanceUid = resource.GetSopInstanceUid().c_str(); - OrthancPluginErrorCode error = backend_.logExportedResource(payload_, &tmp); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.logExportedResource(payload_, &tmp)); } @@ -787,14 +562,8 @@ { ResetAnswers(); - OrthancPluginErrorCode error = backend_.lookupAttachment - (GetContext(), payload_, id, static_cast(contentType)); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.lookupAttachment + (GetContext(), payload_, id, static_cast(contentType))); if (type_ == _OrthancPluginDatabaseAnswerType_None) { @@ -818,14 +587,8 @@ { ResetAnswers(); - OrthancPluginErrorCode error = backend_.lookupGlobalProperty - (GetContext(), payload_, static_cast(property)); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.lookupGlobalProperty + (GetContext(), payload_, static_cast(property))); return ForwardSingleAnswer(target); } @@ -842,13 +605,7 @@ tmp.element = tag.GetElement(); tmp.value = value.c_str(); - OrthancPluginErrorCode error = backend_.lookupIdentifier(GetContext(), payload_, &tmp); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.lookupIdentifier(GetContext(), payload_, &tmp)); ForwardAnswers(target); } @@ -858,15 +615,7 @@ const std::string& value) { ResetAnswers(); - - OrthancPluginErrorCode error = backend_.lookupIdentifier2(GetContext(), payload_, value.c_str()); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.lookupIdentifier2(GetContext(), payload_, value.c_str())); ForwardAnswers(target); } @@ -876,15 +625,7 @@ MetadataType type) { ResetAnswers(); - - OrthancPluginErrorCode error = backend_.lookupMetadata(GetContext(), payload_, id, static_cast(type)); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.lookupMetadata(GetContext(), payload_, id, static_cast(type))); return ForwardSingleAnswer(target); } @@ -893,15 +634,7 @@ int64_t resourceId) { ResetAnswers(); - - OrthancPluginErrorCode error = backend_.lookupParent(GetContext(), payload_, resourceId); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.lookupParent(GetContext(), payload_, resourceId)); return ForwardSingleAnswer(parentId); } @@ -912,13 +645,7 @@ { ResetAnswers(); - OrthancPluginErrorCode error = backend_.lookupResource(GetContext(), payload_, publicId.c_str()); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.lookupResource(GetContext(), payload_, publicId.c_str())); if (type_ == _OrthancPluginDatabaseAnswerType_None) { @@ -941,15 +668,7 @@ bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId) { ResetAnswers(); - - OrthancPluginErrorCode error = backend_.selectPatientToRecycle(GetContext(), payload_); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.selectPatientToRecycle(GetContext(), payload_)); return ForwardSingleAnswer(internalId); } @@ -958,15 +677,7 @@ int64_t patientIdToAvoid) { ResetAnswers(); - - OrthancPluginErrorCode error = backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid)); return ForwardSingleAnswer(internalId); } @@ -974,14 +685,20 @@ void OrthancPluginDatabase::SetGlobalProperty(GlobalProperty property, const std::string& value) { - OrthancPluginErrorCode error = backend_.setGlobalProperty - (payload_, static_cast(property), value.c_str()); + CheckSuccess(backend_.setGlobalProperty + (payload_, static_cast(property), value.c_str())); + } + - if (error != OrthancPluginErrorCode_Success) + void OrthancPluginDatabase::ClearMainDicomTags(int64_t id) + { + if (extensions_.clearMainDicomTags == NULL) { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); + LOG(ERROR) << "Your custom index plugin does not implement the ClearMainDicomTags() extension"; + throw OrthancException(ErrorCode_DatabasePlugin); } + + CheckSuccess(extensions_.clearMainDicomTags(payload_, id)); } @@ -994,22 +711,18 @@ tmp.element = tag.GetElement(); tmp.value = value.c_str(); - OrthancPluginErrorCode error; + OrthancPluginErrorCode code; if (tag.IsIdentifier()) { - error = backend_.setIdentifierTag(payload_, id, &tmp); + code = backend_.setIdentifierTag(payload_, id, &tmp); } else { - error = backend_.setMainDicomTag(payload_, id, &tmp); + code = backend_.setMainDicomTag(payload_, id, &tmp); } - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(code); } @@ -1017,27 +730,15 @@ MetadataType type, const std::string& value) { - OrthancPluginErrorCode error = backend_.setMetadata - (payload_, id, static_cast(type), value.c_str()); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.setMetadata + (payload_, id, static_cast(type), value.c_str())); } void OrthancPluginDatabase::SetProtectedPatient(int64_t internalId, bool isProtected) { - OrthancPluginErrorCode error = backend_.setProtectedPatient(payload_, internalId, isProtected); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.setProtectedPatient(payload_, internalId, isProtected)); } @@ -1048,6 +749,15 @@ void* payload_; PluginsErrorDictionary& errorDictionary_; + void CheckSuccess(OrthancPluginErrorCode code) + { + if (code != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast(code)); + } + } + public: Transaction(const OrthancPluginDatabaseBackend& backend, void* payload, @@ -1060,35 +770,17 @@ virtual void Begin() { - OrthancPluginErrorCode error = backend_.startTransaction(payload_); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.startTransaction(payload_)); } virtual void Rollback() { - OrthancPluginErrorCode error = backend_.rollbackTransaction(payload_); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.rollbackTransaction(payload_)); } virtual void Commit() { - OrthancPluginErrorCode error = backend_.commitTransaction(payload_); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } + CheckSuccess(backend_.commitTransaction(payload_)); } }; @@ -1114,14 +806,14 @@ case _OrthancPluginDatabaseAnswerType_RemainingAncestor: { - ResourceType type = Convert(static_cast(answer.valueInt32)); + ResourceType type = Plugins::Convert(static_cast(answer.valueInt32)); listener.SignalRemainingAncestor(type, answer.valueString); break; } case _OrthancPluginDatabaseAnswerType_DeletedResource: { - ResourceType type = Convert(static_cast(answer.valueInt32)); + ResourceType type = Plugins::Convert(static_cast(answer.valueInt32)); ServerIndexChange change(ChangeType_Deleted, type, answer.valueString); listener.SignalChange(change); break; @@ -1138,14 +830,7 @@ if (extensions_.getDatabaseVersion != NULL) { uint32_t version; - OrthancPluginErrorCode error = extensions_.getDatabaseVersion(&version, payload_); - - if (error != OrthancPluginErrorCode_Success) - { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); - } - + CheckSuccess(extensions_.getDatabaseVersion(&version, payload_)); return version; } else @@ -1163,14 +848,22 @@ { if (extensions_.upgradeDatabase != NULL) { - OrthancPluginErrorCode error = extensions_.upgradeDatabase( + Transaction transaction(backend_, payload_, errorDictionary_); + transaction.Begin(); + + OrthancPluginErrorCode code = extensions_.upgradeDatabase( payload_, targetVersion, reinterpret_cast(&storageArea)); - if (error != OrthancPluginErrorCode_Success) + if (code == OrthancPluginErrorCode_Success) { - errorDictionary_.LogError(error, true); - throw OrthancException(static_cast(error)); + transaction.Commit(); + } + else + { + transaction.Rollback(); + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast(code)); } } } @@ -1261,7 +954,7 @@ case _OrthancPluginDatabaseAnswerType_Resource: { OrthancPluginResourceType type = static_cast(answer.valueInt32); - answerResources_.push_back(std::make_pair(answer.valueInt64, Convert(type))); + answerResources_.push_back(std::make_pair(answer.valueInt64, Plugins::Convert(type))); break; } @@ -1321,7 +1014,7 @@ answerChanges_->push_back (ServerIndexChange(change.seq, static_cast(change.changeType), - Convert(change.resourceType), + Plugins::Convert(change.resourceType), change.publicId, change.date)); } @@ -1347,7 +1040,7 @@ assert(answerExportedResources_ != NULL); answerExportedResources_->push_back (ExportedResource(exported.seq, - Convert(exported.resourceType), + Plugins::Convert(exported.resourceType), exported.publicId, exported.modality, exported.date, diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Engine/OrthancPluginDatabase.h --- a/Plugins/Engine/OrthancPluginDatabase.h Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Engine/OrthancPluginDatabase.h Tue Oct 13 16:42:03 2015 +0200 @@ -72,6 +72,8 @@ return reinterpret_cast(this); } + void CheckSuccess(OrthancPluginErrorCode code); + void ResetAnswers(); void ForwardAnswers(std::list& target); @@ -90,6 +92,16 @@ size_t extensionsSize, void *payload); + virtual void Open() + { + CheckSuccess(backend_.open(payload_)); + } + + virtual void Close() + { + CheckSuccess(backend_.close(payload_)); + } + const SharedLibrary& GetSharedLibrary() const { return library_; @@ -217,6 +229,8 @@ virtual void SetGlobalProperty(GlobalProperty property, const std::string& value); + virtual void ClearMainDicomTags(int64_t id); + virtual void SetMainDicomTag(int64_t id, const DicomTag& tag, const std::string& value); diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -1006,7 +1006,7 @@ else { Json::Value simplified; - SimplifyTags(simplified, instance.GetJson()); + Toolbox::SimplifyTags(simplified, instance.GetJson()); s = writer.write(simplified); } @@ -1709,6 +1709,23 @@ return true; } + case _OrthancPluginService_ReconstructMainDicomTags: + { + const _OrthancPluginReconstructMainDicomTags& p = + *reinterpret_cast(parameters); + + if (pimpl_->database_.get() == NULL) + { + LOG(ERROR) << "The service ReconstructMainDicomTags can only be invoked by custom database plugins"; + throw OrthancException(ErrorCode_DatabasePlugin); + } + + IStorageArea& storage = *reinterpret_cast(p.storageArea); + Toolbox::ReconstructMainDicomTags(*pimpl_->database_, storage, Plugins::Convert(p.level)); + + return true; + } + default: { // This service is unknown to the Orthanc plugin engine diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Engine/PluginsEnumerations.cpp --- a/Plugins/Engine/PluginsEnumerations.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Engine/PluginsEnumerations.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -66,6 +66,28 @@ } + ResourceType Convert(OrthancPluginResourceType type) + { + switch (type) + { + case OrthancPluginResourceType_Patient: + return ResourceType_Patient; + + case OrthancPluginResourceType_Study: + return ResourceType_Study; + + case OrthancPluginResourceType_Series: + return ResourceType_Series; + + case OrthancPluginResourceType_Instance: + return ResourceType_Instance; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + OrthancPluginChangeType Convert(ChangeType type) { switch (type) @@ -188,7 +210,7 @@ } - +#if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0 DcmEVR Convert(OrthancPluginValueRepresentation vr) { switch (vr) @@ -278,5 +300,6 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } +#endif } } diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Engine/PluginsEnumerations.h --- a/Plugins/Engine/PluginsEnumerations.h Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Engine/PluginsEnumerations.h Tue Oct 13 16:42:03 2015 +0200 @@ -37,7 +37,9 @@ #include "../Include/orthanc/OrthancCPlugin.h" #include "../../OrthancServer/ServerEnumerations.h" +#if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0 #include +#endif namespace Orthanc { @@ -45,6 +47,8 @@ { OrthancPluginResourceType Convert(ResourceType type); + ResourceType Convert(OrthancPluginResourceType type); + OrthancPluginChangeType Convert(ChangeType type); OrthancPluginPixelFormat Convert(PixelFormat format); @@ -55,7 +59,9 @@ FileContentType Convert(OrthancPluginContentType type); +#if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0 DcmEVR Convert(OrthancPluginValueRepresentation vr); +#endif } } diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Engine/PluginsManager.cpp --- a/Plugins/Engine/PluginsManager.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Engine/PluginsManager.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -191,7 +191,7 @@ catch (OrthancException& e) { // This service provider has failed - LOG(ERROR) << "Exception while invoking a plugin service: " << e.What(); + LOG(ERROR) << "Exception while invoking plugin service " << service << ": " << e.What(); return static_cast(e.GetErrorCode()); } } diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Include/orthanc/OrthancCDatabasePlugin.h --- a/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Tue Oct 13 16:42:03 2015 +0200 @@ -655,7 +655,12 @@ void* payload, uint32_t targetVersion, OrthancPluginStorageArea* storageArea); - } OrthancPluginDatabaseExtensions; + + OrthancPluginErrorCode (*clearMainDicomTags) ( + /* inputs */ + void* payload, + int64_t id); + } OrthancPluginDatabaseExtensions; /*InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, ¶ms); + } + + #ifdef __cplusplus } #endif diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Include/orthanc/OrthancCppDatabasePlugin.h --- a/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h Tue Oct 13 16:42:03 2015 +0200 @@ -122,6 +122,11 @@ { } + OrthancPluginContext* GetContext() + { + return context_; + } + void LogError(const std::string& message) { OrthancPluginLogError(context_, message.c_str()); @@ -456,8 +461,15 @@ virtual uint32_t GetDatabaseVersion() = 0; + /** + * Upgrade the database to the specified version of the database + * schema. The upgrade script is allowed to make calls to + * OrthancPluginReconstructMainDicomTags(). + **/ virtual void UpgradeDatabase(uint32_t targetVersion, OrthancPluginStorageArea* storageArea) = 0; + + virtual void ClearMainDicomTags(int64_t internalId) = 0; }; @@ -1780,6 +1792,28 @@ } + static OrthancPluginErrorCode ClearMainDicomTags(void* payload, + int64_t internalId) + { + IDatabaseBackend* backend = reinterpret_cast(payload); + + try + { + backend->ClearMainDicomTags(internalId); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + public: /** * Register a custom database back-end written in C++. @@ -1847,6 +1881,7 @@ extensions.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit; extensions.getDatabaseVersion = GetDatabaseVersion; extensions.upgradeDatabase = UpgradeDatabase; + extensions.clearMainDicomTags = ClearMainDicomTags; OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackendV2(context, ¶ms, &extensions, &backend); if (!context) diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Samples/Common/OrthancPlugins.cmake --- a/Plugins/Samples/Common/OrthancPlugins.cmake Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Samples/Common/OrthancPlugins.cmake Tue Oct 13 16:42:03 2015 +0200 @@ -1,34 +1,11 @@ +set(ORTHANC_ROOT ${SAMPLES_ROOT}/../..) include(CheckIncludeFiles) +include(CheckIncludeFileCXX) include(CheckLibraryExists) - - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - link_libraries(uuid) - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pthread") - -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - link_libraries(rpcrt4 ws2_32 secur32) - if (CMAKE_COMPILER_IS_GNUCXX) - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libgcc -static-libstdc++") - endif() - - CHECK_LIBRARY_EXISTS(winpthread pthread_create "" HAVE_WIN_PTHREAD) - if (HAVE_WIN_PTHREAD) - # This line is necessary to compile with recent versions of MinGW, - # otherwise "libwinpthread-1.dll" is not statically linked. - SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic") - endif() -endif () - - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${SAMPLES_ROOT}/Common/VersionScript.map -Wl,--no-undefined") -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_SOURCE_DIR}/Plugins/Samples/Common/ExportedSymbols.list") -endif() +include(FindPythonInterp) +include(${ORTHANC_ROOT}/Resources/CMake/AutoGeneratedCode.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake) if (CMAKE_COMPILER_IS_GNUCXX) @@ -43,8 +20,10 @@ link_libraries(dl rt) endif() + include_directories(${SAMPLES_ROOT}/../Include/) + if (MSVC) include_directories(${SAMPLES_ROOT}/../../Resources/ThirdParty/VisualStudio/) endif() diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Samples/DatabasePlugin/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/DatabasePlugin/CMakeLists.txt Tue Oct 13 16:42:03 2015 +0200 @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 2.8) + +project(SampleDatabasePlugin) + +# Parameters of the build +SET(SAMPLE_DATABASE_VERSION "0.0" CACHE STRING "Version of the plugin") +SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") +SET(STANDALONE_BUILD ON) + +# Advanced parameters to fine-tune linking against system libraries +SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") +SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") +SET(USE_SYSTEM_SQLITE ON CACHE BOOL "Use the system version of SQLite") + +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) +include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) + +include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/SQLiteConfiguration.cmake) + +EmbedResources( + --system-exception # Use "std::runtime_error" instead of "OrthancException" for embedded resources + PREPARE_DATABASE ${ORTHANC_ROOT}/OrthancServer/PrepareDatabase.sql + ) + +message("Setting the version of the plugin to ${SAMPLE_DATABASE_VERSION}") + +add_definitions( + -DORTHANC_SQLITE_STANDALONE=1 + -DORTHANC_ENABLE_LOGGING=0 + -DORTHANC_ENABLE_BASE64=0 + -DORTHANC_ENABLE_MD5=0 + -DORTHANC_ENABLE_DCMTK=0 + -DORTHANC_PLUGINS_ENABLED=1 + -DSAMPLE_DATABASE_VERSION="${SAMPLE_DATABASE_VERSION}" + ) + +add_library(SampleDatabase SHARED + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + ${SQLITE_SOURCES} + ${AUTOGENERATED_SOURCES} + + ${ORTHANC_ROOT}/Core/DicomFormat/DicomArray.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomMap.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomTag.cpp + ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/SQLite/Connection.cpp + ${ORTHANC_ROOT}/Core/SQLite/FunctionContext.cpp + ${ORTHANC_ROOT}/Core/SQLite/Statement.cpp + ${ORTHANC_ROOT}/Core/SQLite/StatementId.cpp + ${ORTHANC_ROOT}/Core/SQLite/StatementReference.cpp + ${ORTHANC_ROOT}/Core/SQLite/Transaction.cpp + ${ORTHANC_ROOT}/Core/Toolbox.cpp + ${ORTHANC_ROOT}/OrthancServer/DatabaseWrapperBase.cpp + ${ORTHANC_ROOT}/Plugins/Engine/PluginsEnumerations.cpp + + Database.cpp + Plugin.cpp + ) + +set_target_properties(SampleDatabase PROPERTIES + VERSION ${SAMPLE_DATABASE_VERSION} + SOVERSION ${SAMPLE_DATABASE_VERSION}) + +install( + TARGETS SampleDatabase + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + ) diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Samples/DatabasePlugin/Database.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/DatabasePlugin/Database.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -0,0 +1,560 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * 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 + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "Database.h" + +#include "../../../Core/DicomFormat/DicomArray.h" + +#include +#include + + +namespace Internals +{ + class SignalFileDeleted : public Orthanc::SQLite::IScalarFunction + { + private: + OrthancPlugins::DatabaseBackendOutput& output_; + + public: + SignalFileDeleted(OrthancPlugins::DatabaseBackendOutput& output) : output_(output) + { + } + + virtual const char* GetName() const + { + return "SignalFileDeleted"; + } + + virtual unsigned int GetCardinality() const + { + return 7; + } + + virtual void Compute(Orthanc::SQLite::FunctionContext& context) + { + std::string uncompressedMD5, compressedMD5; + + if (!context.IsNullValue(5)) + { + uncompressedMD5 = context.GetStringValue(5); + } + + if (!context.IsNullValue(6)) + { + compressedMD5 = context.GetStringValue(6); + } + + output_.SignalDeletedAttachment(context.GetStringValue(0), + context.GetIntValue(1), + context.GetInt64Value(2), + uncompressedMD5, + context.GetIntValue(3), + context.GetInt64Value(4), + compressedMD5); + } + }; + + + class SignalResourceDeleted : public Orthanc::SQLite::IScalarFunction + { + private: + OrthancPlugins::DatabaseBackendOutput& output_; + + public: + SignalResourceDeleted(OrthancPlugins::DatabaseBackendOutput& output) : output_(output) + { + } + + virtual const char* GetName() const + { + return "SignalResourceDeleted"; + } + + virtual unsigned int GetCardinality() const + { + return 2; + } + + virtual void Compute(Orthanc::SQLite::FunctionContext& context) + { + output_.SignalDeletedResource(context.GetStringValue(0), + Orthanc::Plugins::Convert(static_cast(context.GetIntValue(1)))); + } + }; +} + + +class Database::SignalRemainingAncestor : public Orthanc::SQLite::IScalarFunction +{ +private: + bool hasRemainingAncestor_; + std::string remainingPublicId_; + OrthancPluginResourceType remainingType_; + +public: + SignalRemainingAncestor() : + hasRemainingAncestor_(false) + { + } + + void Reset() + { + hasRemainingAncestor_ = false; + } + + virtual const char* GetName() const + { + return "SignalRemainingAncestor"; + } + + virtual unsigned int GetCardinality() const + { + return 2; + } + + virtual void Compute(Orthanc::SQLite::FunctionContext& context) + { + if (!hasRemainingAncestor_ || + remainingType_ >= context.GetIntValue(1)) + { + hasRemainingAncestor_ = true; + remainingPublicId_ = context.GetStringValue(0); + remainingType_ = Orthanc::Plugins::Convert(static_cast(context.GetIntValue(1))); + } + } + + bool HasRemainingAncestor() const + { + return hasRemainingAncestor_; + } + + const std::string& GetRemainingAncestorId() const + { + assert(hasRemainingAncestor_); + return remainingPublicId_; + } + + OrthancPluginResourceType GetRemainingAncestorType() const + { + assert(hasRemainingAncestor_); + return remainingType_; + } +}; + + + +Database::Database(const std::string& path) : + path_(path), + base_(db_) +{ +} + + +void Database::Open() +{ + db_.Open(path_); + + // http://www.sqlite.org/pragma.html + db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;"); + db_.Execute("PRAGMA JOURNAL_MODE=WAL;"); + db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;"); + db_.Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;"); + //db_.Execute("PRAGMA TEMP_STORE=memory"); + + if (!db_.DoesTableExist("GlobalProperties")) + { + std::string query; + Orthanc::EmbeddedResources::GetFileResource(query, Orthanc::EmbeddedResources::PREPARE_DATABASE); + db_.Execute(query); + } + + signalRemainingAncestor_ = new SignalRemainingAncestor; + db_.Register(signalRemainingAncestor_); + db_.Register(new Internals::SignalFileDeleted(GetOutput())); + db_.Register(new Internals::SignalResourceDeleted(GetOutput())); +} + + +void Database::Close() +{ + db_.Close(); +} + + +void Database::AddAttachment(int64_t id, + const OrthancPluginAttachment& attachment) +{ + Orthanc::FileInfo info(attachment.uuid, + static_cast(attachment.contentType), + attachment.uncompressedSize, + attachment.uncompressedHash, + static_cast(attachment.compressionType), + attachment.compressedSize, + attachment.compressedHash); + base_.AddAttachment(id, info); +} + + +void Database::DeleteResource(int64_t id) +{ + signalRemainingAncestor_->Reset(); + + Orthanc::SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?"); + s.BindInt64(0, id); + s.Run(); + + if (signalRemainingAncestor_->HasRemainingAncestor()) + { + GetOutput().SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorId(), + signalRemainingAncestor_->GetRemainingAncestorType()); + } +} + + +static void Answer(OrthancPlugins::DatabaseBackendOutput& output, + const Orthanc::ServerIndexChange& change) +{ + output.AnswerChange(change.GetSeq(), + change.GetChangeType(), + Orthanc::Plugins::Convert(change.GetResourceType()), + change.GetPublicId(), + change.GetDate()); +} + + +static void Answer(OrthancPlugins::DatabaseBackendOutput& output, + const Orthanc::ExportedResource& resource) +{ + output.AnswerExportedResource(resource.GetSeq(), + Orthanc::Plugins::Convert(resource.GetResourceType()), + resource.GetPublicId(), + resource.GetModality(), + resource.GetDate(), + resource.GetPatientId(), + resource.GetStudyInstanceUid(), + resource.GetSeriesInstanceUid(), + resource.GetSopInstanceUid()); +} + + +void Database::GetChanges(bool& done /*out*/, + int64_t since, + uint32_t maxResults) +{ + typedef std::list Changes; + + Changes changes; + base_.GetChanges(changes, done, since, maxResults); + + for (Changes::const_iterator it = changes.begin(); it != changes.end(); ++it) + { + Answer(GetOutput(), *it); + } +} + + +void Database::GetExportedResources(bool& done /*out*/, + int64_t since, + uint32_t maxResults) +{ + typedef std::list Resources; + + Resources resources; + base_.GetExportedResources(resources, done, since, maxResults); + + for (Resources::const_iterator it = resources.begin(); it != resources.end(); ++it) + { + Answer(GetOutput(), *it); + } +} + + +void Database::GetLastChange() +{ + std::list change; + Orthanc::ErrorCode code = base_.GetLastChange(change); + + if (code != Orthanc::ErrorCode_Success) + { + throw OrthancPlugins::DatabaseException(static_cast(code)); + } + + if (!change.empty()) + { + Answer(GetOutput(), change.front()); + } +} + + +void Database::GetLastExportedResource() +{ + std::list resource; + base_.GetLastExportedResource(resource); + + if (!resource.empty()) + { + Answer(GetOutput(), resource.front()); + } +} + + +void Database::GetMainDicomTags(int64_t id) +{ + Orthanc::DicomMap tags; + base_.GetMainDicomTags(tags, id); + + Orthanc::DicomArray arr(tags); + for (size_t i = 0; i < arr.GetSize(); i++) + { + GetOutput().AnswerDicomTag(arr.GetElement(i).GetTag().GetGroup(), + arr.GetElement(i).GetTag().GetElement(), + arr.GetElement(i).GetValue().AsString()); + } +} + + +std::string Database::GetPublicId(int64_t resourceId) +{ + std::string id; + if (base_.GetPublicId(id, resourceId)) + { + return id; + } + else + { + throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_UnknownResource); + } +} + + +OrthancPluginResourceType Database::GetResourceType(int64_t resourceId) +{ + Orthanc::ResourceType result; + Orthanc::ErrorCode code = base_.GetResourceType(result, resourceId); + + if (code == Orthanc::ErrorCode_Success) + { + return Orthanc::Plugins::Convert(result); + } + else + { + throw OrthancPlugins::DatabaseException(static_cast(code)); + } +} + + + +template +static void ConvertList(std::list& target, + const std::list& source) +{ + for (typename std::list::const_iterator + it = source.begin(); it != source.end(); it++) + { + target.push_back(*it); + } +} + + +void Database::ListAvailableMetadata(std::list& target /*out*/, + int64_t id) +{ + std::list tmp; + base_.ListAvailableMetadata(tmp, id); + ConvertList(target, tmp); +} + + +void Database::ListAvailableAttachments(std::list& target /*out*/, + int64_t id) +{ + std::list tmp; + base_.ListAvailableAttachments(tmp, id); + ConvertList(target, tmp); +} + + +void Database::LogChange(const OrthancPluginChange& change) +{ + int64_t id; + OrthancPluginResourceType type; + if (!LookupResource(id, type, change.publicId) || + type != change.resourceType) + { + throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_DatabasePlugin); + } + + Orthanc::ServerIndexChange tmp(change.seq, + static_cast(change.changeType), + Orthanc::Plugins::Convert(change.resourceType), + change.publicId, + change.date); + + base_.LogChange(id, tmp); +} + + +void Database::LogExportedResource(const OrthancPluginExportedResource& resource) +{ + Orthanc::ExportedResource tmp(resource.seq, + Orthanc::Plugins::Convert(resource.resourceType), + resource.publicId, + resource.modality, + resource.date, + resource.patientId, + resource.studyInstanceUid, + resource.seriesInstanceUid, + resource.sopInstanceUid); + + base_.LogExportedResource(tmp); +} + + +bool Database::LookupAttachment(int64_t id, + int32_t contentType) +{ + Orthanc::FileInfo attachment; + if (base_.LookupAttachment(attachment, id, static_cast(contentType))) + { + GetOutput().AnswerAttachment(attachment.GetUuid(), + attachment.GetContentType(), + attachment.GetUncompressedSize(), + attachment.GetUncompressedMD5(), + attachment.GetCompressionType(), + attachment.GetCompressedSize(), + attachment.GetCompressedMD5()); + return true; + } + else + { + return false; + } +} + + +bool Database::LookupParent(int64_t& parentId /*out*/, + int64_t resourceId) +{ + bool found; + Orthanc::ErrorCode code = base_.LookupParent(found, parentId, resourceId); + + if (code == Orthanc::ErrorCode_Success) + { + return found; + } + else + { + throw OrthancPlugins::DatabaseException(static_cast(code)); + } +} + + +bool Database::LookupResource(int64_t& id /*out*/, + OrthancPluginResourceType& type /*out*/, + const char* publicId) +{ + Orthanc::ResourceType tmp; + if (base_.LookupResource(id, tmp, publicId)) + { + type = Orthanc::Plugins::Convert(tmp); + return true; + } + else + { + return false; + } +} + + +void Database::StartTransaction() +{ + transaction_.reset(new Orthanc::SQLite::Transaction(db_)); + transaction_->Begin(); +} + + +void Database::RollbackTransaction() +{ + transaction_->Rollback(); + transaction_.reset(NULL); +} + + +void Database::CommitTransaction() +{ + transaction_->Commit(); + transaction_.reset(NULL); +} + + +uint32_t Database::GetDatabaseVersion() +{ + std::string version; + + if (!LookupGlobalProperty(version, Orthanc::GlobalProperty_DatabaseSchemaVersion)) + { + throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError); + } + + try + { + return boost::lexical_cast(version); + } + catch (boost::bad_lexical_cast&) + { + throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError); + } +} + + +void Database::UpgradeDatabase(uint32_t targetVersion, + OrthancPluginStorageArea* storageArea) +{ + if (targetVersion == 6) + { + OrthancPluginErrorCode code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea, + OrthancPluginResourceType_Study); + if (code == OrthancPluginErrorCode_Success) + { + code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea, + OrthancPluginResourceType_Series); + } + + if (code != OrthancPluginErrorCode_Success) + { + throw OrthancPlugins::DatabaseException(code); + } + + base_.SetGlobalProperty(Orthanc::GlobalProperty_DatabaseSchemaVersion, "6"); + } +} diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Samples/DatabasePlugin/Database.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/DatabasePlugin/Database.h Tue Oct 13 16:42:03 2015 +0200 @@ -0,0 +1,280 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * 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 + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include + +#include "../../../Core/SQLite/Connection.h" +#include "../../../Core/SQLite/Transaction.h" +#include "../../../OrthancServer/DatabaseWrapperBase.h" +#include "../../Engine/PluginsEnumerations.h" + +#include + +class Database : public OrthancPlugins::IDatabaseBackend +{ +private: + class SignalRemainingAncestor; + + std::string path_; + Orthanc::SQLite::Connection db_; + Orthanc::DatabaseWrapperBase base_; + SignalRemainingAncestor* signalRemainingAncestor_; + + std::auto_ptr transaction_; + +public: + Database(const std::string& path); + + virtual void Open(); + + virtual void Close(); + + virtual void AddAttachment(int64_t id, + const OrthancPluginAttachment& attachment); + + virtual void AttachChild(int64_t parent, + int64_t child) + { + base_.AttachChild(parent, child); + } + + virtual void ClearChanges() + { + db_.Execute("DELETE FROM Changes"); + } + + virtual void ClearExportedResources() + { + db_.Execute("DELETE FROM ExportedResources"); + } + + virtual int64_t CreateResource(const char* publicId, + OrthancPluginResourceType type) + { + return base_.CreateResource(publicId, Orthanc::Plugins::Convert(type)); + } + + virtual void DeleteAttachment(int64_t id, + int32_t attachment) + { + base_.DeleteAttachment(id, static_cast(attachment)); + } + + virtual void DeleteMetadata(int64_t id, + int32_t metadataType) + { + base_.DeleteMetadata(id, static_cast(metadataType)); + } + + virtual void DeleteResource(int64_t id); + + virtual void GetAllPublicIds(std::list& target, + OrthancPluginResourceType resourceType) + { + base_.GetAllPublicIds(target, Orthanc::Plugins::Convert(resourceType)); + } + + virtual void GetAllPublicIds(std::list& target, + OrthancPluginResourceType resourceType, + uint64_t since, + uint64_t limit) + { + base_.GetAllPublicIds(target, Orthanc::Plugins::Convert(resourceType), since, limit); + } + + virtual void GetChanges(bool& done /*out*/, + int64_t since, + uint32_t maxResults); + + virtual void GetChildrenInternalId(std::list& target /*out*/, + int64_t id) + { + base_.GetChildrenInternalId(target, id); + } + + virtual void GetChildrenPublicId(std::list& target /*out*/, + int64_t id) + { + base_.GetChildrenPublicId(target, id); + } + + virtual void GetExportedResources(bool& done /*out*/, + int64_t since, + uint32_t maxResults); + + virtual void GetLastChange(); + + virtual void GetLastExportedResource(); + + virtual void GetMainDicomTags(int64_t id); + + virtual std::string GetPublicId(int64_t resourceId); + + virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType) + { + return base_.GetResourceCount(Orthanc::Plugins::Convert(resourceType)); + } + + virtual OrthancPluginResourceType GetResourceType(int64_t resourceId); + + virtual uint64_t GetTotalCompressedSize() + { + return base_.GetTotalCompressedSize(); + } + + virtual uint64_t GetTotalUncompressedSize() + { + return base_.GetTotalUncompressedSize(); + } + + virtual bool IsExistingResource(int64_t internalId) + { + return base_.IsExistingResource(internalId); + } + + virtual bool IsProtectedPatient(int64_t internalId) + { + return base_.IsProtectedPatient(internalId); + } + + virtual void ListAvailableMetadata(std::list& target /*out*/, + int64_t id); + + virtual void ListAvailableAttachments(std::list& target /*out*/, + int64_t id); + + virtual void LogChange(const OrthancPluginChange& change); + + virtual void LogExportedResource(const OrthancPluginExportedResource& resource); + + virtual bool LookupAttachment(int64_t id, + int32_t contentType); + + virtual bool LookupGlobalProperty(std::string& target /*out*/, + int32_t property) + { + return base_.LookupGlobalProperty(target, static_cast(property)); + } + + virtual void LookupIdentifier(std::list& target /*out*/, + uint16_t group, + uint16_t element, + const char* value) + { + base_.LookupIdentifier(target, Orthanc::DicomTag(group, element), value); + } + + virtual void LookupIdentifier(std::list& target /*out*/, + const char* value) + { + base_.LookupIdentifier(target, value); + } + + virtual bool LookupMetadata(std::string& target /*out*/, + int64_t id, + int32_t metadataType) + { + return base_.LookupMetadata(target, id, static_cast(metadataType)); + } + + virtual bool LookupParent(int64_t& parentId /*out*/, + int64_t resourceId); + + virtual bool LookupResource(int64_t& id /*out*/, + OrthancPluginResourceType& type /*out*/, + const char* publicId); + + virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/) + { + return base_.SelectPatientToRecycle(internalId); + } + + virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/, + int64_t patientIdToAvoid) + { + return base_.SelectPatientToRecycle(internalId, patientIdToAvoid); + } + + + virtual void SetGlobalProperty(int32_t property, + const char* value) + { + base_.SetGlobalProperty(static_cast(property), value); + } + + virtual void SetMainDicomTag(int64_t id, + uint16_t group, + uint16_t element, + const char* value) + { + base_.SetMainDicomTag(id, Orthanc::DicomTag(group, element), value); + } + + virtual void SetIdentifierTag(int64_t id, + uint16_t group, + uint16_t element, + const char* value) + { + base_.SetMainDicomTag(id, Orthanc::DicomTag(group, element), value); + } + + virtual void SetMetadata(int64_t id, + int32_t metadataType, + const char* value) + { + base_.SetMetadata(id, static_cast(metadataType), value); + } + + virtual void SetProtectedPatient(int64_t internalId, + bool isProtected) + { + base_.SetProtectedPatient(internalId, isProtected); + } + + virtual void StartTransaction(); + + virtual void RollbackTransaction(); + + virtual void CommitTransaction(); + + virtual uint32_t GetDatabaseVersion(); + + virtual void UpgradeDatabase(uint32_t targetVersion, + OrthancPluginStorageArea* storageArea); + + virtual void ClearMainDicomTags(int64_t internalId) + { + base_.ClearMainDicomTags(internalId); + } +}; diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Samples/DatabasePlugin/Plugin.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/DatabasePlugin/Plugin.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -0,0 +1,101 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * 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 + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "Database.h" + +#include +#include +#include + +static OrthancPluginContext* context_ = NULL; +static std::auto_ptr backend_; + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c) + { + context_ = c; + OrthancPluginLogWarning(context_, "Sample plugin is initializing"); + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(c) == 0) + { + char info[256]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + c->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(context_, info); + return -1; + } + + std::string path = "SampleDatabase.sqlite"; + uint32_t argCount = OrthancPluginGetCommandLineArgumentsCount(context_); + for (uint32_t i = 0; i < argCount; i++) + { + char* tmp = OrthancPluginGetCommandLineArgument(context_, i); + std::string argument(tmp); + OrthancPluginFreeString(context_, tmp); + + if (boost::starts_with(argument, "--database=")) + { + path = argument.substr(11); + } + } + + std::string s = "Using the following SQLite database: " + path; + OrthancPluginLogWarning(context_, s.c_str()); + + backend_.reset(new Database(path)); + OrthancPlugins::DatabaseBackendAdapter::Register(context_, *backend_); + + return 0; + } + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + backend_.reset(NULL); + } + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "sample-database"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return "1.0"; + } +} diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Samples/GdcmDecoding/CMakeLists.txt --- a/Plugins/Samples/GdcmDecoding/CMakeLists.txt Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Samples/GdcmDecoding/CMakeLists.txt Tue Oct 13 16:42:03 2015 +0200 @@ -3,18 +3,15 @@ project(GdcmDecoding) SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") -SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") -SET(USE_SYSTEM_GOOGLE_LOG ON CACHE BOOL "Use the system version of Google Log") +SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") -set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../..) -set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) +SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") +SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") -include(CheckIncludeFiles) -include(CheckIncludeFileCXX) +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) + include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.cmake) include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) find_package(GDCM REQUIRED) diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Samples/ServeFolders/CMakeLists.txt --- a/Plugins/Samples/ServeFolders/CMakeLists.txt Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Samples/ServeFolders/CMakeLists.txt Tue Oct 13 16:42:03 2015 +0200 @@ -5,16 +5,12 @@ SET(SERVE_FOLDERS_VERSION "0.0" CACHE STRING "Version of the plugin") SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") + SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost") -set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../../) set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) - -include(CheckIncludeFiles) -include(CheckIncludeFileCXX) include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) @@ -24,7 +20,6 @@ ${BOOST_SOURCES} ) - message("Setting the version of the plugin to ${SERVE_FOLDERS_VERSION}") add_definitions(-DSERVE_FOLDERS_VERSION="${SERVE_FOLDERS_VERSION}") diff -r f5ddbd9239dd -r 77d3e3a13c10 Plugins/Samples/WebSkeleton/CMakeLists.txt --- a/Plugins/Samples/WebSkeleton/CMakeLists.txt Fri Oct 09 17:20:26 2015 +0200 +++ b/Plugins/Samples/WebSkeleton/CMakeLists.txt Tue Oct 13 16:42:03 2015 +0200 @@ -5,11 +5,11 @@ SET(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)") SET(RESOURCES_ROOT ${CMAKE_SOURCE_DIR}/StaticResources) -include(Framework/Framework.cmake) - set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) +include(Framework/Framework.cmake) + add_library(WebSkeleton SHARED ${AUTOGENERATED_SOURCES} ) diff -r f5ddbd9239dd -r 77d3e3a13c10 Resources/Configuration.json --- a/Resources/Configuration.json Fri Oct 09 17:20:26 2015 +0200 +++ b/Resources/Configuration.json Tue Oct 13 16:42:03 2015 +0200 @@ -243,7 +243,8 @@ "KeepAlive" : false, // If this option is set to "false", Orthanc will run in index-only - // mode. The DICOM files will not be stored on the drive. + // mode. The DICOM files will not be stored on the drive. Note that + // this option might prevent the upgrade to newer versions of Orthanc. "StoreDicom" : true, // DICOM associations are kept open as long as new DICOM commands diff -r f5ddbd9239dd -r 77d3e3a13c10 Resources/ErrorCodes.json --- a/Resources/ErrorCodes.json Fri Oct 09 17:20:26 2015 +0200 +++ b/Resources/ErrorCodes.json Tue Oct 13 16:42:03 2015 +0200 @@ -492,5 +492,15 @@ "Code": 2038, "Name": "DatabaseNotInitialized", "Description": "Plugin trying to call the database during its initialization" + }, + { + "Code": 2039, + "Name": "SslDisabled", + "Description": "Orthanc has been built without SSL support" + }, + { + "Code": 2040, + "Name": "CannotOrderSlices", + "Description": "Unable to order the slices of the series" } ] diff -r f5ddbd9239dd -r 77d3e3a13c10 UnitTestsSources/DicomMapTests.cpp --- a/UnitTestsSources/DicomMapTests.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/UnitTestsSources/DicomMapTests.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -170,18 +170,12 @@ }*/ // Exceptions for the Instance level - if ((/* Accession number, from Image module */ - *it == DicomTag(0x0020, 0x0012) && - level == ResourceType_Instance) || - (/* Image Index, from PET Image module */ - *it == DicomTag(0x0054, 0x1330) && - level == ResourceType_Instance) || - (/* Temporal Position Identifier, from MR Image module */ - *it == DicomTag(0x0020, 0x0100) && - level == ResourceType_Instance) || - (/* Number of Frames, from Multi-frame module attributes, related to Image IOD */ - *it == DicomTag(0x0028, 0x0008) && - level == ResourceType_Instance )) + if (level == ResourceType_Instance && + (*it == DicomTag(0x0020, 0x0012) || /* Accession number, from Image module */ + *it == DicomTag(0x0054, 0x1330) || /* Image Index, from PET Image module */ + *it == DicomTag(0x0020, 0x0100) || /* Temporal Position Identifier, from MR Image module */ + *it == DicomTag(0x0028, 0x0008) || /* Number of Frames, from Multi-frame module attributes, related to Image IOD */ + *it == DICOM_TAG_IMAGE_POSITION_PATIENT)) { ok = true; } diff -r f5ddbd9239dd -r 77d3e3a13c10 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -394,7 +394,7 @@ FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Full, 0, Encoding_Ascii); Json::Value c; - SimplifyTags(c, b); + Toolbox::SimplifyTags(c, b); a[1]["PatientName"] = "Hello2"; // To remove the Data URI Scheme encoding ASSERT_EQ(0, c["ReferencedStudySequence"].compare(a)); @@ -473,7 +473,7 @@ f.ToJson(b, DicomToJsonFormat_Full, 0); Json::Value c; - SimplifyTags(c, b); + Toolbox::SimplifyTags(c, b); ASSERT_EQ(0, c["ReferencedPatientSequence"].compare(a)); ASSERT_NE(0, c["ReferencedStudySequence"].compare(a)); // Because Data URI Scheme decoding was enabled diff -r f5ddbd9239dd -r 77d3e3a13c10 UnitTestsSources/ServerIndexTests.cpp --- a/UnitTestsSources/ServerIndexTests.cpp Fri Oct 09 17:20:26 2015 +0200 +++ b/UnitTestsSources/ServerIndexTests.cpp Tue Oct 13 16:42:03 2015 +0200 @@ -122,10 +122,12 @@ } index_->SetListener(*listener_); + index_->Open(); } virtual void TearDown() { + index_->Close(); index_.reset(NULL); listener_.reset(NULL); } @@ -660,6 +662,7 @@ Toolbox::RemoveFile(path + "/index"); FilesystemStorage storage(path); DatabaseWrapper db; // The SQLite DB is in memory + db.Open(); ServerContext context(db, storage); ServerIndex& index = context.GetIndex(); @@ -669,6 +672,7 @@ ASSERT_EQ(4u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); context.Stop(); + db.Close(); } @@ -728,6 +732,7 @@ Toolbox::RemoveFile(path + "/index"); FilesystemStorage storage(path); DatabaseWrapper db; // The SQLite DB is in memory + db.Open(); ServerContext context(db, storage); ServerIndex& index = context.GetIndex(); @@ -781,4 +786,5 @@ ASSERT_THROW(Toolbox::GetFileSize(path + "/index"), OrthancException); context.Stop(); + db.Close(); }