changeset 2305:62b1a4463d11 refactoring

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 28 Sep 2026 14:45:20 +0200
parents cb959d9a539a
children 4a37b48057af
files OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake OrthancStone/Sources/Loaders/GenericLoadersContext.cpp OrthancStone/Sources/Oracle/GenericOracleRunner.cpp OrthancStone/Sources/Oracle/GenericOracleRunner.h OrthancStone/Sources/Oracle/ThreadedOracle.cpp OrthancStone/Sources/Oracle/ThreadedOracle.h OrthancStone/Sources/Platforms/Native/GenericOracleRunner.cpp OrthancStone/Sources/Platforms/Native/GenericOracleRunner.h OrthancStone/Sources/Platforms/Native/ThreadedOracle.cpp OrthancStone/Sources/Platforms/Native/ThreadedOracle.h OrthancStone/Sources/StoneApplication.cpp
diffstat 11 files changed, 917 insertions(+), 917 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Sun Sep 27 12:16:39 2026 +0200
+++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Mon Sep 28 14:45:20 2026 +0200
@@ -220,8 +220,8 @@
 if (NOT ORTHANC_SANDBOXED AND ENABLE_THREADS AND ENABLE_WEB_CLIENT)
   list(APPEND ORTHANC_STONE_SOURCES
     ${ORTHANC_STONE_ROOT}/Loaders/GenericLoadersContext.cpp
-    ${ORTHANC_STONE_ROOT}/Oracle/GenericOracleRunner.cpp
-    ${ORTHANC_STONE_ROOT}/Oracle/ThreadedOracle.cpp
+    ${ORTHANC_STONE_ROOT}/Platforms/Native/GenericOracleRunner.cpp
+    ${ORTHANC_STONE_ROOT}/Platforms/Native/ThreadedOracle.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Native/NativeEnvironment.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Native/RunnableThread.cpp
     )
--- a/OrthancStone/Sources/Loaders/GenericLoadersContext.cpp	Sun Sep 27 12:16:39 2026 +0200
+++ b/OrthancStone/Sources/Loaders/GenericLoadersContext.cpp	Mon Sep 28 14:45:20 2026 +0200
@@ -23,7 +23,7 @@
 
 #include "GenericLoadersContext.h"
 
-#include "../Oracle/ThreadedOracle.h"
+#include "../Platforms/Native/ThreadedOracle.h"
 #include "../StoneApplication.h"
 
 #include <boost/thread.hpp>
--- a/OrthancStone/Sources/Oracle/GenericOracleRunner.cpp	Sun Sep 27 12:16:39 2026 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,529 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "GenericOracleRunner.h"
-
-#if !defined(ORTHANC_ENABLE_DCMTK)
-#  error The macro ORTHANC_ENABLE_DCMTK must be defined
-#endif
-
-#include "GetOrthancImageCommand.h"
-#include "GetOrthancWebViewerJpegCommand.h"
-#include "HttpCommand.h"
-#include "OracleCommandExceptionMessage.h"
-#include "OrthancRestApiCommand.h"
-#include "ParseDicomFromFileCommand.h"
-#include "ParseDicomFromWadoCommand.h"
-#include "ReadFileCommand.h"
-
-#if ORTHANC_ENABLE_DCMTK == 1
-#  include "ParseDicomSuccessMessage.h"
-#  include <dcmtk/dcmdata/dcdeftag.h>
-#  include <dcmtk/dcmdata/dcfilefo.h>
-static unsigned int BUCKET_DICOMDIR = 0;
-static unsigned int BUCKET_SOP = 1;
-#endif
-
-#include <Compression/GzipCompressor.h>
-#include <HttpClient.h>
-#include <Logging.h>
-#include <OrthancException.h>
-#include <SystemToolbox.h>
-#include <Toolbox.h>
-
-#include <boost/filesystem.hpp>
-
-
-
-namespace OrthancStone
-{
-  static void CopyHttpHeaders(Orthanc::HttpClient& client,
-                              const Orthanc::HttpClient::HttpHeaders& headers)
-  {
-    for (Orthanc::HttpClient::HttpHeaders::const_iterator
-           it = headers.begin(); it != headers.end(); ++it )
-    {
-      client.AddHeader(it->first, it->second);
-    }
-  }
-
-
-  static void DecodeAnswer(std::string& answer,
-                           const Orthanc::HttpClient::HttpHeaders& headers)
-  {
-    Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
-
-    for (Orthanc::HttpClient::HttpHeaders::const_iterator it = headers.begin(); 
-         it != headers.end(); ++it)
-    {
-      std::string s;
-      Orthanc::Toolbox::ToLowerCase(s, it->first);
-
-      if (s == "content-encoding")
-      {
-        if (it->second == "gzip")
-        {
-          contentEncoding = Orthanc::HttpCompression_Gzip;
-        }
-        else 
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
-                                          "Unsupported HTTP Content-Encoding: " + it->second);
-        }
-
-        break;
-      }
-    }
-
-    if (contentEncoding == Orthanc::HttpCompression_Gzip)
-    {
-      std::string compressed;
-      answer.swap(compressed);
-          
-      Orthanc::GzipCompressor compressor;
-      compressor.Uncompress(answer, compressed.c_str(), compressed.size());
-
-      LOG(INFO) << "Uncompressing gzip Encoding: from " << compressed.size()
-                << " to " << answer.size() << " bytes";
-    }
-  }
-
-
-  static void RunHttpCommand(std::string& answer,
-                             Orthanc::HttpClient::HttpHeaders& answerHeaders,
-                             const HttpCommand& command)
-  {
-    Orthanc::HttpClient client;
-    client.SetUrl(command.GetUrl());
-    client.SetMethod(command.GetMethod());
-    client.SetTimeout(command.GetTimeout());
-
-    CopyHttpHeaders(client, command.GetHttpHeaders());
-
-    if (command.HasCredentials())
-    {
-      client.SetCredentials(command.GetUsername().c_str(), command.GetPassword().c_str());
-    }
-
-    if (command.GetMethod() == Orthanc::HttpMethod_Post ||
-        command.GetMethod() == Orthanc::HttpMethod_Put)
-    {
-      client.SetExternalBody(command.GetBody());
-    }
-
-    client.ApplyAndThrowException(answer, answerHeaders);
-    client.ClearBody();
-
-    DecodeAnswer(answer, answerHeaders);
-  }
-
-
-  static void RunInternal(IOracleCallback& callback,
-                          const HttpCommand& command)
-  {
-    std::string answer;
-    Orthanc::HttpClient::HttpHeaders answerHeaders;
-    RunHttpCommand(answer, answerHeaders, command);
-
-    std::unique_ptr<HttpCommand::SuccessMessage> message(new HttpCommand::SuccessMessage(command, answerHeaders));
-    message->SwapAnswer(answer);
-    
-    callback.NotifySuccess(message.release());
-  }
-
-  
-  static void RunOrthancRestApiCommand(std::string& answer,
-                                       Orthanc::HttpClient::HttpHeaders& answerHeaders,
-                                       const Orthanc::WebServiceParameters& orthanc,
-                                       const OrthancRestApiCommand& command)
-  {
-    Orthanc::HttpClient client(orthanc, command.GetUri());
-    client.SetRedirectionFollowed(false);
-    client.SetMethod(command.GetMethod());
-    client.SetTimeout(command.GetTimeout());
-
-    CopyHttpHeaders(client, command.GetHttpHeaders());
-
-    if (command.GetMethod() == Orthanc::HttpMethod_Post ||
-        command.GetMethod() == Orthanc::HttpMethod_Put)
-    {
-      client.SetExternalBody(command.GetBody());
-    }
-
-    client.ApplyAndThrowException(answer, answerHeaders);
-    client.ClearBody();
-    DecodeAnswer(answer, answerHeaders);
-  }
-
-  
-  static void RunInternal(IOracleCallback& callback,
-                          const Orthanc::WebServiceParameters& orthanc,
-                          const OrthancRestApiCommand& command)
-  {
-    std::string answer;
-    Orthanc::HttpClient::HttpHeaders answerHeaders;
-    RunOrthancRestApiCommand(answer, answerHeaders, orthanc, command);
-
-    std::unique_ptr<OrthancRestApiCommand::SuccessMessage> message(new OrthancRestApiCommand::SuccessMessage(command, answerHeaders));
-    message->SwapAnswer(answer);
-
-    callback.NotifySuccess(message.release());
-  }
-
-
-  static void RunInternal(IOracleCallback& callback,
-                          const Orthanc::WebServiceParameters& orthanc,
-                          const GetOrthancImageCommand& command)
-  {
-    Orthanc::HttpClient client(orthanc, command.GetUri());
-    client.SetRedirectionFollowed(false);
-    client.SetTimeout(command.GetTimeout());
-
-    CopyHttpHeaders(client, command.GetHttpHeaders());
-    
-    std::string answer;
-    Orthanc::HttpClient::HttpHeaders answerHeaders;
-    client.ApplyAndThrowException(answer, answerHeaders);
-
-    DecodeAnswer(answer, answerHeaders);
-
-    command.ProcessHttpAnswer(callback, answer, answerHeaders);
-  }
-
-
-  static void RunInternal(IOracleCallback& callback,
-                          const Orthanc::WebServiceParameters& orthanc,
-                          const GetOrthancWebViewerJpegCommand& command)
-  {
-    Orthanc::HttpClient client(orthanc, command.GetUri());
-    client.SetRedirectionFollowed(false);
-    client.SetTimeout(command.GetTimeout());
-
-    CopyHttpHeaders(client, command.GetHttpHeaders());
-
-    std::string answer;
-    Orthanc::HttpClient::HttpHeaders answerHeaders;
-    client.ApplyAndThrowException(answer, answerHeaders);
-
-    DecodeAnswer(answer, answerHeaders);
-
-    command.ProcessHttpAnswer(callback, answer);
-  }
-
-
-  static std::string GetPath(const std::string& root,
-                             const std::string& file)
-  {
-    boost::filesystem::path a(root);
-    boost::filesystem::path b(file);
-
-    boost::filesystem::path c;
-    if (b.is_absolute())
-    {
-      c = b;
-    }
-    else
-    {
-      c = a / b;
-    }
-
-    return c.string();
-  }
-
-
-  static void RunInternal(IOracleCallback& callback,
-                          const std::string& root,
-                          const ReadFileCommand& command)
-  {
-    std::string path = GetPath(root, command.GetPath());
-    LOG(TRACE) << "Oracle reading file: " << path;
-
-    std::string content;
-    Orthanc::SystemToolbox::ReadFile(content, path, true /* log */);
-
-    std::unique_ptr<ReadFileCommand::SuccessMessage> message(new ReadFileCommand::SuccessMessage(command));
-    message->SwapContent(content);
-    callback.NotifySuccess(message.release());
-  }
-
-
-#if ORTHANC_ENABLE_DCMTK == 1
-  static Orthanc::ParsedDicomFile* ParseDicom(uint64_t& fileSize,  /* OUT */
-                                              const std::string& path,
-                                              bool isPixelData)
-  {
-    if (!Orthanc::SystemToolbox::IsRegularFile(path))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile);
-    }
-    
-    LOG(TRACE) << "Parsing DICOM file, " << (isPixelData ? "with" : "without")
-               << " pixel data: " << path;
-    
-    boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
-    
-    fileSize = Orthanc::SystemToolbox::GetFileSize(path);
-    
-    // Check for 32bit systems
-    if (fileSize != static_cast<uint64_t>(static_cast<size_t>(fileSize)))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
-    }
-    
-    DcmFileFormat dicom;
-    bool ok;
-    
-    if (isPixelData)
-    {
-      ok = dicom.loadFile(path.c_str()).good();
-    }
-    else
-    {
-#if DCMTK_VERSION_NUMBER >= 362
-      /**
-       * NB : We could stop at (0x3007, 0x0000) instead of
-       * DCM_PixelData as the Stone framework does not use further
-       * tags (cf. the Orthanc::DICOM_TAG_* constants), but we still
-       * use "PixelData" as this does not change the runtime much, and
-       * as it is more explicit.
-       **/
-      static const DcmTagKey STOP = DCM_PixelData;
-      //static const DcmTagKey STOP(0x3007, 0x0000);
-
-      ok = dicom.loadFileUntilTag(path.c_str(), EXS_Unknown, EGL_noChange,
-                                  DCM_MaxReadLength, ERM_autoDetect, STOP).good();
-#else
-      // The primitive "loadFileUntilTag" was introduced in DCMTK 3.6.2
-      ok = dicom.loadFile(path.c_str()).good();
-#endif
-    }
-
-    if (ok)
-    {
-      std::unique_ptr<Orthanc::ParsedDicomFile> result(new Orthanc::ParsedDicomFile(dicom));
-
-      boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
-      LOG(TRACE) << path << ": parsed in " << (end-start).total_milliseconds() << " ms";
-
-      return result.release();
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                      "Cannot parse file: " + path);
-    }
-  }
-
-  
-  static void RunInternal(IOracleCallback& callback,
-                          boost::shared_ptr<ParsedDicomCache> cache,
-                          const std::string& root,
-                          const ParseDicomFromFileCommand& command)
-  {
-    const std::string path = GetPath(root, command.GetPath());
-
-#if 0
-    // TODO Refactoring - Reactivate the cache!!!
-
-    if (cache)
-    {
-      ParsedDicomCache::Reader reader(*cache, BUCKET_DICOMDIR, path);
-      if (reader.IsValid() &&
-          (!command.IsPixelDataIncluded() ||
-           reader.HasPixelData()))
-      {
-        // Reuse the DICOM file from the cache
-        callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), reader.GetDicom(),
-                                                            reader.GetFileSize(), reader.HasPixelData()));
-        return;
-      }
-    }
-#endif
-
-    uint64_t fileSize;
-    boost::shared_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicom(fileSize, path, command.IsPixelDataIncluded()));
-
-    if (fileSize != static_cast<size_t>(fileSize))
-    {
-      // Cannot load such a large file on 32-bit architecture
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
-    }
-    
-    callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), parsed,
-                                                        static_cast<size_t>(fileSize), command.IsPixelDataIncluded()));
-
-#if 0
-    // TODO Refactoring - Reactivate the cache!!!
-    if (cache)
-    {
-      // Store it into the cache for future use
-      
-      // Invalidate to overwrite DICOM instance that would already
-      // be stored without pixel data
-      cache->Invalidate(BUCKET_DICOMDIR, path);
-      
-      cache->Acquire(BUCKET_DICOMDIR, path, parsed.release(),
-                     static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
-    }
-#endif
-  }
-
-  
-  static void RunInternal(IOracleCallback& callback,
-                          boost::shared_ptr<ParsedDicomCache> cache,
-                          const Orthanc::WebServiceParameters& orthanc,
-                          const ParseDicomFromWadoCommand& command)
-  {
-#if 0
-    // TODO Refactoring - Reactivate the cache!!!
-    if (cache)
-    {
-      ParsedDicomCache::Reader reader(*cache, BUCKET_SOP, command.GetSopInstanceUid());
-      if (reader.IsValid() &&
-          reader.HasPixelData())
-      {
-        // Reuse the DICOM file from the cache
-        callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), reader.GetDicom(),
-                                                            reader.GetFileSize(), reader.HasPixelData()));
-        return;
-      }
-    }
-#endif
-
-    std::string answer;
-    Orthanc::HttpClient::HttpHeaders answerHeaders;
-
-    switch (command.GetRestCommand().GetType())
-    {
-      case IOracleCommand::Type_Http:
-        RunHttpCommand(answer, answerHeaders, dynamic_cast<const HttpCommand&>(command.GetRestCommand()));
-        break;
-        
-      case IOracleCommand::Type_OrthancRestApi:
-        RunOrthancRestApiCommand(answer, answerHeaders, orthanc,
-                                 dynamic_cast<const OrthancRestApiCommand&>(command.GetRestCommand()));
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-
-    size_t fileSize;
-    boost::shared_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, answerHeaders));
-
-    callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), parsed, fileSize,
-                                                        true /* pixel data always is included in WADO-RS */));
-
-#if 0
-    // TODO Refactoring - Reactivate the cache!!!
-    if (cache)
-    {
-      // Store it into the cache for future use
-      cache->Acquire(BUCKET_SOP, command.GetSopInstanceUid(), parsed.release(), fileSize, true);
-    }
-#endif
-  }
-#endif
-
-
-  void GenericOracleRunner::Run(IOracleCallback& callback)
-  {
-    Orthanc::ErrorCode error = Orthanc::ErrorCode_Success;
-    
-    try
-    {
-      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(callback, dynamic_cast<const HttpCommand&>(callback.GetCommand()));
-          break;
-
-        case IOracleCommand::Type_OrthancRestApi:
-          RunInternal(callback, configuration_.GetRemoteOrthancParameters(),
-                      dynamic_cast<const OrthancRestApiCommand&>(callback.GetCommand()));
-          break;
-
-        case IOracleCommand::Type_GetOrthancImage:
-          RunInternal(callback, configuration_.GetRemoteOrthancParameters(),
-                      dynamic_cast<const GetOrthancImageCommand&>(callback.GetCommand()));
-          break;
-
-        case IOracleCommand::Type_GetOrthancWebViewerJpeg:
-          RunInternal(callback, configuration_.GetRemoteOrthancParameters(),
-                      dynamic_cast<const GetOrthancWebViewerJpegCommand&>(callback.GetCommand()));
-          break;
-
-        case IOracleCommand::Type_ReadFile:
-          RunInternal(callback, configuration_.GetRootDirectory(),
-                      dynamic_cast<const ReadFileCommand&>(callback.GetCommand()));
-          break;
-
-        case IOracleCommand::Type_ParseDicomFromFile:
-        case IOracleCommand::Type_ParseDicomFromWado:
-#if ORTHANC_ENABLE_DCMTK == 1
-          switch (callback.GetCommand().GetType())
-          {
-            case IOracleCommand::Type_ParseDicomFromFile:
-              RunInternal(callback, dicomCache_, configuration_.GetRootDirectory(),
-                          dynamic_cast<const ParseDicomFromFileCommand&>(callback.GetCommand()));
-              break;
-
-            case IOracleCommand::Type_ParseDicomFromWado:
-              RunInternal(callback, dicomCache_, configuration_.GetRemoteOrthancParameters(),
-                          dynamic_cast<const ParseDicomFromWadoCommand&>(callback.GetCommand()));
-              break;
-
-            default:
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-          }            
-          break;
-#else
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
-                                          "DCMTK must be enabled to parse DICOM files");
-#endif
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
-    }
-    catch (Orthanc::OrthancException& e)
-    {
-      LOG(ERROR) << "Exception within the oracle: " << e.What();
-      error = e.GetErrorCode();
-    }
-    catch (...)
-    {
-      LOG(ERROR) << "Threaded exception within the oracle";
-      error = Orthanc::ErrorCode_InternalError;
-    }
-
-    if (error != Orthanc::ErrorCode_Success)
-    {
-      callback.NotifyError(Orthanc::OrthancException(error));
-    }
-  }
-}
--- a/OrthancStone/Sources/Oracle/GenericOracleRunner.h	Sun Sep 27 12:16:39 2026 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <OrthancFramework.h>  // To have the macros properly defined
-
-#if !defined(ORTHANC_ENABLE_DCMTK)
-#  error The macro ORTHANC_ENABLE_DCMTK must be defined
-#endif
-
-#if ORTHANC_ENABLE_DCMTK == 1
-#  include "../Toolbox/ParsedDicomCache.h"
-#endif
-
-#include "../StoneApplication.h"
-#include "IOracleCommand.h"
-#include "OracleCallback.h"
-
-#include <Enumerations.h>  // For ORTHANC_OVERRIDE
-#include <WebServiceParameters.h>
-
-namespace OrthancStone
-{
-  class GenericOracleRunner : public boost::noncopyable
-  {
-  private:
-    StoneApplication::Configuration  configuration_;
-
-#if ORTHANC_ENABLE_DCMTK == 1
-    boost::shared_ptr<ParsedDicomCache>  dicomCache_;
-#endif
-
-  public:
-    GenericOracleRunner(const StoneApplication::Configuration& configuration) :
-      configuration_(configuration)
-    {
-    }
-
-#if ORTHANC_ENABLE_DCMTK == 1
-    void SetDicomCache(boost::shared_ptr<ParsedDicomCache> cache)
-    {
-      dicomCache_ = cache;
-    }
-#endif
-
-    void Run(IOracleCallback& callback);
-  };
-}
--- a/OrthancStone/Sources/Oracle/ThreadedOracle.cpp	Sun Sep 27 12:16:39 2026 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,232 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "ThreadedOracle.h"
-
-#include "GenericOracleRunner.h"
-#include "OracleCallback.h"
-#include "SleepOracleCommand.h"
-
-#include <Logging.h>
-#include <OrthancException.h>
-
-namespace OrthancStone
-{
-  class ThreadedOracle::GenericRunnable : public Orthanc::IRunnable
-  {
-  private:
-    StoneApplication::Configuration       configuration_;
-    std::unique_ptr<GenericOracleRunner>  runner_;
-    std::unique_ptr<IOracleCallback>      callback_;
-
-  public:
-    GenericRunnable(const StoneApplication::Configuration& configuration,
-                    GenericOracleRunner* runner /* takes ownership */,
-                    IOracleCallback* callback /* takes ownership */) :
-      configuration_(configuration),
-      runner_(runner),
-      callback_(callback)
-    {
-      if (runner == NULL ||
-          callback == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-    }
-
-    virtual void Run() ORTHANC_OVERRIDE
-    {
-      runner_->Run(*callback_);
-    }
-  };
-
-
-  class ThreadedOracle::SleepRunnable : public Orthanc::IRunnable
-  {
-  private:
-    class Item : public boost::noncopyable
-    {
-    private:
-      std::unique_ptr<IOracleCallback>  callback_;
-      boost::posix_time::ptime          expiration_;
-
-    public:
-      Item(IOracleCallback* callback,
-           unsigned int delay) :
-        callback_(callback)
-      {
-        if (callback == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-        }
-
-        expiration_ = (boost::posix_time::microsec_clock::local_time() +
-                       boost::posix_time::milliseconds(delay));
-      }
-
-      const boost::posix_time::ptime& GetExpirationTime() const
-      {
-        return expiration_;
-      }
-
-      IOracleCallback& GetCallback()
-      {
-        return *callback_;
-      }
-    };
-
-    typedef std::list<Item*>  Content;
-
-    boost::mutex  mutex_;
-    Content       content_;
-
-  public:
-    ~SleepRunnable()
-    {
-      for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
-      {
-        if (*it != NULL)
-        {
-          delete *it;
-        }
-      }
-    }
-
-
-    void Add(IOracleCallback* callback,
-             unsigned int delay)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      content_.push_back(new Item(callback, delay));
-    }
-
-
-    // Awakes expired sleeps
-    virtual void Run() ORTHANC_OVERRIDE
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      const boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
-
-      Content  stillSleeping;
-
-      for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
-      {
-        if (*it != NULL &&
-            (*it)->GetExpirationTime() <= now)
-        {
-          const SleepOracleCommand& command = dynamic_cast<const SleepOracleCommand&>((*it)->GetCallback().GetCommand()); // TODO Refactoring - Remove this
-          (*it)->GetCallback().NotifySuccess(new SleepOracleCommand::TimeoutMessage(command));
-          delete *it;
-          *it = NULL;
-        }
-        else
-        {
-          stillSleeping.push_back(*it);
-        }
-      }
-
-      // Compact the still-sleeping commands
-      content_ = stillSleeping;
-    }
-  };
-
-
-  ThreadedOracle::ThreadedOracle(const StoneApplication::Configuration& configuration) :
-    configuration_(configuration),
-    sleepingThread_(new SleepRunnable, configuration.GetWorkersTimeResolution())
-  {
-    threadPool_.SetThreadsCount(configuration.GetOracleThreadsCount());
-    threadPool_.SetDequeueTimeout(configuration.GetWorkersTimeResolution());
-
-    if (configuration.GetDicomCacheSize() == 0)
-    {
-      LOG(WARNING) << "The DICOM cache is disabled";
-    }
-    else
-    {
-      LOG(INFO) << "The DICOM cache size is set to " << configuration.GetDicomCacheSize() << " bytes";
-      dicomCache_.reset(new ParsedDicomCache(configuration.GetDicomCacheSize()));
-    }
-  }
-
-
-  void ThreadedOracle::Start()
-  {
-    sleepingThread_.Start();
-    threadPool_.Start();
-  }
-
-
-  void ThreadedOracle::Stop()
-  {
-    threadPool_.Stop();
-    sleepingThread_.Stop();
-  }
-
-
-  void ThreadedOracle::SubmitInternal(IOracleCallback* callback /* takes ownership */)
-  {
-    std::unique_ptr<IOracleCallback> protection(callback);
-
-    if (callback == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    if (protection->GetCommand().GetType() == IOracleCommand::Type_Sleep)
-    {
-      const unsigned int delay = dynamic_cast<const SleepOracleCommand&>(protection->GetCommand()).GetDelay();
-      SleepRunnable& runnable = dynamic_cast<SleepRunnable&>(sleepingThread_.GetRunnable());
-      runnable.Add(protection.release(), delay);
-    }
-    else
-    {
-      std::unique_ptr<GenericOracleRunner> runner(new GenericOracleRunner(configuration_));
-
-#if ORTHANC_ENABLE_DCMTK == 1
-      if (dicomCache_)
-      {
-        runner->SetDicomCache(dicomCache_);
-      }
-#endif
-
-      threadPool_.Submit(new GenericRunnable(configuration_, runner.release(), protection.release()));
-    }
-  }
-
-
-  void ThreadedOracle::Submit(IEnvironment& environment,
-                              const boost::shared_ptr<IOracleClient>& client,
-                              IOracleCommand* command /* takes ownership */)
-  {
-    std::unique_ptr<IOracleCommand> protection(command);
-
-    if (!client)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    SubmitInternal(new OracleCallback(environment, client, protection.release()));
-  }
-}
--- a/OrthancStone/Sources/Oracle/ThreadedOracle.h	Sun Sep 27 12:16:39 2026 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <OrthancFramework.h>  // To have the macros properly defined
-#include <Compatibility.h>  // ORTHANC_OVERRIDE
-
-#if !defined(ORTHANC_ENABLE_THREADS)
-#  error The macro ORTHANC_ENABLE_THREADS must be defined
-#endif
-
-#if !defined(ORTHANC_ENABLE_DCMTK)
-#  error The macro ORTHANC_ENABLE_DCMTK must be defined
-#endif
-
-#if ORTHANC_ENABLE_THREADS != 1
-#  error This file can only compiled for native targets
-#endif
-
-#if ORTHANC_ENABLE_DCMTK == 1
-#  include "../Toolbox/ParsedDicomCache.h"
-#endif
-
-#include "../Messages/IMessageEmitter.h"
-#include "../Platforms/Native/RunnableThread.h"
-#include "../StoneApplication.h"
-#include "IOracle.h"
-#include "OracleCallback.h"
-
-#include <MultiThreading/ThreadPool.h>
-
-
-namespace OrthancStone
-{
-  class ThreadedOracle : public IOracle
-  {
-  private:
-    class GenericRunnable;
-    class SleepRunnable;
-
-    StoneApplication::Configuration  configuration_;
-    RunnableThread                   sleepingThread_;
-    Orthanc::ThreadPool              threadPool_;
-
-#if ORTHANC_ENABLE_DCMTK == 1
-    boost::shared_ptr<ParsedDicomCache>  dicomCache_;
-#endif
-
-    void SubmitInternal(IOracleCallback* callback /* takes ownership */);
-
-  public:
-    ThreadedOracle(const StoneApplication::Configuration& configuration);
-
-    void Start();
-
-    void Stop();
-
-    virtual void Submit(IEnvironment& environment,
-                        const boost::shared_ptr<IOracleClient>& client,
-                        IOracleCommand* command /* takes ownership */) ORTHANC_OVERRIDE;
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Platforms/Native/GenericOracleRunner.cpp	Mon Sep 28 14:45:20 2026 +0200
@@ -0,0 +1,529 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GenericOracleRunner.h"
+
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
+#include "../../Oracle/GetOrthancImageCommand.h"
+#include "../../Oracle/GetOrthancWebViewerJpegCommand.h"
+#include "../../Oracle/HttpCommand.h"
+#include "../../Oracle/OracleCommandExceptionMessage.h"
+#include "../../Oracle/OrthancRestApiCommand.h"
+#include "../../Oracle/ParseDicomFromFileCommand.h"
+#include "../../Oracle/ParseDicomFromWadoCommand.h"
+#include "../../Oracle/ReadFileCommand.h"
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "../../Oracle/ParseDicomSuccessMessage.h"
+#  include <dcmtk/dcmdata/dcdeftag.h>
+#  include <dcmtk/dcmdata/dcfilefo.h>
+static unsigned int BUCKET_DICOMDIR = 0;
+static unsigned int BUCKET_SOP = 1;
+#endif
+
+#include <Compression/GzipCompressor.h>
+#include <HttpClient.h>
+#include <Logging.h>
+#include <OrthancException.h>
+#include <SystemToolbox.h>
+#include <Toolbox.h>
+
+#include <boost/filesystem.hpp>
+
+
+
+namespace OrthancStone
+{
+  static void CopyHttpHeaders(Orthanc::HttpClient& client,
+                              const Orthanc::HttpClient::HttpHeaders& headers)
+  {
+    for (Orthanc::HttpClient::HttpHeaders::const_iterator
+           it = headers.begin(); it != headers.end(); ++it )
+    {
+      client.AddHeader(it->first, it->second);
+    }
+  }
+
+
+  static void DecodeAnswer(std::string& answer,
+                           const Orthanc::HttpClient::HttpHeaders& headers)
+  {
+    Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
+
+    for (Orthanc::HttpClient::HttpHeaders::const_iterator it = headers.begin(); 
+         it != headers.end(); ++it)
+    {
+      std::string s;
+      Orthanc::Toolbox::ToLowerCase(s, it->first);
+
+      if (s == "content-encoding")
+      {
+        if (it->second == "gzip")
+        {
+          contentEncoding = Orthanc::HttpCompression_Gzip;
+        }
+        else 
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                          "Unsupported HTTP Content-Encoding: " + it->second);
+        }
+
+        break;
+      }
+    }
+
+    if (contentEncoding == Orthanc::HttpCompression_Gzip)
+    {
+      std::string compressed;
+      answer.swap(compressed);
+          
+      Orthanc::GzipCompressor compressor;
+      compressor.Uncompress(answer, compressed.c_str(), compressed.size());
+
+      LOG(INFO) << "Uncompressing gzip Encoding: from " << compressed.size()
+                << " to " << answer.size() << " bytes";
+    }
+  }
+
+
+  static void RunHttpCommand(std::string& answer,
+                             Orthanc::HttpClient::HttpHeaders& answerHeaders,
+                             const HttpCommand& command)
+  {
+    Orthanc::HttpClient client;
+    client.SetUrl(command.GetUrl());
+    client.SetMethod(command.GetMethod());
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+
+    if (command.HasCredentials())
+    {
+      client.SetCredentials(command.GetUsername().c_str(), command.GetPassword().c_str());
+    }
+
+    if (command.GetMethod() == Orthanc::HttpMethod_Post ||
+        command.GetMethod() == Orthanc::HttpMethod_Put)
+    {
+      client.SetExternalBody(command.GetBody());
+    }
+
+    client.ApplyAndThrowException(answer, answerHeaders);
+    client.ClearBody();
+
+    DecodeAnswer(answer, answerHeaders);
+  }
+
+
+  static void RunInternal(IOracleCallback& callback,
+                          const HttpCommand& command)
+  {
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    RunHttpCommand(answer, answerHeaders, command);
+
+    std::unique_ptr<HttpCommand::SuccessMessage> message(new HttpCommand::SuccessMessage(command, answerHeaders));
+    message->SwapAnswer(answer);
+    
+    callback.NotifySuccess(message.release());
+  }
+
+  
+  static void RunOrthancRestApiCommand(std::string& answer,
+                                       Orthanc::HttpClient::HttpHeaders& answerHeaders,
+                                       const Orthanc::WebServiceParameters& orthanc,
+                                       const OrthancRestApiCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetRedirectionFollowed(false);
+    client.SetMethod(command.GetMethod());
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+
+    if (command.GetMethod() == Orthanc::HttpMethod_Post ||
+        command.GetMethod() == Orthanc::HttpMethod_Put)
+    {
+      client.SetExternalBody(command.GetBody());
+    }
+
+    client.ApplyAndThrowException(answer, answerHeaders);
+    client.ClearBody();
+    DecodeAnswer(answer, answerHeaders);
+  }
+
+  
+  static void RunInternal(IOracleCallback& callback,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const OrthancRestApiCommand& command)
+  {
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    RunOrthancRestApiCommand(answer, answerHeaders, orthanc, command);
+
+    std::unique_ptr<OrthancRestApiCommand::SuccessMessage> message(new OrthancRestApiCommand::SuccessMessage(command, answerHeaders));
+    message->SwapAnswer(answer);
+
+    callback.NotifySuccess(message.release());
+  }
+
+
+  static void RunInternal(IOracleCallback& callback,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const GetOrthancImageCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetRedirectionFollowed(false);
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+    
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    client.ApplyAndThrowException(answer, answerHeaders);
+
+    DecodeAnswer(answer, answerHeaders);
+
+    command.ProcessHttpAnswer(callback, answer, answerHeaders);
+  }
+
+
+  static void RunInternal(IOracleCallback& callback,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const GetOrthancWebViewerJpegCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetRedirectionFollowed(false);
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    client.ApplyAndThrowException(answer, answerHeaders);
+
+    DecodeAnswer(answer, answerHeaders);
+
+    command.ProcessHttpAnswer(callback, answer);
+  }
+
+
+  static std::string GetPath(const std::string& root,
+                             const std::string& file)
+  {
+    boost::filesystem::path a(root);
+    boost::filesystem::path b(file);
+
+    boost::filesystem::path c;
+    if (b.is_absolute())
+    {
+      c = b;
+    }
+    else
+    {
+      c = a / b;
+    }
+
+    return c.string();
+  }
+
+
+  static void RunInternal(IOracleCallback& callback,
+                          const std::string& root,
+                          const ReadFileCommand& command)
+  {
+    std::string path = GetPath(root, command.GetPath());
+    LOG(TRACE) << "Oracle reading file: " << path;
+
+    std::string content;
+    Orthanc::SystemToolbox::ReadFile(content, path, true /* log */);
+
+    std::unique_ptr<ReadFileCommand::SuccessMessage> message(new ReadFileCommand::SuccessMessage(command));
+    message->SwapContent(content);
+    callback.NotifySuccess(message.release());
+  }
+
+
+#if ORTHANC_ENABLE_DCMTK == 1
+  static Orthanc::ParsedDicomFile* ParseDicom(uint64_t& fileSize,  /* OUT */
+                                              const std::string& path,
+                                              bool isPixelData)
+  {
+    if (!Orthanc::SystemToolbox::IsRegularFile(path))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile);
+    }
+    
+    LOG(TRACE) << "Parsing DICOM file, " << (isPixelData ? "with" : "without")
+               << " pixel data: " << path;
+    
+    boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
+    
+    fileSize = Orthanc::SystemToolbox::GetFileSize(path);
+    
+    // Check for 32bit systems
+    if (fileSize != static_cast<uint64_t>(static_cast<size_t>(fileSize)))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+    }
+    
+    DcmFileFormat dicom;
+    bool ok;
+    
+    if (isPixelData)
+    {
+      ok = dicom.loadFile(path.c_str()).good();
+    }
+    else
+    {
+#if DCMTK_VERSION_NUMBER >= 362
+      /**
+       * NB : We could stop at (0x3007, 0x0000) instead of
+       * DCM_PixelData as the Stone framework does not use further
+       * tags (cf. the Orthanc::DICOM_TAG_* constants), but we still
+       * use "PixelData" as this does not change the runtime much, and
+       * as it is more explicit.
+       **/
+      static const DcmTagKey STOP = DCM_PixelData;
+      //static const DcmTagKey STOP(0x3007, 0x0000);
+
+      ok = dicom.loadFileUntilTag(path.c_str(), EXS_Unknown, EGL_noChange,
+                                  DCM_MaxReadLength, ERM_autoDetect, STOP).good();
+#else
+      // The primitive "loadFileUntilTag" was introduced in DCMTK 3.6.2
+      ok = dicom.loadFile(path.c_str()).good();
+#endif
+    }
+
+    if (ok)
+    {
+      std::unique_ptr<Orthanc::ParsedDicomFile> result(new Orthanc::ParsedDicomFile(dicom));
+
+      boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
+      LOG(TRACE) << path << ": parsed in " << (end-start).total_milliseconds() << " ms";
+
+      return result.release();
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Cannot parse file: " + path);
+    }
+  }
+
+  
+  static void RunInternal(IOracleCallback& callback,
+                          boost::shared_ptr<ParsedDicomCache> cache,
+                          const std::string& root,
+                          const ParseDicomFromFileCommand& command)
+  {
+    const std::string path = GetPath(root, command.GetPath());
+
+#if 0
+    // TODO Refactoring - Reactivate the cache!!!
+
+    if (cache)
+    {
+      ParsedDicomCache::Reader reader(*cache, BUCKET_DICOMDIR, path);
+      if (reader.IsValid() &&
+          (!command.IsPixelDataIncluded() ||
+           reader.HasPixelData()))
+      {
+        // Reuse the DICOM file from the cache
+        callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), reader.GetDicom(),
+                                                            reader.GetFileSize(), reader.HasPixelData()));
+        return;
+      }
+    }
+#endif
+
+    uint64_t fileSize;
+    boost::shared_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicom(fileSize, path, command.IsPixelDataIncluded()));
+
+    if (fileSize != static_cast<size_t>(fileSize))
+    {
+      // Cannot load such a large file on 32-bit architecture
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+    }
+    
+    callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), parsed,
+                                                        static_cast<size_t>(fileSize), command.IsPixelDataIncluded()));
+
+#if 0
+    // TODO Refactoring - Reactivate the cache!!!
+    if (cache)
+    {
+      // Store it into the cache for future use
+      
+      // Invalidate to overwrite DICOM instance that would already
+      // be stored without pixel data
+      cache->Invalidate(BUCKET_DICOMDIR, path);
+      
+      cache->Acquire(BUCKET_DICOMDIR, path, parsed.release(),
+                     static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
+    }
+#endif
+  }
+
+  
+  static void RunInternal(IOracleCallback& callback,
+                          boost::shared_ptr<ParsedDicomCache> cache,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const ParseDicomFromWadoCommand& command)
+  {
+#if 0
+    // TODO Refactoring - Reactivate the cache!!!
+    if (cache)
+    {
+      ParsedDicomCache::Reader reader(*cache, BUCKET_SOP, command.GetSopInstanceUid());
+      if (reader.IsValid() &&
+          reader.HasPixelData())
+      {
+        // Reuse the DICOM file from the cache
+        callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), reader.GetDicom(),
+                                                            reader.GetFileSize(), reader.HasPixelData()));
+        return;
+      }
+    }
+#endif
+
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+
+    switch (command.GetRestCommand().GetType())
+    {
+      case IOracleCommand::Type_Http:
+        RunHttpCommand(answer, answerHeaders, dynamic_cast<const HttpCommand&>(command.GetRestCommand()));
+        break;
+        
+      case IOracleCommand::Type_OrthancRestApi:
+        RunOrthancRestApiCommand(answer, answerHeaders, orthanc,
+                                 dynamic_cast<const OrthancRestApiCommand&>(command.GetRestCommand()));
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    size_t fileSize;
+    boost::shared_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, answerHeaders));
+
+    callback.NotifySuccess(new ParseDicomSuccessMessage(command, command.GetSource(), parsed, fileSize,
+                                                        true /* pixel data always is included in WADO-RS */));
+
+#if 0
+    // TODO Refactoring - Reactivate the cache!!!
+    if (cache)
+    {
+      // Store it into the cache for future use
+      cache->Acquire(BUCKET_SOP, command.GetSopInstanceUid(), parsed.release(), fileSize, true);
+    }
+#endif
+  }
+#endif
+
+
+  void GenericOracleRunner::Run(IOracleCallback& callback)
+  {
+    Orthanc::ErrorCode error = Orthanc::ErrorCode_Success;
+    
+    try
+    {
+      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(callback, dynamic_cast<const HttpCommand&>(callback.GetCommand()));
+          break;
+
+        case IOracleCommand::Type_OrthancRestApi:
+          RunInternal(callback, configuration_.GetRemoteOrthancParameters(),
+                      dynamic_cast<const OrthancRestApiCommand&>(callback.GetCommand()));
+          break;
+
+        case IOracleCommand::Type_GetOrthancImage:
+          RunInternal(callback, configuration_.GetRemoteOrthancParameters(),
+                      dynamic_cast<const GetOrthancImageCommand&>(callback.GetCommand()));
+          break;
+
+        case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+          RunInternal(callback, configuration_.GetRemoteOrthancParameters(),
+                      dynamic_cast<const GetOrthancWebViewerJpegCommand&>(callback.GetCommand()));
+          break;
+
+        case IOracleCommand::Type_ReadFile:
+          RunInternal(callback, configuration_.GetRootDirectory(),
+                      dynamic_cast<const ReadFileCommand&>(callback.GetCommand()));
+          break;
+
+        case IOracleCommand::Type_ParseDicomFromFile:
+        case IOracleCommand::Type_ParseDicomFromWado:
+#if ORTHANC_ENABLE_DCMTK == 1
+          switch (callback.GetCommand().GetType())
+          {
+            case IOracleCommand::Type_ParseDicomFromFile:
+              RunInternal(callback, dicomCache_, configuration_.GetRootDirectory(),
+                          dynamic_cast<const ParseDicomFromFileCommand&>(callback.GetCommand()));
+              break;
+
+            case IOracleCommand::Type_ParseDicomFromWado:
+              RunInternal(callback, dicomCache_, configuration_.GetRemoteOrthancParameters(),
+                          dynamic_cast<const ParseDicomFromWadoCommand&>(callback.GetCommand()));
+              break;
+
+            default:
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }            
+          break;
+#else
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
+                                          "DCMTK must be enabled to parse DICOM files");
+#endif
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "Exception within the oracle: " << e.What();
+      error = e.GetErrorCode();
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Threaded exception within the oracle";
+      error = Orthanc::ErrorCode_InternalError;
+    }
+
+    if (error != Orthanc::ErrorCode_Success)
+    {
+      callback.NotifyError(Orthanc::OrthancException(error));
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Platforms/Native/GenericOracleRunner.h	Mon Sep 28 14:45:20 2026 +0200
@@ -0,0 +1,69 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <OrthancFramework.h>  // To have the macros properly defined
+
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "../../Toolbox/ParsedDicomCache.h"
+#endif
+
+#include "../../Oracle/IOracleCommand.h"
+#include "../../Oracle/OracleCallback.h"
+#include "../../StoneApplication.h"
+
+#include <Enumerations.h>  // For ORTHANC_OVERRIDE
+#include <WebServiceParameters.h>
+
+namespace OrthancStone
+{
+  class GenericOracleRunner : public boost::noncopyable
+  {
+  private:
+    StoneApplication::Configuration  configuration_;
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    boost::shared_ptr<ParsedDicomCache>  dicomCache_;
+#endif
+
+  public:
+    GenericOracleRunner(const StoneApplication::Configuration& configuration) :
+      configuration_(configuration)
+    {
+    }
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    void SetDicomCache(boost::shared_ptr<ParsedDicomCache> cache)
+    {
+      dicomCache_ = cache;
+    }
+#endif
+
+    void Run(IOracleCallback& callback);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Platforms/Native/ThreadedOracle.cpp	Mon Sep 28 14:45:20 2026 +0200
@@ -0,0 +1,232 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ThreadedOracle.h"
+
+#include "../../Oracle/OracleCallback.h"
+#include "../../Oracle/SleepOracleCommand.h"
+#include "GenericOracleRunner.h"
+
+#include <Logging.h>
+#include <OrthancException.h>
+
+namespace OrthancStone
+{
+  class ThreadedOracle::GenericRunnable : public Orthanc::IRunnable
+  {
+  private:
+    StoneApplication::Configuration       configuration_;
+    std::unique_ptr<GenericOracleRunner>  runner_;
+    std::unique_ptr<IOracleCallback>      callback_;
+
+  public:
+    GenericRunnable(const StoneApplication::Configuration& configuration,
+                    GenericOracleRunner* runner /* takes ownership */,
+                    IOracleCallback* callback /* takes ownership */) :
+      configuration_(configuration),
+      runner_(runner),
+      callback_(callback)
+    {
+      if (runner == NULL ||
+          callback == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    virtual void Run() ORTHANC_OVERRIDE
+    {
+      runner_->Run(*callback_);
+    }
+  };
+
+
+  class ThreadedOracle::SleepRunnable : public Orthanc::IRunnable
+  {
+  private:
+    class Item : public boost::noncopyable
+    {
+    private:
+      std::unique_ptr<IOracleCallback>  callback_;
+      boost::posix_time::ptime          expiration_;
+
+    public:
+      Item(IOracleCallback* callback,
+           unsigned int delay) :
+        callback_(callback)
+      {
+        if (callback == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+
+        expiration_ = (boost::posix_time::microsec_clock::local_time() +
+                       boost::posix_time::milliseconds(delay));
+      }
+
+      const boost::posix_time::ptime& GetExpirationTime() const
+      {
+        return expiration_;
+      }
+
+      IOracleCallback& GetCallback()
+      {
+        return *callback_;
+      }
+    };
+
+    typedef std::list<Item*>  Content;
+
+    boost::mutex  mutex_;
+    Content       content_;
+
+  public:
+    ~SleepRunnable()
+    {
+      for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+      {
+        if (*it != NULL)
+        {
+          delete *it;
+        }
+      }
+    }
+
+
+    void Add(IOracleCallback* callback,
+             unsigned int delay)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      content_.push_back(new Item(callback, delay));
+    }
+
+
+    // Awakes expired sleeps
+    virtual void Run() ORTHANC_OVERRIDE
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      const boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
+
+      Content  stillSleeping;
+
+      for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+      {
+        if (*it != NULL &&
+            (*it)->GetExpirationTime() <= now)
+        {
+          const SleepOracleCommand& command = dynamic_cast<const SleepOracleCommand&>((*it)->GetCallback().GetCommand()); // TODO Refactoring - Remove this
+          (*it)->GetCallback().NotifySuccess(new SleepOracleCommand::TimeoutMessage(command));
+          delete *it;
+          *it = NULL;
+        }
+        else
+        {
+          stillSleeping.push_back(*it);
+        }
+      }
+
+      // Compact the still-sleeping commands
+      content_ = stillSleeping;
+    }
+  };
+
+
+  ThreadedOracle::ThreadedOracle(const StoneApplication::Configuration& configuration) :
+    configuration_(configuration),
+    sleepingThread_(new SleepRunnable, configuration.GetWorkersTimeResolution())
+  {
+    threadPool_.SetThreadsCount(configuration.GetOracleThreadsCount());
+    threadPool_.SetDequeueTimeout(configuration.GetWorkersTimeResolution());
+
+    if (configuration.GetDicomCacheSize() == 0)
+    {
+      LOG(WARNING) << "The DICOM cache is disabled";
+    }
+    else
+    {
+      LOG(INFO) << "The DICOM cache size is set to " << configuration.GetDicomCacheSize() << " bytes";
+      dicomCache_.reset(new ParsedDicomCache(configuration.GetDicomCacheSize()));
+    }
+  }
+
+
+  void ThreadedOracle::Start()
+  {
+    sleepingThread_.Start();
+    threadPool_.Start();
+  }
+
+
+  void ThreadedOracle::Stop()
+  {
+    threadPool_.Stop();
+    sleepingThread_.Stop();
+  }
+
+
+  void ThreadedOracle::SubmitInternal(IOracleCallback* callback /* takes ownership */)
+  {
+    std::unique_ptr<IOracleCallback> protection(callback);
+
+    if (callback == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    if (protection->GetCommand().GetType() == IOracleCommand::Type_Sleep)
+    {
+      const unsigned int delay = dynamic_cast<const SleepOracleCommand&>(protection->GetCommand()).GetDelay();
+      SleepRunnable& runnable = dynamic_cast<SleepRunnable&>(sleepingThread_.GetRunnable());
+      runnable.Add(protection.release(), delay);
+    }
+    else
+    {
+      std::unique_ptr<GenericOracleRunner> runner(new GenericOracleRunner(configuration_));
+
+#if ORTHANC_ENABLE_DCMTK == 1
+      if (dicomCache_)
+      {
+        runner->SetDicomCache(dicomCache_);
+      }
+#endif
+
+      threadPool_.Submit(new GenericRunnable(configuration_, runner.release(), protection.release()));
+    }
+  }
+
+
+  void ThreadedOracle::Submit(IEnvironment& environment,
+                              const boost::shared_ptr<IOracleClient>& client,
+                              IOracleCommand* command /* takes ownership */)
+  {
+    std::unique_ptr<IOracleCommand> protection(command);
+
+    if (!client)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    SubmitInternal(new OracleCallback(environment, client, protection.release()));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Platforms/Native/ThreadedOracle.h	Mon Sep 28 14:45:20 2026 +0200
@@ -0,0 +1,83 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <OrthancFramework.h>  // To have the macros properly defined
+#include <Compatibility.h>  // ORTHANC_OVERRIDE
+
+#if !defined(ORTHANC_ENABLE_THREADS)
+#  error The macro ORTHANC_ENABLE_THREADS must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
+#if ORTHANC_ENABLE_THREADS != 1
+#  error This file can only compiled for native targets
+#endif
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "../../Toolbox/ParsedDicomCache.h"
+#endif
+
+#include "../../Messages/IMessageEmitter.h"
+#include "../../Oracle/IOracle.h"
+#include "../../Oracle/OracleCallback.h"
+#include "../../StoneApplication.h"
+#include "RunnableThread.h"
+
+#include <MultiThreading/ThreadPool.h>
+
+
+namespace OrthancStone
+{
+  class ThreadedOracle : public IOracle
+  {
+  private:
+    class GenericRunnable;
+    class SleepRunnable;
+
+    StoneApplication::Configuration  configuration_;
+    RunnableThread                   sleepingThread_;
+    Orthanc::ThreadPool              threadPool_;
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    boost::shared_ptr<ParsedDicomCache>  dicomCache_;
+#endif
+
+    void SubmitInternal(IOracleCallback* callback /* takes ownership */);
+
+  public:
+    ThreadedOracle(const StoneApplication::Configuration& configuration);
+
+    void Start();
+
+    void Stop();
+
+    virtual void Submit(IEnvironment& environment,
+                        const boost::shared_ptr<IOracleClient>& client,
+                        IOracleCommand* command /* takes ownership */) ORTHANC_OVERRIDE;
+  };
+}
--- a/OrthancStone/Sources/StoneApplication.cpp	Sun Sep 27 12:16:39 2026 +0200
+++ b/OrthancStone/Sources/StoneApplication.cpp	Mon Sep 28 14:45:20 2026 +0200
@@ -33,7 +33,7 @@
 #  include "Platforms/WebAssembly/WebAssemblyOracle.h"
 #elif ORTHANC_STONE_TARGET_PLATFORM_NATIVE == 1
 #  include "Platforms/Native/NativeEnvironment.h"
-#  include "Oracle/ThreadedOracle.h"
+#  include "Platforms/Native/ThreadedOracle.h"
 #else
 #  error Support your platform here
 #endif