changeset 5904:bed6a8ba5431 get-scu

refactoring Move and Get jobs
author Alain Mazy <am@orthanc.team>
date Thu, 05 Dec 2024 12:02:33 +0100
parents 63ea301075ef
children dad78aa9141e
files OrthancServer/CMakeLists.txt OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp OrthancServer/Sources/ServerJobs/DicomGetScuJob.h OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp OrthancServer/Sources/ServerJobs/DicomMoveScuJob.h OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h
diffstat 8 files changed, 394 insertions(+), 532 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/CMakeLists.txt	Thu Dec 05 09:30:05 2024 +0100
+++ b/OrthancServer/CMakeLists.txt	Thu Dec 05 12:02:33 2024 +0100
@@ -130,6 +130,7 @@
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/ArchiveJob.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/CleaningInstancesJob.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/DicomModalityStoreJob.cpp
+  ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/DicomGetScuJob.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/DicomMoveScuJob.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ServerJobs/LuaJobManager.cpp
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Thu Dec 05 09:30:05 2024 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Thu Dec 05 12:02:33 2024 +0100
@@ -1014,91 +1014,62 @@
       retrieveMethod = context.GetDefaultDicomRetrieveMethod();
     }
 
+    std::unique_ptr<DicomRetrieveScuBaseJob> job;
+
+
     switch (retrieveMethod)
     {
       case RetrieveMethod_Move:
       {
-        std::unique_ptr<DicomMoveScuJob> job(new DicomMoveScuJob(context));
-        job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short));
-
-        job->SetTargetAet(targetAet);
-        job->SetLocalAet(query.GetHandler().GetLocalAet());
-        job->SetRemoteModality(query.GetHandler().GetRemoteModality());
-
-        // TODO: refactor in a base class for DicomGetScuJob and DicomMoveScuJob
-        if (timeout >= 0)
-        {
-          // New in Orthanc 1.7.0
-          job->SetTimeout(static_cast<uint32_t>(timeout));
-        }
-        else if (query.GetHandler().HasTimeout())
-        {
-          // New in Orthanc 1.9.1
-          job->SetTimeout(query.GetHandler().GetTimeout());
-        }
+        job.reset(new DicomMoveScuJob(context));
+        (dynamic_cast<DicomMoveScuJob*>(job.get()))->SetTargetAet(targetAet);
 
         LOG(WARNING) << "Driving C-Move SCU on remote modality "
                     << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle()
                     << " to target modality " << targetAet;
-
-        // TODO: refactor in a base class for DicomGetScuJob and DicomMoveScuJob
-        if (allAnswers)
-        {
-          for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++)
-          {
-            job->AddFindAnswer(query.GetHandler(), i);
-          }
-        }
-        else
-        {
-          job->AddFindAnswer(query.GetHandler(), index);
-        }
-
-        OrthancRestApi::GetApi(call).SubmitCommandsJob
-          (call, job.release(), true /* synchronous by default */, body);
-
       }; break;
       case RetrieveMethod_Get:
       {
-        std::unique_ptr<DicomGetScuJob> job(new DicomGetScuJob(context));
-        job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short));
-        job->SetRemoteModality(query.GetHandler().GetRemoteModality());
-
-        // TODO: refactor in a base class for DicomGetScuJob and DicomMoveScuJob
-        if (timeout >= 0)
-        {
-          // New in Orthanc 1.7.0
-          job->SetTimeout(static_cast<uint32_t>(timeout));
-        }
-        else if (query.GetHandler().HasTimeout())
-        {
-          // New in Orthanc 1.9.1
-          job->SetTimeout(query.GetHandler().GetTimeout());
-        }
+        job.reset(new DicomGetScuJob(context));
 
         LOG(WARNING) << "Driving C-Get SCU on remote modality "
                     << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle();
-
-        // TODO: refactor in a base class for DicomGetScuJob and DicomMoveScuJob
-        if (allAnswers)
-        {
-          for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++)
-          {
-            job->AddFindAnswer(query.GetHandler(), i);
-          }
-        }
-        else
-        {
-          job->AddFindAnswer(query.GetHandler(), index);
-        }
-
-        OrthancRestApi::GetApi(call).SubmitCommandsJob
-          (call, job.release(), true /* synchronous by default */, body);
-
       }; break;
       default:
         throw OrthancException(ErrorCode_NotImplemented);
     }
+
+    job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short));
+
+    job->SetLocalAet(query.GetHandler().GetLocalAet());
+    job->SetRemoteModality(query.GetHandler().GetRemoteModality());
+
+    if (timeout >= 0)
+    {
+      // New in Orthanc 1.7.0
+      job->SetTimeout(static_cast<uint32_t>(timeout));
+    }
+    else if (query.GetHandler().HasTimeout())
+    {
+      // New in Orthanc 1.9.1
+      job->SetTimeout(query.GetHandler().GetTimeout());
+    }
+
+    if (allAnswers)
+    {
+      for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++)
+      {
+        job->AddFindAnswer(query.GetHandler(), i);
+      }
+    }
+    else
+    {
+      job->AddFindAnswer(query.GetHandler(), index);
+    }
+
+    OrthancRestApi::GetApi(call).SubmitCommandsJob
+      (call, job.release(), true /* synchronous by default */, body);
+
   }
 
 
--- a/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp	Thu Dec 05 09:30:05 2024 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp	Thu Dec 05 12:02:33 2024 +0100
@@ -37,52 +37,6 @@
 
 namespace Orthanc
 {
-  class DicomGetScuJob::Command : public SetOfCommandsJob::ICommand
-  {
-  private:
-    DicomGetScuJob&            that_;
-    std::unique_ptr<DicomMap>  findAnswer_;
-
-  public:
-    Command(DicomGetScuJob& that,
-            const DicomMap&  findAnswer) :
-      that_(that),
-      findAnswer_(findAnswer.Clone())
-    {
-    }
-
-    virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE
-    {
-      that_.Retrieve(*findAnswer_);
-      return true;
-    }
-
-    virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE
-    {
-      findAnswer_->Serialize(target);
-    }
-  };
-
-
-  class DicomGetScuJob::Unserializer :
-    public SetOfCommandsJob::ICommandUnserializer
-  {
-  private:
-    DicomGetScuJob&   that_;
-
-  public:
-    explicit Unserializer(DicomGetScuJob&  that) :
-      that_(that)
-    {
-    }
-
-    virtual ICommand* Unserialize(const Json::Value& source) const ORTHANC_OVERRIDE
-    {
-      DicomMap findAnswer;
-      findAnswer.Unserialize(source);
-      return new Command(that_, findAnswer);
-    }
-  };
 
 
   static uint16_t InstanceReceivedHandler(void* callbackContext,
@@ -149,32 +103,6 @@
     connection_->Get(findAnswer, InstanceReceivedHandler, &context_);
   }
 
-  // this method is used to implement the retrieve part of a Q&R 
-  // it keeps only the main dicom tags from the C-Find answer
-  void DicomGetScuJob::AddFindAnswer(const DicomMap& answer)
-  {
-    DicomMap item;
-    item.CopyTagIfExists(answer, DICOM_TAG_QUERY_RETRIEVE_LEVEL);
-    item.CopyTagIfExists(answer, DICOM_TAG_PATIENT_ID);
-    item.CopyTagIfExists(answer, DICOM_TAG_STUDY_INSTANCE_UID);
-    item.CopyTagIfExists(answer, DICOM_TAG_SERIES_INSTANCE_UID);
-    item.CopyTagIfExists(answer, DICOM_TAG_SOP_INSTANCE_UID);
-    item.CopyTagIfExists(answer, DICOM_TAG_ACCESSION_NUMBER);
-
-    query_.Add(item);
-    query_.GetAnswer(query_.GetSize() - 1).Remove(DICOM_TAG_SPECIFIC_CHARACTER_SET); // Remove the "SpecificCharacterSet" (0008,0005) tag that is automatically added if creating a ParsedDicomFile object from a DicomMap
-
-    AddCommand(new Command(*this, answer));
-  }
-
-  void DicomGetScuJob::AddFindAnswer(QueryRetrieveHandler& query,
-                                     size_t i)
-  {
-    DicomMap answer;
-    query.GetAnswer(answer, i);
-    AddFindAnswer(answer);
-  }    
-
   void DicomGetScuJob::AddResourceToRetrieve(ResourceType level, const std::string& dicomId)
   {
     // TODO-GET: when retrieving a single series, one must provide the StudyInstanceUID too
@@ -210,124 +138,4 @@
     AddCommand(new Command(*this, item));
   }
 
-  void DicomGetScuJob::SetLocalAet(const std::string& aet)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      parameters_.SetLocalApplicationEntityTitle(aet);
-    }
-  }
-
-  
-  void DicomGetScuJob::SetRemoteModality(const RemoteModalityParameters& remote)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      parameters_.SetRemoteModality(remote);
-    }
-  }
-
-
-  void DicomGetScuJob::SetTimeout(uint32_t seconds)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      parameters_.SetTimeout(seconds);
-    }
-  }
-
-  
-  void DicomGetScuJob::Stop(JobStopReason reason)
-  {
-    connection_.reset();
-  }
-  
-
-  void DicomGetScuJob::SetQueryFormat(DicomToJsonFormat format)  // TODO-GET: is this usefull ?
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      queryFormat_ = format;
-    }
-  }
-
-
-  void DicomGetScuJob::GetPublicContent(Json::Value& value)
-  {
-    SetOfCommandsJob::GetPublicContent(value);
-
-    value[LOCAL_AET] = parameters_.GetLocalApplicationEntityTitle();
-    value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle();
-
-    value[QUERY] = Json::objectValue;
-    // query_.ToJson(value[QUERY], queryFormat_);
-  }
-
-
-  DicomGetScuJob::DicomGetScuJob(ServerContext& context,
-                                 const Json::Value& serialized) :
-    SetOfCommandsJob(new Unserializer(*this), serialized),
-    context_(context),
-    parameters_(DicomAssociationParameters::UnserializeJob(serialized)),
-    // targetAet_(SerializationToolbox::ReadString(serialized, TARGET_AET)),
-    query_(true),
-    queryFormat_(DicomToJsonFormat_Short)
-  {
-    if (serialized.isMember(QUERY))
-    {
-      const Json::Value& query = serialized[QUERY];
-      if (query.type() == Json::arrayValue)
-      {
-        for (Json::Value::ArrayIndex i = 0; i < query.size(); i++)
-        {
-          DicomMap item;
-          FromDcmtkBridge::FromJson(item, query[i]);
-          // AddToQuery(query_, item);
-        }
-      }
-    }
-
-    if (serialized.isMember(QUERY_FORMAT))
-    {
-      queryFormat_ = StringToDicomToJsonFormat(SerializationToolbox::ReadString(serialized, QUERY_FORMAT));
-    }
-  }
-
-  
-  bool DicomGetScuJob::Serialize(Json::Value& target)
-  {
-    if (!SetOfCommandsJob::Serialize(target))
-    {
-      return false;
-    }
-    else
-    {
-      parameters_.SerializeJob(target);
-      // target[TARGET_AET] = targetAet_;
-
-      // "Short" is for compatibility with Orthanc <= 1.9.4
-      target[QUERY] = Json::objectValue;
-      // query_.ToJson(target[QUERY], DicomToJsonFormat_Short);
-
-      target[QUERY_FORMAT] = EnumerationToString(queryFormat_);
-      
-      return true;
-    }
-  }
 }
--- a/OrthancServer/Sources/ServerJobs/DicomGetScuJob.h	Thu Dec 05 09:30:05 2024 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomGetScuJob.h	Thu Dec 05 12:02:33 2024 +0100
@@ -25,76 +25,35 @@
 
 #include "../../../OrthancFramework/Sources/Compatibility.h"
 #include "../../../OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h"
-#include "../../../OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h"
-
+#include "DicomRetrieveScuBaseJob.h"
 #include "../QueryRetrieveHandler.h"
 
 namespace Orthanc
 {
   class ServerContext;
   
-  class DicomGetScuJob : public SetOfCommandsJob
+  class DicomGetScuJob : public DicomRetrieveScuBaseJob
   {
   private:
-    class Command;
-    class Unserializer;
     
-    ServerContext&              context_;
-    DicomAssociationParameters  parameters_;
-    DicomFindAnswers            query_;
-    DicomToJsonFormat           queryFormat_;  // New in 1.9.5
-
-    std::unique_ptr<DicomControlUserConnection>  connection_;
-    
-    void Retrieve(const DicomMap& findAnswer);
+    virtual void Retrieve(const DicomMap& findAnswer) ORTHANC_OVERRIDE;
     
   public:
     explicit DicomGetScuJob(ServerContext& context) :
-      context_(context),
-      query_(false  /* this is not for worklists */),
-      queryFormat_(DicomToJsonFormat_Short)
+      DicomRetrieveScuBaseJob(context)
     {
     }
 
     DicomGetScuJob(ServerContext& context,
-                    const Json::Value& serialized);
+                   const Json::Value& serialized);
 
-    void AddFindAnswer(const DicomMap& answer);
-    
-    // void AddQuery(const DicomMap& query);
-
-    void AddFindAnswer(QueryRetrieveHandler& query,
-                       size_t i);
 
     void AddResourceToRetrieve(ResourceType level, const std::string& dicomId);
 
-    const DicomAssociationParameters& GetParameters() const
-    {
-      return parameters_;
-    }
-    
-    void SetLocalAet(const std::string& aet);
-
-    void SetRemoteModality(const RemoteModalityParameters& remote);
-
-    void SetTimeout(uint32_t timeout);
-
-    void SetQueryFormat(DicomToJsonFormat format);
-
-    DicomToJsonFormat GetQueryFormat() const
-    {
-      return queryFormat_;
-    }
-
-    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
 
     virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
     {
       target = "DicomGetScu";
     }
-
-    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
-
-    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp	Thu Dec 05 09:30:05 2024 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp	Thu Dec 05 12:02:33 2024 +0100
@@ -36,52 +36,7 @@
 
 namespace Orthanc
 {
-  class DicomMoveScuJob::Command : public SetOfCommandsJob::ICommand
-  {
-  private:
-    DicomMoveScuJob&           that_;
-    std::unique_ptr<DicomMap>  findAnswer_;
 
-  public:
-    Command(DicomMoveScuJob& that,
-            const DicomMap&  findAnswer) :
-      that_(that),
-      findAnswer_(findAnswer.Clone())
-    {
-    }
-
-    virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE
-    {
-      that_.Retrieve(*findAnswer_);
-      return true;
-    }
-
-    virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE
-    {
-      findAnswer_->Serialize(target);
-    }
-  };
-
-
-  class DicomMoveScuJob::Unserializer :
-    public SetOfCommandsJob::ICommandUnserializer
-  {
-  private:
-    DicomMoveScuJob&   that_;
-
-  public:
-    explicit Unserializer(DicomMoveScuJob&  that) :
-      that_(that)
-    {
-    }
-
-    virtual ICommand* Unserialize(const Json::Value& source) const ORTHANC_OVERRIDE
-    {
-      DicomMap findAnswer;
-      findAnswer.Unserialize(source);
-      return new Command(that_, findAnswer);
-    }
-  };
 
 
   void DicomMoveScuJob::Retrieve(const DicomMap& findAnswer)
@@ -95,64 +50,6 @@
   }
 
 
-  static void AddToQuery(DicomFindAnswers& query,
-                         const DicomMap& item)
-  {
-    query.Add(item);
-
-    /**
-     * Compatibility with Orthanc <= 1.9.4: Remove the
-     * "SpecificCharacterSet" (0008,0005) tag that is automatically
-     * added if creating a ParsedDicomFile object from a DicomMap.
-     **/
-    query.GetAnswer(query.GetSize() - 1).Remove(DICOM_TAG_SPECIFIC_CHARACTER_SET);
-  }
-
-  // this method is used to implement the retrieve part of a Q&R 
-  // it keeps only the main dicom tags from the C-Find answer
-  void DicomMoveScuJob::AddFindAnswer(const DicomMap& answer)
-  {
-    DicomMap item;
-    item.CopyTagIfExists(answer, DICOM_TAG_QUERY_RETRIEVE_LEVEL);
-    item.CopyTagIfExists(answer, DICOM_TAG_PATIENT_ID);
-    item.CopyTagIfExists(answer, DICOM_TAG_STUDY_INSTANCE_UID);
-    item.CopyTagIfExists(answer, DICOM_TAG_SERIES_INSTANCE_UID);
-    item.CopyTagIfExists(answer, DICOM_TAG_SOP_INSTANCE_UID);
-    item.CopyTagIfExists(answer, DICOM_TAG_ACCESSION_NUMBER);
-    AddToQuery(query_, item);
-    
-    AddCommand(new Command(*this, answer));
-  }
-
-  // this method is used to implement a C-Move
-  // it keeps all tags from the C-Move query
-  void DicomMoveScuJob::AddQuery(const DicomMap& query)
-  {
-    AddToQuery(query_, query);
-    AddCommand(new Command(*this, query));
-  }
-  
-  void DicomMoveScuJob::AddFindAnswer(QueryRetrieveHandler& query,
-                                      size_t i)
-  {
-    DicomMap answer;
-    query.GetAnswer(answer, i);
-    AddFindAnswer(answer);
-  }    
-
-
-  void DicomMoveScuJob::SetLocalAet(const std::string& aet)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      parameters_.SetLocalApplicationEntityTitle(aet);
-    }
-  }
-
   
   void DicomMoveScuJob::SetTargetAet(const std::string& aet)
   {
@@ -167,109 +64,33 @@
   }
 
   
-  void DicomMoveScuJob::SetRemoteModality(const RemoteModalityParameters& remote)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      parameters_.SetRemoteModality(remote);
-    }
-  }
-
-
-  void DicomMoveScuJob::SetTimeout(uint32_t seconds)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      parameters_.SetTimeout(seconds);
-    }
-  }
-
-  
-  void DicomMoveScuJob::Stop(JobStopReason reason)
-  {
-    connection_.reset();
-  }
-  
-
-  void DicomMoveScuJob::SetQueryFormat(DicomToJsonFormat format)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      queryFormat_ = format;
-    }
-  }
 
 
   void DicomMoveScuJob::GetPublicContent(Json::Value& value)
   {
-    SetOfCommandsJob::GetPublicContent(value);
+    DicomRetrieveScuBaseJob::GetPublicContent(value);
 
-    value[LOCAL_AET] = parameters_.GetLocalApplicationEntityTitle();
-    value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle();
-
-    value[QUERY] = Json::objectValue;
-    query_.ToJson(value[QUERY], queryFormat_);
+    value[TARGET_AET] = targetAet_;
   }
 
 
   DicomMoveScuJob::DicomMoveScuJob(ServerContext& context,
                                    const Json::Value& serialized) :
-    SetOfCommandsJob(new Unserializer(*this), serialized),
-    context_(context),
-    parameters_(DicomAssociationParameters::UnserializeJob(serialized)),
-    targetAet_(SerializationToolbox::ReadString(serialized, TARGET_AET)),
-    query_(true),
-    queryFormat_(DicomToJsonFormat_Short)
+    DicomRetrieveScuBaseJob(context, serialized),
+    targetAet_(SerializationToolbox::ReadString(serialized, TARGET_AET))
   {
-    if (serialized.isMember(QUERY))
-    {
-      const Json::Value& query = serialized[QUERY];
-      if (query.type() == Json::arrayValue)
-      {
-        for (Json::Value::ArrayIndex i = 0; i < query.size(); i++)
-        {
-          DicomMap item;
-          FromDcmtkBridge::FromJson(item, query[i]);
-          AddToQuery(query_, item);
-        }
-      }
-    }
-
-    if (serialized.isMember(QUERY_FORMAT))
-    {
-      queryFormat_ = StringToDicomToJsonFormat(SerializationToolbox::ReadString(serialized, QUERY_FORMAT));
-    }
   }
 
   
   bool DicomMoveScuJob::Serialize(Json::Value& target)
   {
-    if (!SetOfCommandsJob::Serialize(target))
+    if (!DicomRetrieveScuBaseJob::Serialize(target))
     {
       return false;
     }
     else
     {
-      parameters_.SerializeJob(target);
       target[TARGET_AET] = targetAet_;
-
-      // "Short" is for compatibility with Orthanc <= 1.9.4
-      target[QUERY] = Json::objectValue;
-      query_.ToJson(target[QUERY], DicomToJsonFormat_Short);
-
-      target[QUERY_FORMAT] = EnumerationToString(queryFormat_);
       
       return true;
     }
--- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.h	Thu Dec 05 09:30:05 2024 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.h	Thu Dec 05 12:02:33 2024 +0100
@@ -25,7 +25,8 @@
 
 #include "../../../OrthancFramework/Sources/Compatibility.h"
 #include "../../../OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h"
-#include "../../../OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h"
+// #include "../../../OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h"
+#include "DicomRetrieveScuBaseJob.h"
 
 #include "../QueryRetrieveHandler.h"
 
@@ -33,51 +34,22 @@
 {
   class ServerContext;
   
-  class DicomMoveScuJob : public SetOfCommandsJob
+  class DicomMoveScuJob : public DicomRetrieveScuBaseJob
   {
   private:
-    class Command;
-    class Unserializer;
+    std::string                 targetAet_;
     
-    ServerContext&              context_;
-    DicomAssociationParameters  parameters_;
-    std::string                 targetAet_;
-    DicomFindAnswers            query_;
-    DicomToJsonFormat           queryFormat_;  // New in 1.9.5
-
-    std::unique_ptr<DicomControlUserConnection>  connection_;
-    
-    void Retrieve(const DicomMap& findAnswer);
+    virtual void Retrieve(const DicomMap& findAnswer) ORTHANC_OVERRIDE;
     
   public:
     explicit DicomMoveScuJob(ServerContext& context) :
-      context_(context),
-      query_(false  /* this is not for worklists */),
-      queryFormat_(DicomToJsonFormat_Short)
+      DicomRetrieveScuBaseJob(context)
     {
     }
 
     DicomMoveScuJob(ServerContext& context,
                     const Json::Value& serialized);
 
-    void AddFindAnswer(const DicomMap& answer);
-    
-    void AddQuery(const DicomMap& query);
-
-    void AddFindAnswer(QueryRetrieveHandler& query,
-                       size_t i);
-
-    const DicomAssociationParameters& GetParameters() const
-    {
-      return parameters_;
-    }
-    
-    void SetLocalAet(const std::string& aet);
-
-    void SetRemoteModality(const RemoteModalityParameters& remote);
-
-    void SetTimeout(uint32_t timeout);
-
     const std::string& GetTargetAet() const
     {
       return targetAet_;
@@ -85,15 +57,6 @@
     
     void SetTargetAet(const std::string& aet);
 
-    void SetQueryFormat(DicomToJsonFormat format);
-
-    DicomToJsonFormat GetQueryFormat() const
-    {
-      return queryFormat_;
-    }
-
-    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
-
     virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
     {
       target = "DicomMoveScu";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Thu Dec 05 12:02:33 2024 +0100
@@ -0,0 +1,205 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomGetScuJob.h"
+
+#include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
+#include "../../../OrthancFramework/Sources/SerializationToolbox.h"
+#include "../ServerContext.h"
+#include <dcmtk/dcmnet/dimse.h>
+#include <algorithm>
+
+static const char* const LOCAL_AET = "LocalAet";
+static const char* const QUERY = "Query";
+static const char* const QUERY_FORMAT = "QueryFormat";  // New in 1.9.5
+static const char* const REMOTE = "Remote";
+static const char* const TIMEOUT = "Timeout";
+
+namespace Orthanc
+{
+
+  static void AddToQuery(DicomFindAnswers& query,
+                         const DicomMap& item)
+  {
+    query.Add(item);
+
+    /**
+     * Compatibility with Orthanc <= 1.9.4: Remove the
+     * "SpecificCharacterSet" (0008,0005) tag that is automatically
+     * added if creating a ParsedDicomFile object from a DicomMap.
+     **/
+    query.GetAnswer(query.GetSize() - 1).Remove(DICOM_TAG_SPECIFIC_CHARACTER_SET);
+  }
+
+  // this method is used to implement the retrieve part of a Q&R 
+  // it keeps only the main dicom tags from the C-Find answer
+  void DicomRetrieveScuBaseJob::AddFindAnswer(const DicomMap& answer)
+  {
+    DicomMap item;
+    item.CopyTagIfExists(answer, DICOM_TAG_QUERY_RETRIEVE_LEVEL);
+    item.CopyTagIfExists(answer, DICOM_TAG_PATIENT_ID);
+    item.CopyTagIfExists(answer, DICOM_TAG_STUDY_INSTANCE_UID);
+    item.CopyTagIfExists(answer, DICOM_TAG_SERIES_INSTANCE_UID);
+    item.CopyTagIfExists(answer, DICOM_TAG_SOP_INSTANCE_UID);
+    item.CopyTagIfExists(answer, DICOM_TAG_ACCESSION_NUMBER);
+    AddToQuery(query_, item);
+    
+    AddCommand(new Command(*this, answer));
+  }
+
+  void DicomRetrieveScuBaseJob::AddFindAnswer(QueryRetrieveHandler& query,
+                                              size_t i)
+  {
+    DicomMap answer;
+    query.GetAnswer(answer, i);
+    AddFindAnswer(answer);
+  }    
+
+  // this method is used to implement a C-Move
+  // it keeps all tags from the C-Move query
+  void DicomRetrieveScuBaseJob::AddQuery(const DicomMap& query)
+  {
+    AddToQuery(query_, query);
+    AddCommand(new Command(*this, query));
+  }
+ 
+
+  void DicomRetrieveScuBaseJob::SetLocalAet(const std::string& aet)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      parameters_.SetLocalApplicationEntityTitle(aet);
+    }
+  }
+
+  
+  void DicomRetrieveScuBaseJob::SetRemoteModality(const RemoteModalityParameters& remote)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      parameters_.SetRemoteModality(remote);
+    }
+  }
+
+
+  void DicomRetrieveScuBaseJob::SetTimeout(uint32_t seconds)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      parameters_.SetTimeout(seconds);
+    }
+  }
+
+  
+  void DicomRetrieveScuBaseJob::Stop(JobStopReason reason)
+  {
+    connection_.reset();
+  }
+  
+
+  void DicomRetrieveScuBaseJob::SetQueryFormat(DicomToJsonFormat format)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      queryFormat_ = format;
+    }
+  }
+
+
+  void DicomRetrieveScuBaseJob::GetPublicContent(Json::Value& value)
+  {
+    SetOfCommandsJob::GetPublicContent(value);
+
+    value[LOCAL_AET] = parameters_.GetLocalApplicationEntityTitle();
+    value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle();
+
+    value[QUERY] = Json::objectValue;
+    query_.ToJson(value[QUERY], queryFormat_);
+  }
+
+
+  DicomRetrieveScuBaseJob::DicomRetrieveScuBaseJob(ServerContext& context,
+                                                   const Json::Value& serialized) :
+    SetOfCommandsJob(new Unserializer(*this), serialized),
+    context_(context),
+    parameters_(DicomAssociationParameters::UnserializeJob(serialized)),
+    query_(true),
+    queryFormat_(DicomToJsonFormat_Short)
+  {
+    if (serialized.isMember(QUERY))
+    {
+      const Json::Value& query = serialized[QUERY];
+      if (query.type() == Json::arrayValue)
+      {
+        for (Json::Value::ArrayIndex i = 0; i < query.size(); i++)
+        {
+          DicomMap item;
+          FromDcmtkBridge::FromJson(item, query[i]);
+          AddToQuery(query_, item);
+        }
+      }
+    }
+
+    if (serialized.isMember(QUERY_FORMAT))
+    {
+      queryFormat_ = StringToDicomToJsonFormat(SerializationToolbox::ReadString(serialized, QUERY_FORMAT));
+    }
+  }
+
+  
+  bool DicomRetrieveScuBaseJob::Serialize(Json::Value& target)
+  {
+    if (!SetOfCommandsJob::Serialize(target))
+    {
+      return false;
+    }
+    else
+    {
+      parameters_.SerializeJob(target);
+
+      // "Short" is for compatibility with Orthanc <= 1.9.4
+      target[QUERY] = Json::objectValue;
+      query_.ToJson(target[QUERY], DicomToJsonFormat_Short);
+
+      target[QUERY_FORMAT] = EnumerationToString(queryFormat_);
+      
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h	Thu Dec 05 12:02:33 2024 +0100
@@ -0,0 +1,134 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "../../../OrthancFramework/Sources/Compatibility.h"
+#include "../../../OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h"
+#include "../../../OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h"
+
+#include "../QueryRetrieveHandler.h"
+
+namespace Orthanc
+{
+  class ServerContext;
+
+  class DicomRetrieveScuBaseJob : public SetOfCommandsJob
+  {
+  protected:
+    class Command : public SetOfCommandsJob::ICommand
+    {
+    private:
+      DicomRetrieveScuBaseJob &that_;
+      std::unique_ptr<DicomMap> findAnswer_;
+
+    public:
+      Command(DicomRetrieveScuBaseJob &that,
+              const DicomMap &findAnswer) :
+      that_(that),
+      findAnswer_(findAnswer.Clone())
+      {
+      }
+
+      virtual bool Execute(const std::string &jobId) ORTHANC_OVERRIDE
+      {
+        that_.Retrieve(*findAnswer_);
+        return true;
+      }
+
+      virtual void Serialize(Json::Value &target) const ORTHANC_OVERRIDE
+      {
+        findAnswer_->Serialize(target);
+      }
+    };
+
+    class Unserializer : public SetOfCommandsJob::ICommandUnserializer
+    {
+    protected:
+      DicomRetrieveScuBaseJob &that_;
+
+    public:
+      explicit Unserializer(DicomRetrieveScuBaseJob &that) : 
+      that_(that)
+      {
+      }
+
+      virtual ICommand *Unserialize(const Json::Value &source) const ORTHANC_OVERRIDE
+      {
+        DicomMap findAnswer;
+        findAnswer.Unserialize(source);
+        return new Command(that_, findAnswer);
+      }
+    };
+
+    ServerContext &context_;
+    DicomAssociationParameters parameters_;
+    DicomFindAnswers query_;
+    DicomToJsonFormat queryFormat_; // New in 1.9.5
+
+    std::unique_ptr<DicomControlUserConnection> connection_;
+
+    virtual void Retrieve(const DicomMap &findAnswer) = 0;
+
+    explicit DicomRetrieveScuBaseJob(ServerContext &context) : 
+    context_(context),
+    query_(false /* this is not for worklists */),
+    queryFormat_(DicomToJsonFormat_Short)
+    {
+    }
+
+    DicomRetrieveScuBaseJob(ServerContext &context,
+                            const Json::Value &serialized);
+
+  public:
+    void AddFindAnswer(const DicomMap &answer);
+
+    void AddQuery(const DicomMap& query);
+
+    void AddFindAnswer(QueryRetrieveHandler &query,
+                       size_t i);
+
+    const DicomAssociationParameters &GetParameters() const
+    {
+      return parameters_;
+    }
+
+    void SetLocalAet(const std::string &aet);
+
+    void SetRemoteModality(const RemoteModalityParameters &remote);
+
+    void SetTimeout(uint32_t timeout);
+
+    void SetQueryFormat(DicomToJsonFormat format);
+
+    DicomToJsonFormat GetQueryFormat() const
+    {
+      return queryFormat_;
+    }
+
+    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
+
+    virtual void GetPublicContent(Json::Value &value) ORTHANC_OVERRIDE;
+
+    virtual bool Serialize(Json::Value &target) ORTHANC_OVERRIDE;
+  };
+}