changeset 3864:ce5c4b9fa09a transcoding

removing DicomUserConnection
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 27 Apr 2020 18:29:26 +0200
parents 9973d10bc5c4
children e1c2bc9a5cc1
files Core/DicomNetworking/DicomUserConnection.cpp Core/DicomNetworking/DicomUserConnection.h Resources/CMake/OrthancFrameworkConfiguration.cmake Resources/Graveyard/OldScheduler/CallSystemCommand.cpp Resources/Graveyard/OldScheduler/CallSystemCommand.h Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h Resources/Graveyard/OldScheduler/IServerCommand.h Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp Resources/Graveyard/OldScheduler/ServerCommandInstance.h Resources/Graveyard/OldScheduler/ServerJob.cpp Resources/Graveyard/OldScheduler/ServerJob.h Resources/Graveyard/OldScheduler/ServerScheduler.cpp Resources/Graveyard/OldScheduler/ServerScheduler.h Resources/Graveyard/OldScheduler/StorePeerCommand.cpp Resources/Graveyard/OldScheduler/StorePeerCommand.h Resources/Graveyard/OldScheduler/StoreScuCommand.cpp Resources/Graveyard/OldScheduler/StoreScuCommand.h
diffstat 22 files changed, 1 insertions(+), 4106 deletions(-) [+]
line wrap: on
line diff
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-
-/*=========================================================================
-
-  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 <dcmtk/dcmdata/dcdeftag.h>
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <dcmtk/dcmdata/dcistrmb.h>
-#include <dcmtk/dcmdata/dcistrmf.h>
-#include <dcmtk/dcmdata/dcmetinf.h>
-#include <dcmtk/dcmnet/diutil.h>
-
-#include <set>
-
-
-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<const char*>& 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<std::string> 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<const char*> asFallback;
-    asFallback.reserve(fallbackSyntaxes.size());
-    for (std::set<std::string>::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<std::string>::const_iterator it = reservedStorageSOPClasses_.begin();
-             it != reservedStorageSOPClasses_.end(); ++it)
-        {
-          RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
-                                  *it, asPreferred, asFallback, remoteAet_);
-        }
-
-        for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin();
-             it != storageSOPClasses_.end(); ++it)
-        {
-          RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
-                                  *it, asPreferred, asFallback, remoteAet_);
-        }
-
-        for (std::set<std::string>::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<const char*> 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<FindPayload*>(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<DicomTag> 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<DicomMap> fix(fields.Clone());
-
-        std::set<DicomTag> tags;
-        fix->GetTags(tags);
-
-        for (std::set<DicomTag>::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<ParsedDicomFile> 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<ParsedDicomFile> 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<std::string> 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<std::string>& sopClassUids,
-                              const std::vector<std::string>& sopInstanceUids,
-                              const std::vector<StorageCommitmentFailureReason>& 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<DcmItem> 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<std::string>& sopClassUids,
-    const std::vector<std::string>& sopInstanceUids,
-    const std::vector<StorageCommitmentFailureReason>& failureReasons)
-  {
-    if (sopClassUids.size() != sopInstanceUids.size() ||
-        sopClassUids.size() != failureReasons.size())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-    
-    if (IsOpen())
-    {
-      Close();
-    }
-
-    std::vector<std::string> successSopClassUids, successSopInstanceUids, failedSopClassUids, failedSopInstanceUids;
-    std::vector<StorageCommitmentFailureReason> 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<StorageCommitmentFailureReason> 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<std::string>& sopClassUids,
-    const std::vector<std::string>& 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<StorageCommitmentFailureReason> 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;
-    }
-  }
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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 <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/noncopyable.hpp>
-#include <list>
-
-namespace Orthanc
-{
-  class DicomUserConnection : public boost::noncopyable
-  {
-  private:
-    struct PImpl;
-    boost::shared_ptr<PImpl> 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<std::string> storageSOPClasses_;
-    std::list<std::string> reservedStorageSOPClasses_;
-    std::set<std::string> 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<std::string>& sopClassUids,
-      const std::vector<std::string>& sopInstanceUids,
-      const std::vector<StorageCommitmentFailureReason>& failureReasons);
-
-    // transactionUid: To be generated by Toolbox::GenerateDicomPrivateUniqueIdentifier()
-    void RequestStorageCommitment(
-      const std::string& transactionUid,
-      const std::vector<std::string>& sopClassUids,
-      const std::vector<std::string>& sopInstanceUids);
-  };
-}
--- 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
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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<std::string>& 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<std::string> 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;
-  }
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IServerCommand.h"
-#include "../ServerContext.h"
-
-namespace Orthanc
-{
-  class CallSystemCommand : public IServerCommand
-  {
-  private:
-    ServerContext& context_;
-    std::string command_;
-    std::vector<std::string> arguments_;
-
-  public:
-    CallSystemCommand(ServerContext& context,
-                      const std::string& command,
-                      const std::vector<std::string>& arguments);
-
-    virtual bool Apply(ListOfStrings& outputs,
-                       const ListOfStrings& inputs);
-  };
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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;
-  }
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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);
-  };
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <list>
-#include <string>
-#include <boost/noncopyable.hpp>
-
-namespace Orthanc
-{
-  class IServerCommand : public boost::noncopyable
-  {
-  public:
-    typedef std::list<std::string>  ListOfStrings;
-
-    virtual ~IServerCommand()
-    {
-    }
-
-    virtual bool Apply(ListOfStrings& outputs,
-                       const ListOfStrings& inputs) = 0;
-  };
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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<ParsedDicomFile> 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;
-  }
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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);
-  };
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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();
-    }
-  }
-}
-
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "DicomUserConnection.h"
-#include "../../Core/MultiThreading/Locker.h"
-
-#include <boost/thread.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
-
-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();
-  };
-}
-
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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<ServerCommandInstance*>::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_;
-    }
-  }
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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<ServerCommandInstance*> 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<ServerCommandInstance*>& GetNextCommands() const
-    {
-      return next_;
-    }
-  };
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "ServerJob.h"
-
-#include "../../Core/OrthancException.h"
-#include "../../Core/Toolbox.h"
-
-namespace Orthanc
-{
-  void ServerJob::CheckOrdering()
-  {
-    std::map<ServerCommandInstance*, unsigned int> index;
-
-    unsigned int count = 0;
-    for (std::list<ServerCommandInstance*>::const_iterator
-           it = filters_.begin(); it != filters_.end(); ++it)
-    {
-      index[*it] = count++;
-    }
-
-    for (std::list<ServerCommandInstance*>::const_iterator
-           it = filters_.begin(); it != filters_.end(); ++it)
-    {
-      const std::list<ServerCommandInstance*>& nextCommands = (*it)->GetNextCommands();
-
-      for (std::list<ServerCommandInstance*>::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<ServerCommandInstance*>::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<ServerCommandInstance*>::iterator
-           it = filters_.begin(); it != filters_.end(); ++it)
-    {
-      delete *it;
-    }
-
-    for (std::list<IDynamicObject*>::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();
-  }
-
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ServerCommandInstance.h"
-#include "../../Core/MultiThreading/SharedMessageQueue.h"
-
-namespace Orthanc
-{
-  class ServerJob
-  {
-    friend class ServerScheduler;
-
-  private:
-    std::list<ServerCommandInstance*> filters_;
-    std::list<IDynamicObject*> 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);
-  };
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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<IDynamicObject> object(that->queue_.Dequeue(TIMEOUT));
-      if (object.get() != NULL)
-      {
-        ServerCommandInstance& filter = dynamic_cast<ServerCommandInstance&>(*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<ServerCommandInstance*>::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<float>(job->second.success_);
-    }
-
-    return (static_cast<float>(job->second.success_) / 
-            static_cast<float>(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);
-    }
-  }
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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<std::string, JobInfo> Jobs;
-
-    boost::mutex mutex_;
-    boost::condition_variable watchedJobFinished_;
-    Jobs jobs_;
-    SharedMessageQueue queue_;
-    bool finish_;
-    boost::thread worker_;
-    std::map<std::string, JobStatus> 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);
-  };
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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;
-  }
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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);
-  };
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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;
-  }
-}
--- 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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#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);
-  };
-}