changeset 5664:301212a3fa08 find-refactoring

integration mainline->find-refactoring
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 04 Jul 2024 11:59:50 +0200
parents a3c244090f67 (current diff) d77292629430 (diff)
children d8c86698110c
files NEWS OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Sources/ServerIndex.h
diffstat 11 files changed, 129 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Jun 06 13:24:04 2024 +0200
+++ b/NEWS	Thu Jul 04 11:59:50 2024 +0200
@@ -4,6 +4,22 @@
 * TODO-FIND: complete the list of updated routes:
   /studies?expand and sibbling routes now also return "Metadata" (if the DB implements 'extended-api-v1')
 
+REST API
+--------
+
+* Improved parsing of multiple numerical values in DICOM tags.
+  https://discourse.orthanc-server.org/t/qido-includefield-with-sequences/4746/6
+
+Maintenance
+-----------
+
+* DICOM TLS: "DicomTlsTrustedCertificates" is not required anymore when issuing
+  an outgoing SCU connexion when "DicomTlsRemoteCertificateRequired" is set to false.
+* Introduced a new thread to update the statistics at regular interval for the
+  DB plugins that are implementing the UpdateAndGetStatistics function (currently only
+  PostgreSQL).  This avoids very long update times in case you don't call /statistics
+  for a long period.
+
 
 Version 1.12.4 (2024-06-05)
 ===========================
--- a/OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.cpp	Thu Jun 06 13:24:04 2024 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.cpp	Thu Jul 04 11:59:50 2024 +0200
@@ -195,7 +195,7 @@
       throw OrthancException(ErrorCode_BadSequenceOfCalls,
                              "DICOM TLS - No path to the local certificate was provided");
     }
-    else if (trustedCertificatesPath_.empty())
+    else if (remoteCertificateRequired_ && trustedCertificatesPath_.empty())
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls,
                              "DICOM TLS - No path to the trusted remote certificates was provided");
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Thu Jun 06 13:24:04 2024 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Thu Jul 04 11:59:50 2024 +0200
@@ -1978,13 +1978,27 @@
       
         case EVR_SL:  // signed long
         {
-          ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good();
+          if (decoded->find('\\') != std::string::npos)
+          {
+            ok = element.putString(decoded->c_str()).good();
+          }
+          else
+          {
+            ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good();
+          }
           break;
         }
 
         case EVR_SS:  // signed short
         {
-          ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good();
+          if (decoded->find('\\') != std::string::npos)
+          {
+            ok = element.putString(decoded->c_str()).good();
+          }
+          else
+          {
+            ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good();
+          }
           break;
         }
 
@@ -2023,14 +2037,28 @@
 
         case EVR_US:  // unsigned short
         {
-          ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();
+          if (decoded->find('\\') != std::string::npos)
+          {
+            ok = element.putString(decoded->c_str()).good();
+          }
+          else
+          {
+            ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();
+          }
           break;
         }
 
         case EVR_FL:  // float single-precision
         case EVR_OF:  // other float (requires byte swapping)
         {
-          ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good();
+          if (decoded->find('\\') != std::string::npos)
+          {
+            ok = element.putString(decoded->c_str()).good();
+          }
+          else
+          {
+            ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good();
+          }
           break;
         }
 
@@ -2039,7 +2067,14 @@
         case EVR_OD:  // other double (requires byte-swapping)
 #endif
         {
-          ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good();
+          if (decoded->find('\\') != std::string::npos)
+          {
+            ok = element.putString(decoded->c_str()).good();
+          }
+          else
+          {
+            ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good();
+          }
           break;
         }
 
--- a/OrthancFramework/Sources/Toolbox.cpp	Thu Jun 06 13:24:04 2024 +0200
+++ b/OrthancFramework/Sources/Toolbox.cpp	Thu Jul 04 11:59:50 2024 +0200
@@ -1885,7 +1885,7 @@
 
   std::string Toolbox::GenerateUuid()
   {
-#ifdef WIN32
+#ifdef _WIN32
     UUID uuid;
     UuidCreate ( &uuid );
 
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Thu Jun 06 13:24:04 2024 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Thu Jul 04 11:59:50 2024 +0200
@@ -3092,6 +3092,21 @@
 }
 
 
+TEST(ParsedDicomFile, MultipleFloatValue)
+{
+  // from https://discourse.orthanc-server.org/t/qido-includefield-with-sequences/4746/6
+  Json::Value v = Json::objectValue;
+  v["4010,1001"][0]["4010,1004"] = "30\\20\\10";
+  std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+  ASSERT_TRUE(dicom->HasTag(Orthanc::DicomTag(0x4010, 0x1001)));
+
+  DicomMap m;
+  ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DicomTag(0x4010, 0x1001)), 0));
+  ASSERT_EQ(1u, m.GetSize());
+  std::string value = m.GetStringValue(DicomTag(0x4010, 0x1004), "", false);
+  ASSERT_EQ("30\\20\\10", value);
+}
+
 
 TEST(ParsedDicomFile, ImageInformation)
 {
--- a/OrthancServer/Plugins/Engine/PluginsManager.cpp	Thu Jun 06 13:24:04 2024 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsManager.cpp	Thu Jul 04 11:59:50 2024 +0200
@@ -37,7 +37,7 @@
 #include <memory>
 #include <boost/filesystem.hpp>
 
-#ifdef WIN32
+#ifdef _WIN32
 #define PLUGIN_EXTENSION ".dll"
 #elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__)
 #define PLUGIN_EXTENSION ".so"
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Thu Jun 06 13:24:04 2024 +0200
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Thu Jul 04 11:59:50 2024 +0200
@@ -111,7 +111,7 @@
 #include <stdio.h>
 #include <string.h>
 
-#ifdef WIN32
+#ifdef _WIN32
 #  define ORTHANC_PLUGINS_API __declspec(dllexport)
 #elif __GNUC__ >= 4
 #  define ORTHANC_PLUGINS_API __attribute__ ((visibility ("default")))
--- a/OrthancServer/Resources/Configuration.json	Thu Jun 06 13:24:04 2024 +0200
+++ b/OrthancServer/Resources/Configuration.json	Thu Jul 04 11:59:50 2024 +0200
@@ -311,6 +311,8 @@
   // to "true" (resp. "false") corresponds to "--require-peer-cert"
   // (resp. "--ignore-peer-cert") in the DCMTK command-line
   // tools. (new in Orthanc 1.9.3)
+  // Once you set this configuration to true, you must provide a list of
+  // trusted certificates in DicomTlsTrustedCertificates.
   "DicomTlsRemoteCertificateRequired" : true,
 
   // Sets the minimum accepted TLS protocol version for the DICOM server
--- a/OrthancServer/Sources/ServerIndex.cpp	Thu Jun 06 13:24:04 2024 +0200
+++ b/OrthancServer/Sources/ServerIndex.cpp	Thu Jul 04 11:59:50 2024 +0200
@@ -266,6 +266,39 @@
     }
   };
 
+  void ServerIndex::UpdateStatisticsThread(ServerIndex* that,
+                                           unsigned int threadSleepGranularityMilliseconds)
+  {
+    Logging::SetCurrentThreadName("DB-STATS");
+
+    static const unsigned int SLEEP_SECONDS = 60;
+
+    if (threadSleepGranularityMilliseconds > 1000)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    LOG(INFO) << "Starting the update statistics thread (sleep = " << SLEEP_SECONDS << " seconds)";
+
+    unsigned int count = 0;
+    unsigned int countThreshold = (1000 * SLEEP_SECONDS) / threadSleepGranularityMilliseconds;
+
+    while (!that->done_)
+    {
+      boost::this_thread::sleep(boost::posix_time::milliseconds(threadSleepGranularityMilliseconds));
+      count++;
+      
+      if (count >= countThreshold)
+      {
+        uint64_t diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances;
+        that->GetGlobalStatistics(diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances);
+        
+        count = 0;
+      }
+    }
+
+    LOG(INFO) << "Stopping the update statistics thread";
+  }
 
   void ServerIndex::FlushThread(ServerIndex* that,
                                 unsigned int threadSleepGranularityMilliseconds)
@@ -326,11 +359,20 @@
     // execution of Orthanc
     StandaloneRecycling(maximumStorageMode_, maximumStorageSize_, maximumPatients_);
 
+    // For some DB engines (like SQLite), make sure we flush the DB to disk at regular interval
     if (GetDatabaseCapabilities().HasFlushToDisk())
     {
       flushThread_ = boost::thread(FlushThread, this, threadSleepGranularityMilliseconds);
     }
 
+    // For some DB plugins that implements the UpdateAndGetStatistics function, updating 
+    // the statistics can take quite some time if you have not done it for a long time 
+    // -> make sure they are updated at regular interval
+    if (GetDatabaseCapabilities().HasUpdateAndGetStatistics())
+    {
+      updateStatisticsThread_ = boost::thread(UpdateStatisticsThread, this, threadSleepGranularityMilliseconds);
+    }
+
     unstableResourcesMonitorThread_ = boost::thread
       (UnstableResourcesMonitorThread, this, threadSleepGranularityMilliseconds);
   }
@@ -357,6 +399,11 @@
         flushThread_.join();
       }
 
+      if (updateStatisticsThread_.joinable())
+      {
+        updateStatisticsThread_.join();
+      }
+
       if (unstableResourcesMonitorThread_.joinable())
       {
         unstableResourcesMonitorThread_.join();
--- a/OrthancServer/Sources/ServerIndex.h	Thu Jun 06 13:24:04 2024 +0200
+++ b/OrthancServer/Sources/ServerIndex.h	Thu Jul 04 11:59:50 2024 +0200
@@ -42,6 +42,7 @@
     bool done_;
     boost::mutex monitoringMutex_;
     boost::thread flushThread_;
+    boost::thread updateStatisticsThread_;
     boost::thread unstableResourcesMonitorThread_;
 
     LeastRecentlyUsedIndex<std::pair<ResourceType, int64_t>, UnstableResourcePayload>  unstableResources_;
@@ -53,6 +54,9 @@
     static void FlushThread(ServerIndex* that,
                             unsigned int threadSleep);
 
+    static void UpdateStatisticsThread(ServerIndex* that,
+                                       unsigned int threadSleep);
+
     static void UnstableResourcesMonitorThread(ServerIndex* that,
                                                unsigned int threadSleep);
 
--- a/OrthancServer/Sources/main.cpp	Thu Jun 06 13:24:04 2024 +0200
+++ b/OrthancServer/Sources/main.cpp	Thu Jul 04 11:59:50 2024 +0200
@@ -1187,7 +1187,7 @@
       else
       {
         context.SetRestApiWriteToFileSystemEnabled(false);
-        LOG(WARNING) << "REST API cannot write to the file system bacause the \"RestApiWriteToFileSystemEnabled\" configuration is set to false.  The URI /instances/../export is disabled.  This is the most secure configuration.";
+        LOG(WARNING) << "REST API cannot write to the file system because the \"RestApiWriteToFileSystemEnabled\" configuration is set to false.  The URI /instances/../export is disabled.  This is the most secure configuration.";
       }
 
       if (lock.GetConfiguration().GetBooleanParameter("WebDavEnabled", true))