changeset 943:3fb427ac3f53 plugins

integration mainline -> plugins
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 25 Jun 2014 11:40:41 +0200
parents ac42ca61eee6 (current diff) b3f6fb1130cd (diff)
children c068671d12a9
files CMakeLists.txt Core/Toolbox.cpp NEWS OrthancServer/main.cpp Resources/Configuration.json UnitTestsSources/UnitTestsMain.cpp
diffstat 28 files changed, 527 insertions(+), 220 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Jun 24 16:37:06 2014 +0200
+++ b/CMakeLists.txt	Wed Jun 25 11:40:41 2014 +0200
@@ -415,6 +415,10 @@
         )
     endif()
 
+  elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+    # TODO
+    target_link_libraries(OrthancClient pthread)
+
   else()
     message(FATAL_ERROR "Support your platform here")
   endif()
--- a/Core/MultiThreading/Mutex.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/Core/MultiThreading/Mutex.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -37,7 +37,7 @@
 
 #if defined(_WIN32)
 #include <windows.h>
-#elif defined(__linux) || defined(__FreeBSD_kernel__)
+#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__)
 #include <pthread.h>
 #else
 #error Support your platform here
@@ -75,7 +75,7 @@
   }
 
 
-#elif defined(__linux) || defined(__FreeBSD_kernel__)
+#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__)
 
   struct Mutex::PImpl
   {
--- a/Core/MultiThreading/ReaderWriterLock.h	Tue Jun 24 16:37:06 2014 +0200
+++ b/Core/MultiThreading/ReaderWriterLock.h	Wed Jun 25 11:40:41 2014 +0200
@@ -34,9 +34,11 @@
 
 #include "ILockable.h"
 
+#include <boost/noncopyable.hpp>
+
 namespace Orthanc
 {
-  class ReaderWriterLock
+  class ReaderWriterLock : public boost::noncopyable
   {
   private:
     struct PImpl;
--- a/Core/Toolbox.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/Core/Toolbox.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -60,11 +60,11 @@
 #include <unistd.h>
 #endif
 
-#if BOOST_HAS_LOCALE == 1
+#if BOOST_HAS_LOCALE != 1
+#error Since version 0.7.6, Orthanc entirely relies on boost::locale
+#endif
+
 #include <boost/locale.hpp>
-#else
-#include <iconv.h>
-#endif
 
 #include "../Resources/md5/md5.h"
 #include "../Resources/base64/base64.h"
@@ -74,68 +74,11 @@
 // Patch for the missing "_strtoll" symbol when compiling with Visual Studio
 extern "C"
 {
-int64_t _strtoi64(const char *nptr, char **endptr, int base);
-int64_t strtoll(const char *nptr, char **endptr, int base)
-{
-    return _strtoi64(nptr, endptr, base);
-} 
-}
-#endif
-
-
-#if BOOST_HAS_LOCALE == 0
-namespace
-{
-  class IconvRabi
+  int64_t _strtoi64(const char *nptr, char **endptr, int base);
+  int64_t strtoll(const char *nptr, char **endptr, int base)
   {
-  private:
-    iconv_t context_;
-
-  public:
-    IconvRabi(const char* tocode, const char* fromcode)
-    {
-      context_ = iconv_open(tocode, fromcode);
-      if (!context_)
-      {
-        throw Orthanc::OrthancException("Unknown code page");
-      }
-    }
-    
-    ~IconvRabi()
-    {
-      iconv_close(context_);
-    }
-
-    std::string Convert(const std::string& source)
-    {
-      if (source.size() == 0)
-      {
-        return "";
-      }
-
-      std::string result;
-      char* sourcePos = const_cast<char*>(&source[0]);
-      size_t sourceLeft = source.size();
-
-      std::vector<char> storage(source.size() + 10);
-      
-      while (sourceLeft > 0)
-      {
-        char* tmp = &storage[0];
-        size_t outputLeft = storage.size();
-        size_t err = iconv(context_, &sourcePos, &sourceLeft, &tmp, &outputLeft);
-        if (err < 0)
-        {
-          throw Orthanc::OrthancException("Bad character in sequence");
-        }
-
-        size_t count = storage.size() - outputLeft;
-        result += std::string(&storage[0], count);
-      }
-
-      return result;
-    }
-  };
+    return _strtoi64(nptr, endptr, base);
+  } 
 }
 #endif
 
@@ -162,7 +105,7 @@
   {
 #if defined(_WIN32)
     ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000)));
-#elif defined(__linux) || defined(__FreeBSD_kernel__)
+#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__)
     usleep(microSeconds);
 #else
 #error Support your platform here
@@ -554,7 +497,6 @@
   std::string Toolbox::ConvertToUtf8(const std::string& source,
                                      const char* fromEncoding)
   {
-#if BOOST_HAS_LOCALE == 1
     try
     {
       return boost::locale::conv::to_utf<char>(source, fromEncoding);
@@ -564,17 +506,6 @@
       // Bad input string or bad encoding
       return ConvertToAscii(source);
     }
-#else
-    IconvRabi iconv("UTF-8", fromEncoding);
-    try
-    {
-      return iconv.Convert(source);
-    }
-    catch (OrthancException)
-    {
-      return ConvertToAscii(source);
-    }
-#endif
   }
 
 
@@ -582,7 +513,7 @@
   {
     std::string result;
 
-    result.reserve(source.size());
+    result.reserve(source.size() + 1);
     for (size_t i = 0; i < source.size(); i++)
     {
       if (source[i] < 128 && source[i] >= 0 && !iscntrl(source[i]))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DarwinCompilation.txt	Wed Jun 25 11:40:41 2014 +0200
@@ -0,0 +1,52 @@
+This file is a complement to "INSTALL", which contains instructions
+that are specific to Mac OS X (Darwin).
+
+
+Static linking for OS X using XCode
+===================================
+
+The most simple way of building Orthanc under OS X consists in
+statically linking against all the third-party dependencies. In this
+case, no package manager such as Homebrew or MacPorts is required.
+The build tool (CMake) will download the sources of all the required
+packages and automatically compile them.
+
+
+Prerequisites
+-------------
+
+1) XCode must be installed.
+
+2) CMake must be installed (http://www.cmake.org/).
+
+3) It is assumed that Orthanc source code is placed in the folder
+   "~/Orthanc" and that the binaries will be compiled to
+   "~/OrthancBuild".
+
+
+Prepare the build with CMake
+----------------------------
+
+# cd ~/OrthancBuild
+# cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON ..
+
+NB: Adapt the value of "CMAKE_OSX_DEPLOYMENT_TARGET" with respect to
+your version of XCode.
+
+
+Build the Debug version of Orthanc
+----------------------------------
+
+# xcodebuild
+# ./Debug/UnitTests
+
+The binaries of Orthanc are located at "~/OrthancBuild/Debug/Orthanc".
+
+
+Build the Release version of Orthanc
+------------------------------------
+
+# xcodebuild -configuration Release
+# ./Debug/UnitTests
+
+The binaries of Orthanc are located at "~/OrthancBuild/Release/Orthanc".
--- a/INSTALL	Tue Jun 24 16:37:06 2014 +0200
+++ b/INSTALL	Wed Jun 25 11:40:41 2014 +0200
@@ -47,6 +47,13 @@
 
 
 
+Native OS X Compilation
+-----------------------
+
+See the file "DarwinCompilation.txt".
+
+
+
 Native Windows build with Microsoft Visual Studio 2005
 ------------------------------------------------------
 
--- a/LinuxCompilation.txt	Tue Jun 24 16:37:06 2014 +0200
+++ b/LinuxCompilation.txt	Wed Jun 25 11:40:41 2014 +0200
@@ -12,6 +12,10 @@
 automatically compile them. This process should work on all the Linux
 distributions.
 
+We make the assumption that Orthanc source code is placed in the
+folder "~/Orthanc" and that the binaries will be compiled to
+"~/OrthancBuild".
+
 
 To build binaries with debug information:
 
--- a/NEWS	Tue Jun 24 16:37:06 2014 +0200
+++ b/NEWS	Wed Jun 25 11:40:41 2014 +0200
@@ -2,6 +2,8 @@
 ===============================
 
 * Introduction of the Orthanc Plugin SDK
+* Official support of OS X (Darwin)
+* Options to limit the number of results for an incoming C-FIND query
 * Support of kFreeBSD
 
 
--- a/OrthancServer/DicomModification.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/DicomModification.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -37,6 +37,7 @@
 #include "FromDcmtkBridge.h"
 
 #include <memory>   // For std::auto_ptr
+#include <glog/logging.h>
 
 namespace Orthanc
 {
@@ -238,6 +239,7 @@
 
     if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID))
     {
+      LOG(ERROR) << "When modifying a patient, her PatientID is required to be modified";
       throw OrthancException(ErrorCode_BadRequest);
     }
 
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -107,7 +107,7 @@
 #endif 
 
 
-#if defined(__FreeBSD_kernel__)
+#if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX)
 /**
  * TO IMPROVE: "_POSIX_HOST_NAME_MAX is only the minimum value that
  * HOST_NAME_MAX can ever have [...] Therefore you cannot allocate an
@@ -217,21 +217,21 @@
     unsigned int presentationContextId = 1;
 
     for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin();
-         it != reservedStorageSOPClasses_.end(); it++)
+         it != reservedStorageSOPClasses_.end(); ++it)
     {
       RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
                               *it, asPreferred, asFallback);
     }
 
     for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin();
-         it != storageSOPClasses_.end(); it++)
+         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++)
+         it != defaultStorageSOPClasses_.end(); ++it)
     {
       RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
                               *it, asPreferred, asFallback);
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h	Wed Jun 25 11:40:41 2014 +0200
@@ -47,7 +47,13 @@
     {
     }
 
-    virtual void Handle(DicomFindAnswers& answers,
+    /**
+     * Can throw exceptions. Returns "false" iff too many results have
+     * to be returned. In such a case, a "Matching terminated due to
+     * Cancel request" DIMSE code would be returned.
+     * https://www.dabsoft.ch/dicom/4/V.4.1/
+     **/
+    virtual bool Handle(DicomFindAnswers& answers,
                         const DicomMap& input,
                         const std::string& callingAETitle) = 0;
   };
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -40,12 +40,12 @@
 
 namespace Orthanc
 {
-  RemoteModalityParameters::RemoteModalityParameters()
+  RemoteModalityParameters::RemoteModalityParameters() :
+    aet_("ORTHANC"),
+    host_("localhost"),
+    port_(104),
+    manufacturer_(ModalityManufacturer_Generic)
   {
-    aet_ = "ORTHANC";
-    host_ = "localhost";
-    port_ = 104;
-    manufacturer_ = ModalityManufacturer_Generic;
   }
 
   void RemoteModalityParameters::SetPort(int port)
--- a/OrthancServer/Internals/DicomImageDecoder.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/Internals/DicomImageDecoder.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -100,108 +100,19 @@
 
 namespace Orthanc
 {
-  class DicomImageDecoder::ImageSource
-  {
-  private:
-    std::string psmct_;
-    std::auto_ptr<DicomIntegerPixelAccessor> slowAccessor_;
-    std::auto_ptr<ImageAccessor> fastAccessor_;
-
-  public:
-    void Setup(DcmDataset& dataset,
-               unsigned int frame)
-    {
-      psmct_.clear();
-      slowAccessor_.reset(NULL);
-      fastAccessor_.reset(NULL);
-
-      // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data
-
-      DicomMap m;
-      FromDcmtkBridge::Convert(m, dataset);
-
-      /**
-       * Create an accessor to the raw values of the DICOM image.
-       **/
-
-      DcmElement* e;
-      if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() &&
-          e != NULL)
-      {
-        Uint8* pixData = NULL;
-        if (e->getUint8Array(pixData) == EC_Normal)
-        {    
-          slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength()));
-        }
-      }
-      else if (DicomImageDecoder::DecodePsmctRle1(psmct_, dataset))
-      {
-        LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded";
-        Uint8* pixData = NULL;
-        if (psmct_.size() > 0)
-        {
-          pixData = reinterpret_cast<Uint8*>(&psmct_[0]);
-        }
-
-        slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size()));
-      }
-    
-      if (slowAccessor_.get() == NULL)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-      slowAccessor_->SetCurrentFrame(frame);
-
-
-      /**
-       * If possible, create a fast ImageAccessor to the image buffer.
-       **/
-
-      
-    }
-
-    unsigned int GetWidth() const
-    {
-      assert(slowAccessor_.get() != NULL);
-      return slowAccessor_->GetInformation().GetWidth();
-    }
-
-    unsigned int GetHeight() const
-    {
-      assert(slowAccessor_.get() != NULL);
-      return slowAccessor_->GetInformation().GetHeight();
-    }
-
-    unsigned int GetChannelCount() const
-    {
-      assert(slowAccessor_.get() != NULL);
-      return slowAccessor_->GetInformation().GetChannelCount();
-    }
-
-    const DicomIntegerPixelAccessor& GetAccessor() const
-    {
-      assert(slowAccessor_.get() != NULL);
-      return *slowAccessor_;
-    }
-
-    bool HasFastAccessor() const
-    {
-      return fastAccessor_.get() != NULL;
-    }
-
-    const ImageAccessor& GetFastAccessor() const
-    {
-      assert(HasFastAccessor());
-      return *fastAccessor_;
-    }
-  };
-
-
   static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a);
   static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011);
 
-  bool DicomImageDecoder::IsPsmctRle1(DcmDataset& dataset)
+
+  static bool IsJpegLossless(const DcmDataset& dataset)
+  {
+    // http://support.dcmtk.org/docs/dcxfer_8h-source.html
+    return (dataset.getOriginalXfer() == EXS_JPEGLSLossless ||
+            dataset.getOriginalXfer() == EXS_JPEGLSLossy);
+  }
+
+
+  static bool IsPsmctRle1(DcmDataset& dataset)
   {
     DcmElement* e;
     char* c;
@@ -224,8 +135,8 @@
   }
 
 
-  bool DicomImageDecoder::DecodePsmctRle1(std::string& output,
-                                          DcmDataset& dataset)
+  static bool DecodePsmctRle1(std::string& output,
+                              DcmDataset& dataset)
   {
     // Check whether the DICOM instance contains an image encoded with
     // the PMSCT_RLE1 scheme.
@@ -309,6 +220,104 @@
   }
 
 
+  class DicomImageDecoder::ImageSource
+  {
+  private:
+    std::string psmct_;
+    std::auto_ptr<DicomIntegerPixelAccessor> slowAccessor_;
+    std::auto_ptr<ImageAccessor> fastAccessor_;
+
+  public:
+    void Setup(DcmDataset& dataset,
+               unsigned int frame)
+    {
+      psmct_.clear();
+      slowAccessor_.reset(NULL);
+      fastAccessor_.reset(NULL);
+
+      // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data
+
+      DicomMap m;
+      FromDcmtkBridge::Convert(m, dataset);
+
+      /**
+       * Create an accessor to the raw values of the DICOM image.
+       **/
+
+      DcmElement* e;
+      if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() &&
+          e != NULL)
+      {
+        Uint8* pixData = NULL;
+        if (e->getUint8Array(pixData) == EC_Normal)
+        {    
+          slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength()));
+        }
+      }
+      else if (DecodePsmctRle1(psmct_, dataset))
+      {
+        LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded";
+        Uint8* pixData = NULL;
+        if (psmct_.size() > 0)
+        {
+          pixData = reinterpret_cast<Uint8*>(&psmct_[0]);
+        }
+
+        slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size()));
+      }
+    
+      if (slowAccessor_.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      slowAccessor_->SetCurrentFrame(frame);
+
+
+      /**
+       * If possible, create a fast ImageAccessor to the image buffer.
+       **/
+
+      
+    }
+
+    unsigned int GetWidth() const
+    {
+      assert(slowAccessor_.get() != NULL);
+      return slowAccessor_->GetInformation().GetWidth();
+    }
+
+    unsigned int GetHeight() const
+    {
+      assert(slowAccessor_.get() != NULL);
+      return slowAccessor_->GetInformation().GetHeight();
+    }
+
+    unsigned int GetChannelCount() const
+    {
+      assert(slowAccessor_.get() != NULL);
+      return slowAccessor_->GetInformation().GetChannelCount();
+    }
+
+    const DicomIntegerPixelAccessor& GetAccessor() const
+    {
+      assert(slowAccessor_.get() != NULL);
+      return *slowAccessor_;
+    }
+
+    bool HasFastAccessor() const
+    {
+      return fastAccessor_.get() != NULL;
+    }
+
+    const ImageAccessor& GetFastAccessor() const
+    {
+      assert(HasFastAccessor());
+      return *fastAccessor_;
+    }
+  };
+
+
   void DicomImageDecoder::SetupImageBuffer(ImageBuffer& target,
                                            DcmDataset& dataset)
   {
@@ -333,14 +342,6 @@
   }
 
 
-  bool DicomImageDecoder::IsJpegLossless(const DcmDataset& dataset)
-  {
-    // http://support.dcmtk.org/docs/dcxfer_8h-source.html
-    return (dataset.getOriginalXfer() == EXS_JPEGLSLossless ||
-            dataset.getOriginalXfer() == EXS_JPEGLSLossy);
-  }
-
-
   bool DicomImageDecoder::IsUncompressedImage(const DcmDataset& dataset)
   {
     // http://support.dcmtk.org/docs/dcxfer_8h-source.html
--- a/OrthancServer/Internals/DicomImageDecoder.h	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/Internals/DicomImageDecoder.h	Wed Jun 25 11:40:41 2014 +0200
@@ -52,13 +52,8 @@
     static void SetupImageBuffer(ImageBuffer& target,
                                  DcmDataset& dataset);
 
-    static bool DecodePsmctRle1(std::string& output,
-                                DcmDataset& dataset);
-
     static bool IsUncompressedImage(const DcmDataset& dataset);
 
-    static bool IsJpegLossless(const DcmDataset& dataset);
-
     static void DecodeUncompressedImage(ImageBuffer& target,
                                         DcmDataset& dataset,
                                         unsigned int frame);
--- a/OrthancServer/Internals/FindScp.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/Internals/FindScp.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -100,6 +100,7 @@
       DicomFindAnswers answers_;
       DcmDataset* lastRequest_;
       const std::string* callingAETitle_;
+      bool noCroppingOfResults_;
     };
 
 
@@ -125,12 +126,12 @@
 
         try
         {
-          data.handler_->Handle(data.answers_, data.input_, *data.callingAETitle_);
+          data.noCroppingOfResults_ = data.handler_->Handle(data.answers_, data.input_, *data.callingAETitle_);
         }
         catch (OrthancException& e)
         {
           // Internal error!
-          LOG(ERROR) <<  "IFindRequestHandler Failed: " << e.What();
+          LOG(ERROR) <<  "C-FIND request handler has failed: " << e.What();
           response->DimseStatus = STATUS_FIND_Failed_UnableToProcess;
           *responseIdentifiers = NULL;   
           return;
@@ -148,12 +149,21 @@
 
       if (responseCount <= static_cast<int>(data.answers_.GetSize()))
       {
+        // There are pending results that are still to be sent
         response->DimseStatus = STATUS_Pending;
         *responseIdentifiers = ToDcmtkBridge::Convert(data.answers_.GetAnswer(responseCount - 1));
       }
+      else if (data.noCroppingOfResults_)
+      {
+        // Success: All the results have been sent
+        response->DimseStatus = STATUS_Success;
+        *responseIdentifiers = NULL;
+      }
       else
       {
-        response->DimseStatus = STATUS_Success;
+        // Success, but the results were too numerous and had to be cropped
+        LOG(WARNING) <<  "Too many results for an incoming C-FIND query";
+        response->DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest;
         *responseIdentifiers = NULL;
       }
     }
@@ -170,6 +180,7 @@
     data.lastRequest_ = NULL;
     data.handler_ = &handler;
     data.callingAETitle_ = &callingAETitle;
+    data.noCroppingOfResults_ = true;
 
     OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, 
                                           FindScpCallback, &data,
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -442,7 +442,26 @@
   }
 
 
-  void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
+  bool OrthancFindRequestHandler::HasReachedLimit(const DicomFindAnswers& answers,
+                                                  ResourceType level) const
+  {
+    switch (level)
+    {
+      case ResourceType_Patient:
+      case ResourceType_Study:
+      case ResourceType_Series:
+        return (maxResults_ != 0 && answers.GetSize() >= maxResults_);
+
+      case ResourceType_Instance:
+        return (maxInstances_ != 0 && answers.GetSize() >= maxInstances_);
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  bool OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
                                          const DicomMap& input,
                                          const std::string& callingAETitle)
   {
@@ -580,6 +599,12 @@
         
           if (Matches(info, query))
           {
+            if (HasReachedLimit(answers, level))
+            {
+              // Too many results, stop before recording this new match
+              return false;
+            }
+
             AddAnswer(answers, info, query);
           }
         }
@@ -589,6 +614,8 @@
         // This resource has probably been deleted during the find request
       }
     }
+
+    return true;  // All the matching resources have been returned
   }
 }
 
--- a/OrthancServer/OrthancFindRequestHandler.h	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/OrthancFindRequestHandler.h	Wed Jun 25 11:40:41 2014 +0200
@@ -41,15 +41,42 @@
   {
   private:
     ServerContext& context_;
+    unsigned int maxResults_;
+    unsigned int maxInstances_;
+
+    bool HasReachedLimit(const DicomFindAnswers& answers,
+                         ResourceType level) const;
 
   public:
     OrthancFindRequestHandler(ServerContext& context) :
-    context_(context)
+      context_(context), 
+      maxResults_(0),
+      maxInstances_(0)
     {
     }
 
-    virtual void Handle(DicomFindAnswers& answers,
+    virtual bool Handle(DicomFindAnswers& answers,
                         const DicomMap& input,
                         const std::string& callingAETitle);
+
+    unsigned int GetMaxResults() const
+    {
+      return maxResults_;
+    }
+
+    void SetMaxResults(unsigned int results)
+    {
+      maxResults_ = results;
+    }
+
+    unsigned int GetMaxInstances() const
+    {
+      return maxInstances_;
+    }
+
+    void SetMaxInstances(unsigned int instances)
+    {
+      maxInstances_ = instances;
+    }
   };
 }
--- a/OrthancServer/OrthancInitialization.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -299,7 +299,7 @@
     {
       LOG(ERROR) << "Syntax error in the definition of modality \"" << name 
                  << "\". Please check your configuration file.";
-      throw e;
+      throw;
     }
   }
 
@@ -330,7 +330,7 @@
     {
       LOG(ERROR) << "Syntax error in the definition of peer \"" << name 
                  << "\". Please check your configuration file.";
-      throw e;
+      throw;
     }
   }
 
--- a/OrthancServer/ServerContext.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/ServerContext.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -286,7 +286,7 @@
         LogMissingRequiredTag(dicomSummary);
       }
 
-      throw e;
+      throw;
     }
   }
 
--- a/OrthancServer/main.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/OrthancServer/main.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -100,7 +100,32 @@
 
   virtual IFindRequestHandler* ConstructFindRequestHandler()
   {
-    return new OrthancFindRequestHandler(context_);
+    std::auto_ptr<OrthancFindRequestHandler> result(new OrthancFindRequestHandler(context_));
+
+    result->SetMaxResults(Configuration::GetGlobalIntegerParameter("LimitFindResults", 0));
+    result->SetMaxInstances(Configuration::GetGlobalIntegerParameter("LimitFindInstances", 0));
+
+    if (result->GetMaxResults() == 0)
+    {
+      LOG(INFO) << "No limit on the number of C-FIND results at the Patient, Study and Series levels";
+    }
+    else
+    {
+      LOG(INFO) << "Maximum " << result->GetMaxResults() 
+                << " results for C-FIND queries at the Patient, Study and Series levels";
+    }
+
+    if (result->GetMaxInstances() == 0)
+    {
+      LOG(INFO) << "No limit on the number of C-FIND results at the Instance level";
+    }
+    else
+    {
+      LOG(INFO) << "Maximum " << result->GetMaxInstances() 
+                << " instances will be returned for C-FIND queries at the Instance level";
+    }
+
+    return result.release();
   }
 
   virtual IMoveRequestHandler* ConstructMoveRequestHandler()
--- a/Resources/CMake/BoostConfiguration.cmake	Tue Jun 24 16:37:06 2014 +0200
+++ b/Resources/CMake/BoostConfiguration.cmake	Wed Jun 25 11:40:41 2014 +0200
@@ -8,7 +8,7 @@
   #set(Boost_USE_STATIC_LIBS ON)
 
   find_package(Boost
-    COMPONENTS filesystem thread system date_time regex)
+    COMPONENTS filesystem thread system date_time regex locale)
 
   if (NOT Boost_FOUND)
     message(FATAL_ERROR "Unable to locate Boost on this system")
@@ -53,7 +53,9 @@
     )
 
   set(BOOST_SOURCES)
+
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
       ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
     list(APPEND BOOST_SOURCES
       ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp
@@ -77,10 +79,17 @@
     add_definitions(
       -DBOOST_LOCALE_WITH_WCONV=1
       )
+
   else()
     message(FATAL_ERROR "Support your platform here")
   endif()
 
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp
+      )
+  endif()
+
   aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES)
 
   list(APPEND BOOST_SOURCES
@@ -121,6 +130,6 @@
   source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*)
 else()
   add_definitions(
-    -DBOOST_HAS_LOCALE=0
+    -DBOOST_HAS_LOCALE=1
     )
 endif()
--- a/Resources/CMake/Compiler.cmake	Tue Jun 24 16:37:06 2014 +0200
+++ b/Resources/CMake/Compiler.cmake	Wed Jun 25 11:40:41 2014 +0200
@@ -70,6 +70,12 @@
     SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
   endif()
 
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  add_definitions(
+    -D_XOPEN_SOURCE=1
+    )
+  link_libraries(iconv)
+
 endif()
 
 
--- a/Resources/CMake/DcmtkConfiguration.cmake	Tue Jun 24 16:37:06 2014 +0200
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Wed Jun 25 11:40:41 2014 +0200
@@ -12,7 +12,6 @@
 endif()
 
 
-
 if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK)
   SET(DCMTK_VERSION_NUMBER 360)
   SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0)
@@ -88,6 +87,7 @@
   # Source for the logging facility of DCMTK
   AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES)
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
       ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
     list(REMOVE_ITEM DCMTK_SOURCES 
       ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc
--- a/Resources/CMake/GoogleLogConfiguration.cmake	Tue Jun 24 16:37:06 2014 +0200
+++ b/Resources/CMake/GoogleLogConfiguration.cmake	Wed Jun 25 11:40:41 2014 +0200
@@ -29,6 +29,7 @@
   set(ac_google_end_namespace "}")
 
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
       ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
     set(ac_cv_have_unistd_h 1)
     set(ac_cv_have_stdint_h 1)
@@ -85,6 +86,7 @@
   endif()
 
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
       ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
     if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
       # Install the specific configuration for LSB SDK
@@ -92,6 +94,12 @@
         ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationLSB.h
         ${GOOGLE_LOG_SOURCES_DIR}/src/config.h
         COPYONLY)
+    elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin")
+      # Install the specific configuration for Mac OS
+      configure_file(
+        ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationDarwin.h
+        ${GOOGLE_LOG_SOURCES_DIR}/src/config.h
+        COPYONLY)
     else()
       configure_file(
         ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/GoogleLogConfigurationDarwin.h	Wed Jun 25 11:40:41 2014 +0200
@@ -0,0 +1,175 @@
+/* src/config.h.  Generated from config.h.in by configure.  */
+/* src/config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Namespace for Google classes */
+#define GOOGLE_NAMESPACE google
+
+/* Define if you have the `dladdr' function */
+/* #undef HAVE_DLADDR */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <execinfo.h> header file. */
+#define HAVE_EXECINFO_H 1
+
+/* Define if you have the `fcntl' function */
+#define HAVE_FCNTL 1
+
+/* Define to 1 if you have the <glob.h> header file. */
+#define HAVE_GLOB_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `pthread' library (-lpthread). */
+#define HAVE_LIBPTHREAD 1
+
+/* Define to 1 if you have the <libunwind.h> header file. */
+/* #undef HAVE_LIBUNWIND_H */
+
+/* define if you have google gflags library */
+/* #undef HAVE_LIB_GFLAGS */
+
+/* define if you have google gmock library */
+/* #undef HAVE_LIB_GMOCK */
+
+/* define if you have google gtest library */
+/* #undef HAVE_LIB_GTEST */
+
+/* define if you have libunwind */
+/* #undef HAVE_LIB_UNWIND */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* define if the compiler implements namespaces */
+#define HAVE_NAMESPACES 1
+
+/* Define if you have POSIX threads libraries and header files. */
+#define HAVE_PTHREAD 1
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#define HAVE_PWD_H 1
+
+/* define if the compiler implements pthread_rwlock_* */
+#define HAVE_RWLOCK 1
+
+/* Define if you have the `sigaltstack' function */
+#define HAVE_SIGALTSTACK 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <syscall.h> header file. */
+/* #undef HAVE_SYSCALL_H */
+
+/* Define to 1 if you have the <syslog.h> header file. */
+#define HAVE_SYSLOG_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+/* #define HAVE_SYS_STAT_H 1 */
+
+/* Define to 1 if you have the <sys/syscall.h> header file. */
+#define HAVE_SYS_SYSCALL_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+/* #define HAVE_SYS_TYPES_H 1 */
+
+/* Define to 1 if you have the <sys/ucontext.h> header file. */
+/* #define HAVE_SYS_UCONTEXT_H 1 */
+
+/* Define to 1 if you have the <sys/utsname.h> header file. */
+#define HAVE_SYS_UTSNAME_H 1
+
+/* Define to 1 if you have the <ucontext.h> header file. */
+#define HAVE_UCONTEXT_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* define if the compiler supports using expression for operator */
+#define HAVE_USING_OPERATOR 1
+
+/* define if your compiler has __attribute__ */
+#define HAVE___ATTRIBUTE__ 1
+
+/* define if your compiler has __builtin_expect */
+#define HAVE___BUILTIN_EXPECT 1
+
+/* define if your compiler has __sync_val_compare_and_swap */
+#define HAVE___SYNC_VAL_COMPARE_AND_SWAP 1
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+   */
+#define LT_OBJDIR ".libs/"
+
+/* Name of package */
+#define PACKAGE "glog"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "opensource@google.com"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "glog"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "glog 0.3.2"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "glog"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "0.3.2"
+
+/* How to access the PC from a struct ucontext */
+/*#include <ucontext.h>
+#include <sys/ucontext.h>
+#ifdef REG_RIP
+#define PC_FROM_UCONTEXT uc_mcontext.gregs[REG_RIP]
+#else
+#undef PC_FROM_UCONTEXT
+#endif*/
+
+// This is required for older versions of Linux
+#undef PC_FROM_UCONTEXT
+
+/* Define to necessary symbol if this constant uses a non-standard name on
+   your system. */
+/* #undef PTHREAD_CREATE_JOINABLE */
+
+/* The size of `void *', as computed by sizeof. */
+#define SIZEOF_VOID_P 8
+
+/* Define to 1 if you have the ANSI C header files. */
+/* #undef STDC_HEADERS */
+
+/* the namespace where STL code like vector<> is defined */
+#define STL_NAMESPACE std
+
+/* location of source code */
+#define TEST_SRC_DIR "."
+
+/* Version number of package */
+#define VERSION "0.3.2"
+
+/* Stops putting the code inside the Google namespace */
+#define _END_GOOGLE_NAMESPACE_ }
+
+/* Puts following code inside the Google namespace */
+#define _START_GOOGLE_NAMESPACE_ namespace google {
--- a/Resources/CMake/LibCurlConfiguration.cmake	Tue Jun 24 16:37:06 2014 +0200
+++ b/Resources/CMake/LibCurlConfiguration.cmake	Wed Jun 25 11:40:41 2014 +0200
@@ -41,6 +41,7 @@
   endif()
 
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
       ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
     if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
       SET(TMP_OS "x86_64")
--- a/Resources/Configuration.json	Tue Jun 24 16:37:06 2014 +0200
+++ b/Resources/Configuration.json	Wed Jun 25 11:40:41 2014 +0200
@@ -165,5 +165,14 @@
   // will be computed and stored in the Orthanc database. This
   // information can be used to detect disk corruption, at the price
   // of a small performance overhead.
-  "StoreMD5ForAttachments" : true
+  "StoreMD5ForAttachments" : true,
+
+  // The maximum number of results for a single C-FIND request at the
+  // Patient, Study or Series level. Setting this option to "0" means
+  // no limit.
+  "LimitFindResults" : 0,
+
+  // The maximum number of results for a single C-FIND request at the
+  // Instance level. Setting this option to "0" means no limit.
+  "LimitFindInstances" : 0
 }
--- a/UnitTestsSources/UnitTestsMain.cpp	Tue Jun 24 16:37:06 2014 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Wed Jun 25 11:40:41 2014 +0200
@@ -622,6 +622,9 @@
 #if defined(_WIN32)
   ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness());
 
+#elif defined(__APPLE__)
+  ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness());
+
 #elif defined(__linux) || defined(__FreeBSD_kernel__)
 
 #if !defined(__BYTE_ORDER)