changeset 6104:49674012ab49 attach-custom-data

wip: queues for plugins
author Alain Mazy <am@orthanc.team>
date Fri, 09 May 2025 16:14:42 +0200
parents 91527f33f3bf
children d71be7893e49
files NEWS OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Engine/OrthancPlugins.h OrthancServer/Plugins/Engine/PluginsEnumerations.cpp OrthancServer/Plugins/Engine/PluginsEnumerations.h OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto OrthancServer/Sources/Database/IDatabaseWrapper.h OrthancServer/Sources/Database/InstallKeyValueStore.sql OrthancServer/Sources/Database/PrepareDatabase.sql OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.h OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/Sources/ServerEnumerations.h
diffstat 18 files changed, 515 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri May 09 09:16:09 2025 +0200
+++ b/NEWS	Fri May 09 16:14:42 2025 +0200
@@ -6,15 +6,13 @@
 
 * SQLite default DB engine now supports metadata and attachment revisions.
 * Upgraded the DB to allow plugins to store customData for each attachment.
-* New sample Advanced Storage plugin that allows:
-  - using multiple disk for image storage,
-  - use more human friendly storage structure (experimental feature).
 
 Plugins
 -------
 
-* New database plugin SDK (vX) to handle customData for attachments and key-value store.
+* New database plugin SDK (vX) to handle customData for attachments, key-value stores and queues.
 * New storage plugin SDK (v3) to handle customData for attachments.
+* New functions available to all plugins to store key-values and queues.
 
 
 Version 1.12.7 (2025-04-07)
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Fri May 09 16:14:42 2025 +0200
@@ -1451,26 +1451,39 @@
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
 
-    virtual void StoreKeyValue(const std::string& pluginId,
+    virtual void StoreKeyValue(const std::string& storeId,
                                const std::string& key,
                                const std::string& value) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
 
-    virtual void DeleteKeyValue(const std::string& pluginId,
+    virtual void DeleteKeyValue(const std::string& storeId,
                                 const std::string& key) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
 
     virtual bool GetKeyValue(std::string& value,
-                             const std::string& pluginId,
+                             const std::string& storeId,
                              const std::string& key) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
 
+    virtual void EnqueueValue(const std::string& queueId,
+                              const std::string& value) ORTHANC_OVERRIDE
+    {
+      throw OrthancException(ErrorCode_InternalError);  // Not supported
+    }
+
+    virtual bool DequeueValue(std::string& value,
+                              const std::string& queueId,
+                              QueueOrigin origin) ORTHANC_OVERRIDE
+    {
+      throw OrthancException(ErrorCode_InternalError);  // Not supported
+    }
+
   };
 
 
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Fri May 09 16:14:42 2025 +0200
@@ -1064,26 +1064,39 @@
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
 
-    virtual void StoreKeyValue(const std::string& pluginId,
+    virtual void StoreKeyValue(const std::string& storeId,
                                const std::string& key,
                                const std::string& value) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
 
-    virtual void DeleteKeyValue(const std::string& pluginId,
+    virtual void DeleteKeyValue(const std::string& storeId,
                                 const std::string& key) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
 
     virtual bool GetKeyValue(std::string& value,
-                             const std::string& pluginId,
+                             const std::string& storeId,
                              const std::string& key) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
 
+    virtual void EnqueueValue(const std::string& queueId,
+                              const std::string& value) ORTHANC_OVERRIDE
+    {
+      throw OrthancException(ErrorCode_InternalError);  // Not supported
+    }
+
+    virtual bool DequeueValue(std::string& value,
+                              const std::string& queueId,
+                              QueueOrigin origin) ORTHANC_OVERRIDE
+    {
+      throw OrthancException(ErrorCode_InternalError);  // Not supported
+    }
+
   };
 
   
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Fri May 09 16:14:42 2025 +0200
@@ -1808,26 +1808,39 @@
       }
     }
 
-    virtual void StoreKeyValue(const std::string& pluginId,
+    virtual void StoreKeyValue(const std::string& storeId,
                                const std::string& key,
                                const std::string& value) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_NotImplemented);  // TODO_ATTACH_CUSTOM_DATA
     }
 
-    virtual void DeleteKeyValue(const std::string& pluginId,
+    virtual void DeleteKeyValue(const std::string& storeId,
                                 const std::string& key) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_NotImplemented);  // TODO_ATTACH_CUSTOM_DATA
     }
 
     virtual bool GetKeyValue(std::string& value,
-                             const std::string& pluginId,
+                             const std::string& storeId,
                              const std::string& key) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_NotImplemented);  // TODO_ATTACH_CUSTOM_DATA
     }
 
+    virtual void EnqueueValue(const std::string& queueId,
+                              const std::string& value) ORTHANC_OVERRIDE
+    {
+      throw OrthancException(ErrorCode_InternalError);  // Not supported
+    }
+
+    virtual bool DequeueValue(std::string& value,
+                              const std::string& queueId,
+                              QueueOrigin origin) ORTHANC_OVERRIDE
+    {
+      throw OrthancException(ErrorCode_InternalError);  // Not supported
+    }
+
   };
 
 
@@ -1919,6 +1932,7 @@
       dbCapabilities_.SetHasExtendedChanges(systemInfo.has_extended_changes());
       dbCapabilities_.SetHasFindSupport(systemInfo.supports_find());
       dbCapabilities_.SetHasKeyValueStore(systemInfo.has_key_value_store());
+      dbCapabilities_.SetHasQueue(systemInfo.has_queue());
     }
 
     open_ = true;
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Fri May 09 16:14:42 2025 +0200
@@ -4720,6 +4720,38 @@
     }
   }
 
+  bool OrthancPlugins::HasQueue()
+  {
+    PImpl::ServerContextReference lock(*pimpl_);
+
+    return lock.GetContext().GetIndex().HasQueue();
+  }
+
+  void OrthancPlugins::ApplyEnqueueValue(const _OrthancPluginEnqueueValue& parameters)
+  {
+    PImpl::ServerContextReference lock(*pimpl_);
+    std::string value(parameters.value, parameters.valueSize);
+
+    lock.GetContext().GetIndex().EnqueueValue(parameters.pluginIdentifier, value);
+  }
+
+  bool OrthancPlugins::ApplyDequeueValue(const _OrthancPluginDequeueValue& parameters)
+  {
+    PImpl::ServerContextReference lock(*pimpl_);
+
+    std::string value;
+
+    if (lock.GetContext().GetIndex().DequeueValue(value, parameters.pluginIdentifier, Plugins::Convert(parameters.origin)))
+    {
+      CopyToMemoryBuffer(*parameters.value, value.size() > 0 ? value.c_str() : NULL, value.size());
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
   void OrthancPlugins::ApplyLoadDicomInstance(const _OrthancPluginLoadDicomInstance& params)
   {
     std::unique_ptr<IDicomInstance> target;
@@ -5844,6 +5876,40 @@
         }
       }
 
+      case _OrthancPluginService_EnqueueValue:
+      {
+        if (!HasQueue())
+        {
+          LOG(ERROR) << "The DB engine does not support Queues";
+          return false;
+        }
+
+        const _OrthancPluginEnqueueValue& p =
+          *reinterpret_cast<const _OrthancPluginEnqueueValue*>(parameters);
+        ApplyEnqueueValue(p);
+        return true;
+      }
+
+      case _OrthancPluginService_DequeueValue:
+      {
+        if (!HasQueue())
+        {
+          LOG(ERROR) << "The DB engine does not support Queues";
+          return false;
+        }
+
+        const _OrthancPluginDequeueValue& p =
+          *reinterpret_cast<const _OrthancPluginDequeueValue*>(parameters);
+        if (ApplyDequeueValue(p))
+        {
+          return true;
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_UnknownResource);
+        }
+      }
+
       default:
         return false;
     }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h	Fri May 09 16:14:42 2025 +0200
@@ -233,6 +233,12 @@
 
     bool ApplyGetKeyValue(const _OrthancPluginGetKeyValue& parameters);
 
+    bool HasQueue();
+
+    void ApplyEnqueueValue(const _OrthancPluginEnqueueValue& parameters);
+
+    bool ApplyDequeueValue(const _OrthancPluginDequeueValue& parameters);
+
     void ComputeHash(_OrthancPluginService service,
                      const void* parameters);
 
--- a/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp	Fri May 09 16:14:42 2025 +0200
@@ -745,5 +745,35 @@
       }
     }
 
+    OrthancPluginQueueOrigin Convert(QueueOrigin origin)
+    {
+      switch (origin)
+      {
+        case QueueOrigin_Front:
+          return OrthancPluginQueueOrigin_Front;
+
+        case QueueOrigin_Back:
+          return OrthancPluginQueueOrigin_Back;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+    QueueOrigin Convert(OrthancPluginQueueOrigin origin)
+    {
+      switch (origin)
+      {
+        case OrthancPluginQueueOrigin_Front:
+          return QueueOrigin_Front;
+
+        case OrthancPluginQueueOrigin_Back:
+          return QueueOrigin_Back;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
+
   }
 }
--- a/OrthancServer/Plugins/Engine/PluginsEnumerations.h	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.h	Fri May 09 16:14:42 2025 +0200
@@ -81,6 +81,10 @@
     OrthancPluginStoreStatus Convert(StoreStatus type);
 
     StoreStatus Convert(OrthancPluginStoreStatus type);
+
+    OrthancPluginQueueOrigin Convert(QueueOrigin type);
+
+    QueueOrigin Convert(OrthancPluginQueueOrigin type);
   }
 }
 
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Fri May 09 16:14:42 2025 +0200
@@ -473,6 +473,8 @@
     _OrthancPluginService_StoreKeyValue = 47,                       /* New in Orthanc 1.12.99 */
     _OrthancPluginService_DeleteKeyValue = 48,                      /* New in Orthanc 1.12.99 */
     _OrthancPluginService_GetKeyValue = 49,                         /* New in Orthanc 1.12.99 */
+    _OrthancPluginService_EnqueueValue = 50,                        /* New in Orthanc 1.12.99 */
+    _OrthancPluginService_DequeueValue = 51,                        /* New in Orthanc 1.12.99 */
 
 
     /* Registration of callbacks */
@@ -1144,10 +1146,25 @@
     OrthancPluginStoreStatus_AlreadyStored = 1,   /*!< The file has already been stored/adopted (only if OverwriteInstances is set to false)*/
     OrthancPluginStoreStatus_Failure = 2,         /*!< The file could not be stored/adopted */
     OrthancPluginStoreStatus_FilteredOut = 3,     /*!< The file has been filtered out by a lua script or a plugin */
-    OrthancPluginStoreStatus_StorageFull = 4      /*!< The storage is full (only if MaximumStorageSize/MaximumPatientCount is set and MaximumStorageMode is Reject)*/
+    OrthancPluginStoreStatus_StorageFull = 4,     /*!< The storage is full (only if MaximumStorageSize/MaximumPatientCount is set and MaximumStorageMode is Reject)*/
+    
+    _OrthancPluginStoreStatus_INTERNAL = 0x7fffffff
   } OrthancPluginStoreStatus;
 
   /**
+   * The supported types of enqueuing
+   **/
+  typedef enum
+  {
+    OrthancPluginQueueOrigin_Front = 0,     /*!< Pop from the front of the queue */
+    OrthancPluginQueueOrigin_Back = 1,     /*!< Pop from the back of the queue */
+    
+    _OrthancPluginQueueOrigin_INTERNAL = 0x7fffffff
+  } OrthancPluginQueueOrigin;
+
+
+
+  /**
    * @brief A 32-bit memory buffer allocated by the core system of Orthanc.
    *
    * A memory buffer allocated by the core system of Orthanc. When the
@@ -9832,7 +9849,10 @@
    * @brief Tell Orthanc to store a key-value in its store.
    *
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-TODO_ATTACH_CUSTOM_DATA TODO TODO
+   * @param pluginIdentifier A unique identifier identifying both the plugin and the store
+   * @param key The key of the value to store (Note: pluginIdentifier + key must be unique)
+   * @param value The value to store
+   * @param valueSize The lenght of the value to store
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStoreKeyValue(
     OrthancPluginContext*         context,
@@ -9860,7 +9880,8 @@
    * @brief Tell Orthanc to delete a key-value from its store.
    *
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-TODO_ATTACH_CUSTOM_DATA TODO TODO
+   * @param pluginIdentifier A unique identifier identifying both the plugin and the store
+   * @param key The key of the value to store (Note: pluginIdentifier + key must be unique)
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDeleteKeyValue(
     OrthancPluginContext*         context,
@@ -9885,7 +9906,9 @@
    * @brief Get the value associated to this key in the key-value store.
    *
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-TODO_ATTACH_CUSTOM_DATA TODO TODO
+   * @param pluginIdentifier A unique identifier identifying both the plugin and the store
+   * @param key The key of the value to retrieve from the store (Note: pluginIdentifier + key must be unique)
+   * @param value The value retrieved from the store
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetKeyValue(
     OrthancPluginContext*         context,
@@ -9901,6 +9924,65 @@
     return context->InvokeService(context, _OrthancPluginService_GetKeyValue, &params);
   }
 
+
+  typedef struct
+  {
+    const char*                   pluginIdentifier;
+    const char*                   value;
+    uint64_t                      valueSize;
+  } _OrthancPluginEnqueueValue;
+
+  /**
+   * @brief Tell Orthanc to store a value in a queue.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pluginIdentifier A unique identifier identifying both the plugin and the queue
+   * @param value The value to store
+   * @param valueSize The lenght of the value to store
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginEnqueueValue(
+    OrthancPluginContext*         context,
+    const char*                   pluginIdentifier, /* in */
+    const char*                   value, /* in */
+    uint64_t                      valueSize /* in */)
+  {
+    _OrthancPluginEnqueueValue params;
+    params.pluginIdentifier = pluginIdentifier;
+    params.value = value;
+    params.valueSize = valueSize;
+
+    return context->InvokeService(context, _OrthancPluginService_EnqueueValue, &params);
+  }
+
+  typedef struct
+  {
+    const char*                   pluginIdentifier;
+    OrthancPluginQueueOrigin      origin;
+    OrthancPluginMemoryBuffer*    value;
+  } _OrthancPluginDequeueValue;
+  
+  /**
+   * @brief Dequeue a value from a queue.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pluginIdentifier A unique identifier identifying both the plugin and the store
+   * @param origin The extremity of the queue the value is dequeue from (back for LIFO or front for FIFO)
+   * @param value The value retrieved from the queue
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDequeueValue(
+    OrthancPluginContext*         context,
+    const char*                   pluginIdentifier, /* in */
+    OrthancPluginQueueOrigin      origin, /* in */
+    OrthancPluginMemoryBuffer*    value /* out */)
+  {
+    _OrthancPluginDequeueValue params;
+    params.pluginIdentifier = pluginIdentifier;
+    params.origin = origin;
+    params.value = value;
+
+    return context->InvokeService(context, _OrthancPluginService_DequeueValue, &params);
+  }
+
 #ifdef  __cplusplus
 }
 #endif
--- a/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Fri May 09 16:14:42 2025 +0200
@@ -95,6 +95,11 @@
   ORDERING_CAST_FLOAT = 2;
 }
 
+enum QueueOrigin {
+  QUEUE_ORIGIN_FRONT = 0;
+  QUEUE_ORIGIN_BACK = 1;
+}
+
 message ServerIndexChange {
   int64         seq = 1;
   int32         change_type = 2;   // opaque "ChangeType" in Orthanc
@@ -168,6 +173,7 @@
     bool supports_find = 8;         // New in Orthanc 1.12.5
     bool has_extended_changes = 9;  // New in Orthanc 1.12.5
     bool has_key_value_store = 10;  // New in Orthanc 1.12.99
+    bool has_queue = 11;            // New in Orthanc 1.12.99
   }
 }
 
@@ -326,6 +332,8 @@
   OPERATION_STORE_KEY_VALUE = 53;             // New in Orthanc 1.12.99
   OPERATION_DELETE_KEY_VALUE = 54;            // New in Orthanc 1.12.99
   OPERATION_GET_KEY_VALUE = 55;               // New in Orthanc 1.12.99
+  OPERATION_ENQUEUE_VALUE = 56;               // New in Orthanc 1.12.99
+  OPERATION_DEQUEUE_VALUE = 57;               // New in Orthanc 1.12.99
 }
 
 message Rollback {
@@ -1011,6 +1019,26 @@
   }
 }
 
+message EnqueueValue {
+  message Request {
+    string plugin_id = 1;
+    string value = 2;
+  }
+
+  message Response {
+  }
+}
+
+message DequeueValue {
+  message Request {
+    string plugin_id = 1;
+    QueueOrigin origin = 2;
+  }
+
+  message Response {
+    string value = 1;
+  }
+}
 
 message TransactionRequest {
   sfixed64              transaction = 1;
@@ -1072,6 +1100,8 @@
   StoreKeyValue.Request                   store_key_value = 153;
   DeleteKeyValue.Request                  delete_key_value = 154;
   GetKeyValue.Request                     get_key_value = 155;
+  EnqueueValue.Request                    enqueue_value = 156;
+  DequeueValue.Request                    dequeue_value = 157;
 }
 
 message TransactionResponse {
@@ -1131,6 +1161,8 @@
   StoreKeyValue.Response                   store_key_value = 153;
   DeleteKeyValue.Response                  delete_key_value = 154;
   GetKeyValue.Response                     get_key_value = 155;
+  EnqueueValue.Request                    enqueue_value = 156;
+  DequeueValue.Request                    dequeue_value = 157;
 }
 
 enum RequestType {
--- a/OrthancServer/Sources/Database/IDatabaseWrapper.h	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h	Fri May 09 16:14:42 2025 +0200
@@ -58,6 +58,7 @@
       bool hasExtendedChanges_;
       bool hasAttachmentCustomDataSupport_;
       bool hasKeyValueStore_;
+      bool hasQueue_;
 
     public:
       Capabilities() :
@@ -70,7 +71,8 @@
         hasFindSupport_(false),
         hasExtendedChanges_(false),
         hasAttachmentCustomDataSupport_(false),
-        hasKeyValueStore_(false)
+        hasKeyValueStore_(false),
+        hasQueue_(false)
       {
       }
 
@@ -173,6 +175,16 @@
       {
         return hasKeyValueStore_;
       }
+
+      void SetHasQueue(bool value)
+      {
+        hasQueue_ = value;
+      }
+
+      bool HasQueue() const
+      {
+        return hasQueue_;
+      }
     };
 
 
@@ -416,18 +428,28 @@
                                       const std::set<ChangeType>& filterType) = 0;
 
       // New in Orthanc 1.12.99
-      virtual void StoreKeyValue(const std::string& pluginId,
+      virtual void StoreKeyValue(const std::string& storeId,
                                  const std::string& key,
                                  const std::string& value) = 0;
 
       // New in Orthanc 1.12.99
-      virtual void DeleteKeyValue(const std::string& pluginId,
+      virtual void DeleteKeyValue(const std::string& storeId,
                                   const std::string& key) = 0;
 
       // New in Orthanc 1.12.99
       virtual bool GetKeyValue(std::string& value,
-                               const std::string& pluginId,
+                               const std::string& storeId,
                                const std::string& key) = 0;
+
+      // New in Orthanc 1.12.99
+      virtual void EnqueueValue(const std::string& queueId,
+                                const std::string& value) = 0;
+
+      // New in Orthanc 1.12.99
+      virtual bool DequeueValue(std::string& value,
+                                const std::string& queueId,
+                                QueueOrigin origin) = 0;
+
     };
 
 
--- a/OrthancServer/Sources/Database/InstallKeyValueStore.sql	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Sources/Database/InstallKeyValueStore.sql	Fri May 09 16:14:42 2025 +0200
@@ -19,11 +19,19 @@
 -- along with this program. If not, see <http://www.gnu.org/licenses/>.
 
 
-CREATE TABLE KeyValueStore(
-       pluginId TEXT NOT NULL,
+CREATE TABLE KeyValueStores(
+       storeId TEXT NOT NULL,
        key TEXT NOT NULL,
        value TEXT NOT NULL,
-       PRIMARY KEY(pluginId, key)  -- Prevents duplicates
+       PRIMARY KEY(storeId, key)  -- Prevents duplicates
        );
 
-CREATE INDEX KeyValueStoreIndex ON KeyValueStore (pluginId, key);
+CREATE INDEX KeyValueStoresIndex ON KeyValueStores (storeId, key);
+
+CREATE TABLE Queues (
+       id INTEGER PRIMARY KEY AUTOINCREMENT,
+       queueId TEXT NOT NULL,
+       value TEXT
+);
+
+CREATE INDEX QueuesIndex ON Queues (queueId, id);
--- a/OrthancServer/Sources/Database/PrepareDatabase.sql	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Sources/Database/PrepareDatabase.sql	Fri May 09 16:14:42 2025 +0200
@@ -160,15 +160,24 @@
 END;
 
 -- new in Orthanc 1.12.99
-CREATE TABLE KeyValueStore(
-       pluginId TEXT NOT NULL,
+CREATE TABLE KeyValueStores(
+       storeId TEXT NOT NULL,
        key TEXT NOT NULL,
        value TEXT NOT NULL,
-       PRIMARY KEY(pluginId, key)  -- Prevents duplicates
+       PRIMARY KEY(storeId, key)  -- Prevents duplicates
        );
 
 -- new in Orthanc 1.12.99
-CREATE INDEX KeyValueStoreIndex ON KeyValueStore (pluginId, key);
+CREATE INDEX KeyValueStoresIndex ON KeyValueStore (storeId, key);
+
+-- new in Orthanc 1.12.99
+CREATE TABLE Queues (
+       id INTEGER PRIMARY KEY AUTOINCREMENT,
+       queueId TEXT NOT NULL,
+       value TEXT
+);
+
+CREATE INDEX QueuesIndex ON Queues (queueId, id);
 
 -- Set the version of the database schema
 -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Fri May 09 16:14:42 2025 +0200
@@ -2103,33 +2103,33 @@
       }
     }
 
-    virtual void StoreKeyValue(const std::string& pluginId,
+    virtual void StoreKeyValue(const std::string& storeId,
                                const std::string& key,
                                const std::string& value) ORTHANC_OVERRIDE
     {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO KeyValueStore (pluginId, key, value) VALUES(?, ?, ?)");
-      s.BindString(0, pluginId);
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO KeyValueStore (storeId, key, value) VALUES(?, ?, ?)");
+      s.BindString(0, storeId);
       s.BindString(1, key);
       s.BindString(2, value);
       s.Run();
     }
 
-    virtual void DeleteKeyValue(const std::string& pluginId,
+    virtual void DeleteKeyValue(const std::string& storeId,
                                 const std::string& key) ORTHANC_OVERRIDE
     {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM KeyValueStore WHERE pluginId = ? AND key = ?");
-      s.BindString(0, pluginId);
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM KeyValueStore WHERE storeId = ? AND key = ?");
+      s.BindString(0, storeId);
       s.BindString(1, key);
       s.Run();
     }
 
     virtual bool GetKeyValue(std::string& value,
-                             const std::string& pluginId,
+                             const std::string& storeId,
                              const std::string& key) ORTHANC_OVERRIDE
     {
       SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                          "SELECT value FROM KeyValueStore WHERE pluginId=? AND key=?");
-      s.BindString(0, pluginId);
+                          "SELECT value FROM KeyValueStore WHERE storeId=? AND key=?");
+      s.BindString(0, storeId);
       s.BindString(1, key);
 
       if (!s.Step())
@@ -2144,6 +2144,58 @@
       }    
     }
 
+    // New in Orthanc 1.12.99
+    virtual void EnqueueValue(const std::string& queueId,
+                              const std::string& value) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "INSERT INTO Queues (queueId, value) VALUES (?, ?)");
+      s.BindString(0, queueId);
+      s.BindString(1, value);
+      s.Run();
+    }
+
+    // New in Orthanc 1.12.99
+    virtual bool DequeueValue(std::string& value,
+                              const std::string& queueId,
+                              QueueOrigin origin) ORTHANC_OVERRIDE
+    {
+      int64_t rowId;
+      std::unique_ptr<SQLite::Statement> s;
+
+      switch (origin)
+      {
+        case QueueOrigin_Front:
+          s.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT id, value FROM KeyValueStore WHERE queueId=? ORDER BY id ASC LIMIT 1"));
+          break;
+        case QueueOrigin_Back:
+          s.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT id, value FROM KeyValueStore WHERE queueId=? ORDER BY id DESC LIMIT 1"));
+          break;
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      s->BindString(0, queueId);
+      if (!s->Step())
+      {
+        // No value found
+        return false;
+      }
+      else
+      {
+        rowId = s->ColumnInt64(0);
+        value = s->ColumnString(1);
+
+        SQLite::Statement s2(db_, SQLITE_FROM_HERE, 
+                            "DELETE FROM Queues WHERE id = ?");
+        s2.BindInt64(0, rowId);
+        s2.Run();
+
+        return true;
+      }    
+    }
+
+
   };
 
 
@@ -2344,6 +2396,7 @@
     dbCapabilities_.SetHasExtendedChanges(true);
     dbCapabilities_.SetHasFindSupport(HasIntegratedFind());
     dbCapabilities_.SetHasKeyValueStore(true);
+    dbCapabilities_.SetHasQueue(true);
     db_.Open(path);
   }
 
@@ -2359,6 +2412,7 @@
     dbCapabilities_.SetHasExtendedChanges(true);
     dbCapabilities_.SetHasFindSupport(HasIntegratedFind());
     dbCapabilities_.SetHasKeyValueStore(true);
+    dbCapabilities_.SetHasQueue(true);
     db_.OpenInMemory();
   }
 
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Fri May 09 16:14:42 2025 +0200
@@ -3206,6 +3206,12 @@
     return db_.GetDatabaseCapabilities().HasKeyValueStore();
   }
 
+  bool StatelessDatabaseOperations::HasQueue()
+  {
+    boost::shared_lock<boost::shared_mutex> lock(mutex_);
+    return db_.GetDatabaseCapabilities().HasQueue();
+  }
+
   void StatelessDatabaseOperations::ExecuteCount(uint64_t& count,
                                                  const FindRequest& request)
   {
@@ -3327,22 +3333,22 @@
     }
   }
 
-  void StatelessDatabaseOperations::StoreKeyValue(const std::string& pluginId,
+  void StatelessDatabaseOperations::StoreKeyValue(const std::string& storeId,
                                                   const std::string& key,
                                                   const std::string& value)
   {
     class Operations : public IReadWriteOperations
     {
     private:
-      const std::string& pluginId_;
+      const std::string& storeId_;
       const std::string& key_;
       const std::string& value_;
 
     public:
-      Operations(const std::string& pluginId,
+      Operations(const std::string& storeId,
                  const std::string& key,
                  const std::string& value) :
-        pluginId_(pluginId),
+        storeId_(storeId),
         key_(key),
         value_(value)
       {
@@ -3350,43 +3356,43 @@
 
       virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE
       {
-        transaction.StoreKeyValue(pluginId_, key_, value_);
+        transaction.StoreKeyValue(storeId_, key_, value_);
       }
     };
 
-    Operations operations(pluginId, key, value);
+    Operations operations(storeId, key, value);
     Apply(operations);
   }
 
-  void StatelessDatabaseOperations::DeleteKeyValue(const std::string& pluginId,
+  void StatelessDatabaseOperations::DeleteKeyValue(const std::string& storeId,
                                                    const std::string& key)
   {
     class Operations : public IReadWriteOperations
     {
     private:
-      const std::string& pluginId_;
+      const std::string& storeId_;
       const std::string& key_;
 
     public:
-      Operations(const std::string& pluginId,
+      Operations(const std::string& storeId,
                  const std::string& key) :
-        pluginId_(pluginId),
+        storeId_(storeId),
         key_(key)
       {
       }
 
       virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE
       {
-        transaction.DeleteKeyValue(pluginId_, key_);
+        transaction.DeleteKeyValue(storeId_, key_);
       }
     };
 
-    Operations operations(pluginId, key);
+    Operations operations(storeId, key);
     Apply(operations);
   }
 
   bool StatelessDatabaseOperations::GetKeyValue(std::string& value,
-                                                const std::string& pluginId,
+                                                const std::string& storeId,
                                                 const std::string& key)
   {
     class Operations : public ReadOnlyOperationsT3<std::string&, const std::string&, const std::string& >
@@ -3410,10 +3416,76 @@
     };
 
     Operations operations;
-    operations.Apply(*this, value, pluginId, key);
+    operations.Apply(*this, value, storeId, key);
 
     return operations.HasFound();
   }
 
+  void StatelessDatabaseOperations::EnqueueValue(const std::string& queueId,
+                                                 const std::string& value)
+  {
+    class Operations : public IReadWriteOperations
+    {
+    private:
+      const std::string& queueId_;
+      const std::string& value_;
+
+    public:
+      Operations(const std::string& queueId,
+                 const std::string& value) :
+        queueId_(queueId),
+        value_(value)
+      {
+      }
+
+      virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE
+      {
+        transaction.EnqueueValue(queueId_, value_);
+      }
+    };
+
+    Operations operations(queueId, value);
+    Apply(operations);
+  }
+
+  bool StatelessDatabaseOperations::DequeueValue(std::string& value,
+                                                 const std::string& queueId,
+                                                 QueueOrigin origin)
+  {
+    class Operations : public IReadWriteOperations
+    {
+    private:
+      const std::string& queueId_;
+      std::string& value_;
+      QueueOrigin origin_;
+      bool found_;
+
+    public:
+      Operations(std::string& value,
+                 const std::string& queueId,
+                 QueueOrigin origin) :
+        queueId_(queueId),
+        value_(value),
+        origin_(origin),
+        found_(false)
+      {
+      }
+
+      bool HasFound()
+      {
+        return found_;
+      }
+
+      virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE
+      {
+        found_ = transaction.DequeueValue(value_, queueId_, origin_);
+      }
+    };
+
+    Operations operations(value, queueId, origin);
+    Apply(operations);
+
+    return operations.HasFound();
+  }
 
 }
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Fri May 09 16:14:42 2025 +0200
@@ -295,10 +295,10 @@
       }
 
       bool GetKeyValue(std::string& value,
-                       const std::string& pluginId,
+                       const std::string& storeId,
                        const std::string& key)
       {
-        return transaction_.GetKeyValue(value, pluginId, key);
+        return transaction_.GetKeyValue(value, storeId, key);
       }
 
     };
@@ -437,19 +437,33 @@
         transaction_.RemoveLabel(id, label);
       }
 
-      void StoreKeyValue(const std::string& pluginId,
+      void StoreKeyValue(const std::string& storeId,
                          const std::string& key,
                          const std::string& value)
       {
-        transaction_.StoreKeyValue(pluginId, key, value);
+        transaction_.StoreKeyValue(storeId, key, value);
+      }
+
+      void DeleteKeyValue(const std::string& storeId,
+                          const std::string& key)
+      {
+        transaction_.DeleteKeyValue(storeId, key);
       }
 
-      void DeleteKeyValue(const std::string& pluginId,
-                          const std::string& key)
+      void EnqueueValue(const std::string& queueId,
+                        const std::string& value)
       {
-        transaction_.DeleteKeyValue(pluginId, key);
+        transaction_.EnqueueValue(queueId, value);
       }
 
+      bool DequeueValue(std::string& value,
+                        const std::string& queueId,
+                        QueueOrigin origin)
+      {
+        return transaction_.DequeueValue(value, queueId, origin);
+      }
+
+
     };
 
 
@@ -568,6 +582,8 @@
     bool HasFindSupport();
 
     bool HasKeyValueStore();
+
+    bool HasQueue();
     
     void GetExportedResources(Json::Value& target,
                               int64_t since,
@@ -749,16 +765,23 @@
     void ExecuteCount(uint64_t& count,
                       const FindRequest& request);
 
-    void StoreKeyValue(const std::string& pluginId,
+    void StoreKeyValue(const std::string& storeId,
                        const std::string& key,
                        const std::string& value);
 
-    void DeleteKeyValue(const std::string& pluginId,
+    void DeleteKeyValue(const std::string& storeId,
                         const std::string& key);
 
     bool GetKeyValue(std::string& value,
-                     const std::string& pluginId,
+                     const std::string& storeId,
                      const std::string& key);
 
+    void EnqueueValue(const std::string& queueId,
+                      const std::string& value);
+
+    bool DequeueValue(std::string& value,
+                      const std::string& queueId,
+                      QueueOrigin origin);
+
   };
 }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Fri May 09 16:14:42 2025 +0200
@@ -96,6 +96,7 @@
     static const char* const CAPABILITIES = "Capabilities";
     static const char* const HAS_EXTENDED_CHANGES = "HasExtendedChanges";
     static const char* const HAS_KEY_VALUE_STORE = "HasKeyValueStore";
+    static const char* const HAS_QUEUE = "HasQueue";
     static const char* const HAS_EXTENDED_FIND = "HasExtendedFind";
     static const char* const READ_ONLY = "ReadOnly";
 
@@ -213,6 +214,7 @@
     result[CAPABILITIES][HAS_EXTENDED_CHANGES] = OrthancRestApi::GetIndex(call).HasExtendedChanges();
     result[CAPABILITIES][HAS_EXTENDED_FIND] = OrthancRestApi::GetIndex(call).HasFindSupport();
     result[CAPABILITIES][HAS_KEY_VALUE_STORE] = OrthancRestApi::GetIndex(call).HasKeyValueStore();
+    result[CAPABILITIES][HAS_QUEUE] = OrthancRestApi::GetIndex(call).HasQueue();
     
     call.GetOutput().AnswerJson(result);
   }
--- a/OrthancServer/Sources/ServerEnumerations.h	Fri May 09 09:16:09 2025 +0200
+++ b/OrthancServer/Sources/ServerEnumerations.h	Fri May 09 16:14:42 2025 +0200
@@ -259,6 +259,11 @@
     Warnings_007_MissingRequestedTagsNotReadFromDisk       // new in Orthanc 1.12.5
   };
 
+  enum QueueOrigin
+  {
+    QueueOrigin_Front,
+    QueueOrigin_Back
+  };
 
   void InitializeServerEnumerations();