changeset 6963:a710e864790d streaming

integration default->streaming
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 26 Jun 2026 15:12:06 +0200
parents 7b52e79a4aca (current diff) 3948b93ed918 (diff)
children 5eb525758b7f
files NEWS OrthancFramework/Sources/Logging.cpp OrthancServer/Sources/ServerJobs/ArchiveJob.cpp
diffstat 4 files changed, 123 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Jun 18 17:05:30 2026 +0200
+++ b/NEWS	Fri Jun 26 15:12:06 2026 +0200
@@ -53,6 +53,9 @@
   "/modalities/../get", "queries/../answers/../retrieve" now always overwrites the "DicomAet"
   and the "DicomModalities.LocalAet" configurations.
 * Optimized the "/patients|studies|series|instances/../statistics" routes.
+* Updated the naming structure of the zip downloaded from "/../../archive" routes.  Notably added
+  SeriesNumber at the series level and now using " - " instead of " " to separate DICOM tags used
+  in folder names.
 
 Plugin SDK
 ----------
--- a/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Thu Jun 18 17:05:30 2026 +0200
+++ b/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Fri Jun 26 15:12:06 2026 +0200
@@ -238,6 +238,11 @@
         # for HttpClient that returns the answer body in case of HTTP error
         set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
         set(ORTHANC_FRAMEWORK_MD5 "665f8aa70d7c5091bc20da37cf664910")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "004b351797fe")
+        # PixelsMasker 0.1.2 (framework post-1.12.11)
+        # for ScopedThreadNameSetter
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
+        set(ORTHANC_FRAMEWORK_MD5 "f078ca997217b831ab3f6741f08a8c07")
       endif()
     endif()
   endif()
--- a/OrthancFramework/Sources/Logging.cpp	Thu Jun 18 17:05:30 2026 +0200
+++ b/OrthancFramework/Sources/Logging.cpp	Fri Jun 26 15:12:06 2026 +0200
@@ -538,10 +538,10 @@
 #include "SystemToolbox.h"
 #include "Toolbox.h"
 
-#include <fstream>
+#include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/thread.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
+#include <fstream>
 
 
 namespace
@@ -631,14 +631,14 @@
 static std::string                              pluginName_;              // this string can only be non-empty if running from a plugin
 static bool                                     hasOrthancAdvancedLogging_ = false;  // Whether the Orthanc runtime is >= 1.12.4
 static bool                                     hasClearThreadName_ = false;  // Whether the Orthanc runtime is >= 1.12.12
-static boost::recursive_mutex                   threadNamesMutex_;
+static boost::shared_mutex                      threadNamesMutex_;
 static ThreadNamesMap                           threadNames_;
 static bool                                     enableThreadNames_ = true;
 static boost::recursive_mutex                   threadContextsMutex_;
 static ThreadContextsMap                        threadContexts_;
 static bool                                     enableContexts_ = true;
 static std::list<Orthanc::Logging::ILoggingListener*> loggingListeners_;
-static boost::mutex                                   loggingListenersMutex_;
+static boost::shared_mutex                            loggingListenersMutex_;
 
 
 namespace Orthanc
@@ -723,45 +723,42 @@
     }
 
 
-    void SetCurrentThreadNameInternal(const boost::thread::id& id, const std::string& name)
+    void SetCurrentThreadName(const std::string& name)
     {
-      boost::recursive_mutex::scoped_lock lock(threadNamesMutex_);
-
       if (name.size() > 16)
       {
         throw OrthancException(ErrorCode_InternalError, std::string("Thread name can not exceed 16 characters: ") + name);
       }
 
-      // std::cout << "+++++ SetCurrentThreadNameInternal " << id << " " << name << std::endl;
+      if (pluginContext_ == NULL)
+      {
+        const boost::thread::id threadId = boost::this_thread::get_id();
+
+        {
+          boost::unique_lock<boost::shared_mutex> lock(threadNamesMutex_);
+
 #if !defined(NDEBUG)
-      if (threadNames_.find(id) != threadNames_.end())
-      {
-        throw OrthancException(ErrorCode_InternalError, std::string("This thread already has a name or another thread is re-using the same threadId and you have not called 'ClearCurrentThreadName()': ") + name);
-      }
+          if (threadNames_.find(threadId) != threadNames_.end())
+          {
+            throw OrthancException(ErrorCode_InternalError, std::string("This thread already has a name or another thread is re-using the same threadId and you have not called 'ClearCurrentThreadName()': ") + name);
+          }
       
-      for (ThreadNamesMap::const_iterator it = threadNames_.begin(); it != threadNames_.end(); ++it)
-      {
-        if (it->second == name)
-        {
-          throw OrthancException(ErrorCode_InternalError, std::string("Another thread already uses this thread name: ") + name);
-        }
-      }
+          for (ThreadNamesMap::const_iterator it = threadNames_.begin(); it != threadNames_.end(); ++it)
+          {
+            if (it->second == name)
+            {
+              throw OrthancException(ErrorCode_InternalError, std::string("Another thread already uses this thread name: ") + name);
+            }
+          }
 #endif
 
-      threadNames_[id] = name;
+          threadNames_[threadId] = name;
+        }
 
 #if defined(__linux__) && !defined(NDEBUG) && !defined(__LSB_VERSION__)
-      // set the thread name at "system" level too -> required to have the thread names visible in GDB !
-      pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());  // thread names are limited to 15 in Linux
+        // set the thread name at "system" level too -> required to have the thread names visible in GDB !
+        pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());  // thread names are limited to 15 in Linux
 #endif              
-    }
-
-    void SetCurrentThreadName(const std::string& name)
-    {
-      if (pluginContext_ == NULL)
-      {
-        boost::recursive_mutex::scoped_lock lock(threadNamesMutex_);
-        SetCurrentThreadNameInternal(boost::this_thread::get_id(), name);
       }
       else
       {
@@ -769,47 +766,55 @@
       }
     }
 
+
     bool HasCurrentThreadName()
     {
-      boost::thread::id threadId = boost::this_thread::get_id();
+      const boost::thread::id threadId = boost::this_thread::get_id();
 
-      boost::recursive_mutex::scoped_lock lock(threadNamesMutex_);
-      return threadNames_.find(threadId) != threadNames_.end();
+      {
+        boost::shared_lock<boost::shared_mutex> lock(threadNamesMutex_);
+        return threadNames_.find(threadId) != threadNames_.end();
+      }
     }
 
+
     void ClearCurrentThreadName()
     {
       if (pluginContext_ == NULL)
       {
-        boost::thread::id threadId = boost::this_thread::get_id();
+        const boost::thread::id threadId = boost::this_thread::get_id();
 
-        boost::recursive_mutex::scoped_lock lock(threadNamesMutex_);
-
-        // std::cout << "+++++ ClearCurrentThreadName " << threadId << std::endl;
-        threadNames_.erase(threadId);
+        {
+          boost::unique_lock<boost::shared_mutex> lock(threadNamesMutex_);
+          threadNames_.erase(threadId);
+        }
       }
       else if (hasClearThreadName_) // only recent runtimes support it (from 1.12.12)
       {
         pluginContext_->InvokeService(pluginContext_, _OrthancPluginService_ClearCurrentThreadName, NULL);
       }
+    }
 
-    }
 
     std::string GetCurrentThreadName()
     {
-      boost::thread::id threadId = boost::this_thread::get_id();
+      const boost::thread::id threadId = boost::this_thread::get_id();
 
-      boost::recursive_mutex::scoped_lock lock(threadNamesMutex_);
+      {
+        boost::shared_lock<boost::shared_mutex> lock(threadNamesMutex_);
 
-      if (threadNames_.find(threadId) == threadNames_.end())
-      {
-        // set the threadId as the thread name
-        SetCurrentThreadNameInternal(threadId, boost::lexical_cast<std::string>(threadId));
+        std::map<boost::thread::id, std::string>::const_iterator found = threadNames_.find(threadId);
+        if (found != threadNames_.end())
+        {
+          return found->second;
+        }
       }
 
-      return threadNames_[threadId];
+      // "SetCurrentThreadName()" has not been invoked for this thread
+      return boost::lexical_cast<std::string>(threadId);
     }    
 
+
     bool LookupCurrentContext(std::string& result)
     {
       boost::thread::id threadId = boost::this_thread::get_id();
@@ -825,11 +830,13 @@
       return false;
     }    
 
+
     ScopedThreadNameSetter::ScopedThreadNameSetter(const std::string& threadName)
     {
       SetCurrentThreadName(threadName);
     }
 
+
     ScopedThreadNameSetter::~ScopedThreadNameSetter()
     {
       ClearCurrentThreadName();
@@ -893,16 +900,18 @@
 
     void AddLoggingListener(ILoggingListener* listener)
     {
-      boost::mutex::scoped_lock lock(loggingListenersMutex_);
+      boost::unique_lock<boost::shared_mutex> lock(loggingListenersMutex_);
       loggingListeners_.push_back(listener);
     }
 
+
     void ClearLoggingListeners()
     {
-      boost::mutex::scoped_lock lock(loggingListenersMutex_);
+      boost::unique_lock<boost::shared_mutex> lock(loggingListenersMutex_);
       loggingListeners_.clear();
     }
 
+
     static void GetLinePrefix(std::string& prefix,
                               LogLevel level,
                               const char* pluginName,  // when logging in the core but coming from a plugin, pluginName_ is NULL but this argument is != NULL
@@ -1041,12 +1050,14 @@
       }
     }
 
+
     void Finalize()
     {
       boost::mutex::scoped_lock lock(loggingStreamsMutex_);
       loggingStreamsContext_.reset(NULL);
     }
 
+
     void Reset()
     {
       {
@@ -1249,10 +1260,20 @@
         *stream_ << messageStream_.str() << "\n";
         stream_->flush();
 
-        boost::mutex::scoped_lock lock(loggingListenersMutex_);
-        for (std::list<Orthanc::Logging::ILoggingListener*>::iterator it = loggingListeners_.begin(); it != loggingListeners_.end(); ++it)
         {
-          (*it)->HandleLog(level_, category_, pluginName_, file_, line_, messageStream_.str());
+          boost::shared_lock<boost::shared_mutex> lock(loggingListenersMutex_);
+
+          for (std::list<Orthanc::Logging::ILoggingListener*>::iterator it = loggingListeners_.begin(); it != loggingListeners_.end(); ++it)
+          {
+            try
+            {
+              (*it)->HandleLog(level_, category_, pluginName_, file_, line_, messageStream_.str());
+            }
+            catch (...)
+            {
+              // Don't throw in destructors
+            }
+          }
         }
       }
 
@@ -1283,4 +1304,4 @@
 }
 
 #endif   // ORTHANC_ENABLE_LOGGING
-//  NOLINTEND(bugprone-reserved-identifier)
\ No newline at end of file
+//  NOLINTEND(bugprone-reserved-identifier)
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Thu Jun 18 17:05:30 2026 +0200
+++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Fri Jun 26 15:12:06 2026 +0200
@@ -712,6 +712,7 @@
                       const std::string& publicId) ORTHANC_OVERRIDE
     {
       std::string path;
+      std::vector<std::string> pathParts;
 
       DicomMap tags;
       ResourceType resourceIdLevel = GetResourceIdType(level);
@@ -723,18 +724,48 @@
         {
           case ArchiveResourceType_Patient:
           case ArchiveResourceType_PatientInfoFromStudy:
-            path = GetTag(tags, DICOM_TAG_PATIENT_ID) + " " + GetTag(tags, DICOM_TAG_PATIENT_NAME);
-            break;
-
+          {
+            pathParts.push_back(GetTag(tags, DICOM_TAG_PATIENT_ID));
+            std::string patientName = GetTag(tags, DICOM_TAG_PATIENT_NAME);
+            if (!patientName.empty())
+            {
+              pathParts.push_back(patientName);
+            }
+          }; break;
           case ArchiveResourceType_Study:
-            path = GetTag(tags, DICOM_TAG_ACCESSION_NUMBER) + " " + GetTag(tags, DICOM_TAG_STUDY_DESCRIPTION);
-            break;
-
+          {
+            std::string accessionNumber = GetTag(tags, DICOM_TAG_ACCESSION_NUMBER);
+            std::string studyDescription = GetTag(tags, DICOM_TAG_STUDY_DESCRIPTION);
+            if (!accessionNumber.empty())
+            {
+              pathParts.push_back(accessionNumber);
+            }
+            if (!studyDescription.empty())
+            {
+              pathParts.push_back(studyDescription);
+            }
+          }; break;
           case ArchiveResourceType_Series:
           {
+            std::string seriesNumber = GetTag(tags, DICOM_TAG_SERIES_NUMBER);
             std::string modality = GetTag(tags, DICOM_TAG_MODALITY);
-            path = modality + " " + GetTag(tags, DICOM_TAG_SERIES_DESCRIPTION);
-
+            std::string seriesDescription = GetTag(tags, DICOM_TAG_SERIES_DESCRIPTION);
+            
+            if (!seriesNumber.empty())
+            {
+              pathParts.push_back(seriesNumber);
+            }
+            
+            if (!modality.empty())
+            {
+              pathParts.push_back(modality);
+            }
+            
+            if (!seriesDescription.empty())
+            {
+              pathParts.push_back(seriesDescription);
+            }
+            
             if (modality.size() == 0)
             {
               snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm");
@@ -751,16 +782,14 @@
             }
 
             counter_ = 0;
-
-            break;
-          }
-
+          }; break;
           default:
             THROW_WITH_FILE_AND_LINE_INFO(ErrorCode_InternalError);
         }
       }
 
-      path = Toolbox::StripSpaces(path);
+      Toolbox::JoinStrings(path, pathParts, " - ");
+      path =  Toolbox::StripSpaces(path);
 
       if (path.empty() 
           || (static_cast<size_t>(boost::count(path, '^')) == path.size()))  // this happens with non ASCII patient names: only the '^' remains and this is not a valid zip folder name