changeset 6369:99f6accf4270

Get SCU jobs details
author Alain Mazy <am@orthanc.team>
date Fri, 07 Nov 2025 15:02:25 +0100
parents 41152b638a4a
children 7c8e00ea7830
files NEWS OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h
diffstat 4 files changed, 120 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Nov 05 15:52:01 2025 +0100
+++ b/NEWS	Fri Nov 07 15:02:25 2025 +0100
@@ -15,6 +15,10 @@
   actually failed.
 * C-Store, C-Move and C-Get jobs and HTTP responses now include a new "DimseErrorStatus" 
   field (ONLY if the operation fails).
+* C-Get jobs and HTTP responses now include a new "Details" field with the "DimseErrorStatus"
+  of each operation and the list of "RetrievedInstancesIds".  Note that, if you have multiple
+  resources to retrieve and one of them fails, you'll only get those details if you have set
+  "Permissive" to true to allow other operations to continue after the failure.
 
 
 Maintenance
--- a/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp	Wed Nov 05 15:52:01 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp	Fri Nov 07 15:02:25 2025 +0100
@@ -46,7 +46,7 @@
                                           const std::string& calledAet)
   {
     // this code is equivalent to OrthancStoreRequestHandler
-    ServerContext* context = reinterpret_cast<ServerContext*>(callbackContext);
+    DicomRetrieveScuBaseJob* job = reinterpret_cast<DicomRetrieveScuBaseJob*>(callbackContext);
 
     std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromDcmDataset(dataset));
     
@@ -56,7 +56,14 @@
                          (remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str()));
 
       std::string id;
-      ServerContext::StoreResult result = context->Store(id, *toStore, StoreInstanceMode_Default);
+      ServerContext::StoreResult result = job->GetContext().Store(id, *toStore, StoreInstanceMode_Default);
+
+      // keep track of the received instances in the job
+      if (result.GetStatus() == StoreStatus_Success || result.GetStatus() == StoreStatus_AlreadyStored)
+      {
+        job->AddReceivedInstance(id);
+      }
+
       return result.GetCStoreStatusCode();
     }
 
@@ -99,7 +106,7 @@
     }
 
     connection_->SetProgressListener(this);
-    connection_->Get(findAnswer, InstanceReceivedHandler, &context_);
+    connection_->Get(findAnswer, InstanceReceivedHandler, this);
   }
 
   void DicomGetScuJob::AddFindAnswer(const DicomMap& answer)
--- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Wed Nov 05 15:52:01 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Fri Nov 07 15:02:25 2025 +0100
@@ -39,6 +39,53 @@
 namespace Orthanc
 {
 
+  bool DicomRetrieveScuBaseJob::Command::Execute(const std::string &jobId)
+  {
+    try
+    {
+      that_.currentCommand_ = this;
+      that_.Retrieve(*findAnswer_); // here
+    }
+    catch (OrthancException& e)
+    {
+      dimseErrorStatus_ = e.GetDimseErrorStatus();
+      throw e;
+    }
+    return true;
+  }
+
+  void DicomRetrieveScuBaseJob::Command::Serialize(Json::Value &target) const
+  {
+    findAnswer_->Serialize(target["Query"]);
+    target["DimseErrorStatus"] = dimseErrorStatus_;
+    SerializationToolbox::WriteListOfStrings(target, receivedInstancesIds_, "ReceivedInstancesIds");
+  }
+
+  SetOfCommandsJob::ICommand* DicomRetrieveScuBaseJob::Unserializer::Unserialize(const Json::Value &source) const
+  {
+    DicomMap findAnswer;
+
+    if (!source.isMember("Query")) // old format before 1.12.10, just in case we need to read old jobs after an upgrade
+    {
+      findAnswer.Unserialize(source);
+      return new DicomRetrieveScuBaseJob::Command(that_, findAnswer);
+    }
+
+    findAnswer.Unserialize(source["Query"]);
+
+    std::unique_ptr<DicomRetrieveScuBaseJob::Command> command(new DicomRetrieveScuBaseJob::Command(that_, findAnswer));
+    command->SetDimseErrorStatus(static_cast<uint16_t>(source["DimseErrorStatus"].asUInt()));
+
+    std::list<std::string> receivedInstancesIds;
+    SerializationToolbox::ReadListOfStrings(receivedInstancesIds, source, "ReceivedInstancesIds");
+    for (std::list<std::string>::const_iterator it = receivedInstancesIds.begin(); it != receivedInstancesIds.end(); ++it)
+    {
+      command->AddReceivedInstance(*it);
+    }
+
+    return command.release();
+  }
+
   static void AddToQuery(DicomFindAnswers& query,
                          const DicomMap& item)
   {
@@ -152,6 +199,20 @@
 
     value[QUERY] = Json::objectValue;
     query_.ToJson(value[QUERY], queryFormat_);
+
+    value["Details"] = Json::arrayValue;
+    
+    for (size_t i = 0; i < GetCommandsCount(); ++i)
+    {
+      const DicomRetrieveScuBaseJob::Command& command = dynamic_cast<const DicomRetrieveScuBaseJob::Command&>(GetCommand(i));
+
+      Json::Value v;
+      v["DimseErrorStatus"] = command.GetDimseErrorStatus();
+      query_.ToJson(v["Query"], i, DicomToJsonFormat_Short);
+      SerializationToolbox::WriteListOfStrings(v, command.GetReceivedInstancesIds(), "ReceivedInstancesIds");
+
+      value["Details"].append(v);
+    }
   }
 
 
@@ -162,7 +223,8 @@
     nbRemainingSubOperations_(0),
     nbCompletedSubOperations_(0),
     nbFailedSubOperations_(0),
-    nbWarningSubOperations_(0)
+    nbWarningSubOperations_(0),
+    currentCommand_(NULL)
   {
   }
 
@@ -172,12 +234,13 @@
     SetOfCommandsJob(new Unserializer(*this), serialized),
     context_(context),
     parameters_(DicomAssociationParameters::UnserializeJob(serialized)),
-    query_(true),
+    query_(false /* this is not for worklists */),
     queryFormat_(DicomToJsonFormat_Short),
     nbRemainingSubOperations_(0),
     nbCompletedSubOperations_(0),
     nbFailedSubOperations_(0),
-    nbWarningSubOperations_(0)  
+    nbWarningSubOperations_(0),
+    currentCommand_(NULL)
   {
     if (serialized.isMember(QUERY))
     {
@@ -245,4 +308,12 @@
 
     return float(nbCompletedSubOperations_ + nbFailedSubOperations_ + nbWarningSubOperations_) / float(totalOperations);
   }
+
+  void DicomRetrieveScuBaseJob::AddReceivedInstance(const std::string& instanceId)
+  {
+    if (currentCommand_ != NULL)
+    {
+      currentCommand_->AddReceivedInstance(instanceId);
+    }
+  }
 }
--- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h	Wed Nov 05 15:52:01 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h	Fri Nov 07 15:02:25 2025 +0100
@@ -42,23 +42,40 @@
       DicomRetrieveScuBaseJob &that_;
       std::unique_ptr<DicomMap> findAnswer_;
 
+      uint16_t                dimseErrorStatus_;
+      std::list<std::string>  receivedInstancesIds_;
+
     public:
       Command(DicomRetrieveScuBaseJob &that,
               const DicomMap &findAnswer) :
       that_(that),
-      findAnswer_(findAnswer.Clone())
+      findAnswer_(findAnswer.Clone()),
+      dimseErrorStatus_(0)
       {
       }
 
-      virtual bool Execute(const std::string &jobId) ORTHANC_OVERRIDE
+      virtual bool Execute(const std::string &jobId) ORTHANC_OVERRIDE;
+
+      virtual void Serialize(Json::Value &target) const ORTHANC_OVERRIDE;
+
+      uint16_t GetDimseErrorStatus() const
       {
-        that_.Retrieve(*findAnswer_);
-        return true;
+        return dimseErrorStatus_;
       }
 
-      virtual void Serialize(Json::Value &target) const ORTHANC_OVERRIDE
+      void SetDimseErrorStatus(uint16_t dimseErrorStatus)
+      {
+        dimseErrorStatus_ = dimseErrorStatus;
+      }
+
+      void AddReceivedInstance(const std::string& instanceId)
       {
-        findAnswer_->Serialize(target);
+        receivedInstancesIds_.push_back(instanceId);
+      }
+
+      const std::list<std::string>& GetReceivedInstancesIds() const
+      {
+        return receivedInstancesIds_;
       }
     };
 
@@ -73,12 +90,7 @@
       {
       }
 
-      virtual ICommand *Unserialize(const Json::Value &source) const ORTHANC_OVERRIDE
-      {
-        DicomMap findAnswer;
-        findAnswer.Unserialize(source);
-        return new Command(that_, findAnswer);
-      }
+      virtual ICommand *Unserialize(const Json::Value &source) const ORTHANC_OVERRIDE;
     };
 
     ServerContext &context_;
@@ -94,6 +106,8 @@
     uint16_t nbFailedSubOperations_;
     uint16_t nbWarningSubOperations_;
 
+    Command* currentCommand_;
+
     virtual void Retrieve(const DicomMap &findAnswer) = 0;
 
     explicit DicomRetrieveScuBaseJob(ServerContext &context);
@@ -145,5 +159,11 @@
       return true;
     }
 
+    ServerContext& GetContext()
+    {
+      return context_;
+    }
+
+    void AddReceivedInstance(const std::string& instanceId);
   };
 }