# HG changeset patch # User Sebastien Jodogne # Date 1527078505 -7200 # Node ID bd6e0b70e915acd6e667574f976c5da1154a5489 # Parent 83ac5a05ce844b57396edd08828da0faafd347ea# Parent 3603a2e145921d70affc273fa11d05d4041f4990 integration mainline->jobs diff -r 3603a2e14592 -r bd6e0b70e915 CMakeLists.txt --- a/CMakeLists.txt Wed May 23 10:08:04 2018 +0200 +++ b/CMakeLists.txt Wed May 23 14:28:25 2018 +0200 @@ -70,14 +70,6 @@ OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/QueryRetrieveHandler.cpp - OrthancServer/Scheduler/CallSystemCommand.cpp - OrthancServer/Scheduler/DeleteInstanceCommand.cpp - OrthancServer/Scheduler/ModifyInstanceCommand.cpp - OrthancServer/Scheduler/ServerCommandInstance.cpp - OrthancServer/Scheduler/ServerJob.cpp - OrthancServer/Scheduler/ServerScheduler.cpp - OrthancServer/Scheduler/StorePeerCommand.cpp - OrthancServer/Scheduler/StoreScuCommand.cpp OrthancServer/Search/HierarchicalMatcher.cpp OrthancServer/Search/IFindConstraint.cpp OrthancServer/Search/ListConstraint.cpp @@ -90,6 +82,15 @@ OrthancServer/ServerContext.cpp OrthancServer/ServerEnumerations.cpp OrthancServer/ServerIndex.cpp + OrthancServer/ServerJobs/DeleteResourceOperation.cpp + OrthancServer/ServerJobs/DicomModalityStoreJob.cpp + OrthancServer/ServerJobs/LuaJobManager.cpp + OrthancServer/ServerJobs/ModifyInstanceOperation.cpp + OrthancServer/ServerJobs/OrthancJobUnserializer.cpp + OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp + OrthancServer/ServerJobs/StorePeerOperation.cpp + OrthancServer/ServerJobs/StoreScuOperation.cpp + OrthancServer/ServerJobs/SystemCallOperation.cpp OrthancServer/ServerToolbox.cpp OrthancServer/SliceOrdering.cpp ) diff -r 3603a2e14592 -r bd6e0b70e915 Core/DicomNetworking/DicomUserConnection.cpp --- a/Core/DicomNetworking/DicomUserConnection.cpp Wed May 23 10:08:04 2018 +0200 +++ b/Core/DicomNetworking/DicomUserConnection.cpp Wed May 23 14:28:25 2018 +0200 @@ -264,8 +264,6 @@ const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { - CheckIsOpen(); - DcmFileFormat dcmff; Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); @@ -284,23 +282,36 @@ bool isGeneric = IsGenericTransferSyntax(syntax); bool renegotiate; - if (isGeneric) + + if (!IsOpen()) + { + renegotiate = true; + } + else if (isGeneric) { // Are we making a generic-to-specific or specific-to-generic change of // the transfer syntax? If this is the case, renegotiate the connection. renegotiate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()); + + if (renegotiate) + { + LOG(INFO) << "Use of non-generic transfer syntax: the C-Store associated must be renegotiated"; + } } else { // We are using a specific transfer syntax. Renegotiate if the // current connection does not match this transfer syntax. renegotiate = (syntax != connection.GetPreferredTransferSyntax()); + + if (renegotiate) + { + LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; + } } if (renegotiate) { - LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; - if (isGeneric) { connection.ResetPreferredTransferSyntax(); @@ -313,7 +324,6 @@ if (!connection.IsOpen()) { - LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters"; connection.Open(); } @@ -785,13 +795,12 @@ } - DicomUserConnection::DicomUserConnection() : - pimpl_(new PImpl), - preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX), - localAet_("STORESCU"), - remoteAet_("ANY-SCP"), - remoteHost_("127.0.0.1") + void DicomUserConnection::DefaultSetup() { + preferredTransferSyntax_ = DEFAULT_PREFERRED_TRANSFER_SYNTAX; + localAet_ = "STORESCU"; + remoteAet_ = "ANY-SCP"; + remoteHost_ = "127.0.0.1"; remotePort_ = 104; manufacturer_ = ModalityManufacturer_Generic; @@ -809,6 +818,24 @@ ResetStorageSOPClasses(); } + + + DicomUserConnection::DicomUserConnection() : + pimpl_(new PImpl) + { + DefaultSetup(); + } + + + DicomUserConnection::DicomUserConnection(const std::string& localAet, + const RemoteModalityParameters& remote) : + pimpl_(new PImpl) + { + DefaultSetup(); + SetLocalApplicationEntityTitle(localAet); + SetRemoteModality(remote); + } + DicomUserConnection::~DicomUserConnection() { @@ -1217,4 +1244,15 @@ << seconds << " seconds (0 = no timeout)"; defaultTimeout_ = seconds; } + + + bool DicomUserConnection::IsSameAssociation(const std::string& localAet, + const RemoteModalityParameters& remote) const + { + return (localAet_ == localAet && + remoteAet_ == remote.GetApplicationEntityTitle() && + remoteHost_ == remote.GetHost() && + remotePort_ == remote.GetPort() && + manufacturer_ == remote.GetManufacturer()); + } } diff -r 3603a2e14592 -r bd6e0b70e915 Core/DicomNetworking/DicomUserConnection.h --- a/Core/DicomNetworking/DicomUserConnection.h Wed May 23 10:08:04 2018 +0200 +++ b/Core/DicomNetworking/DicomUserConnection.h Wed May 23 14:28:25 2018 +0200 @@ -77,11 +77,18 @@ void CheckStorageSOPClassesInvariant() const; + void DefaultSetup(); + public: DicomUserConnection(); ~DicomUserConnection(); + // This constructor corresponds to behavior of the old class + // "ReusableDicomUserConnection", without the call to "Open()" + DicomUserConnection(const std::string& localAet, + const RemoteModalityParameters& remote); + void SetRemoteModality(const RemoteModalityParameters& parameters); void SetLocalApplicationEntityTitle(const std::string& aet); @@ -201,5 +208,8 @@ ParsedDicomFile& query); static void SetDefaultTimeout(uint32_t seconds); + + bool IsSameAssociation(const std::string& localAet, + const RemoteModalityParameters& remote) const; }; } diff -r 3603a2e14592 -r bd6e0b70e915 Core/DicomNetworking/IDicomConnectionManager.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IDicomConnectionManager.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,60 @@ +/** + * 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 "DicomUserConnection.h" + +namespace Orthanc +{ + class IDicomConnectionManager : public boost::noncopyable + { + public: + virtual ~IDicomConnectionManager() + { + } + + class IResource : public boost::noncopyable + { + public: + virtual ~IResource() + { + } + + virtual DicomUserConnection& GetConnection() = 0; + }; + + virtual IResource* AcquireConnection(const std::string& localAet, + const RemoteModalityParameters& remote) = 0; + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/DicomNetworking/ReusableDicomUserConnection.cpp --- a/Core/DicomNetworking/ReusableDicomUserConnection.cpp Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,188 +0,0 @@ -/** - * 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 "../PrecompiledHeaders.h" -#include "ReusableDicomUserConnection.h" - -#include "../Logging.h" -#include "../OrthancException.h" - -namespace Orthanc -{ - static boost::posix_time::ptime Now() - { - return boost::posix_time::microsec_clock::local_time(); - } - - void ReusableDicomUserConnection::Open(const std::string& localAet, - const RemoteModalityParameters& remote) - { - if (connection_ != NULL && - connection_->GetLocalApplicationEntityTitle() == localAet && - connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() && - connection_->GetRemoteHost() == remote.GetHost() && - connection_->GetRemotePort() == remote.GetPort() && - connection_->GetRemoteManufacturer() == remote.GetManufacturer()) - { - // The current connection can be reused - LOG(INFO) << "Reusing the previous SCU connection"; - return; - } - - Close(); - - connection_ = new DicomUserConnection(); - connection_->SetLocalApplicationEntityTitle(localAet); - connection_->SetRemoteModality(remote); - connection_->Open(); - } - - void ReusableDicomUserConnection::Close() - { - if (connection_ != NULL) - { - delete connection_; - connection_ = NULL; - } - } - - void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that) - { - for (;;) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - if (!that->continue_) - { - //LOG(INFO) << "Finishing the thread watching the global SCU connection"; - return; - } - - { - boost::mutex::scoped_lock lock(that->mutex_); - if (that->connection_ != NULL && - Now() >= that->lastUse_ + that->timeBeforeClose_) - { - LOG(INFO) << "Closing the global SCU connection after timeout"; - that->Close(); - } - } - } - } - - - ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that, - const std::string& localAet, - const RemoteModalityParameters& remote) : - ::Orthanc::Locker(that) - { - that.Open(localAet, remote); - connection_ = that.connection_; - } - - - DicomUserConnection& ReusableDicomUserConnection::Locker::GetConnection() - { - if (connection_ == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - return *connection_; - } - - ReusableDicomUserConnection::ReusableDicomUserConnection() : - connection_(NULL), - timeBeforeClose_(boost::posix_time::seconds(5)) // By default, close connection after 5 seconds - { - lastUse_ = Now(); - continue_ = true; - closeThread_ = boost::thread(CloseThread, this); - } - - ReusableDicomUserConnection::~ReusableDicomUserConnection() - { - if (continue_) - { - LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!"; - Finalize(); - } - } - - void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms) - { - boost::mutex::scoped_lock lock(mutex_); - - if (ms == 0) - { - ms = 1; - } - - timeBeforeClose_ = boost::posix_time::milliseconds(ms); - } - - void ReusableDicomUserConnection::Lock() - { - mutex_.lock(); - } - - void ReusableDicomUserConnection::Unlock() - { - if (connection_ != NULL && - connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp) - { - // "storescp" from DCMTK has problems when reusing a - // connection. Always close. - Close(); - } - - lastUse_ = Now(); - mutex_.unlock(); - } - - - void ReusableDicomUserConnection::Finalize() - { - if (continue_) - { - continue_ = false; - - if (closeThread_.joinable()) - { - closeThread_.join(); - } - - Close(); - } - } -} - diff -r 3603a2e14592 -r bd6e0b70e915 Core/DicomNetworking/ReusableDicomUserConnection.h --- a/Core/DicomNetworking/ReusableDicomUserConnection.h Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * 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 "DicomUserConnection.h" -#include "../../Core/MultiThreading/Locker.h" - -#include -#include - -namespace Orthanc -{ - class ReusableDicomUserConnection : public ILockable - { - private: - boost::mutex mutex_; - DicomUserConnection* connection_; - bool continue_; - boost::posix_time::time_duration timeBeforeClose_; - boost::posix_time::ptime lastUse_; - boost::thread closeThread_; - - void Open(const std::string& localAet, - const RemoteModalityParameters& remote); - - void Close(); - - static void CloseThread(ReusableDicomUserConnection* that); - - protected: - virtual void Lock(); - - virtual void Unlock(); - - public: - class Locker : public ::Orthanc::Locker - { - private: - DicomUserConnection* connection_; - - public: - Locker(ReusableDicomUserConnection& that, - const std::string& localAet, - const RemoteModalityParameters& remote); - - DicomUserConnection& GetConnection(); - }; - - ReusableDicomUserConnection(); - - virtual ~ReusableDicomUserConnection(); - - void SetMillisecondsBeforeClose(uint64_t ms); - - void Finalize(); - }; -} - diff -r 3603a2e14592 -r bd6e0b70e915 Core/DicomNetworking/TimeoutDicomConnectionManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Wed May 23 14:28:25 2018 +0200 @@ -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-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 "../PrecompiledHeaders.h" +#include "TimeoutDicomConnectionManager.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +namespace Orthanc +{ + static boost::posix_time::ptime GetNow() + { + return boost::posix_time::microsec_clock::universal_time(); + } + + class TimeoutDicomConnectionManager::Resource : public IDicomConnectionManager::IResource + { + private: + TimeoutDicomConnectionManager& that_; + + public: + Resource(TimeoutDicomConnectionManager& that) : + that_(that) + { + if (that_.connection_.get() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + ~Resource() + { + that_.Touch(); + } + + DicomUserConnection& GetConnection() + { + assert(that_.connection_.get() != NULL); + return *that_.connection_; + } + }; + + + void TimeoutDicomConnectionManager::Touch() + { + lastUse_ = GetNow(); + } + + + void TimeoutDicomConnectionManager::CheckTimeoutInternal() + { + if (connection_.get() != NULL && + (GetNow() - lastUse_) >= timeout_) + { + Close(); + } + } + + + void TimeoutDicomConnectionManager::SetTimeout(unsigned int timeout) + { + timeout_ = boost::posix_time::milliseconds(timeout); + CheckTimeoutInternal(); + } + + + unsigned int TimeoutDicomConnectionManager::GetTimeout() + { + return timeout_.total_milliseconds(); + } + + + void TimeoutDicomConnectionManager::Close() + { + if (connection_.get() != NULL) + { + LOG(INFO) << "Closing inactive DICOM association with modality: " + << connection_->GetRemoteApplicationEntityTitle(); + + connection_.reset(NULL); + } + } + + + void TimeoutDicomConnectionManager::CheckTimeout() + { + CheckTimeoutInternal(); + } + + + IDicomConnectionManager::IResource* + TimeoutDicomConnectionManager::AcquireConnection(const std::string& localAet, + const RemoteModalityParameters& remote) + { + if (connection_.get() == NULL || + !connection_->IsSameAssociation(localAet, remote)) + { + connection_.reset(new DicomUserConnection(localAet, remote)); + } + + return new Resource(*this); + } +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/DicomNetworking/TimeoutDicomConnectionManager.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,72 @@ +/** + * 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 "IDicomConnectionManager.h" + +#include + +namespace Orthanc +{ + class TimeoutDicomConnectionManager : public IDicomConnectionManager + { + private: + class Resource; + + std::auto_ptr connection_; + boost::posix_time::ptime lastUse_; + boost::posix_time::time_duration timeout_; + + void Touch(); + + void CheckTimeoutInternal(); + + public: + TimeoutDicomConnectionManager() : + timeout_(boost::posix_time::milliseconds(1000)) + { + } + + void SetTimeout(unsigned int timeout); + + unsigned int GetTimeout(); + + void Close(); + + void CheckTimeout(); + + virtual IResource* AcquireConnection(const std::string& localAet, + const RemoteModalityParameters& remote); + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/DicomParsing/DicomModification.cpp --- a/Core/DicomParsing/DicomModification.cpp Wed May 23 10:08:04 2018 +0200 +++ b/Core/DicomParsing/DicomModification.cpp Wed May 23 14:28:25 2018 +0200 @@ -1237,4 +1237,11 @@ patientNameReplaced = (IsReplaced(DICOM_TAG_PATIENT_NAME) && GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName); } + + + void DicomModification::Serialize(Json::Value& value) const + { + // TODO + value = "TODO"; + } } diff -r 3603a2e14592 -r bd6e0b70e915 Core/DicomParsing/DicomModification.h --- a/Core/DicomParsing/DicomModification.h Wed May 23 10:08:04 2018 +0200 +++ b/Core/DicomParsing/DicomModification.h Wed May 23 14:28:25 2018 +0200 @@ -172,5 +172,7 @@ { identifierGenerator_ = &generator; } + + void Serialize(Json::Value& value) const; }; } diff -r 3603a2e14592 -r bd6e0b70e915 Core/Enumerations.cpp --- a/Core/Enumerations.cpp Wed May 23 10:08:04 2018 +0200 +++ b/Core/Enumerations.cpp Wed May 23 14:28:25 2018 +0200 @@ -164,6 +164,9 @@ case ErrorCode_DatabaseUnavailable: return "The database is currently not available (probably a transient situation)"; + case ErrorCode_CanceledJob: + return "This job was canceled"; + case ErrorCode_SQLiteNotOpened: return "SQLite: The database is not opened"; @@ -984,6 +987,34 @@ } + const char* EnumerationToString(JobState state) + { + switch (state) + { + case JobState_Pending: + return "Pending"; + + case JobState_Running: + return "Running"; + + case JobState_Success: + return "Success"; + + case JobState_Failure: + return "Failure"; + + case JobState_Paused: + return "Paused"; + + case JobState_Retry: + return "Retry"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + Encoding StringToEncoding(const char* encoding) { std::string s(encoding); diff -r 3603a2e14592 -r bd6e0b70e915 Core/Enumerations.h --- a/Core/Enumerations.h Wed May 23 10:08:04 2018 +0200 +++ b/Core/Enumerations.h Wed May 23 14:28:25 2018 +0200 @@ -96,6 +96,7 @@ ErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, ErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */, ErrorCode_DatabaseUnavailable = 36 /*!< The database is currently not available (probably a transient situation) */, + ErrorCode_CanceledJob = 37 /*!< This job was canceled */, ErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, ErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, ErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, @@ -542,6 +543,24 @@ TransferSyntax_Rle }; + enum JobState + { + JobState_Pending, + JobState_Running, + JobState_Success, + JobState_Failure, + JobState_Paused, + JobState_Retry + }; + + enum JobStepCode + { + JobStepCode_Success, + JobStepCode_Failure, + JobStepCode_Continue, + JobStepCode_Retry + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -622,6 +641,8 @@ const char* EnumerationToString(ValueRepresentation vr); + const char* EnumerationToString(JobState state); + Encoding StringToEncoding(const char* encoding); ResourceType StringToResourceType(const char* type); diff -r 3603a2e14592 -r bd6e0b70e915 Core/HttpServer/IIncomingHttpRequestFilter.h --- a/Core/HttpServer/IIncomingHttpRequestFilter.h Wed May 23 10:08:04 2018 +0200 +++ b/Core/HttpServer/IIncomingHttpRequestFilter.h Wed May 23 14:28:25 2018 +0200 @@ -49,6 +49,6 @@ const char* ip, const char* username, const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::GetArguments& getArguments) const = 0; + const IHttpHandler::GetArguments& getArguments) = 0; }; } diff -r 3603a2e14592 -r bd6e0b70e915 Core/HttpServer/MongooseServer.cpp --- a/Core/HttpServer/MongooseServer.cpp Wed May 23 10:08:04 2018 +0200 +++ b/Core/HttpServer/MongooseServer.cpp Wed May 23 14:28:25 2018 +0200 @@ -676,7 +676,7 @@ std::string username = GetAuthenticatedUsername(headers); - const IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); + IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); if (filter != NULL) { if (!filter->IsAllowed(method, request->uri, remoteIp, diff -r 3603a2e14592 -r bd6e0b70e915 Core/HttpServer/MongooseServer.h --- a/Core/HttpServer/MongooseServer.h Wed May 23 10:08:04 2018 +0200 +++ b/Core/HttpServer/MongooseServer.h Wed May 23 14:28:25 2018 +0200 @@ -162,7 +162,7 @@ void SetHttpCompressionEnabled(bool enabled); - const IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const + IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const { return filter_; } diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/GenericJobUnserializer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/GenericJobUnserializer.cpp Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,90 @@ +/** + * 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 "../PrecompiledHeaders.h" +#include "GenericJobUnserializer.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +#include "Operations/LogJobOperation.h" +#include "Operations/NullOperationValue.h" +#include "Operations/StringOperationValue.h" + +namespace Orthanc +{ + IJob* GenericJobUnserializer::UnserializeJob(const Json::Value& source) + { + const std::string type = GetString(source, "Type"); + + LOG(ERROR) << "Cannot unserialize job of type: " << type; + throw OrthancException(ErrorCode_BadFileFormat); + } + + + IJobOperation* GenericJobUnserializer::UnserializeOperation(const Json::Value& source) + { + const std::string type = GetString(source, "Type"); + + if (type == "Log") + { + return new LogJobOperation; + } + else + { + LOG(ERROR) << "Cannot unserialize operation of type: " << type; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + JobOperationValue* GenericJobUnserializer::UnserializeValue(const Json::Value& source) + { + const std::string type = GetString(source, "Type"); + + if (type == "String") + { + return new StringOperationValue(GetString(source, "Content")); + } + else if (type == "Null") + { + return new NullOperationValue; + } + else + { + LOG(ERROR) << "Cannot unserialize value of type: " << type; + throw OrthancException(ErrorCode_BadFileFormat); + } + } +} + diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/GenericJobUnserializer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/GenericJobUnserializer.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,49 @@ +/** + * 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 "IJobUnserializer.h" + +namespace Orthanc +{ + class GenericJobUnserializer : public IJobUnserializer + { + public: + virtual IJob* UnserializeJob(const Json::Value& source); + + virtual IJobOperation* UnserializeOperation(const Json::Value& source); + + virtual JobOperationValue* UnserializeValue(const Json::Value& source); + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/IJob.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/IJob.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,68 @@ +/** + * 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 "JobStepResult.h" + +#include +#include + +namespace Orthanc +{ + class IJob : public boost::noncopyable + { + public: + virtual ~IJob() + { + } + + // Method called once the job enters the jobs engine + virtual void Start() = 0; + + virtual JobStepResult ExecuteStep() = 0; + + // Method called once the job is resubmitted after a failure + virtual void SignalResubmit() = 0; + + virtual void ReleaseResources() = 0; // For pausing/canceling jobs + + virtual float GetProgress() = 0; + + virtual void GetJobType(std::string& target) = 0; + + virtual void GetPublicContent(Json::Value& value) = 0; + + virtual void GetInternalContent(Json::Value& value) = 0; + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/IJobUnserializer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/IJobUnserializer.cpp Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,103 @@ +/** + * 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 "../PrecompiledHeaders.h" +#include "IJobUnserializer.h" + +#include "../OrthancException.h" + +namespace Orthanc +{ + void IJobUnserializer::CheckType(const Json::Value& source, + const std::string& expectedType) + { + static const char* TYPE = "Type"; + + if (source.type() != Json::objectValue || + !source.isMember(TYPE) || + source[TYPE].type() != Json::stringValue || + source[TYPE].asString() != expectedType) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + std::string IJobUnserializer::GetString(const Json::Value& source, + const std::string& name) + { + if (source.type() != Json::objectValue || + !source.isMember(name.c_str()) || + source[name.c_str()].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return source[name.c_str()].asString(); + } + } + + + int IJobUnserializer::GetInteger(const Json::Value& source, + const std::string& name) + { + if (source.type() != Json::objectValue || + !source.isMember(name.c_str()) || + (source[name.c_str()].type() != Json::intValue && + source[name.c_str()].type() != Json::uintValue)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return source[name.c_str()].asInt(); + } + } + + + unsigned int IJobUnserializer::GetUnsignedInteger(const Json::Value& source, + const std::string& name) + { + int tmp = GetInteger(source, name); + + if (tmp < 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return static_cast(tmp); + } + } +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/IJobUnserializer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/IJobUnserializer.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,67 @@ +/** + * 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 "IJob.h" +#include "Operations/JobOperationValue.h" +#include "Operations/IJobOperation.h" + +namespace Orthanc +{ + class IJobUnserializer : public boost::noncopyable + { + public: + virtual ~IJobUnserializer() + { + } + + virtual IJob* UnserializeJob(const Json::Value& source) = 0; + + virtual IJobOperation* UnserializeOperation(const Json::Value& source) = 0; + + virtual JobOperationValue* UnserializeValue(const Json::Value& source) = 0; + + static void CheckType(const Json::Value& source, + const std::string& expectedType); + + static std::string GetString(const Json::Value& source, + const std::string& name); + + static int GetInteger(const Json::Value& source, + const std::string& name); + + static unsigned int GetUnsignedInteger(const Json::Value& source, + const std::string& name); + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/JobInfo.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobInfo.cpp Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,154 @@ +/** + * 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 "../PrecompiledHeaders.h" +#include "JobInfo.h" + +#include "../OrthancException.h" + +// This "include" is mandatory for Release builds using Linux Standard Base +#include + +namespace Orthanc +{ + JobInfo::JobInfo(const std::string& id, + int priority, + JobState state, + const JobStatus& status, + const boost::posix_time::ptime& creationTime, + const boost::posix_time::ptime& lastStateChangeTime, + const boost::posix_time::time_duration& runtime) : + id_(id), + priority_(priority), + state_(state), + timestamp_(boost::posix_time::microsec_clock::universal_time()), + creationTime_(creationTime), + lastStateChangeTime_(lastStateChangeTime), + runtime_(runtime), + hasEta_(false), + status_(status) + { + if (state_ == JobState_Running) + { + float ms = static_cast(runtime_.total_milliseconds()); + + if (status_.GetProgress() > 0.01f && + ms > 0.01f) + { + float ratio = static_cast(1.0 - status_.GetProgress()); + long long remaining = boost::math::llround(ratio * ms); + eta_ = timestamp_ + boost::posix_time::milliseconds(remaining); + hasEta_ = true; + } + } + } + + + JobInfo::JobInfo() : + priority_(0), + state_(JobState_Failure), + timestamp_(boost::posix_time::microsec_clock::universal_time()), + creationTime_(timestamp_), + lastStateChangeTime_(timestamp_), + runtime_(boost::posix_time::milliseconds(0)), + hasEta_(false) + { + } + + + bool JobInfo::HasCompletionTime() const + { + return (state_ == JobState_Success || + state_ == JobState_Failure); + } + + + const boost::posix_time::ptime& JobInfo::GetEstimatedTimeOfArrival() const + { + if (hasEta_) + { + return eta_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + const boost::posix_time::ptime& JobInfo::GetCompletionTime() const + { + if (HasCompletionTime()) + { + return lastStateChangeTime_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + void JobInfo::Serialize(Json::Value& target, + bool includeInternalContent) const + { + target = Json::objectValue; + target["ID"] = id_; + target["Priority"] = priority_; + target["ErrorCode"] = static_cast(status_.GetErrorCode()); + target["ErrorDescription"] = EnumerationToString(status_.GetErrorCode()); + target["State"] = EnumerationToString(state_); + target["Timestamp"] = boost::posix_time::to_iso_string(timestamp_); + target["CreationTime"] = boost::posix_time::to_iso_string(creationTime_); + target["EffectiveRuntime"] = static_cast(runtime_.total_milliseconds()) / 1000.0; + target["Progress"] = boost::math::iround(status_.GetProgress() * 100.0f); + + target["Type"] = status_.GetJobType(); + target["PublicContent"] = status_.GetPublicContent(); + + if (includeInternalContent) + { + target["InternalContent"] = status_.GetInternalContent(); + } + + if (HasEstimatedTimeOfArrival()) + { + target["EstimatedTimeOfArrival"] = boost::posix_time::to_iso_string(GetEstimatedTimeOfArrival()); + } + + if (HasCompletionTime()) + { + target["CompletionTime"] = boost::posix_time::to_iso_string(GetCompletionTime()); + } + } +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/JobInfo.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobInfo.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,121 @@ +/** + * 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 "JobStatus.h" + +#include + +namespace Orthanc +{ + class JobInfo + { + private: + std::string id_; + int priority_; + JobState state_; + boost::posix_time::ptime timestamp_; + boost::posix_time::ptime creationTime_; + boost::posix_time::ptime lastStateChangeTime_; + boost::posix_time::time_duration runtime_; + bool hasEta_; + boost::posix_time::ptime eta_; + JobStatus status_; + + public: + JobInfo(const std::string& id, + int priority, + JobState state, + const JobStatus& status, + const boost::posix_time::ptime& creationTime, + const boost::posix_time::ptime& lastStateChangeTime, + const boost::posix_time::time_duration& runtime); + + JobInfo(); + + const std::string& GetIdentifier() const + { + return id_; + } + + int GetPriority() const + { + return priority_; + } + + JobState GetState() const + { + return state_; + } + + const boost::posix_time::ptime& GetInfoTime() const + { + return timestamp_; + } + + const boost::posix_time::ptime& GetCreationTime() const + { + return creationTime_; + } + + const boost::posix_time::time_duration& GetRuntime() const + { + return runtime_; + } + + bool HasEstimatedTimeOfArrival() const + { + return hasEta_; + } + + bool HasCompletionTime() const; + + const boost::posix_time::ptime& GetEstimatedTimeOfArrival() const; + + const boost::posix_time::ptime& GetCompletionTime() const; + + const JobStatus& GetStatus() const + { + return status_; + } + + JobStatus& GetStatus() + { + return status_; + } + + void Serialize(Json::Value& target, + bool includeInternalContent) const; + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/JobStatus.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStatus.cpp Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,70 @@ +/** + * 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 "../PrecompiledHeaders.h" +#include "JobStatus.h" + +namespace Orthanc +{ + JobStatus::JobStatus() : + errorCode_(ErrorCode_InternalError), + progress_(0), + jobType_("Invalid"), + publicContent_(Json::objectValue), + internalContent_(Json::objectValue) + { + } + + + JobStatus::JobStatus(ErrorCode code, + IJob& job) : + errorCode_(code), + progress_(job.GetProgress()), + publicContent_(Json::objectValue), + internalContent_(Json::objectValue) + { + if (progress_ < 0) + { + progress_ = 0; + } + + if (progress_ > 1) + { + progress_ = 1; + } + + job.GetJobType(jobType_); + job.GetPublicContent(publicContent_); + job.GetInternalContent(internalContent_); + } +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/JobStatus.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStatus.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,85 @@ +/** + * 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 "IJob.h" + +namespace Orthanc +{ + class JobStatus + { + private: + ErrorCode errorCode_; + float progress_; + std::string jobType_; + Json::Value publicContent_; + Json::Value internalContent_; + + public: + JobStatus(); + + JobStatus(ErrorCode code, + IJob& job); + + ErrorCode GetErrorCode() const + { + return errorCode_; + } + + void SetErrorCode(ErrorCode error) + { + errorCode_ = error; + } + + float GetProgress() const + { + return progress_; + } + + const std::string& GetJobType() const + { + return jobType_; + } + + const Json::Value& GetPublicContent() const + { + return publicContent_; + } + + const Json::Value& GetInternalContent() const + { + return internalContent_; + } + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/JobStepResult.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStepResult.cpp Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,81 @@ +/** + * 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 "../PrecompiledHeaders.h" +#include "JobStepResult.h" + +#include "../OrthancException.h" + +namespace Orthanc +{ + JobStepResult JobStepResult::Retry(unsigned int timeout) + { + JobStepResult result(JobStepCode_Retry); + result.timeout_ = timeout; + return result; + } + + + JobStepResult JobStepResult::Failure(const ErrorCode& error) + { + JobStepResult result(JobStepCode_Failure); + result.error_ = error; + return result; + } + + + unsigned int JobStepResult::GetRetryTimeout() const + { + if (code_ == JobStepCode_Retry) + { + return timeout_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + ErrorCode JobStepResult::GetFailureCode() const + { + if (code_ == JobStepCode_Failure) + { + return error_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/JobStepResult.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStepResult.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,85 @@ +/** + * 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 "../Enumerations.h" + +namespace Orthanc +{ + class JobStepResult + { + private: + JobStepCode code_; + unsigned int timeout_; + ErrorCode error_; + + explicit JobStepResult(JobStepCode code) : + code_(code), + timeout_(0), + error_(ErrorCode_Success) + { + } + + public: + explicit JobStepResult() : + code_(JobStepCode_Failure), + timeout_(0), + error_(ErrorCode_InternalError) + { + } + + static JobStepResult Success() + { + return JobStepResult(JobStepCode_Success); + } + + static JobStepResult Continue() + { + return JobStepResult(JobStepCode_Continue); + } + + static JobStepResult Retry(unsigned int timeout); + + static JobStepResult Failure(const ErrorCode& error); + + JobStepCode GetCode() const + { + return code_; + } + + unsigned int GetRetryTimeout() const; + + ErrorCode GetFailureCode() const; + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/JobsEngine.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsEngine.cpp Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,264 @@ +/** + * 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 "../PrecompiledHeaders.h" +#include "JobsEngine.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +namespace Orthanc +{ + bool JobsEngine::IsRunning() + { + boost::mutex::scoped_lock lock(stateMutex_); + return (state_ == State_Running); + } + + + bool JobsEngine::ExecuteStep(JobsRegistry::RunningJob& running, + size_t workerIndex) + { + assert(running.IsValid()); + + if (running.IsPauseScheduled()) + { + running.GetJob().ReleaseResources(); + running.MarkPause(); + return false; + } + + if (running.IsCancelScheduled()) + { + running.GetJob().ReleaseResources(); + running.MarkCanceled(); + return false; + } + + JobStepResult result; + + try + { + result = running.GetJob().ExecuteStep(); + } + catch (OrthancException& e) + { + result = JobStepResult::Failure(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + result = JobStepResult::Failure(ErrorCode_BadFileFormat); + } + catch (...) + { + result = JobStepResult::Failure(ErrorCode_InternalError); + } + + switch (result.GetCode()) + { + case JobStepCode_Success: + running.UpdateStatus(ErrorCode_Success); + running.GetJob().ReleaseResources(); + running.MarkSuccess(); + return false; + + case JobStepCode_Failure: + running.GetJob().ReleaseResources(); + running.UpdateStatus(result.GetFailureCode()); + running.MarkFailure(); + return false; + + case JobStepCode_Retry: + running.GetJob().ReleaseResources(); + running.UpdateStatus(ErrorCode_Success); + running.MarkRetry(result.GetRetryTimeout()); + return false; + + case JobStepCode_Continue: + running.UpdateStatus(ErrorCode_Success); + return true; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + void JobsEngine::RetryHandler(JobsEngine* engine) + { + assert(engine != NULL); + + while (engine->IsRunning()) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(200)); + engine->GetRegistry().ScheduleRetries(); + } + } + + + void JobsEngine::Worker(JobsEngine* engine, + size_t workerIndex) + { + assert(engine != NULL); + + LOG(INFO) << "Worker thread " << workerIndex << " has started"; + + while (engine->IsRunning()) + { + JobsRegistry::RunningJob running(engine->GetRegistry(), 100); + + if (running.IsValid()) + { + LOG(INFO) << "Executing job with priority " << running.GetPriority() + << " in worker thread " << workerIndex << ": " << running.GetId(); + + while (engine->IsRunning()) + { + if (!engine->ExecuteStep(running, workerIndex)) + { + break; + } + } + } + } + } + + + JobsEngine::JobsEngine() : + state_(State_Setup), + workers_(1) + { + } + + + JobsEngine::~JobsEngine() + { + if (state_ != State_Setup && + state_ != State_Done) + { + LOG(ERROR) << "INTERNAL ERROR: JobsEngine::Stop() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } + } + + + void JobsEngine::SetWorkersCount(size_t count) + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Setup) + { + // Can only be invoked before calling "Start()" + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + workers_.resize(count); + } + + + void JobsEngine::Start() + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Setup) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + retryHandler_ = boost::thread(RetryHandler, this); + + if (workers_.size() == 0) + { + // Use all the available CPUs + size_t n = boost::thread::hardware_concurrency(); + + if (n == 0) + { + n = 1; + } + + workers_.resize(n); + } + + for (size_t i = 0; i < workers_.size(); i++) + { + assert(workers_[i] == NULL); + workers_[i] = new boost::thread(Worker, this, i); + } + + state_ = State_Running; + + LOG(WARNING) << "The jobs engine has started with " << workers_.size() << " threads"; + } + + + void JobsEngine::Stop() + { + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Running) + { + return; + } + + state_ = State_Stopping; + } + + LOG(INFO) << "Stopping the jobs engine"; + + if (retryHandler_.joinable()) + { + retryHandler_.join(); + } + + for (size_t i = 0; i < workers_.size(); i++) + { + assert(workers_[i] != NULL); + + if (workers_[i]->joinable()) + { + workers_[i]->join(); + } + + delete workers_[i]; + } + + { + boost::mutex::scoped_lock lock(stateMutex_); + state_ = State_Done; + } + + LOG(WARNING) << "The jobs engine has stopped"; + } +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/JobsEngine.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsEngine.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,85 @@ +/** + * 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 "JobsRegistry.h" + +#include + +namespace Orthanc +{ + class JobsEngine + { + private: + enum State + { + State_Setup, + State_Running, + State_Stopping, + State_Done + }; + + boost::mutex stateMutex_; + State state_; + JobsRegistry registry_; + boost::thread retryHandler_; + std::vector workers_; + + bool IsRunning(); + + bool ExecuteStep(JobsRegistry::RunningJob& running, + size_t workerIndex); + + static void RetryHandler(JobsEngine* engine); + + static void Worker(JobsEngine* engine, + size_t workerIndex); + + public: + JobsEngine(); + + ~JobsEngine(); + + void SetWorkersCount(size_t count); + + JobsRegistry& GetRegistry() + { + return registry_; + } + + void Start(); + + void Stop(); + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/JobsRegistry.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsRegistry.cpp Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,1087 @@ +/** + * 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 "../PrecompiledHeaders.h" +#include "JobsRegistry.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../Toolbox.h" + +namespace Orthanc +{ + class JobsRegistry::JobHandler : public boost::noncopyable + { + private: + std::string id_; + JobState state_; + std::auto_ptr job_; + int priority_; // "+inf()" means highest priority + boost::posix_time::ptime creationTime_; + boost::posix_time::ptime lastStateChangeTime_; + boost::posix_time::time_duration runtime_; + boost::posix_time::ptime retryTime_; + bool pauseScheduled_; + bool cancelScheduled_; + JobStatus lastStatus_; + + void Touch() + { + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + + if (state_ == JobState_Running) + { + runtime_ += (now - lastStateChangeTime_); + } + + lastStateChangeTime_ = now; + } + + void SetStateInternal(JobState state) + { + state_ = state; + pauseScheduled_ = false; + cancelScheduled_ = false; + Touch(); + } + + public: + JobHandler(IJob* job, + int priority) : + id_(Toolbox::GenerateUuid()), + state_(JobState_Pending), + job_(job), + priority_(priority), + creationTime_(boost::posix_time::microsec_clock::universal_time()), + lastStateChangeTime_(creationTime_), + runtime_(boost::posix_time::milliseconds(0)), + retryTime_(creationTime_), + pauseScheduled_(false), + cancelScheduled_(false) + { + if (job == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + lastStatus_ = JobStatus(ErrorCode_Success, *job); + job->Start(); + } + + const std::string& GetId() const + { + return id_; + } + + IJob& GetJob() const + { + assert(job_.get() != NULL); + return *job_; + } + + void SetPriority(int priority) + { + priority_ = priority; + } + + int GetPriority() const + { + return priority_; + } + + JobState GetState() const + { + return state_; + } + + void SetState(JobState state) + { + if (state == JobState_Retry) + { + // Use "SetRetryState()" + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + SetStateInternal(state); + } + } + + void SetRetryState(unsigned int timeout) + { + if (state_ == JobState_Running) + { + SetStateInternal(JobState_Retry); + retryTime_ = (boost::posix_time::microsec_clock::universal_time() + + boost::posix_time::milliseconds(timeout)); + } + else + { + // Only valid for running jobs + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + void SchedulePause() + { + if (state_ == JobState_Running) + { + pauseScheduled_ = true; + } + else + { + // Only valid for running jobs + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + void ScheduleCancel() + { + if (state_ == JobState_Running) + { + cancelScheduled_ = true; + } + else + { + // Only valid for running jobs + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + bool IsPauseScheduled() + { + return pauseScheduled_; + } + + bool IsCancelScheduled() + { + return cancelScheduled_; + } + + bool IsRetryReady(const boost::posix_time::ptime& now) const + { + if (state_ != JobState_Retry) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return retryTime_ <= now; + } + } + + const boost::posix_time::ptime& GetCreationTime() const + { + return creationTime_; + } + + const boost::posix_time::ptime& GetLastStateChangeTime() const + { + return lastStateChangeTime_; + } + + const boost::posix_time::time_duration& GetRuntime() const + { + return runtime_; + } + + const JobStatus& GetLastStatus() const + { + return lastStatus_; + } + + void SetLastStatus(const JobStatus& status) + { + lastStatus_ = status; + Touch(); + } + + void SetLastErrorCode(ErrorCode code) + { + lastStatus_.SetErrorCode(code); + } + }; + + + bool JobsRegistry::PriorityComparator::operator() (JobHandler*& a, + JobHandler*& b) const + { + return a->GetPriority() < b->GetPriority(); + } + + +#if defined(NDEBUG) + void JobsRegistry::CheckInvariants() const + { + } + +#else + bool JobsRegistry::IsPendingJob(const JobHandler& job) const + { + PendingJobs copy = pendingJobs_; + while (!copy.empty()) + { + if (copy.top() == &job) + { + return true; + } + + copy.pop(); + } + + return false; + } + + bool JobsRegistry::IsCompletedJob(JobHandler& job) const + { + for (CompletedJobs::const_iterator it = completedJobs_.begin(); + it != completedJobs_.end(); ++it) + { + if (*it == &job) + { + return true; + } + } + + return false; + } + + bool JobsRegistry::IsRetryJob(JobHandler& job) const + { + return retryJobs_.find(&job) != retryJobs_.end(); + } + + void JobsRegistry::CheckInvariants() const + { + { + PendingJobs copy = pendingJobs_; + while (!copy.empty()) + { + assert(copy.top()->GetState() == JobState_Pending); + copy.pop(); + } + } + + assert(completedJobs_.size() <= maxCompletedJobs_); + + for (CompletedJobs::const_iterator it = completedJobs_.begin(); + it != completedJobs_.end(); ++it) + { + assert((*it)->GetState() == JobState_Success || + (*it)->GetState() == JobState_Failure); + } + + for (RetryJobs::const_iterator it = retryJobs_.begin(); + it != retryJobs_.end(); ++it) + { + assert((*it)->GetState() == JobState_Retry); + } + + for (JobsIndex::const_iterator it = jobsIndex_.begin(); + it != jobsIndex_.end(); ++it) + { + JobHandler& job = *it->second; + + assert(job.GetId() == it->first); + + switch (job.GetState()) + { + case JobState_Pending: + assert(!IsRetryJob(job) && IsPendingJob(job) && !IsCompletedJob(job)); + break; + + case JobState_Success: + case JobState_Failure: + assert(!IsRetryJob(job) && !IsPendingJob(job) && IsCompletedJob(job)); + break; + + case JobState_Retry: + assert(IsRetryJob(job) && !IsPendingJob(job) && !IsCompletedJob(job)); + break; + + case JobState_Running: + case JobState_Paused: + assert(!IsRetryJob(job) && !IsPendingJob(job) && !IsCompletedJob(job)); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + } +#endif + + + void JobsRegistry::ForgetOldCompletedJobs() + { + if (maxCompletedJobs_ != 0) + { + while (completedJobs_.size() > maxCompletedJobs_) + { + assert(completedJobs_.front() != NULL); + + std::string id = completedJobs_.front()->GetId(); + assert(jobsIndex_.find(id) != jobsIndex_.end()); + + jobsIndex_.erase(id); + delete(completedJobs_.front()); + completedJobs_.pop_front(); + } + } + } + + + void JobsRegistry::SetCompletedJob(JobHandler& job, + bool success) + { + job.SetState(success ? JobState_Success : JobState_Failure); + + completedJobs_.push_back(&job); + ForgetOldCompletedJobs(); + + someJobComplete_.notify_all(); + } + + + void JobsRegistry::MarkRunningAsCompleted(JobHandler& job, + bool success) + { + LOG(INFO) << "Job has completed with " << (success ? "success" : "failure") + << ": " << job.GetId(); + + CheckInvariants(); + + assert(job.GetState() == JobState_Running); + SetCompletedJob(job, success); + + CheckInvariants(); + } + + + void JobsRegistry::MarkRunningAsRetry(JobHandler& job, + unsigned int timeout) + { + LOG(INFO) << "Job scheduled for retry in " << timeout << "ms: " << job.GetId(); + + CheckInvariants(); + + assert(job.GetState() == JobState_Running && + retryJobs_.find(&job) == retryJobs_.end()); + + retryJobs_.insert(&job); + job.SetRetryState(timeout); + + CheckInvariants(); + } + + + void JobsRegistry::MarkRunningAsPaused(JobHandler& job) + { + LOG(INFO) << "Job paused: " << job.GetId(); + + CheckInvariants(); + assert(job.GetState() == JobState_Running); + + job.SetState(JobState_Paused); + + CheckInvariants(); + } + + + bool JobsRegistry::GetStateInternal(JobState& state, + const std::string& id) + { + CheckInvariants(); + + JobsIndex::const_iterator it = jobsIndex_.find(id); + if (it == jobsIndex_.end()) + { + return false; + } + else + { + state = it->second->GetState(); + return true; + } + } + + + JobsRegistry::~JobsRegistry() + { + for (JobsIndex::iterator it = jobsIndex_.begin(); it != jobsIndex_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + } + + + void JobsRegistry::SetMaxCompletedJobs(size_t n) + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + LOG(INFO) << "The size of the history of the jobs engine is set to: " << n << " job(s)"; + + maxCompletedJobs_ = n; + ForgetOldCompletedJobs(); + + CheckInvariants(); + } + + + void JobsRegistry::ListJobs(std::set& target) + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + for (JobsIndex::const_iterator it = jobsIndex_.begin(); + it != jobsIndex_.end(); ++it) + { + target.insert(it->first); + } + } + + + bool JobsRegistry::GetJobInfo(JobInfo& target, + const std::string& id) + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::const_iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + return false; + } + else + { + const JobHandler& handler = *found->second; + target = JobInfo(handler.GetId(), + handler.GetPriority(), + handler.GetState(), + handler.GetLastStatus(), + handler.GetCreationTime(), + handler.GetLastStateChangeTime(), + handler.GetRuntime()); + return true; + } + } + + + void JobsRegistry::Submit(std::string& id, + IJob* job, // Takes ownership + int priority) + { + std::auto_ptr handler(new JobHandler(job, priority)); + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + id = handler->GetId(); + + pendingJobs_.push(handler.get()); + pendingJobAvailable_.notify_one(); + + jobsIndex_.insert(std::make_pair(id, handler.release())); + + LOG(INFO) << "New job submitted with priority " << priority << ": " << id; + + CheckInvariants(); + } + + + void JobsRegistry::Submit(IJob* job, // Takes ownership + int priority) + { + std::string id; + Submit(id, job, priority); + } + + + bool JobsRegistry::SubmitAndWait(IJob* job, // Takes ownership + int priority) + { + std::string id; + Submit(id, job, priority); + + JobState state; + + { + boost::mutex::scoped_lock lock(mutex_); + + while (GetStateInternal(state, id) && + state != JobState_Success && + state != JobState_Failure) + { + someJobComplete_.wait(lock); + } + } + + return (state == JobState_Success); + } + + + bool JobsRegistry::SetPriority(const std::string& id, + int priority) + { + LOG(INFO) << "Changing priority to " << priority << " for job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else + { + found->second->SetPriority(priority); + + if (found->second->GetState() == JobState_Pending) + { + // If the job is pending, we need to reconstruct the + // priority queue, as the heap condition has changed + + PendingJobs copy; + std::swap(copy, pendingJobs_); + + assert(pendingJobs_.empty()); + while (!copy.empty()) + { + pendingJobs_.push(copy.top()); + copy.pop(); + } + } + + CheckInvariants(); + return true; + } + } + + + void JobsRegistry::RemovePendingJob(const std::string& id) + { + // If the job is pending, we need to reconstruct the priority + // queue to remove it + PendingJobs copy; + std::swap(copy, pendingJobs_); + + assert(pendingJobs_.empty()); + while (!copy.empty()) + { + if (copy.top()->GetId() != id) + { + pendingJobs_.push(copy.top()); + } + + copy.pop(); + } + } + + + void JobsRegistry::RemoveRetryJob(JobHandler* handler) + { + RetryJobs::iterator item = retryJobs_.find(handler); + assert(item != retryJobs_.end()); + retryJobs_.erase(item); + } + + + bool JobsRegistry::Pause(const std::string& id) + { + LOG(INFO) << "Pausing job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else + { + switch (found->second->GetState()) + { + case JobState_Pending: + RemovePendingJob(id); + found->second->SetState(JobState_Paused); + break; + + case JobState_Retry: + RemoveRetryJob(found->second); + found->second->SetState(JobState_Paused); + break; + + case JobState_Paused: + case JobState_Success: + case JobState_Failure: + // Nothing to be done + break; + + case JobState_Running: + found->second->SchedulePause(); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + CheckInvariants(); + return true; + } + } + + + bool JobsRegistry::Cancel(const std::string& id) + { + LOG(INFO) << "Canceling job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else + { + switch (found->second->GetState()) + { + case JobState_Pending: + RemovePendingJob(id); + SetCompletedJob(*found->second, false); + found->second->SetLastErrorCode(ErrorCode_CanceledJob); + break; + + case JobState_Retry: + RemoveRetryJob(found->second); + SetCompletedJob(*found->second, false); + found->second->SetLastErrorCode(ErrorCode_CanceledJob); + break; + + case JobState_Paused: + SetCompletedJob(*found->second, false); + found->second->SetLastErrorCode(ErrorCode_CanceledJob); + break; + + case JobState_Success: + case JobState_Failure: + // Nothing to be done + break; + + case JobState_Running: + found->second->ScheduleCancel(); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + CheckInvariants(); + return true; + } + } + + + bool JobsRegistry::Resume(const std::string& id) + { + LOG(INFO) << "Resuming job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else if (found->second->GetState() != JobState_Paused) + { + LOG(WARNING) << "Cannot resume a job that is not paused: " << id; + return false; + } + else + { + found->second->SetState(JobState_Pending); + pendingJobs_.push(found->second); + pendingJobAvailable_.notify_one(); + CheckInvariants(); + return true; + } + } + + + bool JobsRegistry::Resubmit(const std::string& id) + { + LOG(INFO) << "Resubmitting failed job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else if (found->second->GetState() != JobState_Failure) + { + printf("%s\n", EnumerationToString(found->second->GetState())); + LOG(WARNING) << "Cannot resubmit a job that has not failed: " << id; + return false; + } + else + { + found->second->GetJob().SignalResubmit(); + + bool ok = false; + for (CompletedJobs::iterator it = completedJobs_.begin(); + it != completedJobs_.end(); ++it) + { + if (*it == found->second) + { + ok = true; + completedJobs_.erase(it); + break; + } + } + + assert(ok); + + found->second->SetState(JobState_Pending); + pendingJobs_.push(found->second); + pendingJobAvailable_.notify_one(); + + CheckInvariants(); + return true; + } + } + + + void JobsRegistry::ScheduleRetries() + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + RetryJobs copy; + std::swap(copy, retryJobs_); + + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + + assert(retryJobs_.empty()); + for (RetryJobs::iterator it = copy.begin(); it != copy.end(); ++it) + { + if ((*it)->IsRetryReady(now)) + { + LOG(INFO) << "Retrying job: " << (*it)->GetId(); + (*it)->SetState(JobState_Pending); + pendingJobs_.push(*it); + pendingJobAvailable_.notify_one(); + } + else + { + retryJobs_.insert(*it); + } + } + + CheckInvariants(); + } + + + bool JobsRegistry::GetState(JobState& state, + const std::string& id) + { + boost::mutex::scoped_lock lock(mutex_); + return GetStateInternal(state, id); + } + + + JobsRegistry::RunningJob::RunningJob(JobsRegistry& registry, + unsigned int timeout) : + registry_(registry), + handler_(NULL), + targetState_(JobState_Failure), + targetRetryTimeout_(0), + canceled_(false) + { + { + boost::mutex::scoped_lock lock(registry_.mutex_); + + while (registry_.pendingJobs_.empty()) + { + if (timeout == 0) + { + registry_.pendingJobAvailable_.wait(lock); + } + else + { + bool success = registry_.pendingJobAvailable_.timed_wait + (lock, boost::posix_time::milliseconds(timeout)); + if (!success) + { + // No pending job + return; + } + } + } + + handler_ = registry_.pendingJobs_.top(); + registry_.pendingJobs_.pop(); + + assert(handler_->GetState() == JobState_Pending); + handler_->SetState(JobState_Running); + handler_->SetLastErrorCode(ErrorCode_Success); + + job_ = &handler_->GetJob(); + id_ = handler_->GetId(); + priority_ = handler_->GetPriority(); + } + } + + + JobsRegistry::RunningJob::~RunningJob() + { + if (IsValid()) + { + boost::mutex::scoped_lock lock(registry_.mutex_); + + switch (targetState_) + { + case JobState_Failure: + registry_.MarkRunningAsCompleted(*handler_, false); + + if (canceled_) + { + handler_->SetLastErrorCode(ErrorCode_CanceledJob); + } + + break; + + case JobState_Success: + registry_.MarkRunningAsCompleted(*handler_, true); + break; + + case JobState_Paused: + registry_.MarkRunningAsPaused(*handler_); + break; + + case JobState_Retry: + registry_.MarkRunningAsRetry(*handler_, targetRetryTimeout_); + break; + + default: + assert(0); + } + } + } + + + bool JobsRegistry::RunningJob::IsValid() const + { + return (handler_ != NULL && + job_ != NULL); + } + + + const std::string& JobsRegistry::RunningJob::GetId() const + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return id_; + } + } + + + int JobsRegistry::RunningJob::GetPriority() const + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return priority_; + } + } + + + IJob& JobsRegistry::RunningJob::GetJob() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return *job_; + } + } + + + bool JobsRegistry::RunningJob::IsPauseScheduled() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + boost::mutex::scoped_lock lock(registry_.mutex_); + registry_.CheckInvariants(); + assert(handler_->GetState() == JobState_Running); + + return handler_->IsPauseScheduled(); + } + } + + + bool JobsRegistry::RunningJob::IsCancelScheduled() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + boost::mutex::scoped_lock lock(registry_.mutex_); + registry_.CheckInvariants(); + assert(handler_->GetState() == JobState_Running); + + return handler_->IsCancelScheduled(); + } + } + + + void JobsRegistry::RunningJob::MarkSuccess() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Success; + } + } + + + void JobsRegistry::RunningJob::MarkFailure() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Failure; + } + } + + + void JobsRegistry::RunningJob::MarkCanceled() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Failure; + canceled_ = true; + } + } + + + void JobsRegistry::RunningJob::MarkPause() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Paused; + } + } + + + void JobsRegistry::RunningJob::MarkRetry(unsigned int timeout) + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Retry; + targetRetryTimeout_ = timeout; + } + } + + + void JobsRegistry::RunningJob::UpdateStatus(ErrorCode code) + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + JobStatus status(code, *job_); + + boost::mutex::scoped_lock lock(registry_.mutex_); + registry_.CheckInvariants(); + assert(handler_->GetState() == JobState_Running); + + handler_->SetLastStatus(status); + } + } +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/JobsRegistry.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsRegistry.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,203 @@ +/** + * 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 + +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if ORTHANC_SANDBOXED == 1 +# error The job engine cannot be used in sandboxed environments +#endif + +#include "JobInfo.h" + +#include +#include +#include +#include +#include + +namespace Orthanc +{ + // This class handles the state machine of the jobs engine + class JobsRegistry : public boost::noncopyable + { + private: + class JobHandler; + + struct PriorityComparator + { + bool operator() (JobHandler*& a, + JobHandler*& b) const; + }; + + typedef std::map JobsIndex; + typedef std::list CompletedJobs; + typedef std::set RetryJobs; + typedef std::priority_queue, // Could be a "std::deque" + PriorityComparator> PendingJobs; + + boost::mutex mutex_; + JobsIndex jobsIndex_; + PendingJobs pendingJobs_; + CompletedJobs completedJobs_; + RetryJobs retryJobs_; + + boost::condition_variable pendingJobAvailable_; + boost::condition_variable someJobComplete_; + size_t maxCompletedJobs_; + + +#ifndef NDEBUG + bool IsPendingJob(const JobHandler& job) const; + + bool IsCompletedJob(JobHandler& job) const; + + bool IsRetryJob(JobHandler& job) const; +#endif + + void CheckInvariants() const; + + void ForgetOldCompletedJobs(); + + void SetCompletedJob(JobHandler& job, + bool success); + + void MarkRunningAsCompleted(JobHandler& job, + bool success); + + void MarkRunningAsRetry(JobHandler& job, + unsigned int timeout); + + void MarkRunningAsPaused(JobHandler& job); + + bool GetStateInternal(JobState& state, + const std::string& id); + + void RemovePendingJob(const std::string& id); + + void RemoveRetryJob(JobHandler* handler); + + public: + JobsRegistry() : + maxCompletedJobs_(10) + { + } + + + ~JobsRegistry(); + + void SetMaxCompletedJobs(size_t i); + + void ListJobs(std::set& target); + + bool GetJobInfo(JobInfo& target, + const std::string& id); + + void Submit(std::string& id, + IJob* job, // Takes ownership + int priority); + + void Submit(IJob* job, // Takes ownership + int priority); + + bool SubmitAndWait(IJob* job, // Takes ownership + int priority); + + bool SetPriority(const std::string& id, + int priority); + + bool Pause(const std::string& id); + + bool Resume(const std::string& id); + + bool Resubmit(const std::string& id); + + bool Cancel(const std::string& id); + + void ScheduleRetries(); + + bool GetState(JobState& state, + const std::string& id); + + class RunningJob : public boost::noncopyable + { + private: + JobsRegistry& registry_; + JobHandler* handler_; // Can only be accessed if the + // registry mutex is locked! + IJob* job_; // Will by design be in mutual exclusion, + // because only one RunningJob can be + // executed at a time on a JobHandler + + std::string id_; + int priority_; + JobState targetState_; + unsigned int targetRetryTimeout_; + bool canceled_; + + public: + RunningJob(JobsRegistry& registry, + unsigned int timeout); + + ~RunningJob(); + + bool IsValid() const; + + const std::string& GetId() const; + + int GetPriority() const; + + IJob& GetJob(); + + bool IsPauseScheduled(); + + bool IsCancelScheduled(); + + void MarkSuccess(); + + void MarkFailure(); + + void MarkPause(); + + void MarkCanceled(); + + void MarkRetry(unsigned int timeout); + + void UpdateStatus(ErrorCode code); + }; + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/Operations/IJobOperation.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/IJobOperation.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,54 @@ +/** + * 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 "JobOperationValues.h" +#include "../../DicomNetworking/IDicomConnectionManager.h" + +namespace Orthanc +{ + class IJobOperation : public boost::noncopyable + { + public: + virtual ~IJobOperation() + { + } + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& dicomConnection) = 0; + + virtual void Serialize(Json::Value& result) const = 0; + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/Operations/JobOperationValue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValue.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,74 @@ +/** + * 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 +#include + +namespace Orthanc +{ + class JobOperationValue : public boost::noncopyable + { + public: + enum Type + { + Type_DicomInstance, + Type_Null, + Type_String + }; + + private: + Type type_; + + protected: + JobOperationValue(Type type) : + type_(type) + { + } + + public: + virtual ~JobOperationValue() + { + } + + Type GetType() const + { + return type_; + } + + virtual JobOperationValue* Clone() const = 0; + + virtual void Serialize(Json::Value& target) const = 0; + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/Operations/JobOperationValues.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValues.cpp Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,143 @@ +/** + * 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 "../../PrecompiledHeaders.h" +#include "JobOperationValues.h" + +#include "../IJobUnserializer.h" +#include "../../OrthancException.h" + +#include +#include + +namespace Orthanc +{ + void JobOperationValues::Append(JobOperationValues& target, + bool clear) + { + target.Reserve(target.GetSize() + GetSize()); + + for (size_t i = 0; i < values_.size(); i++) + { + if (clear) + { + target.Append(values_[i]); + values_[i] = NULL; + } + else + { + target.Append(GetValue(i).Clone()); + } + } + + if (clear) + { + Clear(); + } + } + + + void JobOperationValues::Clear() + { + for (size_t i = 0; i < values_.size(); i++) + { + if (values_[i] != NULL) + { + delete values_[i]; + } + } + + values_.clear(); + } + + + void JobOperationValues::Append(JobOperationValue* value) // Takes ownership + { + if (value == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + values_.push_back(value); + } + } + + + JobOperationValue& JobOperationValues::GetValue(size_t index) const + { + if (index >= values_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + assert(values_[index] != NULL); + return *values_[index]; + } + } + + + void JobOperationValues::Serialize(Json::Value& target) const + { + target = Json::arrayValue; + + for (size_t i = 0; i < values_.size(); i++) + { + Json::Value tmp; + values_[i]->Serialize(tmp); + target.append(tmp); + } + } + + + JobOperationValues* Unserialize(IJobUnserializer& unserializer, + const Json::Value& source) + { + if (source.type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + std::auto_ptr result(new JobOperationValues); + + result->Reserve(source.size()); + + for (Json::Value::ArrayIndex i = 0; i < source.size(); i++) + { + result->Append(unserializer.UnserializeValue(source[i])); + } + + return result.release(); + } +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/Operations/JobOperationValues.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValues.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,89 @@ +/** + * 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 "JobOperationValue.h" + +#include + +namespace Orthanc +{ + class IJobUnserializer; + + class JobOperationValues : public boost::noncopyable + { + private: + std::vector values_; + + void Append(JobOperationValues& target, + bool clear); + + public: + ~JobOperationValues() + { + Clear(); + } + + void Move(JobOperationValues& target) + { + return Append(target, true); + } + + void Copy(JobOperationValues& target) + { + return Append(target, false); + } + + void Clear(); + + void Reserve(size_t count) + { + values_.reserve(count); + } + + void Append(JobOperationValue* value); // Takes ownership + + size_t GetSize() const + { + return values_.size(); + } + + JobOperationValue& GetValue(size_t index) const; + + void Serialize(Json::Value& target) const; + + static JobOperationValues* Unserialize(IJobUnserializer& unserializer, + const Json::Value& source); + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/Operations/LogJobOperation.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/LogJobOperation.cpp Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,64 @@ +/** + * 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 "../../PrecompiledHeaders.h" +#include "LogJobOperation.h" + +#include "../../Logging.h" +#include "StringOperationValue.h" + +namespace Orthanc +{ + void LogJobOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + switch (input.GetType()) + { + case JobOperationValue::Type_String: + LOG(INFO) << "Job value: " + << dynamic_cast(input).GetContent(); + break; + + case JobOperationValue::Type_Null: + LOG(INFO) << "Job value: (null)"; + break; + + default: + LOG(INFO) << "Job value: (unsupport)"; + break; + } + + outputs.Append(input.Clone()); + } +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/Operations/LogJobOperation.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/LogJobOperation.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,52 @@ +/** + * 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 "IJobOperation.h" + +namespace Orthanc +{ + class LogJobOperation : public IJobOperation + { + public: + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager); + + virtual void Serialize(Json::Value& result) const + { + result["Type"] = "Log"; + } + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/Operations/NullOperationValue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/NullOperationValue.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,58 @@ +/** + * 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 "JobOperationValue.h" + +namespace Orthanc +{ + class NullOperationValue : public JobOperationValue + { + public: + NullOperationValue() : + JobOperationValue(Type_Null) + { + } + + virtual JobOperationValue* Clone() const + { + return new NullOperationValue; + } + + virtual void Serialize(Json::Value& target) const + { + target["Type"] = "Null"; + } + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,381 @@ +/** + * 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 "../../PrecompiledHeaders.h" +#include "SequenceOfOperationsJob.h" + +#include "../../Logging.h" +#include "../../OrthancException.h" + +namespace Orthanc +{ + class SequenceOfOperationsJob::Operation : public boost::noncopyable + { + private: + size_t index_; + JobOperationValues originalInputs_; + JobOperationValues workInputs_; + std::auto_ptr operation_; + std::list nextOperations_; + size_t currentInput_; + + public: + Operation(size_t index, + IJobOperation* operation) : + index_(index), + operation_(operation), + currentInput_(0) + { + if (operation == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + } + + void AddOriginalInput(const JobOperationValue& value) + { + if (currentInput_ != 0) + { + // Cannot add input after processing has started + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + originalInputs_.Append(value.Clone()); + } + } + + const JobOperationValues& GetOriginalInputs() const + { + return originalInputs_; + } + + void Reset() + { + workInputs_.Clear(); + currentInput_ = 0; + } + + void AddNextOperation(Operation& other) + { + if (other.index_ <= index_) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (currentInput_ != 0) + { + // Cannot add input after processing has started + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + nextOperations_.push_back(&other); + } + } + + bool IsDone() const + { + return currentInput_ >= originalInputs_.GetSize() + workInputs_.GetSize(); + } + + void Step(IDicomConnectionManager& connectionManager) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + const JobOperationValue* input; + + if (currentInput_ < originalInputs_.GetSize()) + { + input = &originalInputs_.GetValue(currentInput_); + } + else + { + input = &workInputs_.GetValue(currentInput_ - originalInputs_.GetSize()); + } + + JobOperationValues outputs; + operation_->Apply(outputs, *input, connectionManager); + + if (!nextOperations_.empty()) + { + std::list::iterator first = nextOperations_.begin(); + outputs.Move((*first)->workInputs_); + + std::list::iterator current = first; + ++current; + + while (current != nextOperations_.end()) + { + (*first)->workInputs_.Copy((*current)->workInputs_); + ++current; + } + } + + currentInput_ += 1; + } + + void Serialize(Json::Value& target) const + { + target = Json::objectValue; + operation_->Serialize(target["Operation"]); + originalInputs_.Serialize(target["OriginalInputs"]); + workInputs_.Serialize(target["WorkInputs"]); + + Json::Value tmp = Json::arrayValue; + for (std::list::const_iterator it = nextOperations_.begin(); + it != nextOperations_.end(); ++it) + { + tmp.append(static_cast((*it)->index_)); + } + + target["NextOperations"] = tmp; + } + }; + + + SequenceOfOperationsJob::SequenceOfOperationsJob() : + done_(false), + current_(0), + trailingTimeout_(boost::posix_time::milliseconds(1000)) + { + } + + + SequenceOfOperationsJob::~SequenceOfOperationsJob() + { + for (size_t i = 0; i < operations_.size(); i++) + { + if (operations_[i] != NULL) + { + delete operations_[i]; + } + } + } + + + void SequenceOfOperationsJob::SetDescription(const std::string& description) + { + boost::mutex::scoped_lock lock(mutex_); + description_ = description; + } + + + void SequenceOfOperationsJob::Register(IObserver& observer) + { + boost::mutex::scoped_lock lock(mutex_); + observers_.push_back(&observer); + } + + + void SequenceOfOperationsJob::Lock::SetTrailingOperationTimeout(unsigned int timeout) + { + that_.trailingTimeout_ = boost::posix_time::milliseconds(timeout); + } + + + void SequenceOfOperationsJob::Lock::SetDicomAssociationTimeout(unsigned int timeout) + { + that_.connectionManager_.SetTimeout(timeout); + } + + + size_t SequenceOfOperationsJob::Lock::AddOperation(IJobOperation* operation) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + size_t index = that_.operations_.size(); + + that_.operations_.push_back(new Operation(index, operation)); + that_.operationAdded_.notify_one(); + + return index; + } + + + void SequenceOfOperationsJob::Lock::AddInput(size_t index, + const JobOperationValue& value) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (index >= that_.operations_.size() || + index < that_.current_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + that_.operations_[index]->AddOriginalInput(value); + } + } + + + void SequenceOfOperationsJob::Lock::Connect(size_t input, + size_t output) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (input >= output || + input >= that_.operations_.size() || + output >= that_.operations_.size() || + input < that_.current_ || + output < that_.current_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + Operation& a = *that_.operations_[input]; + Operation& b = *that_.operations_[output]; + a.AddNextOperation(b); + } + } + + + JobStepResult SequenceOfOperationsJob::ExecuteStep() + { + boost::mutex::scoped_lock lock(mutex_); + + if (current_ == operations_.size()) + { + LOG(INFO) << "Executing the trailing timeout in the sequence of operations"; + operationAdded_.timed_wait(lock, trailingTimeout_); + + if (current_ == operations_.size()) + { + // No operation was added during the trailing timeout: The + // job is over + LOG(INFO) << "The sequence of operations is over"; + done_ = true; + + for (std::list::iterator it = observers_.begin(); + it != observers_.end(); ++it) + { + (*it)->SignalDone(*this); + } + + connectionManager_.Close(); + return JobStepResult::Success(); + } + else + { + LOG(INFO) << "New operation added to the sequence of operations"; + } + } + + assert(current_ < operations_.size()); + + while (current_ < operations_.size() && + operations_[current_]->IsDone()) + { + current_++; + } + + if (current_ < operations_.size()) + { + operations_[current_]->Step(connectionManager_); + } + + connectionManager_.CheckTimeout(); + + return JobStepResult::Continue(); + } + + + void SequenceOfOperationsJob::SignalResubmit() + { + boost::mutex::scoped_lock lock(mutex_); + + current_ = 0; + done_ = false; + + for (size_t i = 0; i < operations_.size(); i++) + { + operations_[i]->Reset(); + } + } + + + void SequenceOfOperationsJob::ReleaseResources() + { + boost::mutex::scoped_lock lock(mutex_); + connectionManager_.Close(); + } + + + float SequenceOfOperationsJob::GetProgress() + { + boost::mutex::scoped_lock lock(mutex_); + + return (static_cast(current_) / + static_cast(operations_.size() + 1)); + } + + + void SequenceOfOperationsJob::GetPublicContent(Json::Value& value) + { + boost::mutex::scoped_lock lock(mutex_); + + value["CountOperations"] = static_cast(operations_.size()); + value["Description"] = description_; + } + + + void SequenceOfOperationsJob::GetInternalContent(Json::Value& value) + { + boost::mutex::scoped_lock lock(mutex_); + + Json::Value tmp = Json::arrayValue; + for (size_t i = 0; i < operations_.size(); i++) + { + Json::Value operation = Json::objectValue; + operations_[i]->Serialize(operation); + tmp.append(operation); + } + + value["Operations"] = tmp; + value["TrailingTimeout"] = static_cast(trailingTimeout_.total_milliseconds()); + value["DicomTimeout"] = connectionManager_.GetTimeout(); + value["Current"] = static_cast(current_); + } +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/Operations/SequenceOfOperationsJob.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,148 @@ +/** + * 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 "../IJob.h" +#include "IJobOperation.h" + +#include "../../DicomNetworking/TimeoutDicomConnectionManager.h" + +#include +#include + +#include + +namespace Orthanc +{ + class SequenceOfOperationsJob : public IJob + { + public: + class IObserver : public boost::noncopyable + { + public: + virtual ~IObserver() + { + } + + virtual void SignalDone(const SequenceOfOperationsJob& job) = 0; + }; + + private: + class Operation; + + std::string description_; + bool done_; + boost::mutex mutex_; + std::vector operations_; + size_t current_; + boost::condition_variable operationAdded_; + boost::posix_time::time_duration trailingTimeout_; + std::list observers_; + TimeoutDicomConnectionManager connectionManager_; + + public: + SequenceOfOperationsJob(); + + virtual ~SequenceOfOperationsJob(); + + void SetDescription(const std::string& description); + + void Register(IObserver& observer); + + // This lock allows adding new operations to the end of the job, + // from another thread than the worker thread, after the job has + // been submitted for processing + class Lock : public boost::noncopyable + { + private: + SequenceOfOperationsJob& that_; + boost::mutex::scoped_lock lock_; + + public: + Lock(SequenceOfOperationsJob& that) : + that_(that), + lock_(that.mutex_) + { + } + + bool IsDone() const + { + return that_.done_; + } + + void SetTrailingOperationTimeout(unsigned int timeout); + + void SetDicomAssociationTimeout(unsigned int timeout); + + size_t AddOperation(IJobOperation* operation); + + size_t GetOperationsCount() const + { + return that_.operations_.size(); + } + + void AddInput(size_t index, + const JobOperationValue& value); + + void Connect(size_t input, + size_t output); + }; + + virtual void Start() + { + } + + virtual JobStepResult ExecuteStep(); + + virtual void SignalResubmit(); + + virtual void ReleaseResources(); + + virtual float GetProgress(); + + virtual void GetJobType(std::string& target) + { + target = "SequenceOfOperations"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual void GetInternalContent(Json::Value& value); + + void AwakeTrailingSleep() + { + operationAdded_.notify_one(); + } + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/Operations/StringOperationValue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/StringOperationValue.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,70 @@ +/** + * 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 "JobOperationValue.h" + +#include + +namespace Orthanc +{ + class StringOperationValue : public JobOperationValue + { + private: + std::string content_; + + public: + StringOperationValue(const std::string& content) : + JobOperationValue(JobOperationValue::Type_String), + content_(content) + { + } + + virtual JobOperationValue* Clone() const + { + return new StringOperationValue(content_); + } + + const std::string& GetContent() const + { + return content_; + } + + virtual void Serialize(Json::Value& target) const + { + target["Type"] = "String"; + target["Content"] = content_; + } + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/SetOfInstancesJob.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/SetOfInstancesJob.cpp Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,203 @@ +/** + * 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 "../PrecompiledHeaders.h" +#include "SetOfInstancesJob.h" + +#include "../OrthancException.h" + +namespace Orthanc +{ + SetOfInstancesJob::SetOfInstancesJob() : + started_(false), + permissive_(false), + position_(0) + { + } + + + void SetOfInstancesJob::Reserve(size_t size) + { + if (started_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + instances_.reserve(size); + } + } + + + void SetOfInstancesJob::AddInstance(const std::string& instance) + { + if (started_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + instances_.push_back(instance); + } + } + + + void SetOfInstancesJob::SetPermissive(bool permissive) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + permissive_ = permissive; + } + } + + + void SetOfInstancesJob::SignalResubmit() + { + if (started_) + { + position_ = 0; + failedInstances_.clear(); + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + float SetOfInstancesJob::GetProgress() + { + if (instances_.size() == 0) + { + return 0; + } + else + { + return (static_cast(position_) / + static_cast(instances_.size())); + } + } + + + JobStepResult SetOfInstancesJob::ExecuteStep() + { + if (!started_) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (instances_.empty() && + position_ == 0) + { + // No instance to handle, we're done + position_ = 1; + return JobStepResult::Success(); + } + + if (position_ >= instances_.size()) + { + // Already done + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + const std::string currentInstance = instances_[position_]; + + bool ok; + + try + { + ok = HandleInstance(currentInstance); + + if (!ok && !permissive_) + { + return JobStepResult::Failure(ErrorCode_InternalError); + } + } + catch (OrthancException& e) + { + if (permissive_) + { + ok = false; + } + else + { + throw; + } + } + + if (!ok) + { + failedInstances_.insert(currentInstance); + } + + position_ += 1; + + if (position_ == instances_.size()) + { + // We're done + return JobStepResult::Success(); + } + else + { + return JobStepResult::Continue(); + } + } + + + void SetOfInstancesJob::GetInternalContent(Json::Value& value) + { + Json::Value v = Json::arrayValue; + + for (size_t i = 0; i < instances_.size(); i++) + { + v.append(instances_[i]); + } + + value["Instances"] = v; + + + v = Json::arrayValue; + + for (std::set::const_iterator it = failedInstances_.begin(); + it != failedInstances_.end(); ++it) + { + v.append(*it); + } + + value["FailedInstances"] = v; + } +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/JobsEngine/SetOfInstancesJob.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/SetOfInstancesJob.h Wed May 23 14:28:25 2018 +0200 @@ -0,0 +1,101 @@ +/** + * 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 "IJob.h" + +#include + +namespace Orthanc +{ + class SetOfInstancesJob : public IJob + { + private: + bool started_; + std::vector instances_; + bool permissive_; + size_t position_; + std::set failedInstances_; + + protected: + virtual bool HandleInstance(const std::string& instance) = 0; + + public: + SetOfInstancesJob(); + + void Reserve(size_t size); + + size_t GetInstancesCount() const + { + return instances_.size(); + } + + void AddInstance(const std::string& instance); + + bool IsPermissive() const + { + return permissive_; + } + + void SetPermissive(bool permissive); + + virtual void SignalResubmit(); + + virtual void Start() + { + started_ = true; + } + + virtual float GetProgress(); + + bool IsStarted() const + { + return started_; + } + + const std::vector& GetInstances() const + { + return instances_; + } + + const std::set& GetFailedInstances() const + { + return failedInstances_; + } + + virtual JobStepResult ExecuteStep(); + + virtual void GetInternalContent(Json::Value& value); + }; +} diff -r 3603a2e14592 -r bd6e0b70e915 Core/MultiThreading/BagOfTasks.h --- a/Core/MultiThreading/BagOfTasks.h Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -/** - * 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 "../ICommand.h" - -#include -#include - -namespace Orthanc -{ - class BagOfTasks : public boost::noncopyable - { - private: - typedef std::list Tasks; - - Tasks tasks_; - - public: - ~BagOfTasks() - { - for (Tasks::iterator it = tasks_.begin(); it != tasks_.end(); ++it) - { - delete *it; - } - } - - ICommand* Pop() - { - ICommand* task = tasks_.front(); - tasks_.pop_front(); - return task; - } - - void Push(ICommand* task) // Takes ownership - { - if (task != NULL) - { - tasks_.push_back(task); - } - } - - size_t GetSize() const - { - return tasks_.size(); - } - - bool IsEmpty() const - { - return tasks_.empty(); - } - }; -} diff -r 3603a2e14592 -r bd6e0b70e915 Core/MultiThreading/BagOfTasksProcessor.cpp --- a/Core/MultiThreading/BagOfTasksProcessor.cpp Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,277 +0,0 @@ -/** - * 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 "../PrecompiledHeaders.h" -#include "BagOfTasksProcessor.h" - -#include "../Logging.h" -#include "../OrthancException.h" - -#include - -namespace Orthanc -{ - class BagOfTasksProcessor::Task : public IDynamicObject - { - private: - uint64_t bag_; - std::auto_ptr command_; - - public: - Task(uint64_t bag, - ICommand* command) : - bag_(bag), - command_(command) - { - } - - bool Execute() - { - try - { - return command_->Execute(); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Exception while processing a bag of tasks: " << e.What(); - return false; - } - catch (std::runtime_error& e) - { - LOG(ERROR) << "Runtime exception while processing a bag of tasks: " << e.what(); - return false; - } - catch (...) - { - LOG(ERROR) << "Native exception while processing a bag of tasks"; - return false; - } - } - - uint64_t GetBag() - { - return bag_; - } - }; - - - void BagOfTasksProcessor::SignalProgress(Task& task, - Bag& bag) - { - assert(bag.done_ < bag.size_); - - bag.done_ += 1; - - if (bag.done_ == bag.size_) - { - exitStatus_[task.GetBag()] = (bag.status_ == BagStatus_Running); - bagFinished_.notify_all(); - } - } - - void BagOfTasksProcessor::Worker(BagOfTasksProcessor* that) - { - while (that->continue_) - { - std::auto_ptr obj(that->queue_.Dequeue(100)); - if (obj.get() != NULL) - { - Task& task = *dynamic_cast(obj.get()); - - { - boost::mutex::scoped_lock lock(that->mutex_); - - Bags::iterator bag = that->bags_.find(task.GetBag()); - assert(bag != that->bags_.end()); - assert(bag->second.done_ < bag->second.size_); - - if (bag->second.status_ != BagStatus_Running) - { - // Do not execute this task, as its parent bag of tasks - // has failed or is tagged as canceled - that->SignalProgress(task, bag->second); - continue; - } - } - - bool success = task.Execute(); - - { - boost::mutex::scoped_lock lock(that->mutex_); - - Bags::iterator bag = that->bags_.find(task.GetBag()); - assert(bag != that->bags_.end()); - - if (!success) - { - bag->second.status_ = BagStatus_Failed; - } - - that->SignalProgress(task, bag->second); - } - } - } - } - - - void BagOfTasksProcessor::Cancel(int64_t bag) - { - boost::mutex::scoped_lock lock(mutex_); - - Bags::iterator it = bags_.find(bag); - if (it != bags_.end()) - { - it->second.status_ = BagStatus_Canceled; - } - } - - - bool BagOfTasksProcessor::Join(int64_t bag) - { - boost::mutex::scoped_lock lock(mutex_); - - while (continue_) - { - ExitStatus::iterator it = exitStatus_.find(bag); - if (it == exitStatus_.end()) // The bag is still running - { - bagFinished_.wait(lock); - } - else - { - bool status = it->second; - exitStatus_.erase(it); - return status; - } - } - - return false; // The processor is stopping - } - - - float BagOfTasksProcessor::GetProgress(int64_t bag) - { - boost::mutex::scoped_lock lock(mutex_); - - Bags::const_iterator it = bags_.find(bag); - if (it == bags_.end()) - { - // The bag of tasks has finished - return 1.0f; - } - else - { - return (static_cast(it->second.done_) / - static_cast(it->second.size_)); - } - } - - - bool BagOfTasksProcessor::Handle::Join() - { - if (hasJoined_) - { - return status_; - } - else - { - status_ = that_.Join(bag_); - hasJoined_ = true; - return status_; - } - } - - - BagOfTasksProcessor::BagOfTasksProcessor(size_t countThreads) : - countBags_(0), - continue_(true) - { - if (countThreads == 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - threads_.resize(countThreads); - - for (size_t i = 0; i < threads_.size(); i++) - { - threads_[i] = new boost::thread(Worker, this); - } - } - - - BagOfTasksProcessor::~BagOfTasksProcessor() - { - continue_ = false; - - bagFinished_.notify_all(); // Wakes up all the pending "Join()" - - for (size_t i = 0; i < threads_.size(); i++) - { - if (threads_[i]) - { - if (threads_[i]->joinable()) - { - threads_[i]->join(); - } - - delete threads_[i]; - threads_[i] = NULL; - } - } - } - - - BagOfTasksProcessor::Handle* BagOfTasksProcessor::Submit(BagOfTasks& tasks) - { - if (tasks.GetSize() == 0) - { - return new Handle(*this, 0, true); - } - - boost::mutex::scoped_lock lock(mutex_); - - uint64_t id = countBags_; - countBags_ += 1; - - Bag bag(tasks.GetSize()); - bags_[id] = bag; - - while (!tasks.IsEmpty()) - { - queue_.Enqueue(new Task(id, tasks.Pop())); - } - - return new Handle(*this, id, false); - } -} diff -r 3603a2e14592 -r bd6e0b70e915 Core/MultiThreading/BagOfTasksProcessor.h --- a/Core/MultiThreading/BagOfTasksProcessor.h Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,150 +0,0 @@ -/** - * 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 "BagOfTasks.h" -#include "SharedMessageQueue.h" - -#include -#include - -namespace Orthanc -{ - class BagOfTasksProcessor : public boost::noncopyable - { - private: - enum BagStatus - { - BagStatus_Running, - BagStatus_Canceled, - BagStatus_Failed - }; - - - struct Bag - { - size_t size_; - size_t done_; - BagStatus status_; - - Bag() : - size_(0), - done_(0), - status_(BagStatus_Failed) - { - } - - explicit Bag(size_t size) : - size_(size), - done_(0), - status_(BagStatus_Running) - { - } - }; - - class Task; - - - typedef std::map Bags; - typedef std::map ExitStatus; - - SharedMessageQueue queue_; - - boost::mutex mutex_; - uint64_t countBags_; - Bags bags_; - std::vector threads_; - ExitStatus exitStatus_; - bool continue_; - - boost::condition_variable bagFinished_; - - static void Worker(BagOfTasksProcessor* that); - - void Cancel(int64_t bag); - - bool Join(int64_t bag); - - float GetProgress(int64_t bag); - - void SignalProgress(Task& task, - Bag& bag); - - public: - class Handle : public boost::noncopyable - { - friend class BagOfTasksProcessor; - - private: - BagOfTasksProcessor& that_; - uint64_t bag_; - bool hasJoined_; - bool status_; - - Handle(BagOfTasksProcessor& that, - uint64_t bag, - bool empty) : - that_(that), - bag_(bag), - hasJoined_(empty) - { - } - - public: - ~Handle() - { - Join(); - } - - void Cancel() - { - that_.Cancel(bag_); - } - - bool Join(); - - float GetProgress() - { - return that_.GetProgress(bag_); - } - }; - - - explicit BagOfTasksProcessor(size_t countThreads); - - ~BagOfTasksProcessor(); - - Handle* Submit(BagOfTasks& tasks); - }; -} diff -r 3603a2e14592 -r bd6e0b70e915 Core/MultiThreading/ILockable.h --- a/Core/MultiThreading/ILockable.h Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/** - * 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 - -namespace Orthanc -{ - class ILockable : public boost::noncopyable - { - friend class Locker; - - protected: - virtual void Lock() = 0; - - virtual void Unlock() = 0; - - public: - virtual ~ILockable() - { - } - }; -} diff -r 3603a2e14592 -r bd6e0b70e915 Core/MultiThreading/Locker.h --- a/Core/MultiThreading/Locker.h Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/** - * 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 "ILockable.h" - -namespace Orthanc -{ - class Locker : public boost::noncopyable - { - private: - ILockable& lockable_; - - public: - Locker(ILockable& lockable) : lockable_(lockable) - { - lockable_.Lock(); - } - - virtual ~Locker() - { - lockable_.Unlock(); - } - }; -} diff -r 3603a2e14592 -r bd6e0b70e915 Core/MultiThreading/Mutex.cpp --- a/Core/MultiThreading/Mutex.cpp Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +0,0 @@ -/** - * 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 "../PrecompiledHeaders.h" -#include "Mutex.h" - -#include "../OrthancException.h" - -#if defined(_WIN32) -#include -#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) -#include -#else -#error Support your platform here -#endif - -namespace Orthanc -{ -#if defined (_WIN32) - - struct Mutex::PImpl - { - CRITICAL_SECTION criticalSection_; - }; - - Mutex::Mutex() - { - pimpl_ = new PImpl; - ::InitializeCriticalSection(&pimpl_->criticalSection_); - } - - Mutex::~Mutex() - { - ::DeleteCriticalSection(&pimpl_->criticalSection_); - delete pimpl_; - } - - void Mutex::Lock() - { - ::EnterCriticalSection(&pimpl_->criticalSection_); - } - - void Mutex::Unlock() - { - ::LeaveCriticalSection(&pimpl_->criticalSection_); - } - - -#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) - - struct Mutex::PImpl - { - pthread_mutex_t mutex_; - }; - - Mutex::Mutex() - { - pimpl_ = new PImpl; - - if (pthread_mutex_init(&pimpl_->mutex_, NULL) != 0) - { - delete pimpl_; - throw OrthancException(ErrorCode_InternalError); - } - } - - Mutex::~Mutex() - { - pthread_mutex_destroy(&pimpl_->mutex_); - delete pimpl_; - } - - void Mutex::Lock() - { - if (pthread_mutex_lock(&pimpl_->mutex_) != 0) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - void Mutex::Unlock() - { - if (pthread_mutex_unlock(&pimpl_->mutex_) != 0) - { - throw OrthancException(ErrorCode_InternalError); - } - } - -#else -#error Support your plateform here -#endif -} diff -r 3603a2e14592 -r bd6e0b70e915 Core/MultiThreading/Mutex.h --- a/Core/MultiThreading/Mutex.h Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * 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 "ILockable.h" - -namespace Orthanc -{ - class Mutex : public ILockable - { - private: - struct PImpl; - - PImpl *pimpl_; - - protected: - virtual void Lock(); - - virtual void Unlock(); - - public: - Mutex(); - - ~Mutex(); - }; -} diff -r 3603a2e14592 -r bd6e0b70e915 Core/MultiThreading/ReaderWriterLock.cpp --- a/Core/MultiThreading/ReaderWriterLock.cpp Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +0,0 @@ -/** - * 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 "../PrecompiledHeaders.h" -#include "ReaderWriterLock.h" - -#include - -namespace Orthanc -{ - namespace - { - // Anonymous namespace to avoid clashes between compilation - // modules. - - class ReaderLockable : public ILockable - { - private: - boost::shared_mutex& lock_; - - protected: - virtual void Lock() - { - lock_.lock_shared(); - } - - virtual void Unlock() - { - lock_.unlock_shared(); - } - - public: - explicit ReaderLockable(boost::shared_mutex& lock) : lock_(lock) - { - } - }; - - - class WriterLockable : public ILockable - { - private: - boost::shared_mutex& lock_; - - protected: - virtual void Lock() - { - lock_.lock(); - } - - virtual void Unlock() - { - lock_.unlock(); - } - - public: - explicit WriterLockable(boost::shared_mutex& lock) : lock_(lock) - { - } - }; - } - - struct ReaderWriterLock::PImpl - { - boost::shared_mutex lock_; - ReaderLockable reader_; - WriterLockable writer_; - - PImpl() : reader_(lock_), writer_(lock_) - { - } - }; - - - ReaderWriterLock::ReaderWriterLock() - { - pimpl_ = new PImpl; - } - - - ReaderWriterLock::~ReaderWriterLock() - { - delete pimpl_; - } - - - ILockable& ReaderWriterLock::ForReader() - { - return pimpl_->reader_; - } - - - ILockable& ReaderWriterLock::ForWriter() - { - return pimpl_->writer_; - } -} diff -r 3603a2e14592 -r bd6e0b70e915 Core/MultiThreading/ReaderWriterLock.h --- a/Core/MultiThreading/ReaderWriterLock.h Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * 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 "ILockable.h" - -#include - -namespace Orthanc -{ - class ReaderWriterLock : public boost::noncopyable - { - private: - struct PImpl; - - PImpl *pimpl_; - - public: - ReaderWriterLock(); - - virtual ~ReaderWriterLock(); - - ILockable& ForReader(); - - ILockable& ForWriter(); - }; -} diff -r 3603a2e14592 -r bd6e0b70e915 Core/MultiThreading/Semaphore.cpp --- a/Core/MultiThreading/Semaphore.cpp Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -/** - * 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 "../PrecompiledHeaders.h" -#include "Semaphore.h" - -#include "../OrthancException.h" - - -namespace Orthanc -{ - Semaphore::Semaphore(unsigned int count) : count_(count) - { - } - - void Semaphore::Release() - { - boost::mutex::scoped_lock lock(mutex_); - - count_++; - condition_.notify_one(); - } - - void Semaphore::Acquire() - { - boost::mutex::scoped_lock lock(mutex_); - - while (count_ == 0) - { - condition_.wait(lock); - } - - count_++; - } -} diff -r 3603a2e14592 -r bd6e0b70e915 Core/MultiThreading/Semaphore.h --- a/Core/MultiThreading/Semaphore.h Wed May 23 10:08:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -/** - * 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 -#include - -namespace Orthanc -{ - class Semaphore : public boost::noncopyable - { - private: - unsigned int count_; - boost::mutex mutex_; - boost::condition_variable condition_; - - public: - explicit Semaphore(unsigned int count); - - void Release(); - - void Acquire(); - - class Locker : public boost::noncopyable - { - private: - Semaphore& that_; - - public: - explicit Locker(Semaphore& that) : - that_(that) - { - that_.Acquire(); - } - - ~Locker() - { - that_.Release(); - } - }; - }; -} diff -r 3603a2e14592 -r bd6e0b70e915 NEWS --- a/NEWS Wed May 23 10:08:04 2018 +0200 +++ b/NEWS Wed May 23 14:28:25 2018 +0200 @@ -1,6 +1,11 @@ Pending changes in the mainline =============================== +General +------- + +* New advanced job engine + Orthanc Explorer ---------------- @@ -9,6 +14,7 @@ REST API -------- +* "/jobs/..." to manage the jobs from the REST API * New option "?short" to list DICOM tags using their hexadecimal ID in: - "/instances/.../tags?short" - "/instances/.../header?short" @@ -27,6 +33,7 @@ ----------- * Fix generation of DICOMDIR if PatientID is empty +* Fix issue 25 (Deadlock with Lua scripts): The event queue is now implemented for Lua Version 1.3.2 (2018-04-18) diff -r 3603a2e14592 -r bd6e0b70e915 OrthancExplorer/explorer.html --- a/OrthancExplorer/explorer.html Wed May 23 10:08:04 2018 +0200 +++ b/OrthancExplorer/explorer.html Wed May 23 14:28:25 2018 +0200 @@ -44,6 +44,7 @@
@@ -62,6 +63,7 @@
@@ -109,6 +111,7 @@
@@ -169,6 +172,7 @@
@@ -223,6 +227,7 @@
@@ -279,6 +284,7 @@
@@ -468,6 +474,48 @@
+ +
+
+

Jobs

+ +
+
+
    +
+
+
+ +
+
+

Job

+ +
+ Jobs +
+
+
+
    +
+ +
+
+
+ + + + +
+
+
+
+