Mercurial > hg > orthanc
changeset 1615:c40fe92a68e7
Primitives to upgrade the database version in plugins
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 16 Sep 2015 15:18:59 +0200 |
parents | 1c9e99d2bfd2 |
children | 644c32c07306 |
files | NEWS OrthancServer/DatabaseWrapper.cpp OrthancServer/DatabaseWrapper.h OrthancServer/IDatabaseWrapper.h OrthancServer/main.cpp Plugins/Engine/OrthancPluginDatabase.cpp Plugins/Engine/OrthancPluginDatabase.h Plugins/Engine/OrthancPlugins.cpp Plugins/Include/orthanc/OrthancCDatabasePlugin.h Plugins/Include/orthanc/OrthancCPlugin.h Plugins/Include/orthanc/OrthancCppDatabasePlugin.h |
diffstat | 11 files changed, 423 insertions(+), 68 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Fri Sep 11 18:46:13 2015 +0200 +++ b/NEWS Wed Sep 16 15:18:59 2015 +0200 @@ -30,6 +30,7 @@ * New function "OrthancPluginRegisterRestCallbackNoLock()" for high-performance plugins * Plugins have access to explicit error codes * Improvements to the sample "ServeFolders" plugin +* Primitives to upgrade the database version in plugins Maintenance -----------
--- a/OrthancServer/DatabaseWrapper.cpp Fri Sep 11 18:46:13 2015 +0200 +++ b/OrthancServer/DatabaseWrapper.cpp Wed Sep 16 15:18:59 2015 +0200 @@ -765,16 +765,6 @@ } } - static void UpgradeDatabase(SQLite::Connection& db, - EmbeddedResources::FileResourceId script) - { - std::string upgrade; - EmbeddedResources::GetFileResource(upgrade, script); - db.BeginTransaction(); - db.Execute(upgrade); - db.CommitTransaction(); - } - DatabaseWrapper::DatabaseWrapper(const std::string& path) : listener_(NULL) { @@ -807,49 +797,26 @@ } // Check the version of the database - std::string version; - if (!LookupGlobalProperty(version, GlobalProperty_DatabaseSchemaVersion)) + std::string tmp; + if (!LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion)) { - version = "Unknown"; + tmp = "Unknown"; } bool ok = false; try { - LOG(INFO) << "Version of the Orthanc database: " << version; - unsigned int v = boost::lexical_cast<unsigned int>(version); - - // This version of Orthanc is only compatible with versions 3, 4 and 5 of the DB schema - ok = (v == 3 || v == 4 || v == 5); - - if (v == 3) - { - LOG(WARNING) << "Upgrading database version from 3 to 4"; - UpgradeDatabase(db_, EmbeddedResources::UPGRADE_DATABASE_3_TO_4); - v = 4; - } - - if (v == 4) - { - LOG(WARNING) << "Upgrading database version from 4 to 5"; - UpgradeDatabase(db_, EmbeddedResources::UPGRADE_DATABASE_4_TO_5); - v = 5; - } - - // Sanity check - if (ORTHANC_DATABASE_VERSION != v) - { - throw OrthancException(ErrorCode_InternalError); - } + LOG(INFO) << "Version of the Orthanc database: " << tmp; + version_ = boost::lexical_cast<unsigned int>(tmp); + ok = true; } catch (boost::bad_lexical_cast&) { - ok = false; } if (!ok) { - LOG(ERROR) << "Incompatible version of the Orthanc database: " << version; + LOG(ERROR) << "Incompatible version of the Orthanc database: " << tmp; throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } @@ -857,6 +824,50 @@ db_.Register(signalRemainingAncestor_); } + + static void ExecuteUpgradeScript(SQLite::Connection& db, + EmbeddedResources::FileResourceId script) + { + std::string upgrade; + EmbeddedResources::GetFileResource(upgrade, script); + db.BeginTransaction(); + db.Execute(upgrade); + db.CommitTransaction(); + } + + + void DatabaseWrapper::Upgrade(unsigned int targetVersion, + IStorageArea& storageArea) + { + if (targetVersion != 5) + { + throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); + } + + // This version of Orthanc is only compatible with versions 3, 4 and 5 of the DB schema + if (version_ != 3 && + version_ != 4 && + version_ != 5) + { + throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); + } + + if (version_ == 3) + { + LOG(WARNING) << "Upgrading database version from 3 to 4"; + ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_3_TO_4); + version_ = 4; + } + + if (version_ == 4) + { + LOG(WARNING) << "Upgrading database version from 4 to 5"; + ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_4_TO_5); + version_ = 5; + } + } + + void DatabaseWrapper::SetListener(IDatabaseListener& listener) { listener_ = &listener;
--- a/OrthancServer/DatabaseWrapper.h Fri Sep 11 18:46:13 2015 +0200 +++ b/OrthancServer/DatabaseWrapper.h Wed Sep 16 15:18:59 2015 +0200 @@ -55,6 +55,7 @@ IDatabaseListener* listener_; SQLite::Connection db_; Internals::SignalRemainingAncestor* signalRemainingAncestor_; + unsigned int version_; void Open(); @@ -222,6 +223,13 @@ virtual void GetAllMetadata(std::map<MetadataType, std::string>& target, int64_t id); + virtual unsigned int GetDatabaseVersion() + { + return version_; + } + + virtual void Upgrade(unsigned int targetVersion, + IStorageArea& storageArea);
--- a/OrthancServer/IDatabaseWrapper.h Fri Sep 11 18:46:13 2015 +0200 +++ b/OrthancServer/IDatabaseWrapper.h Wed Sep 16 15:18:59 2015 +0200 @@ -34,6 +34,7 @@ #include "../Core/DicomFormat/DicomMap.h" #include "../Core/SQLite/ITransaction.h" +#include "../Core/FileStorage/IStorageArea.h" #include "../Core/FileStorage/FileInfo.h" #include "IDatabaseListener.h" #include "ExportedResource.h" @@ -181,5 +182,10 @@ virtual SQLite::ITransaction* StartTransaction() = 0; virtual void SetListener(IDatabaseListener& listener) = 0; + + virtual unsigned int GetDatabaseVersion() = 0; + + virtual void Upgrade(unsigned int targetVersion, + IStorageArea& storageArea) = 0; }; }
--- a/OrthancServer/main.cpp Fri Sep 11 18:46:13 2015 +0200 +++ b/OrthancServer/main.cpp Wed Sep 16 15:18:59 2015 +0200 @@ -315,21 +315,23 @@ << std::endl << "If no configuration file is given on the command line, a set of default " << std::endl << "parameters is used. Please refer to the Orthanc homepage for the full " << std::endl - << "instructions about how to use Orthanc " << std::endl - << "<https://code.google.com/p/orthanc/wiki/OrthancCookbook>." << std::endl + << "instructions about how to use Orthanc <http://www.orthanc-server.com/>." << std::endl << std::endl << "Command-line options:" << std::endl << " --help\t\tdisplay this help and exit" << std::endl << " --logdir=[dir]\tdirectory where to store the log files" << std::endl << "\t\t\t(if not used, the logs are dumped to stderr)" << std::endl << " --config=[file]\tcreate a sample configuration file and exit" << std::endl + << " --verbose\t\tbe verbose in logs" << std::endl << " --trace\t\thighest verbosity in logs (for debug)" << std::endl - << " --verbose\t\tbe verbose in logs" << std::endl + << " --upgrade\t\tallow Orthanc to upgrade the version of the" << std::endl + << "\t\t\tdatabase (beware that the database will become" << std::endl + << "\t\t\tincompatible with former versions of Orthanc)" << std::endl << " --version\t\toutput version information and exit" << std::endl << std::endl << "Exit status:" << std::endl - << " 0 if OK," << std::endl - << " -1 if error (have a look at the logs)." << std::endl + << " 0 if success," << std::endl + << " -1 if error (have a look at the logs)." << std::endl << std::endl; } @@ -519,10 +521,51 @@ } +static bool UpgradeDatabase(IDatabaseWrapper& database, + IStorageArea& storageArea, + bool allowDatabaseUpgrade) +{ + // Upgrade the database, if needed + unsigned int currentVersion = database.GetDatabaseVersion(); + if (currentVersion == ORTHANC_DATABASE_VERSION) + { + return true; + } + + if (!allowDatabaseUpgrade) + { + LOG(ERROR) << "The database 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 " + << currentVersion << " to " << ORTHANC_DATABASE_VERSION; + database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea); + + // Sanity check + currentVersion = database.GetDatabaseVersion(); + if (ORTHANC_DATABASE_VERSION != currentVersion) + { + LOG(ERROR) << "The database was not properly updated, it is still at version " << currentVersion; + throw OrthancException(ErrorCode_InternalError); + } + + return true; +} + + static bool ConfigureServerContext(IDatabaseWrapper& database, IStorageArea& storageArea, - OrthancPlugins *plugins) + OrthancPlugins *plugins, + bool allowDatabaseUpgrade) { + if (!UpgradeDatabase(database, storageArea, allowDatabaseUpgrade)) + { + return false; + } + ServerContext context(database, storageArea); HttpClient::SetDefaultTimeout(Configuration::GetGlobalIntegerParameter("HttpTimeout", 0)); @@ -569,7 +612,8 @@ static bool ConfigurePlugins(int argc, - char* argv[]) + char* argv[], + bool allowDatabaseUpgrade) { std::auto_ptr<IDatabaseWrapper> databasePtr; std::auto_ptr<IStorageArea> storage; @@ -604,14 +648,14 @@ assert(database != NULL); assert(storage.get() != NULL); - return ConfigureServerContext(*database, *storage, &plugins); + return ConfigureServerContext(*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); + return ConfigureServerContext(*databasePtr, *storage, NULL, allowDatabaseUpgrade); #else # error The macro ORTHANC_PLUGINS_ENABLED must be set to 0 or 1 @@ -619,9 +663,11 @@ } -static bool StartOrthanc(int argc, char* argv[]) +static bool StartOrthanc(int argc, + char* argv[], + bool allowDatabaseUpgrade) { - return ConfigurePlugins(argc, argv); + return ConfigurePlugins(argc, argv, allowDatabaseUpgrade); } @@ -629,6 +675,8 @@ { Logging::Initialize(); + bool allowDatabaseUpgrade = false; + for (int i = 1; i < argc; i++) { if (std::string(argv[i]) == "--help") @@ -668,6 +716,11 @@ } } + if (std::string(argv[i]) == "--upgrade") + { + allowDatabaseUpgrade = true; + } + if (boost::starts_with(argv[i], "--config=")) { std::string configurationSample; @@ -706,7 +759,7 @@ { OrthancInitialize(configurationFile); - bool restart = StartOrthanc(argc, argv); + bool restart = StartOrthanc(argc, argv, allowDatabaseUpgrade); if (restart) { OrthancFinalize();
--- a/Plugins/Engine/OrthancPluginDatabase.cpp Fri Sep 11 18:46:13 2015 +0200 +++ b/Plugins/Engine/OrthancPluginDatabase.cpp Wed Sep 16 15:18:59 2015 +0200 @@ -982,6 +982,43 @@ } + unsigned int OrthancPluginDatabase::GetDatabaseVersion() + { + if (extensions_.getDatabaseVersion != NULL) + { + uint32_t version; + if (extensions_.getDatabaseVersion(&version, payload_) != 0) + { + throw OrthancException(ErrorCode_Plugin); + } + + return version; + } + else + { + // Before adding the "GetDatabaseVersion()" extension in plugins + // (OrthancPostgreSQL <= 1.2), the only supported DB schema was + // version 5. + return 5; + } + } + + + void OrthancPluginDatabase::Upgrade(unsigned int targetVersion, + IStorageArea& storageArea) + { + if (extensions_.upgradeDatabase != NULL) + { + if (extensions_.upgradeDatabase( + payload_, targetVersion, + reinterpret_cast<OrthancPluginStorageArea*>(&storageArea)) != 0) + { + throw OrthancException(ErrorCode_Plugin); + } + } + } + + void OrthancPluginDatabase::AnswerReceived(const _OrthancPluginDatabaseAnswer& answer) { if (answer.type == _OrthancPluginDatabaseAnswerType_None)
--- a/Plugins/Engine/OrthancPluginDatabase.h Fri Sep 11 18:46:13 2015 +0200 +++ b/Plugins/Engine/OrthancPluginDatabase.h Wed Sep 16 15:18:59 2015 +0200 @@ -222,6 +222,11 @@ listener_ = &listener; } + virtual unsigned int GetDatabaseVersion(); + + virtual void Upgrade(unsigned int targetVersion, + IStorageArea& storageArea); + void AnswerReceived(const _OrthancPluginDatabaseAnswer& answer); }; }
--- a/Plugins/Engine/OrthancPlugins.cpp Fri Sep 11 18:46:13 2015 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Wed Sep 16 15:18:59 2015 +0200 @@ -165,6 +165,37 @@ } + static OrthancPluginContentType Convert(FileContentType type) + { + switch (type) + { + case FileContentType_Dicom: + return OrthancPluginContentType_Dicom; + + case FileContentType_DicomAsJson: + return OrthancPluginContentType_DicomAsJson; + + default: + return OrthancPluginContentType_Unknown; + } + } + + + static FileContentType Convert(OrthancPluginContentType type) + { + switch (type) + { + case OrthancPluginContentType_Dicom: + return FileContentType_Dicom; + + case OrthancPluginContentType_DicomAsJson: + return FileContentType_DicomAsJson; + + default: + return FileContentType_Unknown; + } + } + struct OrthancPlugins::PImpl { @@ -1618,6 +1649,35 @@ DrawText(parameters); return true; + case _OrthancPluginService_StorageAreaCreate: + { + const _OrthancPluginStorageAreaCreate& p = + *reinterpret_cast<const _OrthancPluginStorageAreaCreate*>(parameters); + IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); + storage.Create(p.uuid, p.content, p.size, Convert(p.type)); + return true; + } + + case _OrthancPluginService_StorageAreaRead: + { + const _OrthancPluginStorageAreaRead& p = + *reinterpret_cast<const _OrthancPluginStorageAreaRead*>(parameters); + IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); + std::string content; + storage.Read(content, p.uuid, Convert(p.type)); + CopyToMemoryBuffer(*p.target, content); + return true; + } + + case _OrthancPluginService_StorageAreaRemove: + { + const _OrthancPluginStorageAreaRemove& p = + *reinterpret_cast<const _OrthancPluginStorageAreaRemove*>(parameters); + IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); + storage.Remove(p.uuid, Convert(p.type)); + return true; + } + default: { // This service is unknown to the Orthanc plugin engine @@ -1654,21 +1714,6 @@ } } - OrthancPluginContentType Convert(FileContentType type) const - { - switch (type) - { - case FileContentType_Dicom: - return OrthancPluginContentType_Dicom; - - case FileContentType_DicomAsJson: - return OrthancPluginContentType_DicomAsJson; - - default: - return OrthancPluginContentType_Unknown; - } - } - public: PluginStorageArea(const _OrthancPluginRegisterStorageArea& params) : params_(params) {
--- a/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Fri Sep 11 18:46:13 2015 +0200 +++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Wed Sep 16 15:18:59 2015 +0200 @@ -643,6 +643,18 @@ OrthancPluginResourceType resourceType, uint64_t since, uint64_t limit); + + int32_t (*getDatabaseVersion) ( + /* outputs */ + uint32_t* version, + /* inputs */ + void* payload); + + int32_t (*upgradeDatabase) ( + /* inputs */ + void* payload, + uint32_t targetVersion, + OrthancPluginStorageArea* storageArea); } OrthancPluginDatabaseExtensions; /*<! @endcond */
--- a/Plugins/Include/orthanc/OrthancCPlugin.h Fri Sep 11 18:46:13 2015 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Wed Sep 16 15:18:59 2015 +0200 @@ -429,6 +429,9 @@ _OrthancPluginService_RegisterDatabaseBackend = 5000, _OrthancPluginService_DatabaseAnswer = 5001, _OrthancPluginService_RegisterDatabaseBackendV2 = 5002, + _OrthancPluginService_StorageAreaCreate = 5003, + _OrthancPluginService_StorageAreaRead = 5004, + _OrthancPluginService_StorageAreaRemove = 5005, /* Primitives for handling images */ _OrthancPluginService_GetImagePixelFormat = 6000, @@ -630,7 +633,7 @@ /** - * @brief Opaque structure that represents a uncompressed image in memory. + * @brief Opaque structure that represents an image that is uncompressed in memory. * @ingroup Images **/ typedef struct _OrthancPluginImage_t OrthancPluginImage; @@ -638,6 +641,14 @@ /** + * @brief Opaque structure that represents the storage area that is actually used by Orthanc. + * @ingroup Images + **/ + typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea; + + + + /** * @brief Signature of a callback function that answers to a REST request. * @ingroup Callbacks **/ @@ -2402,6 +2413,7 @@ * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @return The version. * @ingroup Callbacks + * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase() **/ ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( OrthancPluginContext* context) @@ -3484,6 +3496,127 @@ + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + const void* content; + uint64_t size; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaCreate; + + + /** + * @brief Create a file inside the storage area. + * + * This function creates a new file inside the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be created. + * @param content The content to store in the newly created file. + * @param size The size of the content. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + const void* content, + uint64_t size, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaCreate params; + params.storageArea = storageArea; + params.uuid = uuid; + params.content = content; + params.size = size; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, ¶ms); + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRead; + + + /** + * @brief Read a file from the storage area. + * + * This function reads the content of a given file from the storage + * area that is currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be read. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRead params; + params.target = target; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRemove; + + + /** + * @brief Remove a file from the storage area. + * + * This function removes a given file from the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be removed. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRemove params; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, ¶ms); + } + + + #ifdef __cplusplus } #endif
--- a/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h Fri Sep 11 18:46:13 2015 +0200 +++ b/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h Wed Sep 16 15:18:59 2015 +0200 @@ -430,6 +430,11 @@ virtual void RollbackTransaction() = 0; virtual void CommitTransaction() = 0; + + virtual uint32_t GetDatabaseVersion() = 0; + + virtual void UpgradeDatabase(uint32_t targetVersion, + OrthancPluginStorageArea* storageArea) = 0; }; @@ -1518,6 +1523,43 @@ } } + + static int32_t GetDatabaseVersion(uint32_t* version, + void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + + try + { + *version = backend->GetDatabaseVersion(); + return 0; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return -1; + } + } + + + static int32_t UpgradeDatabase(void* payload, + uint32_t targetVersion, + OrthancPluginStorageArea* storageArea) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + + try + { + backend->UpgradeDatabase(targetVersion, storageArea); + return 0; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return -1; + } + } + public: /** @@ -1584,6 +1626,8 @@ params.close = Close; extensions.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit; + extensions.getDatabaseVersion = GetDatabaseVersion; + extensions.upgradeDatabase = UpgradeDatabase; OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackendV2(context, ¶ms, &extensions, &backend); if (!context)