changeset 2285:cad7215828c2 refactoring

using IOracleCallback in oracles
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 23 Sep 2026 09:41:06 +0200
parents 5150a7999840
children 1d75fb03e228
files OrthancStone/Sources/Oracle/GenericOracleRunner.cpp OrthancStone/Sources/Oracle/GenericOracleRunner.h OrthancStone/Sources/Oracle/GetOrthancImageCommand.cpp OrthancStone/Sources/Oracle/GetOrthancImageCommand.h OrthancStone/Sources/Oracle/GetOrthancWebViewerJpegCommand.cpp OrthancStone/Sources/Oracle/GetOrthancWebViewerJpegCommand.h OrthancStone/Sources/Oracle/OracleCallback.cpp OrthancStone/Sources/Oracle/OracleCallback.h OrthancStone/Sources/Oracle/ThreadedOracle.cpp OrthancStone/Sources/Oracle/ThreadedOracle.h OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h
diffstat 12 files changed, 188 insertions(+), 172 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancStone/Sources/Oracle/GenericOracleRunner.cpp	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Oracle/GenericOracleRunner.cpp	Wed Sep 23 09:41:06 2026 +0200
@@ -138,16 +138,14 @@
   }
 
 
-  static void RunInternal(boost::weak_ptr<IObserver> receiver,
-                          IMessageEmitter& emitter,
+  static void RunInternal(IOracleCallback& callback,
                           const HttpCommand& command)
   {
     std::string answer;
     Orthanc::HttpClient::HttpHeaders answerHeaders;
     RunHttpCommand(answer, answerHeaders, command);
     
-    HttpCommand::SuccessMessage message(command, answerHeaders, answer);
-    emitter.EmitMessage(receiver, message);
+    callback.NotifySuccess(new HttpCommand::SuccessMessage(command, answerHeaders, answer));
   }
 
   
@@ -175,8 +173,7 @@
   }
 
   
-  static void RunInternal(boost::weak_ptr<IObserver> receiver,
-                          IMessageEmitter& emitter,
+  static void RunInternal(IOracleCallback& callback,
                           const Orthanc::WebServiceParameters& orthanc,
                           const OrthancRestApiCommand& command)
   {
@@ -184,13 +181,11 @@
     Orthanc::HttpClient::HttpHeaders answerHeaders;
     RunOrthancRestApiCommand(answer, answerHeaders, orthanc, command);
 
-    OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
-    emitter.EmitMessage(receiver, message);
+    callback.NotifySuccess(new OrthancRestApiCommand::SuccessMessage(command, answerHeaders, answer));
   }
 
 
-  static void RunInternal(boost::weak_ptr<IObserver> receiver,
-                          IMessageEmitter& emitter,
+  static void RunInternal(IOracleCallback& callback,
                           const Orthanc::WebServiceParameters& orthanc,
                           const GetOrthancImageCommand& command)
   {
@@ -206,12 +201,11 @@
 
     DecodeAnswer(answer, answerHeaders);
 
-    command.ProcessHttpAnswer(receiver, emitter, answer, answerHeaders);
+    command.ProcessHttpAnswer(callback, answer, answerHeaders);
   }
 
 
-  static void RunInternal(boost::weak_ptr<IObserver> receiver,
-                          IMessageEmitter& emitter,
+  static void RunInternal(IOracleCallback& callback,
                           const Orthanc::WebServiceParameters& orthanc,
                           const GetOrthancWebViewerJpegCommand& command)
   {
@@ -227,7 +221,7 @@
 
     DecodeAnswer(answer, answerHeaders);
 
-    command.ProcessHttpAnswer(receiver, emitter, answer);
+    command.ProcessHttpAnswer(callback, answer);
   }
 
 
@@ -251,8 +245,7 @@
   }
 
 
-  static void RunInternal(boost::weak_ptr<IObserver> receiver,
-                          IMessageEmitter& emitter,
+  static void RunInternal(IOracleCallback& callback,
                           const std::string& root,
                           const ReadFileCommand& command)
   {
@@ -262,8 +255,7 @@
     std::string content;
     Orthanc::SystemToolbox::ReadFile(content, path, true /* log */);
 
-    ReadFileCommand::SuccessMessage message(command, content);
-    emitter.EmitMessage(receiver, message);
+    callback.NotifySuccess(new ReadFileCommand::SuccessMessage(command, content));
   }
 
 
@@ -335,8 +327,7 @@
   }
 
   
-  static void RunInternal(boost::weak_ptr<IObserver> receiver,
-                          IMessageEmitter& emitter,
+  static void RunInternal(IOracleCallback& callback,
                           boost::shared_ptr<ParsedDicomCache> cache,
                           const std::string& root,
                           const ParseDicomFromFileCommand& command)
@@ -351,9 +342,8 @@
            reader.HasPixelData()))
       {
         // Reuse the DICOM file from the cache
-        ParseDicomSuccessMessage message(command, command.GetSource(), reader.GetDicom(),
-                                         reader.GetFileSize(), reader.HasPixelData());
-        emitter.EmitMessage(receiver, message);
+        callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), reader.GetDicom(),
+                                                            reader.GetFileSize(), reader.HasPixelData()));
         return;
       }
     }
@@ -367,12 +357,8 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
     }
     
-    {
-      ParseDicomSuccessMessage message
-        (command, command.GetSource(), *parsed,
-         static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
-      emitter.EmitMessage(receiver, message);
-    }
+    callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), *parsed,
+                                                        static_cast<size_t>(fileSize), command.IsPixelDataIncluded()));
 
     if (cache)
     {
@@ -388,8 +374,7 @@
   }
 
   
-  static void RunInternal(boost::weak_ptr<IObserver> receiver,
-                          IMessageEmitter& emitter,
+  static void RunInternal(IOracleCallback& callback,
                           boost::shared_ptr<ParsedDicomCache> cache,
                           const Orthanc::WebServiceParameters& orthanc,
                           const ParseDicomFromWadoCommand& command)
@@ -401,9 +386,8 @@
           reader.HasPixelData())
       {
         // Reuse the DICOM file from the cache
-        ParseDicomSuccessMessage message(command, command.GetSource(), reader.GetDicom(),
-                                         reader.GetFileSize(), reader.HasPixelData());
-        emitter.EmitMessage(receiver, message);
+        callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), reader.GetDicom(),
+                                                            reader.GetFileSize(), reader.HasPixelData()));
         return;
       }
     }
@@ -429,11 +413,8 @@
     size_t fileSize;
     std::unique_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, answerHeaders));
 
-    {
-      ParseDicomSuccessMessage message(command, command.GetSource(), *parsed, fileSize,
-                                       true /* pixel data always is included in WADO-RS */);
-      emitter.EmitMessage(receiver, message);
-    }
+    callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), *parsed, fileSize,
+                                                        true /* pixel data always is included in WADO-RS */));
 
     if (cache)
     {
@@ -444,57 +425,55 @@
 #endif
 
 
-  void GenericOracleRunner::Run(boost::weak_ptr<IObserver> receiver,
-                                IMessageEmitter& emitter,
-                                const IOracleCommand& command)
+  void GenericOracleRunner::Run(IOracleCallback& callback)
   {
     Orthanc::ErrorCode error = Orthanc::ErrorCode_Success;
     
     try
     {
-      switch (command.GetType())
+      switch (callback.GetCommand().GetType())
       {
         case IOracleCommand::Type_Sleep:
           throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType,
                                           "Sleep command cannot be executed by the runner");
 
         case IOracleCommand::Type_Http:
-          RunInternal(receiver, emitter, dynamic_cast<const HttpCommand&>(command));
+          RunInternal(callback, dynamic_cast<const HttpCommand&>(callback.GetCommand()));
           break;
 
         case IOracleCommand::Type_OrthancRestApi:
-          RunInternal(receiver, emitter, orthanc_,
-                      dynamic_cast<const OrthancRestApiCommand&>(command));
+          RunInternal(callback, orthanc_,
+                      dynamic_cast<const OrthancRestApiCommand&>(callback.GetCommand()));
           break;
 
         case IOracleCommand::Type_GetOrthancImage:
-          RunInternal(receiver, emitter, orthanc_,
-                      dynamic_cast<const GetOrthancImageCommand&>(command));
+          RunInternal(callback, orthanc_,
+                      dynamic_cast<const GetOrthancImageCommand&>(callback.GetCommand()));
           break;
 
         case IOracleCommand::Type_GetOrthancWebViewerJpeg:
-          RunInternal(receiver, emitter, orthanc_,
-                      dynamic_cast<const GetOrthancWebViewerJpegCommand&>(command));
+          RunInternal(callback, orthanc_,
+                      dynamic_cast<const GetOrthancWebViewerJpegCommand&>(callback.GetCommand()));
           break;
 
         case IOracleCommand::Type_ReadFile:
-          RunInternal(receiver, emitter, rootDirectory_,
-                      dynamic_cast<const ReadFileCommand&>(command));
+          RunInternal(callback, rootDirectory_,
+                      dynamic_cast<const ReadFileCommand&>(callback.GetCommand()));
           break;
 
         case IOracleCommand::Type_ParseDicomFromFile:
         case IOracleCommand::Type_ParseDicomFromWado:
 #if ORTHANC_ENABLE_DCMTK == 1
-          switch (command.GetType())
+          switch (callback.GetCommand().GetType())
           {
             case IOracleCommand::Type_ParseDicomFromFile:
-              RunInternal(receiver, emitter, dicomCache_, rootDirectory_,
-                          dynamic_cast<const ParseDicomFromFileCommand&>(command));
+              RunInternal(callback, dicomCache_, rootDirectory_,
+                          dynamic_cast<const ParseDicomFromFileCommand&>(callback.GetCommand()));
               break;
 
             case IOracleCommand::Type_ParseDicomFromWado:
-              RunInternal(receiver, emitter, dicomCache_, orthanc_,
-                          dynamic_cast<const ParseDicomFromWadoCommand&>(command));
+              RunInternal(callback, dicomCache_, orthanc_,
+                          dynamic_cast<const ParseDicomFromWadoCommand&>(callback.GetCommand()));
               break;
 
             default:
@@ -523,8 +502,7 @@
 
     if (error != Orthanc::ErrorCode_Success)
     {
-      OracleCommandExceptionMessage message(command, error);
-      emitter.EmitMessage(receiver, message);
+      callback.NotifyError(Orthanc::OrthancException(error));
     }
   }
 }
--- a/OrthancStone/Sources/Oracle/GenericOracleRunner.h	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Oracle/GenericOracleRunner.h	Wed Sep 23 09:41:06 2026 +0200
@@ -34,7 +34,7 @@
 #endif
 
 #include "IOracleCommand.h"
-#include "../Messages/IMessageEmitter.h"
+#include "OracleCallback.h"
 
 #include <Enumerations.h>  // For ORTHANC_OVERRIDE
 #include <WebServiceParameters.h>
@@ -84,8 +84,6 @@
     }
 #endif
 
-    void Run(boost::weak_ptr<IObserver> receiver,
-             IMessageEmitter& emitter,
-             const IOracleCommand& command);
+    void Run(IOracleCallback& callback);
   };
 }
--- a/OrthancStone/Sources/Oracle/GetOrthancImageCommand.cpp	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Oracle/GetOrthancImageCommand.cpp	Wed Sep 23 09:41:06 2026 +0200
@@ -82,8 +82,7 @@
   }
 
 
-  void GetOrthancImageCommand::ProcessHttpAnswer(boost::weak_ptr<IObserver> receiver,
-                                                 IMessageEmitter& emitter,
+  void GetOrthancImageCommand::ProcessHttpAnswer(IOracleCallback& callback,
                                                  const std::string& answer,
                                                  const HttpHeaders& answerHeaders) const
   {
@@ -186,7 +185,6 @@
     //}
 
 
-    SuccessMessage message(*this, *image, contentType);
-    emitter.EmitMessage(receiver, message);
+    callback.NotifySuccess(new SuccessMessage(*this, *image, contentType));
   }
 }
--- a/OrthancStone/Sources/Oracle/GetOrthancImageCommand.h	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Oracle/GetOrthancImageCommand.h	Wed Sep 23 09:41:06 2026 +0200
@@ -26,6 +26,8 @@
 #include "../Messages/IMessageEmitter.h"
 #include "OracleCommandBase.h"
 
+#include "OracleCallback.h"   // TODO Refactoring
+
 #include <Images/ImageAccessor.h>
 
 #include <map>
@@ -136,8 +138,7 @@
       return timeout_;
     }
 
-    void ProcessHttpAnswer(boost::weak_ptr<IObserver> receiver,
-                           IMessageEmitter& emitter,
+    void ProcessHttpAnswer(IOracleCallback& callback,
                            const std::string& answer,
                            const HttpHeaders& answerHeaders) const;
   };
--- a/OrthancStone/Sources/Oracle/GetOrthancWebViewerJpegCommand.cpp	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Oracle/GetOrthancWebViewerJpegCommand.cpp	Wed Sep 23 09:41:06 2026 +0200
@@ -65,8 +65,7 @@
   }
 
 
-  void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(boost::weak_ptr<IObserver> receiver,
-                                                         IMessageEmitter& emitter,
+  void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(IOracleCallback& callback,
                                                          const std::string& answer) const
   {
     // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()"
@@ -134,8 +133,7 @@
       }
       else
       {
-        SuccessMessage message(*this, *reader);
-        emitter.EmitMessage(receiver, message);
+        callback.NotifySuccess(new SuccessMessage(*this, *reader));
         return;
       }
     }
@@ -153,8 +151,7 @@
       }
       else
       {
-        SuccessMessage message(*this, *reader);
-        emitter.EmitMessage(receiver, message);
+        callback.NotifySuccess(new SuccessMessage(*this, *reader));
         return;
       }
     }
@@ -196,7 +193,6 @@
       Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
     }
 
-    SuccessMessage message(*this, *image);
-    emitter.EmitMessage(receiver, message);
+    callback.NotifySuccess(new SuccessMessage(*this, *image));
   }
 }
--- a/OrthancStone/Sources/Oracle/GetOrthancWebViewerJpegCommand.h	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Oracle/GetOrthancWebViewerJpegCommand.h	Wed Sep 23 09:41:06 2026 +0200
@@ -26,6 +26,8 @@
 #include "../Messages/IMessageEmitter.h"
 #include "OracleCommandBase.h"
 
+#include "OracleCallback.h"   // TODO Refactoring
+
 #include <Images/ImageAccessor.h>
 
 #include <map>
@@ -149,8 +151,7 @@
 
     std::string GetUri() const;
 
-    void ProcessHttpAnswer(boost::weak_ptr<IObserver> receiver,
-                           IMessageEmitter& emitter,
+    void ProcessHttpAnswer(IOracleCallback& callback,
                            const std::string& answer) const;
   };
 }
--- a/OrthancStone/Sources/Oracle/OracleCallback.cpp	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Oracle/OracleCallback.cpp	Wed Sep 23 09:41:06 2026 +0200
@@ -23,6 +23,8 @@
 
 #include "OracleCallback.h"
 
+#include "OracleCommandExceptionMessage.h"
+
 
 namespace OrthancStone
 {
@@ -69,7 +71,42 @@
   }
 
 
-  const IOracleCommand& OracleCallback::GetCommand() const
+  OldOracleCallback::OldOracleCallback(IOracleCommand* command,
+                                       boost::weak_ptr<IObserver> receiver,
+                                       IMessageEmitter& emitter) :
+    command_(command),
+    receiver_(receiver),
+    emitter_(emitter)
+  {
+    if (command == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+
+
+  void OldOracleCallback::NotifySuccess(IMessage* message /* takes ownership */)
+  {
+    std::unique_ptr<IMessage> protection(message);
+    emitter_.EmitMessage(receiver_, *protection);
+  }
+
+
+  void OldOracleCallback::NotifyError(const Orthanc::OrthancException& error)
+  {
+    if (command_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      OracleCommandExceptionMessage message(*command_, error);
+      emitter_.EmitMessage(receiver_, message);
+    }
+  }
+
+
+  const IOracleCommand& OldOracleCallback::GetCommand() const
   {
     if (command_.get() == NULL)
     {
--- a/OrthancStone/Sources/Oracle/OracleCallback.h	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Oracle/OracleCallback.h	Wed Sep 23 09:41:06 2026 +0200
@@ -25,9 +25,23 @@
 
 #include "IEnvironment.h"
 
+#include "../Messages/IMessageEmitter.h"   // TODO Refactoring - Remove this
+
+
 namespace OrthancStone
 {
-  class OracleCallback : public boost::noncopyable
+  class IOracleCallback : public Orthanc::IDynamicObject  // TODO Refactoring - Remove this
+  {
+  public:
+    virtual void NotifySuccess(IMessage* result) = 0;
+
+    virtual void NotifyError(const Orthanc::OrthancException& error) = 0;
+
+    virtual const IOracleCommand& GetCommand() const = 0;
+  };
+
+
+  class OracleCallback : public IOracleCallback
   {
   private:
     IEnvironment&                    environment_;
@@ -39,10 +53,38 @@
                    const boost::shared_ptr<IOracleClient>& client,
                    IOracleCommand* command /* takes ownership */);
 
-    void NotifySuccess(IMessage* result);
+    virtual void NotifySuccess(IMessage* result) ORTHANC_OVERRIDE;
+
+    virtual void NotifyError(const Orthanc::OrthancException& error) ORTHANC_OVERRIDE;
+
+    virtual const IOracleCommand& GetCommand() const ORTHANC_OVERRIDE  // TODO Refactoring - Remove this
+    {
+      return *command_;
+    }
+  };
+
 
-    void NotifyError(const Orthanc::OrthancException& error);
+  class OldOracleCallback : public IOracleCallback  // TODO Refactoring - Remove this
+  {
+  private:
+    std::unique_ptr<IOracleCommand>  command_;
+    boost::weak_ptr<IObserver>       receiver_;
+    IMessageEmitter&                 emitter_;
 
-    const IOracleCommand& GetCommand() const;  // TODO Refactoring - Remove this
+  public:
+    OldOracleCallback(IOracleCommand* command /* takes ownership */,
+                      boost::weak_ptr<IObserver> receiver,
+                      IMessageEmitter& emitter);
+
+    virtual void NotifySuccess(IMessage* message /* takes ownership */) ORTHANC_OVERRIDE;
+
+    virtual void NotifyError(const Orthanc::OrthancException& error) ORTHANC_OVERRIDE;
+
+    virtual const IOracleCommand& GetCommand() const ORTHANC_OVERRIDE;
+
+    const boost::weak_ptr<IObserver>& GetReceiver() const
+    {
+      return receiver_;
+    }
   };
 }
--- a/OrthancStone/Sources/Oracle/ThreadedOracle.cpp	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Oracle/ThreadedOracle.cpp	Wed Sep 23 09:41:06 2026 +0200
@@ -31,60 +31,31 @@
 
 namespace OrthancStone
 {
-  class ThreadedOracle::Item : public Orthanc::IDynamicObject
-  {
-  private:
-    boost::weak_ptr<IObserver>      receiver_;
-    std::unique_ptr<IOracleCommand>   command_;
-
-  public:
-    Item(boost::weak_ptr<IObserver> receiver,
-         IOracleCommand* command) :
-      receiver_(receiver),
-      command_(command)
-    {
-      if (command == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-    }
-
-    boost::weak_ptr<IObserver> GetReceiver()
-    {
-      return receiver_;
-    }
-
-    IOracleCommand& GetCommand()
-    {
-      assert(command_.get() != NULL);
-      return *command_;
-    }
-  };
-
-
   class ThreadedOracle::SleepingCommands : public boost::noncopyable
   {
   private:
     class Item
     {
     private:
-      boost::weak_ptr<IObserver>         receiver_;
-      std::unique_ptr<SleepOracleCommand>  command_;
-      boost::posix_time::ptime           expiration_;
+      std::unique_ptr<OldOracleCallback>  callback_;
+      boost::posix_time::ptime            expiration_;
+
+      const SleepOracleCommand& GetCommand() const
+      {
+        return dynamic_cast<const SleepOracleCommand&>(callback_->GetCommand());
+      }
 
     public:
-      Item(boost::weak_ptr<IObserver> receiver,
-           SleepOracleCommand* command) :
-        receiver_(receiver),
-        command_(command)
+      explicit Item(OldOracleCallback* callback) :
+        callback_(callback)
       {
-        if (command == NULL)
+        if (callback == NULL)
         {
           throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
         }
 
         expiration_ = (boost::posix_time::microsec_clock::local_time() + 
-                       boost::posix_time::milliseconds(command_->GetDelay()));
+                       boost::posix_time::milliseconds(GetCommand().GetDelay()));
       }
 
       const boost::posix_time::ptime& GetExpirationTime() const
@@ -94,10 +65,7 @@
 
       void Awake(IMessageEmitter& emitter)
       {
-        assert(command_.get() != NULL);
-
-        SleepOracleCommand::TimeoutMessage message(*command_);
-        emitter.EmitMessage(receiver_, message);
+        callback_->NotifySuccess(new SleepOracleCommand::TimeoutMessage(GetCommand()));
       }
     };
 
@@ -118,12 +86,10 @@
       }
     }
 
-    void Add(boost::weak_ptr<IObserver> receiver,
-             SleepOracleCommand* command)   // Takes ownership
+    void Add(OldOracleCallback* callback /* takes ownership */)
     {
       boost::mutex::scoped_lock lock(mutex_);
-
-      content_.push_back(new Item(receiver, command));
+      content_.push_back(new Item(callback));
     }
 
     void AwakeExpired(IMessageEmitter& emitter)
@@ -161,20 +127,11 @@
 
     if (object.get() != NULL)
     {
-      Item& item = dynamic_cast<Item&>(*object);
+      std::unique_ptr<OldOracleCallback> item(dynamic_cast<OldOracleCallback*>(object.release()));
 
-      if (item.GetCommand().GetType() == IOracleCommand::Type_Sleep)
+      if (item->GetCommand().GetType() == IOracleCommand::Type_Sleep)
       {
-        SleepOracleCommand& command = dynamic_cast<SleepOracleCommand&>(item.GetCommand());
-          
-        std::unique_ptr<SleepOracleCommand> copy(new SleepOracleCommand(command.GetDelay()));
-          
-        if (command.HasPayload())
-        {
-          copy->AcquirePayload(command.ReleasePayload());
-        }
-          
-        sleepingCommands_->Add(item.GetReceiver(), copy.release());
+        sleepingCommands_->Add(item.release());
       }
       else
       {
@@ -193,7 +150,7 @@
 #endif
         }
 
-        runner.Run(item.GetReceiver(), emitter_, item.GetCommand());
+        runner.Run(*item);
       }
     }
   }
@@ -415,7 +372,7 @@
   bool ThreadedOracle::Schedule(boost::shared_ptr<IObserver> receiver,
                                 IOracleCommand* command)
   {
-    std::unique_ptr<Item> item(new Item(receiver, command));
+    std::unique_ptr<OldOracleCallback> item(new OldOracleCallback(command, receiver, emitter_));
 
     {
       boost::mutex::scoped_lock lock(mutex_);
--- a/OrthancStone/Sources/Oracle/ThreadedOracle.h	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Oracle/ThreadedOracle.h	Wed Sep 23 09:41:06 2026 +0200
@@ -64,7 +64,6 @@
       State_Stopped
     };
 
-    class Item;
     class SleepingCommands;
 
     IMessageEmitter&                     emitter_;
--- a/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp	Wed Sep 23 09:41:06 2026 +0200
@@ -134,16 +134,29 @@
 
     void EmitException(const Orthanc::OrthancException& exception)
     {
-      assert(command_.get() != NULL);
-      OracleCommandExceptionMessage message(*command_, exception);
-      oracle_.EmitMessage(receiver_, message);
+      if (command_.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        OracleCommandExceptionMessage message(*command_, exception);
+        oracle_.EmitMessage(receiver_, message);
+      }
     }
 
     void ProcessFetchResult(const std::string& answer,
                             const HttpHeaders& headers)
     {
-      assert(command_.get() != NULL);
-      oracle_.ProcessFetchResult(receiver_, answer, headers, *command_);
+      if (command_.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        OldOracleCallback callback(command_.release(), receiver_, oracle_);
+        oracle_.ProcessFetchResult(callback, answer, headers);
+      }
     }
 
     static void SuccessCallback(emscripten_fetch_t *fetch)
@@ -472,54 +485,49 @@
   };
 
 
-  void WebAssemblyOracle::ProcessFetchResult(boost::weak_ptr<IObserver>& receiver,
+  void WebAssemblyOracle::ProcessFetchResult(IOracleCallback& callback,
                                              const std::string& answer,
-                                             const HttpHeaders& headers,
-                                             const IOracleCommand& command)
+                                             const HttpHeaders& headers)
   {
-    switch (command.GetType())
+    switch (callback.GetCommand().GetType())
     {
       case IOracleCommand::Type_Http:
       {
-        HttpCommand::SuccessMessage message(dynamic_cast<const HttpCommand&>(command), headers, answer);
-        EmitMessage(receiver, message);
+        callback.NotifySuccess(new HttpCommand::SuccessMessage(
+                                 dynamic_cast<const HttpCommand&>(callback.GetCommand()), headers, answer));
         break;
       }
 
       case IOracleCommand::Type_OrthancRestApi:
       {
         LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call EmitMessage(message);";
-        OrthancRestApiCommand::SuccessMessage message
-          (dynamic_cast<const OrthancRestApiCommand&>(command), headers, answer);
-        EmitMessage(receiver, message);
+        callback.NotifySuccess(new OrthancRestApiCommand::SuccessMessage(
+                                 dynamic_cast<const OrthancRestApiCommand&>(callback.GetCommand()), headers, answer));
         break;
       }
 
       case IOracleCommand::Type_GetOrthancImage:
       {
-        dynamic_cast<const GetOrthancImageCommand&>(command).ProcessHttpAnswer(receiver, *this, answer, headers);
+        dynamic_cast<const GetOrthancImageCommand&>(callback.GetCommand()).ProcessHttpAnswer(callback, answer, headers);
         break;
       }
 
       case IOracleCommand::Type_GetOrthancWebViewerJpeg:
       {
-        dynamic_cast<const GetOrthancWebViewerJpegCommand&>(command).ProcessHttpAnswer(receiver, *this, answer);
+        dynamic_cast<const GetOrthancWebViewerJpegCommand&>(callback.GetCommand()).ProcessHttpAnswer(callback, answer);
         break;
       }
 
       case IOracleCommand::Type_ParseDicomFromWado:
       {
 #if ORTHANC_ENABLE_DCMTK == 1
-        const ParseDicomFromWadoCommand& c = dynamic_cast<const ParseDicomFromWadoCommand&>(command);
+        const ParseDicomFromWadoCommand& c = dynamic_cast<const ParseDicomFromWadoCommand&>(callback.GetCommand());
               
         size_t fileSize;
         std::unique_ptr<Orthanc::ParsedDicomFile> dicom
           (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers));
 
-        {
-          ParseDicomSuccessMessage message(c, c.GetSource(), *dicom, fileSize, true);
-          EmitMessage(receiver, message);
-        }
+        callback.NotifySuccess(new ParseDicomSuccessMessage(c, c.GetSource(), *dicom, fileSize, true));
 
         if (dicomCache_.get())
         {
@@ -534,7 +542,7 @@
 
       default:
         LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): "
-                   << command.GetType();
+                   << callback.GetCommand().GetType();
         throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }
   }
--- a/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h	Tue Sep 22 19:03:59 2026 +0200
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h	Wed Sep 23 09:41:06 2026 +0200
@@ -33,6 +33,8 @@
 #  error This file can only compiled for WebAssembly
 #endif
 
+#include "../../Oracle/OracleCallback.h"   // TODO Refactoring
+
 #include "../../Messages/IObservable.h"
 #include "../../Messages/IMessageEmitter.h"
 #include "../../Oracle/IEnvironment.h"
@@ -93,10 +95,9 @@
     std::unique_ptr<ParsedDicomCache>  dicomCache_;
 #endif
 
-    void ProcessFetchResult(boost::weak_ptr<IObserver>& receiver,
+    void ProcessFetchResult(IOracleCallback& callback,
                             const std::string& answer,
-                            const HttpHeaders& headers,
-                            const IOracleCommand& command);
+                            const HttpHeaders& headers);
 
   public:
     WebAssemblyOracle() :