# HG changeset patch # User Alain Mazy # Date 1726504297 -7200 # Node ID 56352ae8812064f655f638334ef85a141eec5c01 # Parent f1ccb67fce31ecabf63c44909436251348654808 wip: new ReadOnly configuration diff -r f1ccb67fce31 -r 56352ae88120 NEWS --- a/NEWS Sat Sep 14 11:24:11 2024 +0200 +++ b/NEWS Mon Sep 16 18:31:37 2024 +0200 @@ -5,6 +5,18 @@ - /studies?expand and sibbling routes now also return "Metadata" (if the DB implements 'extended-api-v1') - /studies?since=x&limit=0 and sibbling routes: limit=0 now means "no limit" instead of "no results" +General +------- + +* Database: + - Introduced database optimizations "ExtendedFind" to replace many small SQL queries + by a small number of large SQL queries to greatly reduce the cost of DB latency. + Furthermore, this "ExtendedFind" brings new sorting and filtering features to the + Rest API (TODO). + - Introduced database optimizations "ExtendedChanges" to allow filtering of /changes. + - Reduced the number of SQL queries when ingesting DICOM files. + + REST API -------- diff -r f1ccb67fce31 -r 56352ae88120 OrthancFramework/Resources/CodeGeneration/ErrorCodes.json --- a/OrthancFramework/Resources/CodeGeneration/ErrorCodes.json Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancFramework/Resources/CodeGeneration/ErrorCodes.json Mon Sep 16 18:31:37 2024 +0200 @@ -262,6 +262,11 @@ "Name": "DuplicateResource", "Description": "Duplicate resource" }, + { + "Code": 47, + "Name": "IncompatibleConfigurations", + "Description": "Your configuration file contains configuration that are mutually incompatible" + }, diff -r f1ccb67fce31 -r 56352ae88120 OrthancFramework/Sources/Enumerations.cpp --- a/OrthancFramework/Sources/Enumerations.cpp Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancFramework/Sources/Enumerations.cpp Mon Sep 16 18:31:37 2024 +0200 @@ -186,6 +186,9 @@ case ErrorCode_DuplicateResource: return "Duplicate resource"; + case ErrorCode_IncompatibleConfigurations: + return "Your configuration file contains configuration that are mutually incompatible"; + case ErrorCode_SQLiteNotOpened: return "SQLite: The database is not opened"; @@ -220,7 +223,7 @@ return "SQLite: Cannot step over a cached statement"; case ErrorCode_SQLiteBindOutOfRange: - return "SQLite: Bing a value while out of range (serious error)"; + return "SQLite: Bind a value while out of range (serious error)"; case ErrorCode_SQLitePrepareStatement: return "SQLite: Cannot prepare a cached statement"; diff -r f1ccb67fce31 -r 56352ae88120 OrthancFramework/Sources/Enumerations.h --- a/OrthancFramework/Sources/Enumerations.h Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancFramework/Sources/Enumerations.h Mon Sep 16 18:31:37 2024 +0200 @@ -172,6 +172,7 @@ ErrorCode_MainDicomTagsMultiplyDefined = 44 /*!< A main DICOM Tag has been defined multiple times for the same resource level */, ErrorCode_ForbiddenAccess = 45 /*!< Access to a resource is forbidden */, ErrorCode_DuplicateResource = 46 /*!< Duplicate resource */, + ErrorCode_IncompatibleConfigurations = 47 /*!< Your configuration file contains configuration that are mutually incompatible */, ErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, ErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, ErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, @@ -183,7 +184,7 @@ ErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, ErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, ErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, - ErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, + ErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bind a value while out of range (serious error) */, ErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, ErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, ErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, diff -r f1ccb67fce31 -r 56352ae88120 OrthancFramework/Sources/SQLite/OrthancSQLiteException.h --- a/OrthancFramework/Sources/SQLite/OrthancSQLiteException.h Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancFramework/Sources/SQLite/OrthancSQLiteException.h Mon Sep 16 18:31:37 2024 +0200 @@ -129,7 +129,7 @@ return "SQLite: Cannot step over a cached statement"; case ErrorCode_SQLiteBindOutOfRange: - return "SQLite: Bing a value while out of range (serious error)"; + return "SQLite: Bind a value while out of range (serious error)"; case ErrorCode_SQLitePrepareStatement: return "SQLite: Cannot prepare a cached statement"; diff -r f1ccb67fce31 -r 56352ae88120 OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h --- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Mon Sep 16 18:31:37 2024 +0200 @@ -262,6 +262,7 @@ OrthancPluginErrorCode_MainDicomTagsMultiplyDefined = 44 /*!< A main DICOM Tag has been defined multiple times for the same resource level */, OrthancPluginErrorCode_ForbiddenAccess = 45 /*!< Access to a resource is forbidden */, OrthancPluginErrorCode_DuplicateResource = 46 /*!< Duplicate resource */, + OrthancPluginErrorCode_IncompatibleConfigurations = 47 /*!< Your configuration file contains configuration that are mutually incompatible */, OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, @@ -273,7 +274,7 @@ OrthancPluginErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, OrthancPluginErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, OrthancPluginErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, - OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, + OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bind a value while out of range (serious error) */, OrthancPluginErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, OrthancPluginErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, diff -r f1ccb67fce31 -r 56352ae88120 OrthancServer/Resources/Configuration.json --- a/OrthancServer/Resources/Configuration.json Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancServer/Resources/Configuration.json Mon Sep 16 18:31:37 2024 +0200 @@ -991,6 +991,13 @@ // Display a warning message when Orthanc and its plugins are unable // to decode a frame (new in Orthanc 1.12.5). "W003_DecoderFailure": true - } + }, + + // Configure Orthanc in read only mode. + // In this mode, many Orthanc features that requires a write access to the + // Index DB or the disk storage won't be available at all. + // (new in Orthanc 1.12.5) + "ReadOnly" : false + } diff -r f1ccb67fce31 -r 56352ae88120 OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp --- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Mon Sep 16 18:31:37 2024 +0200 @@ -481,6 +481,10 @@ else { assert(writeOperations != NULL); + if (readOnly_) + { + throw OrthancException(ErrorCode_ReadOnly, "The DB is trying to execute a ReadWrite transaction while Orthanc has been started in ReadOnly mode."); + } Transaction transaction(db_, *factory_, TransactionType_ReadWrite); { @@ -518,10 +522,11 @@ } - StatelessDatabaseOperations::StatelessDatabaseOperations(IDatabaseWrapper& db) : + StatelessDatabaseOperations::StatelessDatabaseOperations(IDatabaseWrapper& db, bool readOnly) : db_(db), mainDicomTagsRegistry_(new MainDicomTagsRegistry), - maxRetries_(0) + maxRetries_(0), + readOnly_(readOnly) { } diff -r f1ccb67fce31 -r 56352ae88120 OrthancServer/Sources/Database/StatelessDatabaseOperations.h --- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Mon Sep 16 18:31:37 2024 +0200 @@ -598,6 +598,7 @@ boost::shared_mutex mutex_; std::unique_ptr factory_; unsigned int maxRetries_; + bool readOnly_; void ApplyInternal(IReadOnlyOperations* readOperations, IReadWriteOperations* writeOperations); @@ -608,7 +609,7 @@ unsigned int maximumPatientCount); public: - explicit StatelessDatabaseOperations(IDatabaseWrapper& database); + explicit StatelessDatabaseOperations(IDatabaseWrapper& database, bool readOnly); void SetTransactionContextFactory(ITransactionContextFactory* factory /* takes ownership */); diff -r f1ccb67fce31 -r 56352ae88120 OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Mon Sep 16 18:31:37 2024 +0200 @@ -4369,13 +4369,23 @@ Register("/series", ListResources); Register("/studies", ListResources); - Register("/instances/{id}", DeleteSingleResource); + if (!context_.IsReadOnly()) + { + Register("/instances/{id}", DeleteSingleResource); + Register("/patients/{id}", DeleteSingleResource); + Register("/series/{id}", DeleteSingleResource); + Register("/studies/{id}", DeleteSingleResource); + + Register("/tools/bulk-delete", BulkDelete); + } + else + { + LOG(WARNING) << "READ-ONLY SYSTEM: DELETE routes are not available"; + } + Register("/instances/{id}", GetSingleResource); - Register("/patients/{id}", DeleteSingleResource); Register("/patients/{id}", GetSingleResource); - Register("/series/{id}", DeleteSingleResource); Register("/series/{id}", GetSingleResource); - Register("/studies/{id}", DeleteSingleResource); Register("/studies/{id}", GetSingleResource); Register("/instances/{id}/statistics", GetResourceStatistics); @@ -4420,7 +4430,16 @@ Register("/instances/{id}/numpy", GetNumpyInstance); // New in Orthanc 1.10.0 Register("/patients/{id}/protected", IsProtectedPatient); - Register("/patients/{id}/protected", SetPatientProtection); + + if (!context_.IsReadOnly()) + { + Register("/patients/{id}/protected", SetPatientProtection); + } + else + { + LOG(WARNING) << "READ-ONLY SYSTEM: PUT /patients/{id}/protected route is not available"; + } + std::vector resourceTypes; resourceTypes.push_back("patients"); @@ -4438,14 +4457,15 @@ // New in Orthanc 1.12.0 Register("/" + resourceTypes[i] + "/{id}/labels", ListLabels); Register("/" + resourceTypes[i] + "/{id}/labels/{label}", GetLabel); - Register("/" + resourceTypes[i] + "/{id}/labels/{label}", RemoveLabel); - Register("/" + resourceTypes[i] + "/{id}/labels/{label}", AddLabel); + + if (!context_.IsReadOnly()) + { + Register("/" + resourceTypes[i] + "/{id}/labels/{label}", RemoveLabel); + Register("/" + resourceTypes[i] + "/{id}/labels/{label}", AddLabel); + } Register("/" + resourceTypes[i] + "/{id}/attachments", ListAttachments); - Register("/" + resourceTypes[i] + "/{id}/attachments/{name}", DeleteAttachment); Register("/" + resourceTypes[i] + "/{id}/attachments/{name}", GetAttachmentOperations); - Register("/" + resourceTypes[i] + "/{id}/attachments/{name}", UploadAttachment); - Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/compress", ChangeAttachmentCompression); Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/compressed-data", GetAttachmentData<0>); Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/compressed-md5", GetAttachmentCompressedMD5); Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/compressed-size", GetAttachmentCompressedSize); @@ -4453,12 +4473,29 @@ Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/is-compressed", IsAttachmentCompressed); Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/md5", GetAttachmentMD5); Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/size", GetAttachmentSize); - Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/uncompress", ChangeAttachmentCompression); Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/info", GetAttachmentInfo); Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/verify-md5", VerifyAttachment); + + if (!context_.IsReadOnly()) + { + Register("/" + resourceTypes[i] + "/{id}/attachments/{name}", DeleteAttachment); + Register("/" + resourceTypes[i] + "/{id}/attachments/{name}", UploadAttachment); + Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/compress", ChangeAttachmentCompression); + Register("/" + resourceTypes[i] + "/{id}/attachments/{name}/uncompress", ChangeAttachmentCompression); + } } - Register("/tools/invalidate-tags", InvalidateTags); + if (context_.IsReadOnly()) + { + LOG(WARNING) << "READ-ONLY SYSTEM: deactivating PUT, POST and DELETE attachments routes"; + LOG(WARNING) << "READ-ONLY SYSTEM: deactivating PUT and DELETE labels routes"; + } + + if (!context_.IsReadOnly()) + { + Register("/tools/invalidate-tags", InvalidateTags); + } + Register("/tools/lookup", Lookup); Register("/tools/find", Find); @@ -4485,13 +4522,19 @@ Register("/series/{id}/ordered-slices", OrderSlices); Register("/series/{id}/numpy", GetNumpySeries); // New in Orthanc 1.10.0 - Register("/patients/{id}/reconstruct", ReconstructResource); - Register("/studies/{id}/reconstruct", ReconstructResource); - Register("/series/{id}/reconstruct", ReconstructResource); - Register("/instances/{id}/reconstruct", ReconstructResource); - Register("/tools/reconstruct", ReconstructAllResources); + if (!context_.IsReadOnly()) + { + Register("/patients/{id}/reconstruct", ReconstructResource); + Register("/studies/{id}/reconstruct", ReconstructResource); + Register("/series/{id}/reconstruct", ReconstructResource); + Register("/instances/{id}/reconstruct", ReconstructResource); + Register("/tools/reconstruct", ReconstructAllResources); + } + else + { + LOG(WARNING) << "READ-ONLY SYSTEM: deactivating /reconstruct routes"; + } Register("/tools/bulk-content", BulkContent); - Register("/tools/bulk-delete", BulkDelete); } } diff -r f1ccb67fce31 -r 56352ae88120 OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Mon Sep 16 18:31:37 2024 +0200 @@ -95,6 +95,7 @@ static const char* const CAPABILITIES = "Capabilities"; static const char* const HAS_EXTENDED_CHANGES = "HasExtendedChanges"; static const char* const HAS_EXTENDED_FIND = "HasExtendedFind"; + static const char* const READ_ONLY = "ReadOnly"; if (call.IsDocumentation()) { @@ -143,6 +144,8 @@ "Whether the database back-end supports labels (new in Orthanc 1.12.0)") .SetAnswerField(CAPABILITIES, RestApiCallDocumentation::Type_JsonObject, "Whether the back-end supports optional features like 'HasExtendedChanges', 'HasExtendedFind' (new in Orthanc 1.12.5) ") + .SetAnswerField(READ_ONLY, RestApiCallDocumentation::Type_Boolean, + "Whether Orthanc is running in read only mode (new in Orthanc 1.12.5)") .SetHttpGetSample("https://orthanc.uclouvain.be/demo/system", true); return; } @@ -174,6 +177,7 @@ result[STORAGE_AREA_PLUGIN] = Json::nullValue; result[DATABASE_BACKEND_PLUGIN] = Json::nullValue; + result[READ_ONLY] = context.IsReadOnly(); #if ORTHANC_ENABLE_PLUGINS == 1 result[PLUGINS_ENABLED] = true; diff -r f1ccb67fce31 -r 56352ae88120 OrthancServer/Sources/ServerContext.cpp --- a/OrthancServer/Sources/ServerContext.cpp Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancServer/Sources/ServerContext.cpp Mon Sep 16 18:31:37 2024 +0200 @@ -389,7 +389,8 @@ ingestTranscodingOfCompressed_(true), preferredTransferSyntax_(DicomTransferSyntax_LittleEndianExplicit), deidentifyLogs_(false), - serverStartTimeUtc_(boost::posix_time::second_clock::universal_time()) + serverStartTimeUtc_(boost::posix_time::second_clock::universal_time()), + readOnly_(false) { try { diff -r f1ccb67fce31 -r 56352ae88120 OrthancServer/Sources/ServerContext.h --- a/OrthancServer/Sources/ServerContext.h Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancServer/Sources/ServerContext.h Mon Sep 16 18:31:37 2024 +0200 @@ -277,6 +277,7 @@ boost::mutex dynamicOptionsMutex_; bool isUnknownSopClassAccepted_; std::set acceptedTransferSyntaxes_; + bool readOnly_; StoreResult StoreAfterTranscoding(std::string& resultPublicId, DicomInstanceToStore& dicom, @@ -345,6 +346,21 @@ return compressionEnabled_; } + void SetReadOnly(bool readOnly) + { + readOnly_ = true; + } + + bool IsReadOnly() const + { + return readOnly_; + } + + bool IsSaveJobs() const + { + return saveJobs_; + } + bool AddAttachment(int64_t& newRevision, const std::string& resourceId, FileContentType attachmentType, diff -r f1ccb67fce31 -r 56352ae88120 OrthancServer/Sources/ServerIndex.cpp --- a/OrthancServer/Sources/ServerIndex.cpp Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancServer/Sources/ServerIndex.cpp Mon Sep 16 18:31:37 2024 +0200 @@ -347,7 +347,7 @@ ServerIndex::ServerIndex(ServerContext& context, IDatabaseWrapper& db, unsigned int threadSleepGranularityMilliseconds) : - StatelessDatabaseOperations(db), + StatelessDatabaseOperations(db, context.IsReadOnly()), done_(false), maximumStorageMode_(MaxStorageMode_Recycle), maximumStorageSize_(0), @@ -357,12 +357,22 @@ // Initial recycling if the parameters have changed since the last // execution of Orthanc - StandaloneRecycling(maximumStorageMode_, maximumStorageSize_, maximumPatients_); + if (!context.IsReadOnly()) + { + StandaloneRecycling(maximumStorageMode_, maximumStorageSize_, maximumPatients_); + } // For some DB engines (like SQLite), make sure we flush the DB to disk at regular interval if (GetDatabaseCapabilities().HasFlushToDisk()) { - flushThread_ = boost::thread(FlushThread, this, threadSleepGranularityMilliseconds); + if (context.IsReadOnly()) + { + LOG(WARNING) << "READ-ONLY SYSTEM: not starting the flush disk thread"; + } + else + { + flushThread_ = boost::thread(FlushThread, this, threadSleepGranularityMilliseconds); + } } // For some DB plugins that implements the UpdateAndGetStatistics function, updating @@ -370,11 +380,25 @@ // -> make sure they are updated at regular interval if (GetDatabaseCapabilities().HasUpdateAndGetStatistics()) { - updateStatisticsThread_ = boost::thread(UpdateStatisticsThread, this, threadSleepGranularityMilliseconds); + if (context.IsReadOnly()) + { + LOG(WARNING) << "READ-ONLY SYSTEM: not starting the UpdateStatisticsThread"; + } + else + { + updateStatisticsThread_ = boost::thread(UpdateStatisticsThread, this, threadSleepGranularityMilliseconds); + } } - unstableResourcesMonitorThread_ = boost::thread - (UnstableResourcesMonitorThread, this, threadSleepGranularityMilliseconds); + if (context.IsReadOnly()) + { + LOG(WARNING) << "READ-ONLY SYSTEM: not starting the unstable resources monitor thread"; + } + else + { + unstableResourcesMonitorThread_ = boost::thread + (UnstableResourcesMonitorThread, this, threadSleepGranularityMilliseconds); + } } diff -r f1ccb67fce31 -r 56352ae88120 OrthancServer/Sources/main.cpp --- a/OrthancServer/Sources/main.cpp Sat Sep 14 11:24:11 2024 +0200 +++ b/OrthancServer/Sources/main.cpp Mon Sep 16 18:31:37 2024 +0200 @@ -825,6 +825,7 @@ PrintErrorCode(ErrorCode_MainDicomTagsMultiplyDefined, "A main DICOM Tag has been defined multiple times for the same resource level"); PrintErrorCode(ErrorCode_ForbiddenAccess, "Access to a resource is forbidden"); PrintErrorCode(ErrorCode_DuplicateResource, "Duplicate resource"); + PrintErrorCode(ErrorCode_IncompatibleConfigurations, "Your configuration file contains configuration that are mutually incompatible"); PrintErrorCode(ErrorCode_SQLiteNotOpened, "SQLite: The database is not opened"); PrintErrorCode(ErrorCode_SQLiteAlreadyOpened, "SQLite: Connection is already open"); PrintErrorCode(ErrorCode_SQLiteCannotOpen, "SQLite: Unable to open the database"); @@ -836,7 +837,7 @@ PrintErrorCode(ErrorCode_SQLiteFlush, "SQLite: Unable to flush the database"); PrintErrorCode(ErrorCode_SQLiteCannotRun, "SQLite: Cannot run a cached statement"); PrintErrorCode(ErrorCode_SQLiteCannotStep, "SQLite: Cannot step over a cached statement"); - PrintErrorCode(ErrorCode_SQLiteBindOutOfRange, "SQLite: Bing a value while out of range (serious error)"); + PrintErrorCode(ErrorCode_SQLiteBindOutOfRange, "SQLite: Bind a value while out of range (serious error)"); PrintErrorCode(ErrorCode_SQLitePrepareStatement, "SQLite: Cannot prepare a cached statement"); PrintErrorCode(ErrorCode_SQLiteTransactionAlreadyStarted, "SQLite: Beginning the same transaction twice"); PrintErrorCode(ErrorCode_SQLiteTransactionCommit, "SQLite: Failure when committing the transaction"); @@ -1563,41 +1564,57 @@ { OrthancConfiguration::ReaderLock lock; - context.SetCompressionEnabled(lock.GetConfiguration().GetBooleanParameter("StorageCompression", false)); - context.SetStoreMD5ForAttachments(lock.GetConfiguration().GetBooleanParameter("StoreMD5ForAttachments", true)); + // New option in Orthanc 1.12.5 + context.SetReadOnly(lock.GetConfiguration().GetBooleanParameter("ReadOnly", false)); + + if (context.IsReadOnly()) + { + LOG(WARNING) << "READ-ONLY SYSTEM: ignoring these configurations: StorageCompression, StoreMD5ForAttachments, OverwriteInstances, MaximumPatientCount, MaximumStorageSize, MaximumStorageMode"; - // New option in Orthanc 1.4.2 - context.SetOverwriteInstances(lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false)); + if (context.IsSaveJobs()) + { + throw OrthancException(ErrorCode_IncompatibleConfigurations, "\"SaveJobs\" can not be true when \"ReadOnly\" is true"); + } + } + else + { + context.SetCompressionEnabled(lock.GetConfiguration().GetBooleanParameter("StorageCompression", false)); + context.SetStoreMD5ForAttachments(lock.GetConfiguration().GetBooleanParameter("StoreMD5ForAttachments", true)); + + // New option in Orthanc 1.4.2 + context.SetOverwriteInstances(lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false)); - try - { - context.GetIndex().SetMaximumPatientCount(lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumPatientCount", 0)); - } - catch (...) - { - context.GetIndex().SetMaximumPatientCount(0); + try + { + context.GetIndex().SetMaximumPatientCount(lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumPatientCount", 0)); + } + catch (...) + { + context.GetIndex().SetMaximumPatientCount(0); + } + + try + { + uint64_t size = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumStorageSize", 0); + context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024); + } + catch (...) + { + context.GetIndex().SetMaximumStorageSize(0); + } + + try + { + std::string mode = lock.GetConfiguration().GetStringParameter("MaximumStorageMode", "Recycle"); + context.GetIndex().SetMaximumStorageMode(StringToMaxStorageMode(mode)); + } + catch (...) + { + context.GetIndex().SetMaximumStorageMode(MaxStorageMode_Recycle); + } } - try - { - uint64_t size = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumStorageSize", 0); - context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024); - } - catch (...) - { - context.GetIndex().SetMaximumStorageSize(0); - } - - try - { - std::string mode = lock.GetConfiguration().GetStringParameter("MaximumStorageMode", "Recycle"); - context.GetIndex().SetMaximumStorageMode(StringToMaxStorageMode(mode)); - } - catch (...) - { - context.GetIndex().SetMaximumStorageMode(MaxStorageMode_Recycle); - } - + // note: this config is valid in ReadOnlyMode try { uint64_t size = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumStorageCacheSize", 128);