Mercurial > hg > orthanc
changeset 775:d3ba35466225 lua-scripting
integration mainline -> lua-scripting
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 30 Apr 2014 15:35:10 +0200 |
parents | a64ca424e0e2 (current diff) 4e3593c3511d (diff) |
children | 9ae0bb3f188b |
files | UnitTestsSources/MultiThreading.cpp |
diffstat | 23 files changed, 1027 insertions(+), 214 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Thu Apr 24 12:01:05 2014 +0200 +++ b/CMakeLists.txt Wed Apr 30 15:35:10 2014 +0200 @@ -203,6 +203,7 @@ OrthancServer/DicomProtocol/DicomFindAnswers.cpp OrthancServer/DicomProtocol/DicomServer.cpp OrthancServer/DicomProtocol/DicomUserConnection.cpp + OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp OrthancServer/FromDcmtkBridge.cpp OrthancServer/Internals/CommandDispatcher.cpp OrthancServer/Internals/FindScp.cpp
--- a/Core/MultiThreading/ILockable.h Thu Apr 24 12:01:05 2014 +0200 +++ b/Core/MultiThreading/ILockable.h Wed Apr 30 15:35:10 2014 +0200 @@ -38,13 +38,16 @@ { class ILockable : public boost::noncopyable { + friend class Locker; + + protected: + virtual void Lock() = 0; + + virtual void Unlock() = 0; + public: virtual ~ILockable() { } - - virtual void Lock() = 0; - - virtual void Unlock() = 0; }; }
--- a/Core/MultiThreading/Locker.h Thu Apr 24 12:01:05 2014 +0200 +++ b/Core/MultiThreading/Locker.h Wed Apr 30 15:35:10 2014 +0200 @@ -36,7 +36,7 @@ namespace Orthanc { - class Locker + class Locker : public boost::noncopyable { private: ILockable& lockable_;
--- a/Core/MultiThreading/Mutex.h Thu Apr 24 12:01:05 2014 +0200 +++ b/Core/MultiThreading/Mutex.h Wed Apr 30 15:35:10 2014 +0200 @@ -43,13 +43,14 @@ PImpl *pimpl_; + protected: + virtual void Lock(); + + virtual void Unlock(); + public: Mutex(); ~Mutex(); - - virtual void Lock(); - - virtual void Unlock(); }; }
--- a/Core/MultiThreading/ReaderWriterLock.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/Core/MultiThreading/ReaderWriterLock.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -46,11 +46,7 @@ private: boost::shared_mutex& lock_; - public: - ReaderLockable(boost::shared_mutex& lock) : lock_(lock) - { - } - + protected: virtual void Lock() { lock_.lock_shared(); @@ -60,6 +56,11 @@ { lock_.unlock_shared(); } + + public: + ReaderLockable(boost::shared_mutex& lock) : lock_(lock) + { + } }; @@ -68,11 +69,7 @@ private: boost::shared_mutex& lock_; - public: - WriterLockable(boost::shared_mutex& lock) : lock_(lock) - { - } - + protected: virtual void Lock() { lock_.lock(); @@ -82,6 +79,12 @@ { lock_.unlock(); } + + public: + WriterLockable(boost::shared_mutex& lock) : lock_(lock) + { + } + }; }
--- a/NEWS Thu Apr 24 12:01:05 2014 +0200 +++ b/NEWS Wed Apr 30 15:35:10 2014 +0200 @@ -1,6 +1,9 @@ Pending changes in the mainline =============================== +* Reuse of the previous SCU connection to avoid unecessary handshakes +* Dynamic negotiation of SOP classes for C-Store SCU +* Fix missing licensing terms about reuse of some DCMTK code Version 0.7.4 (2014/04/16)
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -30,6 +30,54 @@ **/ + +/*========================================================================= + + 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 "DicomUserConnection.h" #include "../../Core/OrthancException.h" @@ -60,6 +108,15 @@ 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 { struct DicomUserConnection::PImpl @@ -103,55 +160,38 @@ } - void DicomUserConnection::CopyParameters(const DicomUserConnection& other) + static void RegisterStorageSOPClass(T_ASC_Parameters* params, + unsigned int& presentationContextId, + const std::string& sopClass, + const char* asPreferred[], + std::vector<const char*>& asFallback) { - Close(); - localAet_ = other.localAet_; - distantAet_ = other.distantAet_; - distantHost_ = other.distantHost_; - distantPort_ = other.distantPort_; - manufacturer_ = other.manufacturer_; - preferredTransferSyntax_ = other.preferredTransferSyntax_; + Check(ASC_addPresentationContext(params, presentationContextId, + sopClass.c_str(), asPreferred, 1)); + presentationContextId += 2; + + if (asFallback.size() > 0) + { + Check(ASC_addPresentationContext(params, presentationContextId, + sopClass.c_str(), &asFallback[0], asFallback.size())); + presentationContextId += 2; + } } - - + + void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) { - // Fallback transfer syntaxes + // 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); - - // Transfer syntaxes for C-ECHO, C-FIND and C-MOVE - std::vector<std::string> transferSyntaxes; - transferSyntaxes.push_back(UID_VerificationSOPClass); - transferSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); - transferSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); - transferSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); - - // TODO: Allow the set below to be configured - std::set<std::string> uselessSyntaxes; - uselessSyntaxes.insert(UID_BlendingSoftcopyPresentationStateStorage); - uselessSyntaxes.insert(UID_GrayscaleSoftcopyPresentationStateStorage); - uselessSyntaxes.insert(UID_ColorSoftcopyPresentationStateStorage); - uselessSyntaxes.insert(UID_PseudoColorSoftcopyPresentationStateStorage); - - // Add the transfer syntaxes for C-STORE - for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) - { - // Test to make some room to allow the ECHO and FIND requests - if (uselessSyntaxes.find(dcmShortSCUStorageSOPClassUIDs[i]) == uselessSyntaxes.end()) - { - transferSyntaxes.push_back(dcmShortSCUStorageSOPClassUIDs[i]); - } - } - - // Flatten the fallback transfer syntaxes array - const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; - 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 @@ -160,19 +200,28 @@ asFallback.push_back(it->c_str()); } + CheckStorageSOPClassesInvariant(); unsigned int presentationContextId = 1; - for (size_t i = 0; i < transferSyntaxes.size(); i++) + + for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin(); + it != reservedStorageSOPClasses_.end(); it++) { - Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, - transferSyntaxes[i].c_str(), asPreferred, 1)); - presentationContextId += 2; + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback); + } - if (asFallback.size() > 0) - { - Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, - transferSyntaxes[i].c_str(), &asFallback[0], asFallback.size())); - presentationContextId += 2; - } + for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin(); + it != storageSOPClasses_.end(); it++) + { + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback); + } + + for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin(); + it != defaultStorageSOPClasses_.end(); it++) + { + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback); } } @@ -192,8 +241,16 @@ DcmFileFormat dcmff; Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); + // Determine the storage SOP class UID for this instance + static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016); + OFString sopClassUid; + if (dcmff.getDataset()->findAndGetOFString(DCM_SOP_CLASS_UID, sopClassUid).good()) + { + connection.AddStorageSOPClass(sopClassUid.c_str()); + } + // Determine whether a new presentation context must be - // negociated, depending on the transfer syntax of this instance + // negotiated, depending on the transfer syntax of this instance DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); const std::string syntax(xfer.getXferID()); bool isGeneric = IsGenericTransferSyntax(syntax); @@ -201,8 +258,8 @@ if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax())) { // Making a generic-to-specific or specific-to-generic change of - // the transfer syntax. Renegociate the connection. - LOG(INFO) << "Renegociating a C-Store association due to a change in the transfer syntax"; + // the transfer syntax. Renegotiate the connection. + LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; if (isGeneric) { @@ -212,7 +269,11 @@ { connection.SetPreferredTransferSyntax(syntax); } + } + if (!connection.IsOpen()) + { + LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters"; connection.Open(); } @@ -232,7 +293,7 @@ if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); if (!modalityName) modalityName = "unknown SOP class"; throw OrthancException("DicomUserConnection: No presentation context for modality " + - std::string(modalityName)); + std::string(modalityName)); } // Prepare the transmission of data @@ -288,88 +349,88 @@ std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(fields)); switch (model) { - case FindRootModel_Patient: - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); - sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; + case FindRootModel_Patient: + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); + sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); + // Accession number + if (!fields.HasTag(0x0008, 0x0050)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); - // Patient ID - if (!fields.HasTag(0x0010, 0x0020)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), ""); + // Patient ID + if (!fields.HasTag(0x0010, 0x0020)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), ""); - break; + break; - case FindRootModel_Study: - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + case FindRootModel_Study: + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); + // Accession number + if (!fields.HasTag(0x0008, 0x0050)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); - // Study instance UID - if (!fields.HasTag(0x0020, 0x000d)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); + // Study instance UID + if (!fields.HasTag(0x0020, 0x000d)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); - break; + break; - case FindRootModel_Series: - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + case FindRootModel_Series: + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); + // Accession number + if (!fields.HasTag(0x0008, 0x0050)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); - // Study instance UID - if (!fields.HasTag(0x0020, 0x000d)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); + // Study instance UID + if (!fields.HasTag(0x0020, 0x000d)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); - // Series instance UID - if (!fields.HasTag(0x0020, 0x000e)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); + // Series instance UID + if (!fields.HasTag(0x0020, 0x000e)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); - break; + break; - case FindRootModel_Instance: - if (manufacturer_ == ModalityManufacturer_ClearCanvas || - manufacturer_ == ModalityManufacturer_Dcm4Chee) - { - // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>. - // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J - // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE"); - } - else - { - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); - } + case FindRootModel_Instance: + if (manufacturer_ == ModalityManufacturer_ClearCanvas || + manufacturer_ == ModalityManufacturer_Dcm4Chee) + { + // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>. + // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J + // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE"); + } + else + { + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); + } - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); + // Accession number + if (!fields.HasTag(0x0008, 0x0050)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); - // Study instance UID - if (!fields.HasTag(0x0020, 0x000d)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); + // Study instance UID + if (!fields.HasTag(0x0020, 0x000d)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); - // Series instance UID - if (!fields.HasTag(0x0020, 0x000e)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); + // Series instance UID + if (!fields.HasTag(0x0020, 0x000e)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); - // SOP Instance UID - if (!fields.HasTag(0x0008, 0x0018)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), ""); + // SOP Instance UID + if (!fields.HasTag(0x0008, 0x0018)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), ""); - break; + break; - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); } // Figure out which of the accepted presentation contexts should be used @@ -501,6 +562,35 @@ } + void DicomUserConnection::ResetStorageSOPClasses() + { + CheckStorageSOPClassesInvariant(); + + storageSOPClasses_.clear(); + defaultStorageSOPClasses_.clear(); + + // Copy the short list of storage SOP classes from DCMTK, making + // room for the 4 SOP classes reserved for C-ECHO, C-FIND, C-MOVE. + + std::set<std::string> uncommon; + uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage); + uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage); + uncommon.insert(UID_ColorSoftcopyPresentationStateStorage); + uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage); + + // 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(); + } + + DicomUserConnection::DicomUserConnection() : pimpl_(new PImpl), preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX), @@ -514,6 +604,14 @@ 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); + + ResetStorageSOPClasses(); } DicomUserConnection::~DicomUserConnection() @@ -521,6 +619,16 @@ Close(); } + + void DicomUserConnection::Connect(const RemoteModalityParameters& parameters) + { + SetDistantApplicationEntityTitle(parameters.GetApplicationEntityTitle()); + SetDistantHost(parameters.GetHost()); + SetDistantPort(parameters.GetPort()); + SetDistantManufacturer(parameters.GetManufacturer()); + } + + void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) { if (localAet_ != aet) @@ -594,6 +702,11 @@ return; } + LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() + << "\" to AET \"" << GetDistantApplicationEntityTitle() << "\" on host " + << GetDistantHost() << ":" << GetDistantPort() + << " (manufacturer: " << EnumerationToString(GetDistantManufacturer()) << ")"; + Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ 30, &pimpl_->net_)); Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); @@ -607,11 +720,11 @@ char distantHostAndPort[HOST_NAME_MAX]; #ifdef _MSC_VER - _snprintf + _snprintf #else - snprintf + snprintf #endif - (distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_); + (distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_); Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, distantHostAndPort)); @@ -742,4 +855,60 @@ dcmConnectionTimeout.set(seconds); } + + 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_.size() > 0); // Necessarily true because condition (*) is false + defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin()); + } + + // Explicitly register the new storage syntax + storageSOPClasses_.insert(sop); + + CheckStorageSOPClassesInvariant(); + } + }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.h Wed Apr 30 15:35:10 2014 +0200 @@ -34,10 +34,12 @@ #include "DicomFindAnswers.h" #include "../ServerEnumerations.h" +#include "RemoteModalityParameters.h" #include <stdint.h> #include <boost/shared_ptr.hpp> #include <boost/noncopyable.hpp> +#include <list> namespace Orthanc { @@ -62,6 +64,9 @@ std::string distantHost_; uint16_t distantPort_; ModalityManufacturer manufacturer_; + std::set<std::string> storageSOPClasses_; + std::list<std::string> reservedStorageSOPClasses_; + std::set<std::string> defaultStorageSOPClasses_; void CheckIsOpen() const; @@ -74,12 +79,16 @@ void Move(const std::string& targetAet, const DicomMap& fields); + void ResetStorageSOPClasses(); + + void CheckStorageSOPClassesInvariant() const; + public: DicomUserConnection(); ~DicomUserConnection(); - void CopyParameters(const DicomUserConnection& other); + void Connect(const RemoteModalityParameters& parameters); void SetLocalApplicationEntityTitle(const std::string& aet); @@ -125,6 +134,8 @@ return preferredTransferSyntax_; } + void AddStorageSOPClass(const char* sop); + void Open(); void Close();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.h Wed Apr 30 15:35:10 2014 +0200 @@ -0,0 +1,112 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * 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 "../ServerEnumerations.h" + +#include <string> + +namespace Orthanc +{ + class RemoteModalityParameters + { + // TODO Use the flyweight pattern for this class + + private: + std::string symbolicName_; + std::string aet_; + std::string host_; + int port_; + ModalityManufacturer manufacturer_; + + public: + RemoteModalityParameters() : + symbolicName_(""), + aet_(""), + host_(""), + port_(104), + manufacturer_(ModalityManufacturer_Generic) + { + } + + RemoteModalityParameters(const std::string& symbolic, + const std::string& aet, + const std::string& host, + int port, + ModalityManufacturer manufacturer) : + symbolicName_(symbolic), + aet_(aet), + host_(host), + port_(port), + manufacturer_(manufacturer) + { + } + + RemoteModalityParameters(const std::string& aet, + const std::string& host, + int port, + ModalityManufacturer manufacturer) : + symbolicName_(""), + aet_(aet), + host_(host), + port_(port), + manufacturer_(manufacturer) + { + } + + const std::string& GetSymbolicName() const + { + return symbolicName_; + } + + const std::string& GetApplicationEntityTitle() const + { + return aet_; + } + + const std::string& GetHost() const + { + return host_; + } + + int GetPort() const + { + return port_; + } + + ModalityManufacturer GetManufacturer() const + { + return manufacturer_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -0,0 +1,178 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * 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 "ReusableDicomUserConnection.h" + +#include "../../Core/OrthancException.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + static boost::posix_time::ptime Now() + { + return boost::posix_time::microsec_clock::local_time(); + } + + void ReusableDicomUserConnection::Open(const std::string& remoteAet, + const std::string& address, + int port, + ModalityManufacturer manufacturer) + { + if (connection_ != NULL && + connection_->GetDistantApplicationEntityTitle() == remoteAet && + connection_->GetDistantHost() == address && + connection_->GetDistantPort() == port && + connection_->GetDistantManufacturer() == manufacturer) + { + // The current connection can be reused + LOG(INFO) << "Reusing the previous SCU connection"; + return; + } + + Close(); + + connection_ = new DicomUserConnection(); + connection_->SetLocalApplicationEntityTitle(localAet_); + connection_->SetDistantApplicationEntityTitle(remoteAet); + connection_->SetDistantHost(address); + connection_->SetDistantPort(port); + connection_->SetDistantManufacturer(manufacturer); + 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::Connection::Connection(ReusableDicomUserConnection& that, + const std::string& aet, + const std::string& address, + int port, + ModalityManufacturer manufacturer) : + Locker(that) + { + that.Open(aet, address, port, manufacturer); + connection_ = that.connection_; + } + + + ReusableDicomUserConnection::Connection::Connection(ReusableDicomUserConnection& that, + const RemoteModalityParameters& remote) : + Locker(that) + { + that.Open(remote.GetApplicationEntityTitle(), remote.GetHost(), + remote.GetPort(), remote.GetManufacturer()); + connection_ = that.connection_; + } + + + DicomUserConnection& ReusableDicomUserConnection::Connection::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 + localAet_("ORTHANC") + { + lastUse_ = Now(); + continue_ = true; + closeThread_ = boost::thread(CloseThread, this); + } + + ReusableDicomUserConnection::~ReusableDicomUserConnection() + { + continue_ = false; + closeThread_.join(); + Close(); + } + + void ReusableDicomUserConnection::SetMillisecondsBeforeClose(unsigned int ms) + { + boost::mutex::scoped_lock lock(mutex_); + timeBeforeClose_ = boost::posix_time::milliseconds(ms); + } + + void ReusableDicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) + { + boost::mutex::scoped_lock lock(mutex_); + Close(); + localAet_ = aet; + } + + void ReusableDicomUserConnection::Lock() + { + mutex_.lock(); + } + + void ReusableDicomUserConnection::Unlock() + { + lastUse_ = Now(); + mutex_.unlock(); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h Wed Apr 30 15:35:10 2014 +0200 @@ -0,0 +1,103 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * 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_; + std::string localAet_; + + void Open(const std::string& remoteAet, + const std::string& address, + int port, + ModalityManufacturer manufacturer); + + void Close(); + + static void CloseThread(ReusableDicomUserConnection* that); + + protected: + virtual void Lock(); + + virtual void Unlock(); + + public: + class Connection : public Locker + { + private: + DicomUserConnection* connection_; + + public: + Connection(ReusableDicomUserConnection& that, + const RemoteModalityParameters& remote); + + Connection(ReusableDicomUserConnection& that, + const std::string& aet, + const std::string& address, + int port, + ModalityManufacturer manufacturer); + + DicomUserConnection& GetConnection(); + }; + + ReusableDicomUserConnection(); + + virtual ~ReusableDicomUserConnection(); + + unsigned int GetMillisecondsBeforeClose() const + { + return timeBeforeClose_.total_milliseconds(); + } + + void SetMillisecondsBeforeClose(unsigned int ms); + + const std::string& GetLocalApplicationEntityTitle() const; + + void SetLocalApplicationEntityTitle(const std::string& aet); + }; +} +
--- a/OrthancServer/Internals/CommandDispatcher.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/Internals/CommandDispatcher.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -30,6 +30,55 @@ **/ + + +/*========================================================================= + + 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 "CommandDispatcher.h" #include "FindScp.h"
--- a/OrthancServer/Internals/FindScp.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/Internals/FindScp.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -30,6 +30,55 @@ **/ + +/*========================================================================= + + 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 "FindScp.h" #include "../FromDcmtkBridge.h"
--- a/OrthancServer/Internals/MoveScp.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/Internals/MoveScp.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -30,6 +30,55 @@ **/ + + +/*========================================================================= + + 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 "MoveScp.h" #include <memory>
--- a/OrthancServer/Internals/StoreScp.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/Internals/StoreScp.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -30,6 +30,55 @@ **/ + + +/*========================================================================= + + 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 "StoreScp.h" #include "../FromDcmtkBridge.h"
--- a/OrthancServer/OrthancInitialization.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -508,25 +508,6 @@ } - void ConnectToModalityUsingSymbolicName(DicomUserConnection& connection, - const std::string& name) - { - std::string aet, address; - int port; - ModalityManufacturer manufacturer; - GetDicomModalityUsingSymbolicName(name, aet, address, port, manufacturer); - - LOG(WARNING) << "Connecting to remote DICOM modality: AET=" << aet << ", address=" << address << ", port=" << port; - - connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); - connection.SetDistantApplicationEntityTitle(aet); - connection.SetDistantHost(address); - connection.SetDistantPort(port); - connection.SetDistantManufacturer(manufacturer); - connection.Open(); - } - - bool IsSameAETitle(const std::string& aet1, const std::string& aet2) { @@ -587,25 +568,31 @@ } - void ConnectToModalityUsingAETitle(DicomUserConnection& connection, - const std::string& aet) + RemoteModalityParameters GetModalityUsingSymbolicName(const std::string& name) { - std::string symbolicName, address; + std::string aet, address; int port; ModalityManufacturer manufacturer; - if (!LookupDicomModalityUsingAETitle(aet, symbolicName, address, port, manufacturer)) - { - throw OrthancException("Unknown modality: " + aet); - } + GetDicomModalityUsingSymbolicName(name, aet, address, port, manufacturer); - LOG(WARNING) << "Connecting to remote DICOM modality: AET=" << aet << ", address=" << address << ", port=" << port; + return RemoteModalityParameters(name, aet, address, port, manufacturer); + } + - connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); - connection.SetDistantApplicationEntityTitle(aet); - connection.SetDistantHost(address); - connection.SetDistantPort(port); - connection.SetDistantManufacturer(manufacturer); - connection.Open(); + RemoteModalityParameters GetModalityUsingAet(const std::string& aet) + { + std::string name, address; + int port; + ModalityManufacturer manufacturer; + + if (LookupDicomModalityUsingAETitle(aet, name, address, port, manufacturer)) + { + return RemoteModalityParameters(name, aet, address, port, manufacturer); + } + else + { + throw OrthancException("Unknown modality for AET: " + aet); + } } }
--- a/OrthancServer/OrthancInitialization.h Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/OrthancInitialization.h Wed Apr 30 15:35:10 2014 +0200 @@ -37,7 +37,7 @@ #include <json/json.h> #include <stdint.h> #include "../Core/HttpServer/MongooseServer.h" -#include "DicomProtocol/DicomUserConnection.h" +#include "DicomProtocol/RemoteModalityParameters.h" #include "ServerEnumerations.h" namespace Orthanc @@ -86,14 +86,12 @@ void GetGlobalListOfStringsParameter(std::list<std::string>& target, const std::string& key); - void ConnectToModalityUsingSymbolicName(DicomUserConnection& connection, - const std::string& name); - - void ConnectToModalityUsingAETitle(DicomUserConnection& connection, - const std::string& aet); - bool IsKnownAETitle(const std::string& aet); bool IsSameAETitle(const std::string& aet1, const std::string& aet2); + + RemoteModalityParameters GetModalityUsingSymbolicName(const std::string& name); + + RemoteModalityParameters GetModalityUsingAet(const std::string& aet); }
--- a/OrthancServer/OrthancMoveRequestHandler.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -33,7 +33,6 @@ #include <glog/logging.h> -#include "DicomProtocol/DicomUserConnection.h" #include "OrthancInitialization.h" namespace Orthanc @@ -47,17 +46,17 @@ private: ServerContext& context_; std::vector<std::string> instances_; - DicomUserConnection connection_; size_t position_; + RemoteModalityParameters remote_; public: OrthancMoveRequestIterator(ServerContext& context, - const std::string& target, + const std::string& aet, const std::string& publicId) : context_(context), position_(0) { - LOG(INFO) << "Sending resource " << publicId << " to modality \"" << target << "\""; + LOG(INFO) << "Sending resource " << publicId << " to modality \"" << aet << "\""; std::list<std::string> tmp; context_.GetIndex().GetChildInstances(tmp, publicId); @@ -67,8 +66,8 @@ { instances_.push_back(*it); } - - ConnectToModalityUsingAETitle(connection_, target); + + remote_ = GetModalityUsingAet(aet); } virtual unsigned int GetSubOperationCount() const @@ -87,7 +86,12 @@ std::string dicom; context_.ReadFile(dicom, id, FileContentType_Dicom); - connection_.Store(dicom); + + { + ReusableDicomUserConnection::Connection connection + (context_.GetReusableDicomUserConnection(), remote_); + connection.GetConnection().Store(dicom); + } return Status_Success; } @@ -121,10 +125,10 @@ } - IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& target, + IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& aet, const DicomMap& input) { - LOG(WARNING) << "Move-SCU request received for AET \"" << target << "\""; + LOG(WARNING) << "Move-SCU request received for AET \"" << aet << "\""; /** @@ -173,6 +177,6 @@ throw OrthancException(ErrorCode_BadRequest); } - return new OrthancMoveRequestIterator(context_, target, publicId); + return new OrthancMoveRequestIterator(context_, aet, publicId); } }
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -32,7 +32,6 @@ #include "OrthancRestApi.h" -#include "../DicomProtocol/DicomUserConnection.h" #include "../OrthancInitialization.h" #include "../../Core/HttpClient.h" @@ -66,6 +65,8 @@ static void DicomFindPatient(RestApi::PostCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + DicomMap m; DicomMap::SetupFindPatientTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -73,11 +74,11 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + RemoteModalityParameters remote = GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), remote); DicomFindAnswers answers; - connection.FindPatient(answers, m); + connection.GetConnection().FindPatient(answers, m); Json::Value result; answers.ToJson(result); @@ -86,6 +87,8 @@ static void DicomFindStudy(RestApi::PostCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + DicomMap m; DicomMap::SetupFindStudyTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -99,11 +102,11 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - + RemoteModalityParameters remote = GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), remote); + DicomFindAnswers answers; - connection.FindStudy(answers, m); + connection.GetConnection().FindStudy(answers, m); Json::Value result; answers.ToJson(result); @@ -112,6 +115,8 @@ static void DicomFindSeries(RestApi::PostCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + DicomMap m; DicomMap::SetupFindSeriesTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -126,11 +131,11 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - + RemoteModalityParameters remote = GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), remote); + DicomFindAnswers answers; - connection.FindSeries(answers, m); + connection.GetConnection().FindSeries(answers, m); Json::Value result; answers.ToJson(result); @@ -139,6 +144,8 @@ static void DicomFindInstance(RestApi::PostCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + DicomMap m; DicomMap::SetupFindInstanceTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -154,11 +161,11 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - + RemoteModalityParameters remote = GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), remote); + DicomFindAnswers answers; - connection.FindInstance(answers, m); + connection.GetConnection().FindInstance(answers, m); Json::Value result; answers.ToJson(result); @@ -167,6 +174,8 @@ static void DicomFind(RestApi::PostCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + DicomMap m; DicomMap::SetupFindPatientTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -174,11 +183,11 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - + RemoteModalityParameters remote = GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), remote); + DicomFindAnswers patients; - connection.FindPatient(patients, m); + connection.GetConnection().FindPatient(patients, m); // Loop over the found patients Json::Value result = Json::arrayValue; @@ -195,7 +204,7 @@ m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); DicomFindAnswers studies; - connection.FindStudy(studies, m); + connection.GetConnection().FindStudy(studies, m); patient["Studies"] = Json::arrayValue; @@ -214,7 +223,7 @@ m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); DicomFindAnswers series; - connection.FindSeries(series, m); + connection.GetConnection().FindSeries(series, m); // Loop over the found series study["Series"] = Json::arrayValue; @@ -309,8 +318,8 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, remote); + RemoteModalityParameters p = GetModalityUsingSymbolicName(remote); + ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), p); for (std::list<std::string>::const_iterator it = instances.begin(); it != instances.end(); ++it) @@ -319,7 +328,7 @@ std::string dicom; context.ReadFile(dicom, *it, FileContentType_Dicom); - connection.Store(dicom); + connection.GetConnection().Store(dicom); } call.GetOutput().AnswerBuffer("{}", "application/json");
--- a/OrthancServer/ServerContext.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/ServerContext.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -35,6 +35,7 @@ #include "../Core/HttpServer/FilesystemHttpSender.h" #include "../Core/Lua/LuaFunctionCall.h" #include "ServerToolbox.h" +#include "OrthancInitialization.h" #include <glog/logging.h> #include <EmbeddedResources.h> @@ -65,6 +66,9 @@ provider_(*this), dicomCache_(provider_, DICOM_CACHE_SIZE) { + scu_.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); + //scu_.SetMillisecondsBeforeClose(1); // The connection is always released + lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); }
--- a/OrthancServer/ServerContext.h Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/ServerContext.h Wed Apr 30 15:35:10 2014 +0200 @@ -39,6 +39,7 @@ #include "../Core/Lua/LuaContext.h" #include "ServerIndex.h" #include "FromDcmtkBridge.h" +#include "DicomProtocol/ReusableDicomUserConnection.h" namespace Orthanc { @@ -70,6 +71,7 @@ DicomCacheProvider provider_; MemoryCache dicomCache_; + ReusableDicomUserConnection scu_; LuaContext lua_; @@ -150,5 +152,10 @@ { return accessor_.IsStoreMD5(); } + + ReusableDicomUserConnection& GetReusableDicomUserConnection() + { + return scu_; + } }; }
--- a/OrthancServer/main.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/OrthancServer/main.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -41,7 +41,7 @@ #include "../Core/Lua/LuaFunctionCall.h" #include "../Core/DicomFormat/DicomArray.h" #include "DicomProtocol/DicomServer.h" -#include "DicomProtocol/DicomUserConnection.h" +#include "DicomProtocol/ReusableDicomUserConnection.h" #include "OrthancInitialization.h" #include "ServerContext.h" #include "OrthancFindRequestHandler.h"
--- a/UnitTestsSources/MultiThreading.cpp Thu Apr 24 12:01:05 2014 +0200 +++ b/UnitTestsSources/MultiThreading.cpp Wed Apr 30 15:35:10 2014 +0200 @@ -214,7 +214,31 @@ +#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h" +TEST(ReusableDicomUserConnection, DISABLED_Basic) +{ + ReusableDicomUserConnection c; + c.SetMillisecondsBeforeClose(200); + printf("START\n"); fflush(stdout); + + { + ReusableDicomUserConnection::Connection cc(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + cc.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281"); + } + + printf("**\n"); fflush(stdout); + Toolbox::USleep(1000000); + printf("**\n"); fflush(stdout); + + { + ReusableDicomUserConnection::Connection cc(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + cc.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277"); + } + + Toolbox::ServerBarrier(); + printf("DONE\n"); fflush(stdout); +}