# HG changeset patch # User Sebastien Jodogne # Date 1539007731 -7200 # Node ID 251614c2edac9fef1fc0f2155a63803d28944a50 # Parent 437e6ba20a5e8ed54c6da84639a47a25e3fdf58b DicomMoveScuJob diff -r 437e6ba20a5e -r 251614c2edac CMakeLists.txt --- a/CMakeLists.txt Mon Oct 08 11:40:31 2018 +0200 +++ b/CMakeLists.txt Mon Oct 08 16:08:51 2018 +0200 @@ -84,6 +84,7 @@ OrthancServer/ServerIndex.cpp OrthancServer/ServerJobs/ArchiveJob.cpp OrthancServer/ServerJobs/DicomModalityStoreJob.cpp + OrthancServer/ServerJobs/DicomMoveScuJob.cpp OrthancServer/ServerJobs/LuaJobManager.cpp OrthancServer/ServerJobs/MergeStudyJob.cpp OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp diff -r 437e6ba20a5e -r 251614c2edac Core/JobsEngine/JobsRegistry.cpp --- a/Core/JobsEngine/JobsRegistry.cpp Mon Oct 08 11:40:31 2018 +0200 +++ b/Core/JobsEngine/JobsRegistry.cpp Mon Oct 08 16:08:51 2018 +0200 @@ -685,22 +685,53 @@ } - bool JobsRegistry::SubmitAndWait(IJob* job, // Takes ownership + bool JobsRegistry::SubmitAndWait(Json::Value& successContent, + IJob* job, // Takes ownership int priority) { std::string id; Submit(id, job, priority); - JobState state = JobState_Pending; + JobState state = JobState_Pending; // Dummy initialization { boost::mutex::scoped_lock lock(mutex_); - while (GetStateInternal(state, id) && - state != JobState_Success && - state != JobState_Failure) + for (;;) { - someJobComplete_.wait(lock); + if (!GetStateInternal(state, id)) + { + // Job has finished and has been lost (should not happen) + state = JobState_Failure; + break; + } + else if (state == JobState_Failure) + { + // Failure + break; + } + else if (state == JobState_Success) + { + // Success, try and retrieve the status of the job + JobsIndex::const_iterator it = jobsIndex_.find(id); + if (it == jobsIndex_.end()) + { + // Should not happen + state = JobState_Failure; + } + else + { + const JobStatus& status = it->second->GetLastStatus(); + successContent = status.GetPublicContent(); + } + + break; + } + else + { + // This job has not finished yet, wait for new completion + someJobComplete_.wait(lock); + } } } diff -r 437e6ba20a5e -r 251614c2edac Core/JobsEngine/JobsRegistry.h --- a/Core/JobsEngine/JobsRegistry.h Mon Oct 08 11:40:31 2018 +0200 +++ b/Core/JobsEngine/JobsRegistry.h Mon Oct 08 16:08:51 2018 +0200 @@ -161,7 +161,8 @@ void Submit(IJob* job, // Takes ownership int priority); - bool SubmitAndWait(IJob* job, // Takes ownership + bool SubmitAndWait(Json::Value& successContent, + IJob* job, // Takes ownership int priority); bool SetPriority(const std::string& id, diff -r 437e6ba20a5e -r 251614c2edac NEWS --- a/NEWS Mon Oct 08 11:40:31 2018 +0200 +++ b/NEWS Mon Oct 08 16:08:51 2018 +0200 @@ -11,6 +11,7 @@ Maintenance ----------- +* Executing a query/retrieve from the REST API now creates a job * Fix: Closing DICOM associations after running query/retrieve from REST API diff -r 437e6ba20a5e -r 251614c2edac OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Oct 08 11:40:31 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Oct 08 16:08:51 2018 +0200 @@ -164,7 +164,9 @@ context.AddChildInstances(*job, call.GetUriComponent("id", "")); - if (context.GetJobsEngine().GetRegistry().SubmitAndWait(job.release(), priority)) + Json::Value publicContent; + if (context.GetJobsEngine().GetRegistry().SubmitAndWait + (publicContent, job.release(), priority)) { Json::Value json; if (output->Format(json)) diff -r 437e6ba20a5e -r 251614c2edac OrthancServer/OrthancRestApi/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Mon Oct 08 11:40:31 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Mon Oct 08 16:08:51 2018 +0200 @@ -35,6 +35,7 @@ #include "OrthancRestApi.h" #include "../../Core/Logging.h" +#include "../../Core/SerializationToolbox.h" #include "../ServerContext.h" namespace Orthanc @@ -139,4 +140,102 @@ { return GetContext(call).GetIndex(); } + + + + static const char* KEY_PERMISSIVE = "Permissive"; + static const char* KEY_PRIORITY = "Priority"; + static const char* KEY_SYNCHRONOUS = "Synchronous"; + static const char* KEY_ASYNCHRONOUS = "Asynchronous"; + + void OrthancRestApi::SubmitCommandsJob(RestApiPostCall& call, + SetOfCommandsJob* job, + bool isDefaultSynchronous, + const Json::Value& body) const + { + std::auto_ptr raii(job); + + if (job == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + if (body.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + job->SetDescription("REST API"); + + if (body.isMember(KEY_PERMISSIVE)) + { + job->SetPermissive(SerializationToolbox::ReadBoolean(body, KEY_PERMISSIVE)); + } + else + { + job->SetPermissive(false); + } + + int priority = 0; + + if (body.isMember(KEY_PRIORITY)) + { + priority = SerializationToolbox::ReadInteger(body, KEY_PRIORITY); + } + + bool synchronous = isDefaultSynchronous; + + if (body.isMember(KEY_SYNCHRONOUS)) + { + synchronous = SerializationToolbox::ReadBoolean(body, KEY_SYNCHRONOUS); + } + else if (body.isMember(KEY_ASYNCHRONOUS)) + { + synchronous = !SerializationToolbox::ReadBoolean(body, KEY_ASYNCHRONOUS); + } + + if (synchronous) + { + Json::Value successContent; + if (context_.GetJobsEngine().GetRegistry().SubmitAndWait + (successContent, raii.release(), priority)) + { + // Success in synchronous execution + call.GetOutput().AnswerJson(successContent); + } + else + { + // Error during synchronous execution + call.GetOutput().SignalError(HttpStatus_500_InternalServerError); + } + } + else + { + // Asynchronous mode: Submit the job, but don't wait for its completion + std::string id; + context_.GetJobsEngine().GetRegistry().Submit(id, raii.release(), priority); + + Json::Value v; + v["ID"] = id; + v["Path"] = "/jobs/" + id; + call.GetOutput().AnswerJson(v); + } + } + + + void OrthancRestApi::SubmitCommandsJob(RestApiPostCall& call, + SetOfCommandsJob* job, + bool isDefaultSynchronous) const + { + std::auto_ptr raii(job); + + Json::Value body; + + if (!call.ParseJsonRequest(body)) + { + body = Json::objectValue; + } + + SubmitCommandsJob(call, raii.release(), isDefaultSynchronous, body); + } } diff -r 437e6ba20a5e -r 251614c2edac OrthancServer/OrthancRestApi/OrthancRestApi.h --- a/OrthancServer/OrthancRestApi/OrthancRestApi.h Mon Oct 08 11:40:31 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Mon Oct 08 16:08:51 2018 +0200 @@ -33,6 +33,7 @@ #pragma once +#include "../../Core/JobsEngine/SetOfCommandsJob.h" #include "../../Core/RestApi/RestApi.h" #include "../../Core/DicomParsing/DicomModification.h" #include "../ServerEnumerations.h" @@ -96,5 +97,14 @@ const std::string& publicId, ResourceType resourceType, StoreStatus status) const; + + void SubmitCommandsJob(RestApiPostCall& call, + SetOfCommandsJob* job, + bool isDefaultSynchronous, + const Json::Value& body) const; + + void SubmitCommandsJob(RestApiPostCall& call, + SetOfCommandsJob* job, + bool isDefaultSynchronous) const; }; } diff -r 437e6ba20a5e -r 251614c2edac OrthancServer/OrthancRestApi/OrthancRestArchive.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Mon Oct 08 11:40:31 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Mon Oct 08 16:08:51 2018 +0200 @@ -78,7 +78,9 @@ job->SetDescription("REST API"); - if (context.GetJobsEngine().GetRegistry().SubmitAndWait(job.release(), 0 /* TODO priority */)) + Json::Value publicContent; + if (context.GetJobsEngine().GetRegistry().SubmitAndWait + (publicContent, job.release(), 0 /* TODO priority */)) { // The archive is now created: Prepare the sending of the ZIP file FilesystemHttpSender sender(tmp->GetPath()); diff -r 437e6ba20a5e -r 251614c2edac OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Mon Oct 08 11:40:31 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Mon Oct 08 16:08:51 2018 +0200 @@ -36,9 +36,11 @@ #include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Core/Logging.h" +#include "../../Core/SerializationToolbox.h" #include "../OrthancInitialization.h" #include "../QueryRetrieveHandler.h" #include "../ServerJobs/DicomModalityStoreJob.h" +#include "../ServerJobs/DicomMoveScuJob.h" #include "../ServerJobs/OrthancPeerStoreJob.h" #include "../ServerToolbox.h" @@ -470,9 +472,9 @@ { } - QueryRetrieveHandler* operator->() + QueryRetrieveHandler& GetHandler() const { - return &handler_; + return handler_; } }; @@ -490,7 +492,7 @@ static void ListQueryAnswers(RestApiGetCall& call) { QueryAccessor query(call); - size_t count = query->GetAnswerCount(); + size_t count = query.GetHandler().GetAnswersCount(); Json::Value result = Json::arrayValue; for (size_t i = 0; i < count; i++) @@ -509,62 +511,92 @@ QueryAccessor query(call); DicomMap map; - query->GetAnswer(map, index); + query.GetHandler().GetAnswer(map, index); AnswerDicomMap(call, map, call.HasArgument("simplify")); } + static void SubmitRetrieveJob(RestApiPostCall& call, + bool allAnswers, + size_t index) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string targetAet; + + Json::Value body; + if (call.ParseJsonRequest(body)) + { + targetAet = SerializationToolbox::ReadString(body, "TargetAet"); + } + else + { + body = Json::objectValue; + call.BodyToString(targetAet); + } + + std::auto_ptr job(new DicomMoveScuJob(context)); + + { + QueryAccessor query(call); + job->SetTargetAet(targetAet); + job->SetLocalAet(query.GetHandler().GetLocalAet()); + job->SetRemoteModality(query.GetHandler().GetRemoteModality()); + + LOG(WARNING) << "Driving C-Move SCU on remote modality " + << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle() + << " to target modality " << targetAet; + + 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); + } + + static void RetrieveOneAnswer(RestApiPostCall& call) { size_t index = boost::lexical_cast(call.GetUriComponent("index", "")); - - std::string modality; - call.BodyToString(modality); - - LOG(WARNING) << "Driving C-Move SCU on modality: " << modality; - - QueryAccessor query(call); - query->Retrieve(modality, index); - - // Retrieve has succeeded - call.GetOutput().AnswerBuffer("{}", "application/json"); + SubmitRetrieveJob(call, false, index); } static void RetrieveAllAnswers(RestApiPostCall& call) { - std::string modality; - call.BodyToString(modality); - - LOG(WARNING) << "Driving C-Move SCU on modality: " << modality; - - QueryAccessor query(call); - query->Retrieve(modality); - - // Retrieve has succeeded - call.GetOutput().AnswerBuffer("{}", "application/json"); + SubmitRetrieveJob(call, true, 0); } static void GetQueryArguments(RestApiGetCall& call) { QueryAccessor query(call); - AnswerDicomMap(call, query->GetQuery(), call.HasArgument("simplify")); + AnswerDicomMap(call, query.GetHandler().GetQuery(), call.HasArgument("simplify")); } static void GetQueryLevel(RestApiGetCall& call) { QueryAccessor query(call); - call.GetOutput().AnswerBuffer(EnumerationToString(query->GetLevel()), "text/plain"); + call.GetOutput().AnswerBuffer(EnumerationToString(query.GetHandler().GetLevel()), "text/plain"); } static void GetQueryModality(RestApiGetCall& call) { QueryAccessor query(call); - call.GetOutput().AnswerBuffer(query->GetModalitySymbolicName(), "text/plain"); + call.GetOutput().AnswerBuffer(query.GetHandler().GetModalitySymbolicName(), "text/plain"); } @@ -594,7 +626,7 @@ size_t index = boost::lexical_cast(call.GetUriComponent("index", "")); DicomMap map; - query->GetAnswer(map, index); + query.GetHandler().GetAnswer(map, index); RestApi::AutoListChildren(call); } @@ -708,6 +740,8 @@ job->SetPermissive(permissive); + Json::Value publicContent; + if (asynchronous) { // Asynchronous mode: Submit the job, but don't wait for its completion @@ -718,7 +752,8 @@ v["ID"] = id; call.GetOutput().AnswerJson(v); } - else if (context.GetJobsEngine().GetRegistry().SubmitAndWait(job.release(), priority)) + else if (context.GetJobsEngine().GetRegistry().SubmitAndWait + (publicContent, job.release(), priority)) { // Synchronous mode: We have submitted and waited for completion call.GetOutput().AnswerBuffer("{}", "application/json"); diff -r 437e6ba20a5e -r 251614c2edac OrthancServer/QueryRetrieveHandler.cpp --- a/OrthancServer/QueryRetrieveHandler.cpp Mon Oct 08 11:40:31 2018 +0200 +++ b/OrthancServer/QueryRetrieveHandler.cpp Mon Oct 08 16:08:51 2018 +0200 @@ -138,7 +138,7 @@ } - size_t QueryRetrieveHandler::GetAnswerCount() + size_t QueryRetrieveHandler::GetAnswersCount() { Run(); return answers_.GetSize(); @@ -151,36 +151,4 @@ Run(); answers_.GetAnswer(i).ExtractDicomSummary(target); } - - - void QueryRetrieveHandler::RetrieveInternal(DicomUserConnection& connection, - const std::string& target, - size_t i) - { - DicomMap map; - GetAnswer(map, i); - connection.Move(target, map); - } - - - void QueryRetrieveHandler::Retrieve(const std::string& target, - size_t i) - { - DicomUserConnection connection(localAet_, modality_); - connection.Open(); - - RetrieveInternal(connection, target, i); - } - - - void QueryRetrieveHandler::Retrieve(const std::string& target) - { - DicomUserConnection connection(localAet_, modality_); - connection.Open(); - - for (size_t i = 0; i < GetAnswerCount(); i++) - { - RetrieveInternal(connection, target, i); - } - } } diff -r 437e6ba20a5e -r 251614c2edac OrthancServer/QueryRetrieveHandler.h --- a/OrthancServer/QueryRetrieveHandler.h Mon Oct 08 11:40:31 2018 +0200 +++ b/OrthancServer/QueryRetrieveHandler.h Mon Oct 08 16:08:51 2018 +0200 @@ -41,7 +41,7 @@ { private: ServerContext& context_; - const std::string& localAet_; + std::string localAet_; bool done_; RemoteModalityParameters modality_; ResourceType level_; @@ -51,20 +51,21 @@ void Invalidate(); - void RetrieveInternal(DicomUserConnection& connection, - const std::string& target, - size_t i); - public: QueryRetrieveHandler(ServerContext& context); void SetModality(const std::string& symbolicName); - const RemoteModalityParameters& GetModality() const + const RemoteModalityParameters& GetRemoteModality() const { return modality_; } + const std::string& GetLocalAet() const + { + return localAet_; + } + const std::string& GetModalitySymbolicName() const { return modalityName_; @@ -87,14 +88,9 @@ void Run(); - size_t GetAnswerCount(); + size_t GetAnswersCount(); void GetAnswer(DicomMap& target, size_t i); - - void Retrieve(const std::string& target, - size_t i); - - void Retrieve(const std::string& target); }; } diff -r 437e6ba20a5e -r 251614c2edac OrthancServer/ServerJobs/DicomMoveScuJob.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/DicomMoveScuJob.cpp Mon Oct 08 16:08:51 2018 +0200 @@ -0,0 +1,199 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 . + **/ + + +#include "DicomMoveScuJob.h" + +#include "../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + class DicomMoveScuJob::Command : public SetOfCommandsJob::ICommand + { + private: + DicomMoveScuJob& that_; + std::auto_ptr findAnswer_; + + public: + Command(DicomMoveScuJob& that, + const DicomMap& findAnswer) : + that_(that), + findAnswer_(findAnswer.Clone()) + { + } + + virtual bool Execute() + { + that_.Retrieve(*findAnswer_); + return true; + } + + virtual void Serialize(Json::Value& target) const + { + findAnswer_->Serialize(target); + } + }; + + + class DicomMoveScuJob::Unserializer : + public SetOfCommandsJob::ICommandUnserializer + { + private: + DicomMoveScuJob& that_; + + public: + Unserializer(DicomMoveScuJob& that) : + that_(that) + { + } + + virtual ICommand* Unserialize(const Json::Value& source) const + { + DicomMap findAnswer; + findAnswer.Unserialize(source); + return new Command(that_, findAnswer); + } + }; + + + + void DicomMoveScuJob::Retrieve(const DicomMap& findAnswer) + { + if (connection_.get() == NULL) + { + connection_.reset(new DicomUserConnection(localAet_, remote_)); + connection_->Open(); + } + + connection_->Move(targetAet_, findAnswer); + } + + + void DicomMoveScuJob::AddFindAnswer(const DicomMap& answer) + { + AddCommand(new Command(*this, answer)); + } + + + 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 + { + localAet_ = aet; + } + } + + + void DicomMoveScuJob::SetTargetAet(const std::string& aet) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetAet_ = aet; + } + } + + + void DicomMoveScuJob::SetRemoteModality(const RemoteModalityParameters& remote) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + remote_ = remote; + } + } + + + void DicomMoveScuJob::Stop(JobStopReason reason) + { + connection_.reset(); + } + + + void DicomMoveScuJob::GetPublicContent(Json::Value& value) + { + SetOfCommandsJob::GetPublicContent(value); + + value["LocalAet"] = localAet_; + value["RemoteAet"] = remote_.GetApplicationEntityTitle(); + } + + + static const char* LOCAL_AET = "LocalAet"; + static const char* TARGET_AET = "TargetAet"; + static const char* REMOTE = "Remote"; + + DicomMoveScuJob::DicomMoveScuJob(ServerContext& context, + const Json::Value& serialized) : + SetOfCommandsJob(new Unserializer(*this), serialized), + context_(context) + { + localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET); + targetAet_ = SerializationToolbox::ReadString(serialized, TARGET_AET); + remote_ = RemoteModalityParameters(serialized[REMOTE]); + } + + + bool DicomMoveScuJob::Serialize(Json::Value& target) + { + if (!SetOfCommandsJob::Serialize(target)) + { + return false; + } + else + { + target[LOCAL_AET] = localAet_; + target[TARGET_AET] = targetAet_; + remote_.Serialize(target[REMOTE]); + return true; + } + } +} diff -r 437e6ba20a5e -r 251614c2edac OrthancServer/ServerJobs/DicomMoveScuJob.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/DicomMoveScuJob.h Mon Oct 08 16:08:51 2018 +0200 @@ -0,0 +1,104 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 . + **/ + + +#pragma once + +#include "../../Core/JobsEngine/SetOfCommandsJob.h" +#include "../../Core/DicomNetworking/DicomUserConnection.h" + +#include "../QueryRetrieveHandler.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class DicomMoveScuJob : public SetOfCommandsJob + { + private: + class Command; + class Unserializer; + + ServerContext& context_; + std::string localAet_; + std::string targetAet_; + RemoteModalityParameters remote_; + std::auto_ptr connection_; + + void Retrieve(const DicomMap& findAnswer); + + public: + DicomMoveScuJob(ServerContext& context) : + context_(context) + { + } + + DicomMoveScuJob(ServerContext& context, + const Json::Value& serialized); + + void AddFindAnswer(const DicomMap& answer); + + void AddFindAnswer(QueryRetrieveHandler& query, + size_t i); + + const std::string& GetLocalAet() const + { + return localAet_; + } + + void SetLocalAet(const std::string& aet); + + const std::string& GetTargetAet() const + { + return targetAet_; + } + + void SetTargetAet(const std::string& aet); + + const RemoteModalityParameters& GetRemoteModality() const + { + return remote_; + } + + void SetRemoteModality(const RemoteModalityParameters& remote); + + virtual void Stop(JobStopReason reason); + + virtual void GetJobType(std::string& target) + { + target = "DicomMoveScu"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& target); + }; +} diff -r 437e6ba20a5e -r 251614c2edac OrthancServer/ServerJobs/OrthancJobUnserializer.cpp --- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Mon Oct 08 11:40:31 2018 +0200 +++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Mon Oct 08 16:08:51 2018 +0200 @@ -46,6 +46,7 @@ #include "Operations/SystemCallOperation.h" #include "DicomModalityStoreJob.h" +#include "DicomMoveScuJob.h" #include "OrthancPeerStoreJob.h" #include "ResourceModificationJob.h" #include "MergeStudyJob.h" @@ -88,6 +89,10 @@ { return new SplitStudyJob(context_, source); } + else if (type == "DicomMoveScu") + { + return new DicomMoveScuJob(context_, source); + } else { return GenericJobUnserializer::UnserializeJob(source); diff -r 437e6ba20a5e -r 251614c2edac Resources/CMake/OrthancFrameworkParameters.cmake --- a/Resources/CMake/OrthancFrameworkParameters.cmake Mon Oct 08 11:40:31 2018 +0200 +++ b/Resources/CMake/OrthancFrameworkParameters.cmake Mon Oct 08 16:08:51 2018 +0200 @@ -17,7 +17,7 @@ # Version of the Orthanc API, can be retrieved from "/system" URI in # order to check whether new URI endpoints are available even if using # the mainline version of Orthanc -set(ORTHANC_API_VERSION "1.1") +set(ORTHANC_API_VERSION "1.2") ##################################################################### diff -r 437e6ba20a5e -r 251614c2edac UnitTestsSources/MultiThreadingTests.cpp --- a/UnitTestsSources/MultiThreadingTests.cpp Mon Oct 08 11:40:31 2018 +0200 +++ b/UnitTestsSources/MultiThreadingTests.cpp Mon Oct 08 16:08:51 2018 +0200 @@ -716,8 +716,14 @@ engine.SetWorkersCount(3); engine.Start(); - ASSERT_TRUE(engine.GetRegistry().SubmitAndWait(new DummyJob(), rand() % 10)); - ASSERT_FALSE(engine.GetRegistry().SubmitAndWait(new DummyJob(true), rand() % 10)); + Json::Value content = Json::nullValue; + ASSERT_TRUE(engine.GetRegistry().SubmitAndWait(content, new DummyJob(), rand() % 10)); + ASSERT_EQ(Json::objectValue, content.type()); + ASSERT_EQ("world", content["hello"].asString()); + + content = Json::nullValue; + ASSERT_FALSE(engine.GetRegistry().SubmitAndWait(content, new DummyJob(true), rand() % 10)); + ASSERT_EQ(Json::nullValue, content.type()); engine.Stop(); }