# HG changeset patch # User Sebastien Jodogne # Date 1588004966 -7200 # Node ID ce5c4b9fa09a152421f99123fb84833e96e3d5f1 # Parent 9973d10bc5c4b711b5f7b6a178becfe5c7cbf402 removing DicomUserConnection diff -r 9973d10bc5c4 -r ce5c4b9fa09a Core/DicomNetworking/DicomUserConnection.cpp --- a/Core/DicomNetworking/DicomUserConnection.cpp Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1848 +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-2020 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 . - **/ - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../PrecompiledHeaders.h" -#include "DicomUserConnection.h" - -#if !defined(DCMTK_VERSION_NUMBER) -# error The macro DCMTK_VERSION_NUMBER must be defined -#endif - -#include "../Compatibility.h" -#include "../DicomFormat/DicomArray.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "../DicomParsing/FromDcmtkBridge.h" -#include "../DicomParsing/ToDcmtkBridge.h" -#include "NetworkingCompatibility.h" - -#include -#include -#include -#include -#include -#include - -#include - - -static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax; - -/** - * "If we have more than 64 storage SOP classes, tools such as - * storescu will fail because they attempt to negotiate two - * presentation contexts for each SOP class, and there is a total - * limit of 128 contexts for one association." - **/ -static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64; - - -namespace Orthanc -{ - // By default, the timeout for DICOM SCU (client) connections is set to 10 seconds - static uint32_t defaultTimeout_ = 10; - - struct DicomUserConnection::PImpl - { - // Connection state - uint32_t dimseTimeout_; - uint32_t acseTimeout_; - T_ASC_Network* net_; - T_ASC_Parameters* params_; - T_ASC_Association* assoc_; - - bool IsOpen() const - { - return assoc_ != NULL; - } - - void CheckIsOpen() const; - - void Store(std::string& sopClassUidOut /* out */, - std::string& sopInstanceUidOut /* out */, - DcmInputStream& is, - DicomUserConnection& connection, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - }; - - - static void Check(const OFCondition& cond, - const std::string& aet, - const std::string& command) - { - if (cond.bad()) - { - // Reformat the error message from DCMTK by turning multiline - // errors into a single line - - std::string s(cond.text()); - std::string info; - info.reserve(s.size()); - - bool isMultiline = false; - for (size_t i = 0; i < s.size(); i++) - { - if (s[i] == '\r') - { - // Ignore - } - else if (s[i] == '\n') - { - if (isMultiline) - { - info += "; "; - } - else - { - info += " ("; - isMultiline = true; - } - } - else - { - info.push_back(s[i]); - } - } - - if (isMultiline) - { - info += ")"; - } - - throw OrthancException(ErrorCode_NetworkProtocol, - "DicomUserConnection - " + command + - " to AET \"" + aet + "\": " + info); - } - } - - void DicomUserConnection::PImpl::CheckIsOpen() const - { - if (!IsOpen()) - { - throw OrthancException(ErrorCode_NetworkProtocol, - "DicomUserConnection: First open the connection"); - } - } - - - void DicomUserConnection::CheckIsOpen() const - { - pimpl_->CheckIsOpen(); - } - - - static void RegisterStorageSOPClass(T_ASC_Parameters* params, - unsigned int& presentationContextId, - const std::string& sopClass, - const char* asPreferred[], - std::vector& asFallback, - const std::string& aet) - { - // Presentation context IDs must be odd numbers, hence the - // increments by 2: - // http://dicom.nema.org/medical/dicom/2019e/output/chtml/part08/sect_9.3.2.2.html - - Check(ASC_addPresentationContext(params, presentationContextId, - sopClass.c_str(), asPreferred, 1), - aet, "initializing"); - presentationContextId += 2; - - if (asFallback.size() > 0) - { - Check(ASC_addPresentationContext(params, presentationContextId, - sopClass.c_str(), &asFallback[0], asFallback.size()), - aet, "initializing"); - presentationContextId += 2; - } - } - - - void DicomUserConnection::SetupPresentationContexts(Mode mode, - const std::string& preferredTransferSyntax) - { - // Flatten an array with the preferred transfer syntax - const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; - - // Setup the fallback transfer syntaxes - std::set fallbackSyntaxes; - fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); - fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); - fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); - fallbackSyntaxes.erase(preferredTransferSyntax); - - // Flatten an array with the fallback transfer syntaxes - std::vector asFallback; - asFallback.reserve(fallbackSyntaxes.size()); - for (std::set::const_iterator - it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it) - { - asFallback.push_back(it->c_str()); - } - - CheckStorageSOPClassesInvariant(); - - switch (mode) - { - case Mode_Generic: - { - unsigned int presentationContextId = 1; - - for (std::list::const_iterator it = reservedStorageSOPClasses_.begin(); - it != reservedStorageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback, remoteAet_); - } - - for (std::set::const_iterator it = storageSOPClasses_.begin(); - it != storageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback, remoteAet_); - } - - for (std::set::const_iterator it = defaultStorageSOPClasses_.begin(); - it != defaultStorageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback, remoteAet_); - } - - break; - } - - case Mode_RequestStorageCommitment: - case Mode_ReportStorageCommitment: - { - const char* as = UID_StorageCommitmentPushModelSOPClass; - - std::vector ts; - ts.push_back(UID_LittleEndianExplicitTransferSyntax); - ts.push_back(UID_LittleEndianImplicitTransferSyntax); - - T_ASC_SC_ROLE role; - switch (mode) - { - case Mode_RequestStorageCommitment: - role = ASC_SC_ROLE_DEFAULT; - break; - - case Mode_ReportStorageCommitment: - role = ASC_SC_ROLE_SCP; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - Check(ASC_addPresentationContext(pimpl_->params_, 1 /*presentationContextId*/, - as, &ts[0], ts.size(), role), - remoteAet_, "initializing"); - - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - - static bool IsGenericTransferSyntax(const std::string& syntax) - { - return (syntax == UID_LittleEndianExplicitTransferSyntax || - syntax == UID_BigEndianExplicitTransferSyntax || - syntax == UID_LittleEndianImplicitTransferSyntax); - } - - - void DicomUserConnection::PImpl::Store(std::string& sopClassUidOut, - std::string& sopInstanceUidOut, - DcmInputStream& is, - DicomUserConnection& connection, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - DcmFileFormat dcmff; - Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength), - connection.remoteAet_, "C-STORE"); - - // Determine the storage SOP class UID for this instance - OFString sopClassUid; - if (dcmff.getDataset()->findAndGetOFString(DCM_SOPClassUID, sopClassUid).good()) - { - connection.AddStorageSOPClass(sopClassUid.c_str()); - } - - // Determine whether a new presentation context must be - // negotiated, depending on the transfer syntax of this instance - DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); - const std::string syntax(xfer.getXferID()); - bool isGeneric = IsGenericTransferSyntax(syntax); - - bool renegotiate; - - 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) - { - if (isGeneric) - { - connection.ResetPreferredTransferSyntax(); - } - else - { - connection.SetPreferredTransferSyntax(syntax); - } - } - - if (!connection.IsOpen()) - { - connection.Open(); - } - - // Figure out which SOP class and SOP instance is encapsulated in the file - DIC_UI sopClass; - DIC_UI sopInstance; - -#if DCMTK_VERSION_NUMBER >= 364 - if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance))) -#else - if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance)) -#endif - { - throw OrthancException(ErrorCode_NoSopClassOrInstance, - "Unable to determine the SOP class/instance for C-STORE with AET " + - connection.remoteAet_); - } - - sopClassUidOut.assign(sopClass); - sopInstanceUidOut.assign(sopInstance); - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass); - if (presID == 0) - { - const char *modalityName = dcmSOPClassUIDToModality(sopClass); - if (modalityName == NULL) modalityName = dcmFindNameOfUID(sopClass); - if (modalityName == NULL) modalityName = "unknown SOP class"; - throw OrthancException(ErrorCode_NoPresentationContext, - "Unable to determine the accepted presentation contexts for C-STORE with AET " + - connection.remoteAet_ + " (" + std::string(modalityName) + ")"); - } - - // Prepare the transmission of data - T_DIMSE_C_StoreRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = assoc_->nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - strncpy(request.AffectedSOPInstanceUID, sopInstance, DIC_UI_LEN); - - if (!moveOriginatorAET.empty()) - { - strncpy(request.MoveOriginatorApplicationEntityTitle, - moveOriginatorAET.c_str(), DIC_AE_LEN); - request.opts = O_STORE_MOVEORIGINATORAETITLE; - - request.MoveOriginatorID = moveOriginatorID; // The type DIC_US is an alias for uint16_t - request.opts |= O_STORE_MOVEORIGINATORID; - } - - // Finally conduct transmission of data - T_DIMSE_C_StoreRSP response; - DcmDataset* statusDetail = NULL; - Check(DIMSE_storeUser(assoc_, presID, &request, - NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL, - /*opt_blockMode*/ (dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ dimseTimeout_, - &response, &statusDetail, NULL), - connection.remoteAet_, "C-STORE"); - - if (statusDetail != NULL) - { - delete statusDetail; - } - - - /** - * New in Orthanc 1.6.0: Deal with failures during C-STORE. - * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_B.2.3.html#table_B.2-1 - **/ - - if (response.DimseStatus != 0x0000 && // Success - response.DimseStatus != 0xB000 && // Warning - Coercion of Data Elements - response.DimseStatus != 0xB007 && // Warning - Data Set does not match SOP Class - response.DimseStatus != 0xB006) // Warning - Elements Discarded - { - char buf[16]; - sprintf(buf, "%04X", response.DimseStatus); - throw OrthancException(ErrorCode_NetworkProtocol, - "C-STORE SCU to AET \"" + connection.remoteAet_ + - "\" has failed with DIMSE status 0x" + buf); - } - } - - - namespace - { - struct FindPayload - { - DicomFindAnswers* answers; - const char* level; - bool isWorklist; - }; - } - - - static void FindCallback( - /* in */ - void *callbackData, - T_DIMSE_C_FindRQ *request, /* original find request */ - int responseCount, - T_DIMSE_C_FindRSP *response, /* pending response received */ - DcmDataset *responseIdentifiers /* pending response identifiers */ - ) - { - FindPayload& payload = *reinterpret_cast(callbackData); - - if (responseIdentifiers != NULL) - { - if (payload.isWorklist) - { - ParsedDicomFile answer(*responseIdentifiers); - payload.answers->Add(answer); - } - else - { - DicomMap m; - FromDcmtkBridge::ExtractDicomSummary(m, *responseIdentifiers); - - if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) - { - m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level, false); - } - - payload.answers->Add(m); - } - } - } - - - static void NormalizeFindQuery(DicomMap& fixedQuery, - ResourceType level, - const DicomMap& fields) - { - std::set allowedTags; - - // WARNING: Do not add "break" or reorder items in this switch-case! - switch (level) - { - case ResourceType_Instance: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance); - - case ResourceType_Series: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Series); - - case ResourceType_Study: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Study); - - case ResourceType_Patient: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - switch (level) - { - case ResourceType_Patient: - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES); - break; - - case ResourceType_Study: - allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY); - allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES); - allowedTags.insert(DICOM_TAG_SOP_CLASSES_IN_STUDY); - break; - - case ResourceType_Series: - allowedTags.insert(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES); - break; - - default: - break; - } - - allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET); - - DicomArray query(fields); - for (size_t i = 0; i < query.GetSize(); i++) - { - const DicomTag& tag = query.GetElement(i).GetTag(); - if (allowedTags.find(tag) == allowedTags.end()) - { - LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag; - } - else - { - fixedQuery.SetValue(tag, query.GetElement(i).GetValue()); - } - } - } - - - static ParsedDicomFile* ConvertQueryFields(const DicomMap& fields, - ModalityManufacturer manufacturer) - { - // Fix outgoing C-Find requests issue for Syngo.Via and its - // solution was reported by Emsy Chan by private mail on - // 2015-06-17. According to Robert van Ommen (2015-11-30), the - // same fix is required for Agfa Impax. This was generalized for - // generic manufacturer since it seems to affect PhilipsADW, - // GEWAServer as well: - // https://bitbucket.org/sjodogne/orthanc/issues/31/ - - switch (manufacturer) - { - case ModalityManufacturer_GenericNoWildcardInDates: - case ModalityManufacturer_GenericNoUniversalWildcard: - { - std::unique_ptr fix(fields.Clone()); - - std::set tags; - fix->GetTags(tags); - - for (std::set::const_iterator it = tags.begin(); it != tags.end(); ++it) - { - // Replace a "*" wildcard query by an empty query ("") for - // "date" or "all" value representations depending on the - // type of manufacturer. - if (manufacturer == ModalityManufacturer_GenericNoUniversalWildcard || - (manufacturer == ModalityManufacturer_GenericNoWildcardInDates && - FromDcmtkBridge::LookupValueRepresentation(*it) == ValueRepresentation_Date)) - { - const DicomValue* value = fix->TestAndGetValue(*it); - - if (value != NULL && - !value->IsNull() && - value->GetContent() == "*") - { - fix->SetValue(*it, "", false); - } - } - } - - return new ParsedDicomFile(*fix, GetDefaultDicomEncoding(), false /* be strict */); - } - - default: - return new ParsedDicomFile(fields, GetDefaultDicomEncoding(), false /* be strict */); - } - } - - - static void ExecuteFind(DicomFindAnswers& answers, - T_ASC_Association* association, - DcmDataset* dataset, - const char* sopClass, - bool isWorklist, - const char* level, - uint32_t dimseTimeout, - const std::string& remoteAet) - { - assert(isWorklist ^ (level != NULL)); - - FindPayload payload; - payload.answers = &answers; - payload.level = level; - payload.isWorklist = isWorklist; - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(association, sopClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_DicomFindUnavailable, - "Remote AET is " + remoteAet); - } - - T_DIMSE_C_FindRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = association->nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - - T_DIMSE_C_FindRSP response; - DcmDataset* statusDetail = NULL; - -#if DCMTK_VERSION_NUMBER >= 364 - int responseCount; -#endif - - OFCondition cond = DIMSE_findUser(association, presID, &request, dataset, -#if DCMTK_VERSION_NUMBER >= 364 - responseCount, -#endif - FindCallback, &payload, - /*opt_blockMode*/ (dimseTimeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ dimseTimeout, - &response, &statusDetail); - - if (statusDetail) - { - delete statusDetail; - } - - Check(cond, remoteAet, "C-FIND"); - - - /** - * New in Orthanc 1.6.0: Deal with failures during C-FIND. - * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#table_C.4-1 - **/ - - if (response.DimseStatus != 0x0000 && // Success - response.DimseStatus != 0xFF00 && // Pending - Matches are continuing - response.DimseStatus != 0xFF01) // Pending - Matches are continuing - { - char buf[16]; - sprintf(buf, "%04X", response.DimseStatus); - - if (response.DimseStatus == STATUS_FIND_Failed_UnableToProcess) - { - throw OrthancException(ErrorCode_NetworkProtocol, - HttpStatus_422_UnprocessableEntity, - "C-FIND SCU to AET \"" + remoteAet + - "\" has failed with DIMSE status 0x" + buf + - " (unable to process - invalid query ?)" - ); - } - else - { - throw OrthancException(ErrorCode_NetworkProtocol, - "C-FIND SCU to AET \"" + remoteAet + - "\" has failed with DIMSE status 0x" + buf); - } - } - - } - - - void DicomUserConnection::Find(DicomFindAnswers& result, - ResourceType level, - const DicomMap& originalFields, - bool normalize) - { - CheckIsOpen(); - - std::unique_ptr query; - - if (normalize) - { - DicomMap fields; - NormalizeFindQuery(fields, level, originalFields); - query.reset(ConvertQueryFields(fields, manufacturer_)); - } - else - { - query.reset(new ParsedDicomFile(originalFields, - GetDefaultDicomEncoding(), - false /* be strict */)); - } - - DcmDataset* dataset = query->GetDcmtkObject().getDataset(); - - const char* clevel = NULL; - const char* sopClass = NULL; - - switch (level) - { - case ResourceType_Patient: - clevel = "PATIENT"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT"); - sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; - break; - - case ResourceType_Study: - clevel = "STUDY"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - case ResourceType_Series: - clevel = "SERIES"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - case ResourceType_Instance: - clevel = "IMAGE"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - - const char* universal; - if (manufacturer_ == ModalityManufacturer_GE) - { - universal = "*"; - } - else - { - universal = ""; - } - - - // Add the expected tags for this query level. - // WARNING: Do not reorder or add "break" in this switch-case! - switch (level) - { - case ResourceType_Instance: - if (!dataset->tagExists(DCM_SOPInstanceUID)) - { - DU_putStringDOElement(dataset, DCM_SOPInstanceUID, universal); - } - - case ResourceType_Series: - if (!dataset->tagExists(DCM_SeriesInstanceUID)) - { - DU_putStringDOElement(dataset, DCM_SeriesInstanceUID, universal); - } - - case ResourceType_Study: - if (!dataset->tagExists(DCM_AccessionNumber)) - { - DU_putStringDOElement(dataset, DCM_AccessionNumber, universal); - } - - if (!dataset->tagExists(DCM_StudyInstanceUID)) - { - DU_putStringDOElement(dataset, DCM_StudyInstanceUID, universal); - } - - case ResourceType_Patient: - if (!dataset->tagExists(DCM_PatientID)) - { - DU_putStringDOElement(dataset, DCM_PatientID, universal); - } - - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - assert(clevel != NULL && sopClass != NULL); - ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, false, clevel, - pimpl_->dimseTimeout_, remoteAet_); - } - - - void DicomUserConnection::MoveInternal(const std::string& targetAet, - ResourceType level, - const DicomMap& fields) - { - CheckIsOpen(); - - std::unique_ptr query(ConvertQueryFields(fields, manufacturer_)); - DcmDataset* dataset = query->GetDcmtkObject().getDataset(); - - const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel; - switch (level) - { - case ResourceType_Patient: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT"); - break; - - case ResourceType_Study: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY"); - break; - - case ResourceType_Series: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES"); - break; - - case ResourceType_Instance: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_DicomMoveUnavailable, - "Remote AET is " + remoteAet_); - } - - T_DIMSE_C_MoveRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = pimpl_->assoc_->nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN); - - T_DIMSE_C_MoveRSP response; - DcmDataset* statusDetail = NULL; - DcmDataset* responseIdentifiers = NULL; - OFCondition cond = DIMSE_moveUser(pimpl_->assoc_, presID, &request, dataset, - NULL, NULL, - /*opt_blockMode*/ (pimpl_->dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, - pimpl_->net_, NULL, NULL, - &response, &statusDetail, &responseIdentifiers); - - if (statusDetail) - { - delete statusDetail; - } - - if (responseIdentifiers) - { - delete responseIdentifiers; - } - - Check(cond, remoteAet_, "C-MOVE"); - - - /** - * New in Orthanc 1.6.0: Deal with failures during C-MOVE. - * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.2.html#table_C.4-2 - **/ - - if (response.DimseStatus != 0x0000 && // Success - response.DimseStatus != 0xFF00) // Pending - Sub-operations are continuing - { - char buf[16]; - sprintf(buf, "%04X", response.DimseStatus); - - if (response.DimseStatus == STATUS_MOVE_Failed_UnableToProcess) - { - throw OrthancException(ErrorCode_NetworkProtocol, - HttpStatus_422_UnprocessableEntity, - "C-MOVE SCU to AET \"" + remoteAet_ + - "\" has failed with DIMSE status 0x" + buf + - " (unable to process - resource not found ?)" - ); - } - else - { - throw OrthancException(ErrorCode_NetworkProtocol, - "C-MOVE SCU to AET \"" + remoteAet_ + - "\" has failed with DIMSE status 0x" + buf); - } - } - } - - - void DicomUserConnection::ResetStorageSOPClasses() - { - CheckStorageSOPClassesInvariant(); - - storageSOPClasses_.clear(); - defaultStorageSOPClasses_.clear(); - - // Copy the short list of storage SOP classes from DCMTK, making - // room for the 5 SOP classes reserved for C-ECHO, C-FIND, C-MOVE at (**). - - std::set uncommon; - uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage); - uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage); - uncommon.insert(UID_ColorSoftcopyPresentationStateStorage); - uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage); - uncommon.insert(UID_XAXRFGrayscaleSoftcopyPresentationStateStorage); - - // Add the storage syntaxes for C-STORE - for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) - { - if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end()) - { - defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]); - } - } - - CheckStorageSOPClassesInvariant(); - } - - - void DicomUserConnection::DefaultSetup() - { - preferredTransferSyntax_ = DEFAULT_PREFERRED_TRANSFER_SYNTAX; - localAet_ = "STORESCU"; - remoteAet_ = "ANY-SCP"; - remoteHost_ = "127.0.0.1"; - remotePort_ = 104; - manufacturer_ = ModalityManufacturer_Generic; - - SetTimeout(defaultTimeout_); - pimpl_->net_ = NULL; - pimpl_->params_ = NULL; - pimpl_->assoc_ = NULL; - - // SOP classes for C-ECHO, C-FIND and C-MOVE (**) - reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass); - reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); - reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); - reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); - reservedStorageSOPClasses_.push_back(UID_FINDModalityWorklistInformationModel); - - 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() - { - Close(); - } - - - void DicomUserConnection::SetRemoteModality(const RemoteModalityParameters& parameters) - { - SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle()); - SetRemoteHost(parameters.GetHost()); - SetRemotePort(parameters.GetPortNumber()); - SetRemoteManufacturer(parameters.GetManufacturer()); - } - - - void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) - { - if (localAet_ != aet) - { - Close(); - localAet_ = aet; - } - } - - void DicomUserConnection::SetRemoteApplicationEntityTitle(const std::string& aet) - { - if (remoteAet_ != aet) - { - Close(); - remoteAet_ = aet; - } - } - - void DicomUserConnection::SetRemoteManufacturer(ModalityManufacturer manufacturer) - { - if (manufacturer_ != manufacturer) - { - Close(); - manufacturer_ = manufacturer; - } - } - - void DicomUserConnection::ResetPreferredTransferSyntax() - { - SetPreferredTransferSyntax(DEFAULT_PREFERRED_TRANSFER_SYNTAX); - } - - void DicomUserConnection::SetPreferredTransferSyntax(const std::string& preferredTransferSyntax) - { - if (preferredTransferSyntax_ != preferredTransferSyntax) - { - Close(); - preferredTransferSyntax_ = preferredTransferSyntax; - } - } - - - void DicomUserConnection::SetRemoteHost(const std::string& host) - { - if (remoteHost_ != host) - { - if (host.size() > HOST_NAME_MAX - 10) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Invalid host name (too long): " + host); - } - - Close(); - remoteHost_ = host; - } - } - - void DicomUserConnection::SetRemotePort(uint16_t port) - { - if (remotePort_ != port) - { - Close(); - remotePort_ = port; - } - } - - void DicomUserConnection::OpenInternal(Mode mode) - { - if (IsOpen()) - { - // Don't reopen the connection - return; - } - - LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() - << "\" to AET \"" << GetRemoteApplicationEntityTitle() << "\" on host " - << GetRemoteHost() << ":" << GetRemotePort() - << " (manufacturer: " << EnumerationToString(GetRemoteManufacturer()) << ")"; - - Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_), remoteAet_, "connecting"); - Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU), remoteAet_, "connecting"); - - // Set this application's title and the called application's title in the params - Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL), - remoteAet_, "connecting"); - - // Set the network addresses of the local and remote entities - char localHost[HOST_NAME_MAX]; - gethostname(localHost, HOST_NAME_MAX - 1); - - char remoteHostAndPort[HOST_NAME_MAX]; - -#ifdef _MSC_VER - _snprintf -#else - snprintf -#endif - (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", remoteHost_.c_str(), remotePort_); - - Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort), - remoteAet_, "connecting"); - - // Set various options - Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false), - remoteAet_, "connecting"); - - SetupPresentationContexts(mode, preferredTransferSyntax_); - - // Do the association - Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_), - remoteAet_, "connecting"); - - if (ASC_countAcceptedPresentationContexts(pimpl_->params_) == 0) - { - throw OrthancException(ErrorCode_NoPresentationContext, - "Unable to negotiate a presentation context with AET " + - remoteAet_); - } - -#if 0 - // Manual loop over the accepted transfer syntaxes - LST_HEAD **l = &pimpl_->params_->DULparams.acceptedPresentationContext; - if (*l != NULL) - { - DUL_PRESENTATIONCONTEXT* pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l); - LST_Position(l, (LST_NODE*)pc); - while (pc) - { - if (pc->result == ASC_P_ACCEPTANCE) - { - printf("Accepted: %d [%s] [%s]\n", pc->presentationContextID, pc->abstractSyntax, pc->acceptedTransferSyntax); - } - else - { - printf("Rejected: %d [%s]\n", pc->presentationContextID, pc->abstractSyntax); - } - pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l); - } - } -#endif - } - - void DicomUserConnection::Close() - { - if (pimpl_->assoc_ != NULL) - { - ASC_releaseAssociation(pimpl_->assoc_); - ASC_destroyAssociation(&pimpl_->assoc_); - pimpl_->assoc_ = NULL; - pimpl_->params_ = NULL; - } - else - { - if (pimpl_->params_ != NULL) - { - ASC_destroyAssociationParameters(&pimpl_->params_); - pimpl_->params_ = NULL; - } - } - - if (pimpl_->net_ != NULL) - { - ASC_dropNetwork(&pimpl_->net_); - pimpl_->net_ = NULL; - } - } - - bool DicomUserConnection::IsOpen() const - { - return pimpl_->IsOpen(); - } - - void DicomUserConnection::Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const void* buffer, - size_t size, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - // Prepare an input stream for the memory buffer - DcmInputBufferStream is; - if (size > 0) - is.setBuffer(buffer, size); - is.setEos(); - - pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID); - } - - void DicomUserConnection::Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& buffer, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - if (buffer.size() > 0) - Store(sopClassUid, sopInstanceUid, &buffer[0], buffer.size(), - moveOriginatorAET, moveOriginatorID); - else - Store(sopClassUid, sopInstanceUid, NULL, 0, moveOriginatorAET, moveOriginatorID); - } - - void DicomUserConnection::StoreFile(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& path, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - // Prepare an input stream for the file - DcmInputFileStream is(path.c_str()); - pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID); - } - - bool DicomUserConnection::Echo() - { - CheckIsOpen(); - DIC_US status; - Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++, - /*opt_blockMode*/ (pimpl_->dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, - &status, NULL), remoteAet_, "C-ECHO"); - return status == STATUS_Success; - } - - - static void TestAndCopyTag(DicomMap& result, - const DicomMap& source, - const DicomTag& tag) - { - if (!source.HasTag(tag)) - { - throw OrthancException(ErrorCode_BadRequest); - } - else - { - result.SetValue(tag, source.GetValue(tag)); - } - } - - - void DicomUserConnection::Move(const std::string& targetAet, - ResourceType level, - const DicomMap& findResult) - { - DicomMap move; - switch (level) - { - case ResourceType_Patient: - TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID); - break; - - case ResourceType_Study: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - break; - - case ResourceType_Series: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); - break; - - case ResourceType_Instance: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - MoveInternal(targetAet, level, move); - } - - - void DicomUserConnection::Move(const std::string& targetAet, - const DicomMap& findResult) - { - if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) - { - throw OrthancException(ErrorCode_InternalError); - } - - const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); - ResourceType level = StringToResourceType(tmp.c_str()); - - Move(targetAet, level, findResult); - } - - - void DicomUserConnection::MovePatient(const std::string& targetAet, - const std::string& patientId) - { - DicomMap query; - query.SetValue(DICOM_TAG_PATIENT_ID, patientId, false); - MoveInternal(targetAet, ResourceType_Patient, query); - } - - void DicomUserConnection::MoveStudy(const std::string& targetAet, - const std::string& studyUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - MoveInternal(targetAet, ResourceType_Study, query); - } - - void DicomUserConnection::MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); - MoveInternal(targetAet, ResourceType_Series, query); - } - - void DicomUserConnection::MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); - query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, false); - MoveInternal(targetAet, ResourceType_Instance, query); - } - - - void DicomUserConnection::SetTimeout(uint32_t seconds) - { - if (seconds == 0) - { - DisableTimeout(); - } - else - { - dcmConnectionTimeout.set(seconds); - pimpl_->dimseTimeout_ = seconds; - pimpl_->acseTimeout_ = seconds; // Timeout used during association negociation and ASC_releaseAssociation() - } - } - - - void DicomUserConnection::DisableTimeout() - { - /** - * Global timeout (seconds) for connecting to remote hosts. - * Default value is -1 which selects infinite timeout, i.e. blocking connect(). - */ - dcmConnectionTimeout.set(-1); - pimpl_->dimseTimeout_ = 0; - pimpl_->acseTimeout_ = 10; // Timeout used during association negociation and ASC_releaseAssociation() - } - - - void DicomUserConnection::CheckStorageSOPClassesInvariant() const - { - assert(storageSOPClasses_.size() + - defaultStorageSOPClasses_.size() + - reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES); - } - - void DicomUserConnection::AddStorageSOPClass(const char* sop) - { - CheckStorageSOPClassesInvariant(); - - if (storageSOPClasses_.find(sop) != storageSOPClasses_.end()) - { - // This storage SOP class is already explicitly registered. Do - // nothing. - return; - } - - if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end()) - { - // This storage SOP class is not explicitly registered, but is - // used by default. Just register it explicitly. - defaultStorageSOPClasses_.erase(sop); - storageSOPClasses_.insert(sop); - - CheckStorageSOPClassesInvariant(); - return; - } - - // This storage SOP class is neither explicitly, nor implicitly - // registered. Close the connection and register it explicitly. - - Close(); - - if (reservedStorageSOPClasses_.size() + - storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) // (*) - { - // The maximum number of SOP classes is reached - ResetStorageSOPClasses(); - defaultStorageSOPClasses_.erase(sop); - } - else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + - defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) - { - // Make room in the default storage syntaxes - assert(!defaultStorageSOPClasses_.empty()); // Necessarily true because condition (*) is false - defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin()); - } - - // Explicitly register the new storage syntax - storageSOPClasses_.insert(sop); - - CheckStorageSOPClassesInvariant(); - } - - - void DicomUserConnection::FindWorklist(DicomFindAnswers& result, - ParsedDicomFile& query) - { - CheckIsOpen(); - - DcmDataset* dataset = query.GetDcmtkObject().getDataset(); - const char* sopClass = UID_FINDModalityWorklistInformationModel; - - ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, true, - NULL, pimpl_->dimseTimeout_, remoteAet_); - } - - - void DicomUserConnection::SetDefaultTimeout(uint32_t seconds) - { - LOG(INFO) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " - << 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.GetPortNumber() && - manufacturer_ == remote.GetManufacturer()); - } - - - static void FillSopSequence(DcmDataset& dataset, - const DcmTagKey& tag, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids, - const std::vector& failureReasons, - bool hasFailureReasons) - { - assert(sopClassUids.size() == sopInstanceUids.size() && - (hasFailureReasons ? - failureReasons.size() == sopClassUids.size() : - failureReasons.empty())); - - if (sopInstanceUids.empty()) - { - // Add an empty sequence - if (!dataset.insertEmptyElement(tag).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - else - { - for (size_t i = 0; i < sopClassUids.size(); i++) - { - std::unique_ptr item(new DcmItem); - if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() || - !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() || - (hasFailureReasons && - !item->putAndInsertUint16(DCM_FailureReason, failureReasons[i]).good()) || - !dataset.insertSequenceItem(tag, item.release()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - } - } - - - - - void DicomUserConnection::ReportStorageCommitment( - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids, - const std::vector& failureReasons) - { - if (sopClassUids.size() != sopInstanceUids.size() || - sopClassUids.size() != failureReasons.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (IsOpen()) - { - Close(); - } - - std::vector successSopClassUids, successSopInstanceUids, failedSopClassUids, failedSopInstanceUids; - std::vector failedReasons; - - successSopClassUids.reserve(sopClassUids.size()); - successSopInstanceUids.reserve(sopClassUids.size()); - failedSopClassUids.reserve(sopClassUids.size()); - failedSopInstanceUids.reserve(sopClassUids.size()); - failedReasons.reserve(sopClassUids.size()); - - for (size_t i = 0; i < sopClassUids.size(); i++) - { - switch (failureReasons[i]) - { - case StorageCommitmentFailureReason_Success: - successSopClassUids.push_back(sopClassUids[i]); - successSopInstanceUids.push_back(sopInstanceUids[i]); - break; - - case StorageCommitmentFailureReason_ProcessingFailure: - case StorageCommitmentFailureReason_NoSuchObjectInstance: - case StorageCommitmentFailureReason_ResourceLimitation: - case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported: - case StorageCommitmentFailureReason_ClassInstanceConflict: - case StorageCommitmentFailureReason_DuplicateTransactionUID: - failedSopClassUids.push_back(sopClassUids[i]); - failedSopInstanceUids.push_back(sopInstanceUids[i]); - failedReasons.push_back(failureReasons[i]); - break; - - default: - { - char buf[16]; - sprintf(buf, "%04xH", failureReasons[i]); - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Unsupported failure reason for storage commitment: " + std::string(buf)); - } - } - } - - try - { - OpenInternal(Mode_ReportStorageCommitment); - - /** - * N-EVENT-REPORT - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1 - * - * Status code: - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 - **/ - - /** - * Send the "EVENT_REPORT_RQ" request - **/ - - LOG(INFO) << "Reporting modality \"" << remoteAet_ - << "\" about storage commitment transaction: " << transactionUid - << " (" << successSopClassUids.size() << " successes, " - << failedSopClassUids.size() << " failures)"; - const DIC_US messageId = pimpl_->assoc_->nextMsgID++; - - { - T_DIMSE_Message message; - memset(&message, 0, sizeof(message)); - message.CommandField = DIMSE_N_EVENT_REPORT_RQ; - - T_DIMSE_N_EventReportRQ& content = message.msg.NEventReportRQ; - content.MessageID = messageId; - strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); - strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); - content.DataSetType = DIMSE_DATASET_PRESENT; - - DcmDataset dataset; - if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - - { - std::vector empty; - FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids, - successSopInstanceUids, empty, false); - } - - // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html - if (failedSopClassUids.empty()) - { - content.EventTypeID = 1; // "Storage Commitment Request Successful" - } - else - { - content.EventTypeID = 2; // "Storage Commitment Request Complete - Failures Exist" - - // Failure reason - // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 - FillSopSequence(dataset, DCM_FailedSOPSequence, failedSopClassUids, - failedSopInstanceUids, failedReasons, true); - } - - int presID = ASC_findAcceptedPresentationContextID( - pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to send N-EVENT-REPORT request to AET: " + remoteAet_); - } - - if (!DIMSE_sendMessageUsingMemoryData( - pimpl_->assoc_, presID, &message, NULL /* status detail */, - &dataset, NULL /* callback */, NULL /* callback context */, - NULL /* commandSet */).good()) - { - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - - /** - * Read the "EVENT_REPORT_RSP" response - **/ - - { - T_ASC_PresentationContextID presID = 0; - T_DIMSE_Message message; - - const int timeout = pimpl_->dimseTimeout_; - if (!DIMSE_receiveCommand(pimpl_->assoc_, - (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout, - &presID, &message, NULL /* no statusDetail */).good() || - message.CommandField != DIMSE_N_EVENT_REPORT_RSP) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to read N-EVENT-REPORT response from AET: " + remoteAet_); - } - - const T_DIMSE_N_EventReportRSP& content = message.msg.NEventReportRSP; - if (content.MessageIDBeingRespondedTo != messageId || - !(content.opts & O_NEVENTREPORT_AFFECTEDSOPCLASSUID) || - !(content.opts & O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID) || - //(content.opts & O_NEVENTREPORT_EVENTTYPEID) || // Pedantic test - The "content.EventTypeID" is not used by Orthanc - std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || - std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || - content.DataSetType != DIMSE_DATASET_NULL) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Badly formatted N-EVENT-REPORT response from AET: " + remoteAet_); - } - - if (content.DimseStatus != 0 /* success */) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "The request cannot be handled by remote AET: " + remoteAet_); - } - } - - Close(); - } - catch (OrthancException&) - { - Close(); - throw; - } - } - - - - void DicomUserConnection::RequestStorageCommitment( - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids) - { - if (sopClassUids.size() != sopInstanceUids.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - for (size_t i = 0; i < sopClassUids.size(); i++) - { - if (sopClassUids[i].empty() || - sopInstanceUids[i].empty()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "The SOP class/instance UIDs cannot be empty, found: \"" + - sopClassUids[i] + "\" / \"" + sopInstanceUids[i] + "\""); - } - } - - if (transactionUid.size() < 5 || - transactionUid.substr(0, 5) != "2.25.") - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (IsOpen()) - { - Close(); - } - - try - { - OpenInternal(Mode_RequestStorageCommitment); - - /** - * N-ACTION - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4 - * - * Status code: - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 - **/ - - /** - * Send the "N_ACTION_RQ" request - **/ - - LOG(INFO) << "Request to modality \"" << remoteAet_ - << "\" about storage commitment for " << sopClassUids.size() - << " instances, with transaction UID: " << transactionUid; - const DIC_US messageId = pimpl_->assoc_->nextMsgID++; - - { - T_DIMSE_Message message; - memset(&message, 0, sizeof(message)); - message.CommandField = DIMSE_N_ACTION_RQ; - - T_DIMSE_N_ActionRQ& content = message.msg.NActionRQ; - content.MessageID = messageId; - strncpy(content.RequestedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); - strncpy(content.RequestedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); - content.ActionTypeID = 1; // "Request Storage Commitment" - content.DataSetType = DIMSE_DATASET_PRESENT; - - DcmDataset dataset; - if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - - { - std::vector empty; - FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids, empty, false); - } - - int presID = ASC_findAcceptedPresentationContextID( - pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to send N-ACTION request to AET: " + remoteAet_); - } - - if (!DIMSE_sendMessageUsingMemoryData( - pimpl_->assoc_, presID, &message, NULL /* status detail */, - &dataset, NULL /* callback */, NULL /* callback context */, - NULL /* commandSet */).good()) - { - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - - /** - * Read the "N_ACTION_RSP" response - **/ - - { - T_ASC_PresentationContextID presID = 0; - T_DIMSE_Message message; - - const int timeout = pimpl_->dimseTimeout_; - if (!DIMSE_receiveCommand(pimpl_->assoc_, - (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout, - &presID, &message, NULL /* no statusDetail */).good() || - message.CommandField != DIMSE_N_ACTION_RSP) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to read N-ACTION response from AET: " + remoteAet_); - } - - const T_DIMSE_N_ActionRSP& content = message.msg.NActionRSP; - if (content.MessageIDBeingRespondedTo != messageId || - !(content.opts & O_NACTION_AFFECTEDSOPCLASSUID) || - !(content.opts & O_NACTION_AFFECTEDSOPINSTANCEUID) || - //(content.opts & O_NACTION_ACTIONTYPEID) || // Pedantic test - The "content.ActionTypeID" is not used by Orthanc - std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || - std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || - content.DataSetType != DIMSE_DATASET_NULL) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Badly formatted N-ACTION response from AET: " + remoteAet_); - } - - if (content.DimseStatus != 0 /* success */) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "The request cannot be handled by remote AET: " + remoteAet_); - } - } - - Close(); - } - catch (OrthancException&) - { - Close(); - throw; - } - } -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Core/DicomNetworking/DicomUserConnection.h --- a/Core/DicomNetworking/DicomUserConnection.h Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,253 +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-2020 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 ORTHANC_ENABLE_DCMTK_NETWORKING != 1 -# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 -#endif - -#include "DicomFindAnswers.h" -#include "../Enumerations.h" -#include "RemoteModalityParameters.h" - -#include -#include -#include -#include - -namespace Orthanc -{ - class DicomUserConnection : public boost::noncopyable - { - private: - struct PImpl; - boost::shared_ptr pimpl_; - - enum Mode - { - Mode_Generic, - Mode_ReportStorageCommitment, - Mode_RequestStorageCommitment - }; - - // Connection parameters - std::string preferredTransferSyntax_; - std::string localAet_; - std::string remoteAet_; - std::string remoteHost_; - uint16_t remotePort_; - ModalityManufacturer manufacturer_; - std::set storageSOPClasses_; - std::list reservedStorageSOPClasses_; - std::set defaultStorageSOPClasses_; - - void CheckIsOpen() const; - - void SetupPresentationContexts(Mode mode, - const std::string& preferredTransferSyntax); - - void MoveInternal(const std::string& targetAet, - ResourceType level, - const DicomMap& fields); - - void ResetStorageSOPClasses(); - - void CheckStorageSOPClassesInvariant() const; - - void DefaultSetup(); - - void OpenInternal(Mode mode); - - 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); - - const std::string& GetLocalApplicationEntityTitle() const - { - return localAet_; - } - - void SetRemoteApplicationEntityTitle(const std::string& aet); - - const std::string& GetRemoteApplicationEntityTitle() const - { - return remoteAet_; - } - - void SetRemoteHost(const std::string& host); - - const std::string& GetRemoteHost() const - { - return remoteHost_; - } - - void SetRemotePort(uint16_t port); - - uint16_t GetRemotePort() const - { - return remotePort_; - } - - void SetRemoteManufacturer(ModalityManufacturer manufacturer); - - ModalityManufacturer GetRemoteManufacturer() const - { - return manufacturer_; - } - - void ResetPreferredTransferSyntax(); - - void SetPreferredTransferSyntax(const std::string& preferredTransferSyntax); - - const std::string& GetPreferredTransferSyntax() const - { - return preferredTransferSyntax_; - } - - void AddStorageSOPClass(const char* sop); - - void Open() - { - OpenInternal(Mode_Generic); - } - - void Close(); - - bool IsOpen() const; - - bool Echo(); - - void Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const void* buffer, - size_t size, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const void* buffer, - size_t size) - { - Store(sopClassUid, sopInstanceUid, buffer, size, "", 0); // Not a C-Move - } - - void Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& buffer, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& buffer) - { - Store(sopClassUid, sopInstanceUid, buffer, "", 0); // Not a C-Move - } - - void StoreFile(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& path, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void StoreFile(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& path) - { - StoreFile(sopClassUid, sopInstanceUid, path, "", 0); // Not a C-Move - } - - void Find(DicomFindAnswers& result, - ResourceType level, - const DicomMap& fields, - bool normalize); // Whether to normalize the DICOM query - - void Move(const std::string& targetAet, - ResourceType level, - const DicomMap& findResult); - - void Move(const std::string& targetAet, - const DicomMap& findResult); - - void MovePatient(const std::string& targetAet, - const std::string& patientId); - - void MoveStudy(const std::string& targetAet, - const std::string& studyUid); - - void MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid); - - void MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid); - - void SetTimeout(uint32_t seconds); - - void DisableTimeout(); - - void FindWorklist(DicomFindAnswers& result, - ParsedDicomFile& query); - - static void SetDefaultTimeout(uint32_t seconds); - - bool IsSameAssociation(const std::string& localAet, - const RemoteModalityParameters& remote) const; - - void ReportStorageCommitment( - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids, - const std::vector& failureReasons); - - // transactionUid: To be generated by Toolbox::GenerateDicomPrivateUniqueIdentifier() - void RequestStorageCommitment( - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids); - }; -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/CMake/OrthancFrameworkConfiguration.cmake --- a/Resources/CMake/OrthancFrameworkConfiguration.cmake Mon Apr 27 18:16:20 2020 +0200 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Mon Apr 27 18:29:26 2020 +0200 @@ -484,10 +484,9 @@ ${ORTHANC_ROOT}/Core/DicomNetworking/DicomAssociation.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/DicomAssociationParameters.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/DicomControlUserConnection.cpp - ${ORTHANC_ROOT}/Core/DicomNetworking/DicomStoreUserConnection.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/DicomFindAnswers.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/DicomServer.cpp - ${ORTHANC_ROOT}/Core/DicomNetworking/DicomUserConnection.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/DicomStoreUserConnection.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/CommandDispatcher.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/FindScp.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/MoveScp.cpp diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/CallSystemCommand.cpp --- a/Resources/Graveyard/OldScheduler/CallSystemCommand.cpp Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +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-2020 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 "../PrecompiledHeadersServer.h" -#include "CallSystemCommand.h" - -#include "../../Core/Logging.h" -#include "../../Core/Toolbox.h" -#include "../../Core/TemporaryFile.h" - -namespace Orthanc -{ - CallSystemCommand::CallSystemCommand(ServerContext& context, - const std::string& command, - const std::vector& arguments) : - context_(context), - command_(command), - arguments_(arguments) - { - } - - bool CallSystemCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Calling system command " << command_ << " on instance " << *it; - - try - { - std::string dicom; - context_.ReadDicom(dicom, *it); - - TemporaryFile tmp; - tmp.Write(dicom); - - std::vector args = arguments_; - args.push_back(tmp.GetPath()); - - SystemToolbox::ExecuteSystemCommand(command_, args); - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to call system command " << command_ - << " on instance " << *it << " in a Lua script: " << e.What(); - } - } - - return true; - } -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/CallSystemCommand.h --- a/Resources/Graveyard/OldScheduler/CallSystemCommand.h Mon Apr 27 18:16:20 2020 +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-2020 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 "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class CallSystemCommand : public IServerCommand - { - private: - ServerContext& context_; - std::string command_; - std::vector arguments_; - - public: - CallSystemCommand(ServerContext& context, - const std::string& command, - const std::vector& arguments); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp --- a/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +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-2020 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 "../PrecompiledHeadersServer.h" -#include "DeleteInstanceCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - bool DeleteInstanceCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Deleting instance " << *it; - - try - { - Json::Value tmp; - context_.DeleteResource(tmp, *it, ResourceType_Instance); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to delete instance " << *it << ": " << e.What(); - } - } - - return true; - } -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h --- a/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h Mon Apr 27 18:16:20 2020 +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-2020 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 "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class DeleteInstanceCommand : public IServerCommand - { - private: - ServerContext& context_; - - public: - DeleteInstanceCommand(ServerContext& context) : context_(context) - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/IServerCommand.h --- a/Resources/Graveyard/OldScheduler/IServerCommand.h Mon Apr 27 18:16:20 2020 +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-2020 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 -#include - -namespace Orthanc -{ - class IServerCommand : public boost::noncopyable - { - public: - typedef std::list ListOfStrings; - - virtual ~IServerCommand() - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) = 0; - }; -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp --- a/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,124 +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-2020 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 "../PrecompiledHeadersServer.h" -#include "ModifyInstanceCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - ModifyInstanceCommand::ModifyInstanceCommand(ServerContext& context, - RequestOrigin origin, - DicomModification* modification) : - context_(context), - origin_(origin), - modification_(modification) - { - modification_->SetAllowManualIdentifiers(true); - - if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID)) - { - modification_->SetLevel(ResourceType_Patient); - } - else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - modification_->SetLevel(ResourceType_Study); - } - else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - modification_->SetLevel(ResourceType_Series); - } - else - { - modification_->SetLevel(ResourceType_Instance); - } - - if (origin_ != RequestOrigin_Lua) - { - // TODO If issued from HTTP, "remoteIp" and "username" must be provided - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - ModifyInstanceCommand::~ModifyInstanceCommand() - { - if (modification_) - { - delete modification_; - } - } - - - bool ModifyInstanceCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Modifying resource " << *it; - - try - { - std::auto_ptr modified; - - { - ServerContext::DicomCacheLocker lock(context_, *it); - modified.reset(lock.GetDicom().Clone(true)); - } - - modification_->Apply(*modified); - - DicomInstanceToStore toStore; - assert(origin_ == RequestOrigin_Lua); - toStore.SetLuaOrigin(); - toStore.SetParsedDicomFile(*modified); - // TODO other metadata - toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, *it); - - std::string modifiedId; - context_.Store(modifiedId, toStore); - - // Only chain with other commands if this command succeeds - outputs.push_back(modifiedId); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to modify instance " << *it << ": " << e.What(); - } - } - - return true; - } -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h --- a/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +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-2020 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 "IServerCommand.h" -#include "../ServerContext.h" -#include "../../Core/DicomParsing/DicomModification.h" - -namespace Orthanc -{ - class ModifyInstanceCommand : public IServerCommand - { - private: - ServerContext& context_; - RequestOrigin origin_; - DicomModification* modification_; - - public: - ModifyInstanceCommand(ServerContext& context, - RequestOrigin origin, - DicomModification* modification); // takes the ownership - - virtual ~ModifyInstanceCommand(); - - const DicomModification& GetModification() const - { - return *modification_; - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp --- a/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp Mon Apr 27 18:16:20 2020 +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-2020 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 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h --- a/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h Mon Apr 27 18:16:20 2020 +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-2020 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 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp --- a/Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +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-2020 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 "../PrecompiledHeadersServer.h" -#include "ServerCommandInstance.h" - -#include "../../Core/OrthancException.h" - -namespace Orthanc -{ - bool ServerCommandInstance::Execute(IListener& listener) - { - ListOfStrings outputs; - - bool success = false; - - try - { - if (command_->Apply(outputs, inputs_)) - { - success = true; - } - } - catch (OrthancException&) - { - } - - if (!success) - { - listener.SignalFailure(jobId_); - return true; - } - - for (std::list::iterator - it = next_.begin(); it != next_.end(); ++it) - { - for (ListOfStrings::const_iterator - output = outputs.begin(); output != outputs.end(); ++output) - { - (*it)->AddInput(*output); - } - } - - listener.SignalSuccess(jobId_); - return true; - } - - - ServerCommandInstance::ServerCommandInstance(IServerCommand *command, - const std::string& jobId) : - command_(command), - jobId_(jobId), - connectedToSink_(false) - { - if (command_ == NULL) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - ServerCommandInstance::~ServerCommandInstance() - { - if (command_ != NULL) - { - delete command_; - } - } -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/ServerCommandInstance.h --- a/Resources/Graveyard/OldScheduler/ServerCommandInstance.h Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../../Core/IDynamicObject.h" -#include "IServerCommand.h" - -namespace Orthanc -{ - class ServerCommandInstance : public IDynamicObject - { - friend class ServerScheduler; - - public: - class IListener - { - public: - virtual ~IListener() - { - } - - virtual void SignalSuccess(const std::string& jobId) = 0; - - virtual void SignalFailure(const std::string& jobId) = 0; - }; - - private: - typedef IServerCommand::ListOfStrings ListOfStrings; - - IServerCommand *command_; - std::string jobId_; - ListOfStrings inputs_; - std::list next_; - bool connectedToSink_; - - bool Execute(IListener& listener); - - public: - ServerCommandInstance(IServerCommand *command, - const std::string& jobId); - - virtual ~ServerCommandInstance(); - - const std::string& GetJobId() const - { - return jobId_; - } - - void AddInput(const std::string& input) - { - inputs_.push_back(input); - } - - void ConnectOutput(ServerCommandInstance& next) - { - next_.push_back(&next); - } - - void SetConnectedToSink(bool connected = true) - { - connectedToSink_ = connected; - } - - bool IsConnectedToSink() const - { - return connectedToSink_; - } - - const std::list& GetNextCommands() const - { - return next_; - } - }; -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/ServerJob.cpp --- a/Resources/Graveyard/OldScheduler/ServerJob.cpp Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +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-2020 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 "../PrecompiledHeadersServer.h" -#include "ServerJob.h" - -#include "../../Core/OrthancException.h" -#include "../../Core/Toolbox.h" - -namespace Orthanc -{ - void ServerJob::CheckOrdering() - { - std::map index; - - unsigned int count = 0; - for (std::list::const_iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - index[*it] = count++; - } - - for (std::list::const_iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - const std::list& nextCommands = (*it)->GetNextCommands(); - - for (std::list::const_iterator - next = nextCommands.begin(); next != nextCommands.end(); ++next) - { - if (index.find(*next) == index.end() || - index[*next] <= index[*it]) - { - // You must reorder your calls to "ServerJob::AddCommand" - throw OrthancException(ErrorCode_BadJobOrdering); - } - } - } - } - - - size_t ServerJob::Submit(SharedMessageQueue& target, - ServerCommandInstance::IListener& listener) - { - if (submitted_) - { - // This job has already been submitted - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - CheckOrdering(); - - size_t size = filters_.size(); - - for (std::list::iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - target.Enqueue(*it); - } - - filters_.clear(); - submitted_ = true; - - return size; - } - - - ServerJob::ServerJob() : - jobId_(Toolbox::GenerateUuid()), - submitted_(false), - description_("no description") - { - } - - - ServerJob::~ServerJob() - { - for (std::list::iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - delete *it; - } - - for (std::list::iterator - it = payloads_.begin(); it != payloads_.end(); ++it) - { - delete *it; - } - } - - - ServerCommandInstance& ServerJob::AddCommand(IServerCommand* filter) - { - if (submitted_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - filters_.push_back(new ServerCommandInstance(filter, jobId_)); - - return *filters_.back(); - } - - - IDynamicObject& ServerJob::AddPayload(IDynamicObject* payload) - { - if (submitted_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - payloads_.push_back(payload); - - return *filters_.back(); - } - -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/ServerJob.h --- a/Resources/Graveyard/OldScheduler/ServerJob.h Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +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-2020 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 "ServerCommandInstance.h" -#include "../../Core/MultiThreading/SharedMessageQueue.h" - -namespace Orthanc -{ - class ServerJob - { - friend class ServerScheduler; - - private: - std::list filters_; - std::list payloads_; - std::string jobId_; - bool submitted_; - std::string description_; - - void CheckOrdering(); - - size_t Submit(SharedMessageQueue& target, - ServerCommandInstance::IListener& listener); - - public: - ServerJob(); - - ~ServerJob(); - - const std::string& GetId() const - { - return jobId_; - } - - void SetDescription(const std::string& description) - { - description_ = description; - } - - const std::string& GetDescription() const - { - return description_; - } - - ServerCommandInstance& AddCommand(IServerCommand* filter); - - // Take the ownership of a payload to a job. This payload will be - // automatically freed when the job succeeds or fails. - IDynamicObject& AddPayload(IDynamicObject* payload); - }; -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/ServerScheduler.cpp --- a/Resources/Graveyard/OldScheduler/ServerScheduler.cpp Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,359 +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-2020 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 "../PrecompiledHeadersServer.h" -#include "ServerScheduler.h" - -#include "../../Core/OrthancException.h" -#include "../../Core/Logging.h" - -namespace Orthanc -{ - namespace - { - // Anonymous namespace to avoid clashes between compilation modules - class Sink : public IServerCommand - { - private: - ListOfStrings& target_; - - public: - explicit Sink(ListOfStrings& target) : target_(target) - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - target_.push_back(*it); - } - - return true; - } - }; - } - - - ServerScheduler::JobInfo& ServerScheduler::GetJobInfo(const std::string& jobId) - { - Jobs::iterator info = jobs_.find(jobId); - - if (info == jobs_.end()) - { - throw OrthancException(ErrorCode_InternalError); - } - - return info->second; - } - - - void ServerScheduler::SignalSuccess(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - JobInfo& info = GetJobInfo(jobId); - info.success_++; - - assert(info.failures_ == 0); - - if (info.success_ >= info.size_) - { - if (info.watched_) - { - watchedJobStatus_[jobId] = JobStatus_Success; - watchedJobFinished_.notify_all(); - } - - LOG(INFO) << "Job successfully finished (" << info.description_ << ")"; - jobs_.erase(jobId); - - availableJob_.Release(); - } - } - - - void ServerScheduler::SignalFailure(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - JobInfo& info = GetJobInfo(jobId); - info.failures_++; - - if (info.success_ + info.failures_ >= info.size_) - { - if (info.watched_) - { - watchedJobStatus_[jobId] = JobStatus_Failure; - watchedJobFinished_.notify_all(); - } - - LOG(ERROR) << "Job has failed (" << info.description_ << ")"; - jobs_.erase(jobId); - - availableJob_.Release(); - } - } - - - void ServerScheduler::Worker(ServerScheduler* that) - { - static const int32_t TIMEOUT = 100; - - LOG(WARNING) << "The server scheduler has started"; - - while (!that->finish_) - { - std::auto_ptr object(that->queue_.Dequeue(TIMEOUT)); - if (object.get() != NULL) - { - ServerCommandInstance& filter = dynamic_cast(*object); - - // Skip the execution of this filter if its parent job has - // previously failed. - bool jobHasFailed; - { - boost::mutex::scoped_lock lock(that->mutex_); - JobInfo& info = that->GetJobInfo(filter.GetJobId()); - jobHasFailed = (info.failures_ > 0 || info.cancel_); - } - - if (jobHasFailed) - { - that->SignalFailure(filter.GetJobId()); - } - else - { - filter.Execute(*that); - } - } - } - } - - - void ServerScheduler::SubmitInternal(ServerJob& job, - bool watched) - { - availableJob_.Acquire(); - - boost::mutex::scoped_lock lock(mutex_); - - JobInfo info; - info.size_ = job.Submit(queue_, *this); - info.cancel_ = false; - info.success_ = 0; - info.failures_ = 0; - info.description_ = job.GetDescription(); - info.watched_ = watched; - - assert(info.size_ > 0); - - if (watched) - { - watchedJobStatus_[job.GetId()] = JobStatus_Running; - } - - jobs_[job.GetId()] = info; - - LOG(INFO) << "New job submitted (" << job.description_ << ")"; - } - - - ServerScheduler::ServerScheduler(unsigned int maxJobs) : availableJob_(maxJobs) - { - if (maxJobs == 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - finish_ = false; - worker_ = boost::thread(Worker, this); - } - - - ServerScheduler::~ServerScheduler() - { - if (!finish_) - { - LOG(ERROR) << "INTERNAL ERROR: ServerScheduler::Finalize() should be invoked manually to avoid mess in the destruction order!"; - Stop(); - } - } - - - void ServerScheduler::Stop() - { - if (!finish_) - { - finish_ = true; - - if (worker_.joinable()) - { - worker_.join(); - } - } - } - - - void ServerScheduler::Submit(ServerJob& job) - { - if (job.filters_.empty()) - { - return; - } - - SubmitInternal(job, false); - } - - - bool ServerScheduler::SubmitAndWait(ListOfStrings& outputs, - ServerJob& job) - { - std::string jobId = job.GetId(); - - outputs.clear(); - - if (job.filters_.empty()) - { - return true; - } - - // Add a sink filter to collect all the results of the filters - // that have no next filter. - ServerCommandInstance& sink = job.AddCommand(new Sink(outputs)); - - for (std::list::iterator - it = job.filters_.begin(); it != job.filters_.end(); ++it) - { - if ((*it) != &sink && - (*it)->IsConnectedToSink()) - { - (*it)->ConnectOutput(sink); - } - } - - // Submit the job - SubmitInternal(job, true); - - // Wait for the job to complete (either success or failure) - JobStatus status; - - { - boost::mutex::scoped_lock lock(mutex_); - - assert(watchedJobStatus_.find(jobId) != watchedJobStatus_.end()); - - while (watchedJobStatus_[jobId] == JobStatus_Running) - { - watchedJobFinished_.wait(lock); - } - - status = watchedJobStatus_[jobId]; - watchedJobStatus_.erase(jobId); - } - - return (status == JobStatus_Success); - } - - - bool ServerScheduler::SubmitAndWait(ServerJob& job) - { - ListOfStrings ignoredSink; - return SubmitAndWait(ignoredSink, job); - } - - - bool ServerScheduler::IsRunning(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - return jobs_.find(jobId) != jobs_.end(); - } - - - void ServerScheduler::Cancel(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - Jobs::iterator job = jobs_.find(jobId); - - if (job != jobs_.end()) - { - job->second.cancel_ = true; - LOG(WARNING) << "Canceling a job (" << job->second.description_ << ")"; - } - } - - - float ServerScheduler::GetProgress(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - Jobs::iterator job = jobs_.find(jobId); - - if (job == jobs_.end() || - job->second.size_ == 0 /* should never happen */) - { - // This job is not running - return 1; - } - - if (job->second.failures_ != 0) - { - return 1; - } - - if (job->second.size_ == 1) - { - return static_cast(job->second.success_); - } - - return (static_cast(job->second.success_) / - static_cast(job->second.size_ - 1)); - } - - - void ServerScheduler::GetListOfJobs(ListOfStrings& jobs) - { - boost::mutex::scoped_lock lock(mutex_); - - jobs.clear(); - - for (Jobs::const_iterator - it = jobs_.begin(); it != jobs_.end(); ++it) - { - jobs.push_back(it->first); - } - } -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/ServerScheduler.h --- a/Resources/Graveyard/OldScheduler/ServerScheduler.h Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +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-2020 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 "ServerJob.h" - -#include "../../Core/MultiThreading/Semaphore.h" - -namespace Orthanc -{ - class ServerScheduler : public ServerCommandInstance::IListener - { - private: - struct JobInfo - { - bool watched_; - bool cancel_; - size_t size_; - size_t success_; - size_t failures_; - std::string description_; - }; - - enum JobStatus - { - JobStatus_Running = 1, - JobStatus_Success = 2, - JobStatus_Failure = 3 - }; - - typedef IServerCommand::ListOfStrings ListOfStrings; - typedef std::map Jobs; - - boost::mutex mutex_; - boost::condition_variable watchedJobFinished_; - Jobs jobs_; - SharedMessageQueue queue_; - bool finish_; - boost::thread worker_; - std::map watchedJobStatus_; - Semaphore availableJob_; - - JobInfo& GetJobInfo(const std::string& jobId); - - virtual void SignalSuccess(const std::string& jobId); - - virtual void SignalFailure(const std::string& jobId); - - static void Worker(ServerScheduler* that); - - void SubmitInternal(ServerJob& job, - bool watched); - - public: - explicit ServerScheduler(unsigned int maxjobs); - - ~ServerScheduler(); - - void Stop(); - - void Submit(ServerJob& job); - - bool SubmitAndWait(ListOfStrings& outputs, - ServerJob& job); - - bool SubmitAndWait(ServerJob& job); - - bool IsRunning(const std::string& jobId); - - void Cancel(const std::string& jobId); - - // Returns a number between 0 and 1 - float GetProgress(const std::string& jobId); - - bool IsRunning(const ServerJob& job) - { - return IsRunning(job.GetId()); - } - - void Cancel(const ServerJob& job) - { - Cancel(job.GetId()); - } - - float GetProgress(const ServerJob& job) - { - return GetProgress(job.GetId()); - } - - void GetListOfJobs(ListOfStrings& jobs); - }; -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/StorePeerCommand.cpp --- a/Resources/Graveyard/OldScheduler/StorePeerCommand.cpp Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +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-2020 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 "../PrecompiledHeadersServer.h" -#include "StorePeerCommand.h" - -#include "../../Core/Logging.h" -#include "../../Core/HttpClient.h" - -namespace Orthanc -{ - StorePeerCommand::StorePeerCommand(ServerContext& context, - const WebServiceParameters& peer, - bool ignoreExceptions) : - context_(context), - peer_(peer), - ignoreExceptions_(ignoreExceptions) - { - } - - bool StorePeerCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - // Configure the HTTP client - HttpClient client(peer_, "instances"); - client.SetMethod(HttpMethod_Post); - - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Sending resource " << *it << " to peer \"" - << peer_.GetUrl() << "\""; - - try - { - context_.ReadDicom(client.GetBody(), *it); - - std::string answer; - if (!client.Apply(answer)) - { - LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << peer_.GetUrl() << "\""; - throw OrthancException(ErrorCode_NetworkProtocol); - } - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to forward to an Orthanc peer in (instance " - << *it << ", peer " << peer_.GetUrl() << "): " << e.What(); - - if (!ignoreExceptions_) - { - throw; - } - } - } - - return true; - } -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/StorePeerCommand.h --- a/Resources/Graveyard/OldScheduler/StorePeerCommand.h Mon Apr 27 18:16:20 2020 +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-2020 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 "IServerCommand.h" -#include "../ServerContext.h" -#include "../OrthancInitialization.h" - -namespace Orthanc -{ - class StorePeerCommand : public IServerCommand - { - private: - ServerContext& context_; - WebServiceParameters peer_; - bool ignoreExceptions_; - - public: - StorePeerCommand(ServerContext& context, - const WebServiceParameters& peer, - bool ignoreExceptions); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/StoreScuCommand.cpp --- a/Resources/Graveyard/OldScheduler/StoreScuCommand.cpp Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +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-2020 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 "../PrecompiledHeadersServer.h" -#include "StoreScuCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - StoreScuCommand::StoreScuCommand(ServerContext& context, - const std::string& localAet, - const RemoteModalityParameters& modality, - bool ignoreExceptions) : - context_(context), - modality_(modality), - ignoreExceptions_(ignoreExceptions), - localAet_(localAet), - moveOriginatorID_(0) - { - } - - - void StoreScuCommand::SetMoveOriginator(const std::string& aet, - uint16_t id) - { - moveOriginatorAET_ = aet; - moveOriginatorID_ = id; - } - - - bool StoreScuCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); - - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Sending resource " << *it << " to modality \"" - << modality_.GetApplicationEntityTitle() << "\""; - - try - { - std::string dicom; - context_.ReadDicom(dicom, *it); - - locker.GetConnection().Store(dicom, moveOriginatorAET_, moveOriginatorID_); - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - // Ignore transmission errors (e.g. if the remote modality is - // powered off) - LOG(ERROR) << "Unable to forward to a modality in (instance " - << *it << "): " << e.What(); - - if (!ignoreExceptions_) - { - throw; - } - } - } - - return true; - } -} diff -r 9973d10bc5c4 -r ce5c4b9fa09a Resources/Graveyard/OldScheduler/StoreScuCommand.h --- a/Resources/Graveyard/OldScheduler/StoreScuCommand.h Mon Apr 27 18:16:20 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +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-2020 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 "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class StoreScuCommand : public IServerCommand - { - private: - ServerContext& context_; - RemoteModalityParameters modality_; - bool ignoreExceptions_; - std::string localAet_; - std::string moveOriginatorAET_; - uint16_t moveOriginatorID_; - - public: - StoreScuCommand(ServerContext& context, - const std::string& localAet, - const RemoteModalityParameters& modality, - bool ignoreExceptions); - - void SetMoveOriginator(const std::string& aet, - uint16_t id); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -}