Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package orthanc-dicomweb for 
openSUSE:Factory checked in at 2023-09-06 18:56:59
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/orthanc-dicomweb (Old)
 and      /work/SRC/openSUSE:Factory/.orthanc-dicomweb.new.1766 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "orthanc-dicomweb"

Wed Sep  6 18:56:59 2023 rev:13 rq:1108997 version:1.15

Changes:
--------
--- /work/SRC/openSUSE:Factory/orthanc-dicomweb/orthanc-dicomweb.changes        
2023-07-12 17:27:56.446747622 +0200
+++ 
/work/SRC/openSUSE:Factory/.orthanc-dicomweb.new.1766/orthanc-dicomweb.changes  
    2023-09-06 18:58:47.920853588 +0200
@@ -1,0 +2,16 @@
+Sun Sep  3 22:23:04 UTC 2023 - Axel Braun <axel.br...@gmx.de>
+
+- Version 1.15 
+
+* speed improvement:
+  - Now storing the output of /dicom-web/studies/../series/../metadata route 
in an attachment that can be used
+    by the "Full" mode.
+    The json file is gzipped and stored in attachment 4301 everytime a series 
is stable or the first time
+    its /dicom-web/studies/../series/../metadata route is called in "Full" 
mode if the attachment does not exist yet.
+    A new route /studies/{orthancId}/update-dicomweb-cache has also been added 
to allow e.g. the Housekeeper plugin
+    to generate these attachment for old studies.
+    This cache can be disabled by setting "EnableMetadataCache" to false.  
However, disabling the cache
+    won't delete the already cached data.  You may call DELETE 
/series/../attachments/4031 to clear the cache.
+* framework.diff added to compile against Orthanc framework
+
+-------------------------------------------------------------------

Old:
----
  OrthancDicomWeb-1.14.tar.gz

New:
----
  OrthancDicomWeb-1.15.tar.gz
  framework.diff

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ orthanc-dicomweb.spec ++++++
--- /var/tmp/diff_new_pack.WXP8PL/_old  2023-09-06 18:58:50.080930590 +0200
+++ /var/tmp/diff_new_pack.WXP8PL/_new  2023-09-06 18:58:50.084930733 +0200
@@ -21,7 +21,7 @@
 Summary:        WebViewer plugin for Orthanc
 License:        AGPL-3.0-or-later
 Group:          Productivity/Graphics/Viewers
-Version:        1.14
+Version:        1.15
 Release:        0
 URL:            https://orthanc-server.com
 Source0:        
https://www.orthanc-server.com/downloads/get.php?path=/plugin-dicom-web/OrthancDicomWeb-%{version}.tar.gz
@@ -35,6 +35,7 @@
 Source8:        babel-polyfill-6.26.0.min.js.gz
 Source9:        orthanc-dicomweb-readme.SUSE
 Source10:       dicomweb.json
+Patch0:         framework.diff
 
 BuildRequires:  cmake
 BuildRequires:  e2fsprogs-devel
@@ -64,6 +65,7 @@
 
 %prep
 %setup -q -n OrthancDicomWeb-%{version}
+%autopatch -p1
 
 #OrthanPlugins may ask for additional files to be loaded
 #Putting them into this folder prevents download of sources from the web

++++++ OrthancDicomWeb-1.14.tar.gz -> OrthancDicomWeb-1.15.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OrthancDicomWeb-1.14/.hg_archival.txt 
new/OrthancDicomWeb-1.15/.hg_archival.txt
--- old/OrthancDicomWeb-1.14/.hg_archival.txt   2023-07-05 14:13:54.000000000 
+0200
+++ new/OrthancDicomWeb-1.15/.hg_archival.txt   2023-08-24 16:29:33.000000000 
+0200
@@ -1,6 +1,6 @@
 repo: d5f45924411123cfd02d035fd50b8e37536eadef
-node: b208c07f0fcea822e44208ee4d9927ea0af803ee
-branch: OrthancDicomWeb-1.14
+node: 8ccaf9f005a783c7ccd0a98aa438c58bd07a922a
+branch: OrthancDicomWeb-1.15
 latesttag: null
-latesttagdistance: 540
-changessincelatesttag: 572
+latesttagdistance: 549
+changessincelatesttag: 581
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OrthancDicomWeb-1.14/CMakeLists.txt 
new/OrthancDicomWeb-1.15/CMakeLists.txt
--- old/OrthancDicomWeb-1.14/CMakeLists.txt     2023-07-05 14:13:54.000000000 
+0200
+++ new/OrthancDicomWeb-1.15/CMakeLists.txt     2023-08-24 16:29:33.000000000 
+0200
@@ -22,13 +22,13 @@
 
 project(OrthancDicomWeb)
 
-set(ORTHANC_DICOM_WEB_VERSION "1.14")
+set(ORTHANC_DICOM_WEB_VERSION "1.15")
 
 if (ORTHANC_DICOM_WEB_VERSION STREQUAL "mainline")
   set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
 else()
-  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.12.1")
+  set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "daf4807631c5")  # TODO: upgrade to 
1.12.2 when available
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
 endif()
 
@@ -85,7 +85,8 @@
   set(ENABLE_PUGIXML ON)
   set(ENABLE_MODULE_JOBS OFF)
   set(USE_BOOST_ICONV ON)
-  
+  set(ENABLE_ZLIB ON)
+    
   
include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
   include_directories(${ORTHANC_FRAMEWORK_ROOT})
 endif()
@@ -204,6 +205,8 @@
 
 add_dependencies(OrthancDicomWeb AutogeneratedTarget)
 
+DefineSourceBasenameForTarget(OrthancDicomWeb)
+
 message("Setting the version of the library to ${ORTHANC_DICOM_WEB_VERSION}")
 
 add_definitions(-DORTHANC_DICOM_WEB_VERSION="${ORTHANC_DICOM_WEB_VERSION}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OrthancDicomWeb-1.14/NEWS 
new/OrthancDicomWeb-1.15/NEWS
--- old/OrthancDicomWeb-1.14/NEWS       2023-07-05 14:13:54.000000000 +0200
+++ new/OrthancDicomWeb-1.15/NEWS       2023-08-24 16:29:33.000000000 +0200
@@ -1,3 +1,16 @@
+Version 1.15 (2023-08-24)
+=========================
+
+* speed improvement:
+  - Now storing the output of /dicom-web/studies/../series/../metadata route 
in an attachment that can be used
+    by the "Full" mode.
+    The json file is gzipped and stored in attachment 4301 everytime a series 
is stable or the first time
+    its /dicom-web/studies/../series/../metadata route is called in "Full" 
mode if the attachment does not exist yet.
+    A new route /studies/{orthancId}/update-dicomweb-cache has also been added 
to allow e.g. the Housekeeper plugin
+    to generate these attachment for old studies.
+    This cache can be disabled by setting "EnableMetadataCache" to false.  
However, disabling the cache
+    won't delete the already cached data.  You may call DELETE 
/series/../attachments/4031 to clear the cache.
+
 Version 1.14 (2023-07-05)
 =========================
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/Configuration.cpp 
new/OrthancDicomWeb-1.15/Plugin/Configuration.cpp
--- old/OrthancDicomWeb-1.14/Plugin/Configuration.cpp   2023-07-05 
14:13:54.000000000 +0200
+++ new/OrthancDicomWeb-1.15/Plugin/Configuration.cpp   2023-08-24 
16:29:33.000000000 +0200
@@ -676,6 +676,11 @@
       return GetUnsignedIntegerValue("MetadataWorkerThreadsCount", 4);
     }
 
+    bool IsMetadataCacheEnabled()
+    {
+      return GetBooleanValue("EnableMetadataCache", true);
+    }
+
     
     MetadataMode GetMetadataMode(Orthanc::ResourceType level)
     {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/Configuration.h 
new/OrthancDicomWeb-1.15/Plugin/Configuration.h
--- old/OrthancDicomWeb-1.14/Plugin/Configuration.h     2023-07-05 
14:13:54.000000000 +0200
+++ new/OrthancDicomWeb-1.15/Plugin/Configuration.h     2023-08-24 
16:29:33.000000000 +0200
@@ -48,7 +48,7 @@
 
   enum MetadataMode
   {
-    MetadataMode_Full,           // Read all the DICOM instances from the 
storage area
+    MetadataMode_Full,           // Read all the DICOM instances from the 
storage area and store them in an attachment on StableSeries event
     MetadataMode_MainDicomTags,  // Only use the Orthanc database (main DICOM 
tags only)
     MetadataMode_Extrapolate     // Extrapolate user-specified tags from a few 
DICOM instances
   };
@@ -139,5 +139,7 @@
     void SaveDicomWebServers();
 
     unsigned int GetMetadataWorkerThreadsCount();
+
+    bool IsMetadataCacheEnabled();
   }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/DicomWebFormatter.cpp 
new/OrthancDicomWeb-1.15/Plugin/DicomWebFormatter.cpp
--- old/OrthancDicomWeb-1.14/Plugin/DicomWebFormatter.cpp       2023-07-05 
14:13:54.000000000 +0200
+++ new/OrthancDicomWeb-1.15/Plugin/DicomWebFormatter.cpp       2023-08-24 
16:29:33.000000000 +0200
@@ -165,7 +165,7 @@
     first_(true)
   {
     if (context_ == NULL ||
-        output_ == NULL)
+        (isXml_ && output_ == NULL))  // allow no output when working with 
Json output.
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
     }
@@ -289,8 +289,8 @@
   }
 
 
-  void DicomWebFormatter::HttpWriter::AddDicomWebSerializedJson(const void* 
data,
-                                                                size_t size)
+  void DicomWebFormatter::HttpWriter::AddDicomWebInstanceSerializedJson(const 
void* data,
+                                                                        size_t 
size)
   {
     if (isXml_)
     {
@@ -318,6 +318,41 @@
     jsonBuffer_.AddChunk(data, size);
   }
 
+  void DicomWebFormatter::HttpWriter::AddDicomWebSeriesSerializedJson(const 
void* data,
+                                                                      size_t 
size)
+  {
+    if (isXml_)
+    {
+      // This function can only be used in the JSON case
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+#if !defined(NDEBUG)  // In debug mode, check that the value is actually a 
JSON string
+    Json::Value json;
+    if (!OrthancPlugins::ReadJson(json, data, size))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+#endif
+    
+    if (size <= 2 ||
+        reinterpret_cast<const char*>(data)[0] != '[' ||
+        reinterpret_cast<const char*>(data)[size-1] != ']')
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "The 
series metadata json does not contain an array.");
+    }
+
+    if (first_)
+    {
+      first_ = false;
+    }
+    else
+    {
+      jsonBuffer_.AddChunk(",");
+    }
+    
+    jsonBuffer_.AddChunk(reinterpret_cast<const char*>(data) + 1, size - 2);  
// remove leading and trailing []
+  }
 
   void DicomWebFormatter::HttpWriter::Send()
   {
@@ -331,6 +366,15 @@
     }
   }
 
+  void DicomWebFormatter::HttpWriter::CloseAndGetJsonOutput(std::string& 
target)
+  {
+    if (!isXml_)
+    {
+      jsonBuffer_.AddChunk("]");
+      
+      jsonBuffer_.Flatten(target);
+    }
+  }
 
   void DicomWebFormatter::HttpWriter::AddInstance(const DicomInstance& 
instance,
                                                   const std::string& bulkRoot)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/DicomWebFormatter.h 
new/OrthancDicomWeb-1.15/Plugin/DicomWebFormatter.h
--- old/OrthancDicomWeb-1.14/Plugin/DicomWebFormatter.h 2023-07-05 
14:13:54.000000000 +0200
+++ new/OrthancDicomWeb-1.15/Plugin/DicomWebFormatter.h 2023-08-24 
16:29:33.000000000 +0200
@@ -115,11 +115,16 @@
 
       void AddOrthancJson(const Json::Value& value);
 
-      void AddDicomWebSerializedJson(const void* data,
-                                     size_t size);
+      void AddDicomWebInstanceSerializedJson(const void* data,
+                                             size_t size);
+
+      void AddDicomWebSeriesSerializedJson(const void* data,
+                                           size_t size);
 
       void Send();
 
+      void CloseAndGetJsonOutput(std::string& target);
+
       void AddInstance(const DicomInstance& instance,
                        const std::string& bulkRoot);
     };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/Plugin.cpp 
new/OrthancDicomWeb-1.15/Plugin/Plugin.cpp
--- old/OrthancDicomWeb-1.14/Plugin/Plugin.cpp  2023-07-05 14:13:54.000000000 
+0200
+++ new/OrthancDicomWeb-1.15/Plugin/Plugin.cpp  2023-08-24 16:29:33.000000000 
+0200
@@ -471,6 +471,10 @@
         OrthancPlugins::Configuration::LoadDicomWebServers();
         break;
 
+      case OrthancPluginChangeType_StableSeries:
+        CacheSeriesMetadata(resourceId);
+        break;
+
       default:
         break;
     }
@@ -599,6 +603,8 @@
         OrthancPlugins::RegisterRestCallback<RetrieveInstanceRendered>(root + 
"studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true);
         OrthancPlugins::RegisterRestCallback<RetrieveFrameRendered>(root + 
"studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", 
true);
 
+        
OrthancPlugins::RegisterRestCallback<UpdateSeriesMetadataCache>("/studies/([^/]*)/update-dicomweb-cache",
 true);
+
         OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/WadoRs.cpp 
new/OrthancDicomWeb-1.15/Plugin/WadoRs.cpp
--- old/OrthancDicomWeb-1.14/Plugin/WadoRs.cpp  2023-07-05 14:13:54.000000000 
+0200
+++ new/OrthancDicomWeb-1.15/Plugin/WadoRs.cpp  2023-08-24 16:29:33.000000000 
+0200
@@ -29,12 +29,18 @@
 #include <HttpServer/HttpContentNegociation.h>
 #include <Logging.h>
 #include <Toolbox.h>
+#include <SerializationToolbox.h>
 #include <MultiThreading/SharedMessageQueue.h>
+#include <Compression/GzipCompressor.h>
 
 #include <memory>
 #include <boost/thread/mutex.hpp>
 #include <boost/thread.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/predicate.hpp>
 
+static const std::string SERIES_METADATA_ATTACHMENT_ID = "4301";
+static std::string WADO_BASE_PLACEHOLDER = "$WADO_BASE_PLACEHOLDER$";
 static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
 static const char* const REQUESTED_TAGS = "RequestedTags";
 static const char* const INSTANCES = "Instances";
@@ -878,7 +884,7 @@
   {
     if (buffer.RestApiGet("/instances/" + orthancId + 
"/attachments/4444/data", false))
     {
-      writer.AddDicomWebSerializedJson(buffer.GetData(), buffer.GetSize());
+      writer.AddDicomWebInstanceSerializedJson(buffer.GetData(), 
buffer.GetSize());
     }
     else if (buffer.RestApiGet("/instances/" + orthancId + "/file", false))
     {
@@ -894,7 +900,7 @@
       }
 
       buffer.RestApiPut("/instances/" + orthancId + "/attachments/4444", 
dicomweb, false);
-      writer.AddDicomWebSerializedJson(dicomweb.c_str(), dicomweb.size());
+      writer.AddDicomWebInstanceSerializedJson(dicomweb.c_str(), 
dicomweb.size());
     }
   }
 #endif
@@ -1074,7 +1080,7 @@
 }
 
 
-static void GetChildrenIdentifiers(std::list<std::string>& target,
+static void GetChildrenIdentifiers(std::set<std::string>& target,
                                    std::string& resourceDicomUid,
                                    Orthanc::ResourceType level,
                                    const std::string& orthancId)
@@ -1119,7 +1125,7 @@
 
     for (Json::Value::ArrayIndex i = 0; i < children.size(); i++)
     {
-      target.push_back(children[i].asString());
+      target.insert(children[i].asString());
     }
   }  
 }
@@ -1250,7 +1256,7 @@
           instanceToLoad->bulkRoot = (data->wadoBase +
                               "studies/" + instanceToLoad->studyInstanceUid +
                               "/series/" + instanceToLoad->seriesInstanceUid + 
-                              "/instances/" + 
instanceResource["MainDicomTags"]["SOPInstanceUID"].asString() + "/bulk");
+                              "/instances/" + 
instanceResource[MAIN_DICOM_TAGS]["SOPInstanceUID"].asString() + "/bulk");
         }
       }
 
@@ -1278,7 +1284,7 @@
   }
 }
 
-void RetrieveSeriesMetadataInternal(OrthancPluginRestOutput* output,
+void RetrieveSeriesMetadataInternal(std::set<std::string>& instancesIds,
                                     
OrthancPlugins::DicomWebFormatter::HttpWriter& writer,
                                     MainDicomTagsCache& cache,
                                     const OrthancPlugins::MetadataMode& mode,
@@ -1289,7 +1295,6 @@
                                     const std::string& wadoBase)
 {
   ChildrenMainDicomMaps instancesDicomMaps;
-  std::list<std::string> instancesIds;
   std::string seriesDicomUid;
 
   unsigned int workersCount =  
OrthancPlugins::Configuration::GetMetadataWorkerThreadsCount();
@@ -1299,6 +1304,10 @@
   if (oneLargeQuery)
   {
     GetChildrenMainDicomTags(instancesDicomMaps, seriesDicomUid, 
Orthanc::ResourceType_Series, seriesOrthancId);
+    for (ChildrenMainDicomMaps::const_iterator it = 
instancesDicomMaps.begin(); it != instancesDicomMaps.end(); ++it)
+    {
+      instancesIds.insert(it->first);
+    }
   }
   else
   {
@@ -1334,7 +1343,7 @@
     }
     else
     {
-      for (std::list<std::string>::const_iterator i = instancesIds.begin(); i 
!= instancesIds.end(); ++i)
+      for (std::set<std::string>::const_iterator i = instancesIds.begin(); i 
!= instancesIds.end(); ++i)
       {
         instancesQueue.Enqueue(new InstanceToLoad(*i, "", writerMutex, writer, 
isXml, OrthancPluginDicomWebBinaryMode_BulkDataUri, studyInstanceUid, 
seriesInstanceUid));
       }
@@ -1358,18 +1367,190 @@
   }
   else
   { // old single threaded code
-    std::list<std::string> instances;
+    std::set<std::string> instances;
     std::string seriesDicomUid;  // not used
 
     GetChildrenIdentifiers(instances, seriesDicomUid, 
Orthanc::ResourceType_Series, seriesOrthancId);
 
-    for (std::list<std::string>::const_iterator i = instances.begin(); i != 
instances.end(); ++i)
+    for (std::set<std::string>::const_iterator i = instances.begin(); i != 
instances.end(); ++i)
     {
       WriteInstanceMetadata(writer, mode, cache, *i, studyInstanceUid, 
seriesInstanceUid, wadoBase);
     }
   }
 }
 
+void CacheSeriesMetadataInternal(std::string& serializedSeriesMetadata,
+                                 
OrthancPlugins::DicomWebFormatter::HttpWriter& writer,
+                                 MainDicomTagsCache& cache,
+                                 const std::string& studyInstanceUid, 
+                                 const std::string& seriesInstanceUid, 
+                                 const std::string& seriesOrthancId)
+{
+  Orthanc::GzipCompressor compressor;
+  std::string compressedSeriesMetadata;
+  std::set<std::string> instancesIds;
+
+  // compute the series metadata with a placeholder WADO base url because, the 
base url might change (e.g if there are 2 Orthanc connected to the same DB)
+  RetrieveSeriesMetadataInternal(instancesIds, writer, cache, 
OrthancPlugins::MetadataMode_Full, false /* isXml */, seriesOrthancId, 
studyInstanceUid, seriesInstanceUid, WADO_BASE_PLACEHOLDER);
+  writer.CloseAndGetJsonOutput(serializedSeriesMetadata);
+
+  // save in attachments for future use
+  Orthanc::IBufferCompressor::Compress(compressedSeriesMetadata, compressor, 
serializedSeriesMetadata);
+  std::string instancesMd5;
+  Orthanc::Toolbox::ComputeMD5(instancesMd5, instancesIds);
+
+  std::string cacheContent = "2;" + instancesMd5 + ";" + 
compressedSeriesMetadata; 
+
+  Json::Value putResult;
+  std::string attachmentUrl = "/series/" + seriesOrthancId + "/attachments/" + 
SERIES_METADATA_ATTACHMENT_ID;
+  if (!OrthancPlugins::RestApiPut(putResult, attachmentUrl, cacheContent, 
false))
+  {
+    LOG(WARNING) << "DicomWEB: failed to write series metadata attachment";
+  }
+
+}
+
+void CacheSeriesMetadata(const std::string& seriesOrthancId)
+{
+  if (!OrthancPlugins::Configuration::IsMetadataCacheEnabled())
+  {
+    return;
+  }
+
+  LOG(INFO) << "DicomWEB: pre-computing the WADO-RS series metadata for series 
" << seriesOrthancId;
+
+  std::string studyInstanceUid, seriesInstanceUid;
+  
+  Json::Value result;
+  if (OrthancPlugins::RestApiGet(result, "/series/" + seriesOrthancId, false))
+  {
+    seriesInstanceUid = 
result[MAIN_DICOM_TAGS]["SeriesInstanceUID"].asString();
+    if (OrthancPlugins::RestApiGet(result, "/studies/" + 
result["ParentStudy"].asString(), false))
+    {
+      studyInstanceUid = 
result[MAIN_DICOM_TAGS]["StudyInstanceUID"].asString();
+
+      MainDicomTagsCache cache;
+      OrthancPlugins::DicomWebFormatter::HttpWriter writer(NULL /* output */, 
false /* isXml */);  // we cache only the JSON format -> no need for an 
HttpOutput
+
+      std::string serializedSeriesMetadataNotUsed;
+      CacheSeriesMetadataInternal(serializedSeriesMetadataNotUsed, writer, 
cache, studyInstanceUid, seriesInstanceUid, seriesOrthancId);
+    }
+  }
+}
+
+void UpdateSeriesMetadataCache(OrthancPluginRestOutput* output,
+                               const char* /*url*/,
+                               const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Post)
+  {
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), 
output, "POST");
+    return;
+  }
+
+  if (request->groupsCount != 1)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
+  }
+
+  if (!OrthancPlugins::Configuration::IsMetadataCacheEnabled())
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "The 
metadata cache is disabled in the Orthanc configuration.");
+  }
+
+  std::string studyId(request->groups[0]);
+
+  LOG(INFO) << "DicomWEB: updating the series metadata cache for study " << 
studyId;
+
+  Json::Value study;
+
+  if (OrthancPlugins::RestApiGet(study, "/studies/" + studyId, false) && 
study.type() == Json::objectValue)
+  {
+    for (Json::ArrayIndex i = 0; i < study["Series"].size(); ++i)
+    {
+      CacheSeriesMetadata(study["Series"][i].asString());
+    }
+  }
+
+  std::string answer = "{}"; 
+  OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, 
answer.c_str(), answer.size(), "application/json");
+}
+
+
+
+void 
RetrieveSeriesMetadataInternalWithCache(OrthancPlugins::DicomWebFormatter::HttpWriter&
 writer,
+                                             MainDicomTagsCache& cache,
+                                             const 
OrthancPlugins::MetadataMode& mode,
+                                             bool isXml,
+                                             const std::string& 
seriesOrthancId,
+                                             const std::string& 
studyInstanceUid,
+                                             const std::string& 
seriesInstanceUid,
+                                             const std::string& wadoBase)
+{
+  if (OrthancPlugins::Configuration::IsMetadataCacheEnabled() &&
+      mode == OrthancPlugins::MetadataMode_Full && 
+      !isXml)
+  {
+    // check if we already have computed the series metadata and saved them in 
an attachment
+    std::string serializedSeriesMetadata;
+    std::string cacheContent;
+    bool hasBeenReadFromCache = false;
+    Orthanc::GzipCompressor compressor;
+
+    std::string attachmentUrl = "/series/" + seriesOrthancId + "/attachments/" 
+ SERIES_METADATA_ATTACHMENT_ID;
+
+    if (OrthancPlugins::RestApiGetString(cacheContent, attachmentUrl + 
"/data", false))
+    {
+      if (boost::starts_with(cacheContent, "2;"))  // version 2, cacheContent 
is "2;sorted-instances-list-md5;compressedSeriesMetadata"
+      {
+        // check that the instances count have not changed since we have saved 
the data in cache 
+        // StableSeries event will always overwrite it but this is usefull if 
retrieving the metadata while
+        // the instances are being received
+        // Note: we can not use Toolbox::SplitString because the compressed 
metadata contain a lot of ";"
+        const char* secondSemiColon = strchr(&cacheContent[2], ';');
+        std::string instancesMd5InCache(&cacheContent[2], secondSemiColon - 
&cacheContent[2]);
+        std::string compressedSeriesMetadata(secondSemiColon + 1, 
cacheContent.size() - (secondSemiColon+1 - cacheContent.c_str()));
+        
+        Json::Value seriesInfo;
+
+        if (!OrthancPlugins::RestApiGet(seriesInfo, "/series/" + 
seriesOrthancId, false))
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+        }
+        std::set<std::string> currentInstancesIds;
+        Orthanc::SerializationToolbox::ReadSetOfStrings(currentInstancesIds, 
seriesInfo, "Instances");
+        std::string currentInstancesMd5;
+        Orthanc::Toolbox::ComputeMD5(currentInstancesMd5, currentInstancesIds);
+
+        if (currentInstancesMd5 == instancesMd5InCache)
+        {
+          Orthanc::IBufferCompressor::Uncompress(serializedSeriesMetadata, 
compressor, compressedSeriesMetadata);
+
+          hasBeenReadFromCache = true;
+        }
+      }
+    }
+
+    if (!hasBeenReadFromCache)  // regenerate and overwrite current cache
+    {
+      MainDicomTagsCache tmpCache;
+      OrthancPlugins::DicomWebFormatter::HttpWriter tmpWriter(NULL /* output 
*/, false /* isXml */);  // we cache only the JSON format -> no need for an 
HttpOutput
+
+      CacheSeriesMetadataInternal(serializedSeriesMetadata, tmpWriter, 
tmpCache, studyInstanceUid, seriesInstanceUid, seriesOrthancId);
+    }
+
+    boost::replace_all(serializedSeriesMetadata, WADO_BASE_PLACEHOLDER, 
wadoBase);
+
+    writer.AddDicomWebSeriesSerializedJson(serializedSeriesMetadata.c_str(), 
serializedSeriesMetadata.size());
+  }
+  else
+  {
+    std::set<std::string> instancesIdsNotUsed;
+    RetrieveSeriesMetadataInternal(instancesIdsNotUsed, writer, cache, mode, 
isXml, seriesOrthancId, studyInstanceUid, seriesInstanceUid, wadoBase);
+  }
+
+}
+
 
 void RetrieveSeriesMetadata(OrthancPluginRestOutput* output,
                             const char* /*url*/,
@@ -1389,7 +1570,7 @@
   if (LocateSeries(output, seriesOrthancId, studyInstanceUid, 
seriesInstanceUid, request))
   {
     std::string wadoBase = 
OrthancPlugins::Configuration::GetBasePublicUrl(request);
-    RetrieveSeriesMetadataInternal(output, writer, cache, mode, isXml, 
seriesOrthancId, studyInstanceUid, seriesInstanceUid, wadoBase);
+    RetrieveSeriesMetadataInternalWithCache(writer, cache, mode, isXml, 
seriesOrthancId, studyInstanceUid, seriesInstanceUid, wadoBase);
   }
 
   writer.Send();
@@ -1415,17 +1596,17 @@
 
     std::string wadoBase = 
OrthancPlugins::Configuration::GetBasePublicUrl(request);
     
-    std::list<std::string> series;
+    std::set<std::string> series;
     std::string studyDicomUid;
     GetChildrenIdentifiers(series, studyDicomUid, Orthanc::ResourceType_Study, 
studyOrthancId);
 
-    for (std::list<std::string>::const_iterator s = series.begin(); s != 
series.end(); ++s)
+    for (std::set<std::string>::const_iterator s = series.begin(); s != 
series.end(); ++s)
     {
-      std::list<std::string> instances;
+      std::set<std::string> instances;
       std::string seriesDicomUid;
       GetChildrenIdentifiers(instances, seriesDicomUid, 
Orthanc::ResourceType_Series, *s);
 
-      RetrieveSeriesMetadataInternal(output, writer, cache, mode, isXml, *s, 
studyDicomUid, seriesDicomUid, wadoBase);
+      RetrieveSeriesMetadataInternalWithCache(writer, cache, mode, isXml, *s, 
studyDicomUid, seriesDicomUid, wadoBase);
     }
 
     writer.Send();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/WadoRs.h 
new/OrthancDicomWeb-1.15/Plugin/WadoRs.h
--- old/OrthancDicomWeb-1.14/Plugin/WadoRs.h    2023-07-05 14:13:54.000000000 
+0200
+++ new/OrthancDicomWeb-1.15/Plugin/WadoRs.h    2023-08-24 16:29:33.000000000 
+0200
@@ -63,6 +63,12 @@
                             const char* url,
                             const OrthancPluginHttpRequest* request);
 
+void UpdateSeriesMetadataCache(OrthancPluginRestOutput* output,
+                               const char* url,
+                               const OrthancPluginHttpRequest* request);
+
+void CacheSeriesMetadata(const std::string& seriesOrthancId);
+
 void RetrieveInstanceMetadata(OrthancPluginRestOutput* output,
                               const char* url,
                               const OrthancPluginHttpRequest* request);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OrthancDicomWeb-1.14/README 
new/OrthancDicomWeb-1.15/README
--- old/OrthancDicomWeb-1.14/README     2023-07-05 14:13:54.000000000 +0200
+++ new/OrthancDicomWeb-1.15/README     2023-08-24 16:29:33.000000000 +0200
@@ -39,6 +39,14 @@
 http://book.orthanc-server.com/plugins/dicomweb.html
 
 
+Contributing
+------------
+
+Instructions for contributing to the Orthanc project are included in
+the Orthanc Book:
+https://book.orthanc-server.com/developers/repositories.html
+
+
 Licensing: AGPL
 ---------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/OrthancDicomWeb-1.14/Resources/CMake/JavaScriptLibraries.cmake 
new/OrthancDicomWeb-1.15/Resources/CMake/JavaScriptLibraries.cmake
--- old/OrthancDicomWeb-1.14/Resources/CMake/JavaScriptLibraries.cmake  
2023-07-05 14:13:54.000000000 +0200
+++ new/OrthancDicomWeb-1.15/Resources/CMake/JavaScriptLibraries.cmake  
2023-08-24 16:29:33.000000000 +0200
@@ -18,7 +18,7 @@
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
 
-set(BASE_URL "https://orthanc.uclouvain.be/third-party-downloads/dicom-web";)
+set(BASE_URL "http://orthanc.osimis.io/ThirdPartyDownloads/dicom-web";)
 
 DownloadPackage(
   "da0189f7c33bf9f652ea65401e0a3dc9"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/OrthancDicomWeb-1.14/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake 
new/OrthancDicomWeb-1.15/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake
--- 
old/OrthancDicomWeb-1.14/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake 
    2023-07-05 14:13:54.000000000 +0200
+++ 
new/OrthancDicomWeb-1.15/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake 
    2023-08-24 16:29:33.000000000 +0200
@@ -153,11 +153,9 @@
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.2")
         set(ORTHANC_FRAMEWORK_MD5 "ede3de356493a8868545f8cb4b8bc8b5")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.3")
-        set(ORTHANC_FRAMEWORK_MD5 "f941c0f5771db7616e7b7961026a60e2")
+        set(ORTHANC_FRAMEWORK_MD5 "5c1b11009d782f248739919db6bf7f7a")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.0")
         set(ORTHANC_FRAMEWORK_MD5 "d32a0cde03b6eb603d8dd2b33d38bf1b")
-      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.1")
-        set(ORTHANC_FRAMEWORK_MD5 "8a435140efc8ff4a01d8242f092f21de")
 
       # Below this point are development snapshots that were used to
       # release some plugin, before an official release of the Orthanc
@@ -181,6 +179,9 @@
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "b2e08d83e21d")
         # WSI 1.1 (framework pre-1.10.0), to remove "-std=c++11"
         set(ORTHANC_FRAMEWORK_MD5 "2eaa073cbb4b44ffba199ad93393b2b1")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "daf4807631c5")
+        # DICOMweb 1.15 (framework pre-1.12.2)
+        set(ORTHANC_FRAMEWORK_MD5 "c644aff2817306b3207c98c92e43f35f")
       endif()
     endif()
   endif()



++++++ framework.diff ++++++
--- a/CMakeLists.txt    Thu Aug 24 16:29:33 2023 +0200
+++ b/CMakeLists.txt    Mon Sep 04 07:49:29 2023 +0200
@@ -54,6 +54,10 @@
 set(BUILD_BABEL_POLYFILL OFF CACHE BOOL "Retrieve babel-polyfill from npm")
 
 
+# Hotfix to compile against system-wide Orthanc framework
+function(DefineSourceBasenameForTarget targetname)
+endfunction()
+
 
 # Download and setup the Orthanc framework
 
include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake)
--- a/Plugin/WadoRs.cpp Thu Aug 24 16:29:33 2023 +0200
+++ b/Plugin/WadoRs.cpp Mon Sep 04 07:49:29 2023 +0200
@@ -70,6 +70,20 @@
 }
 
 
+// Hotfix: This function corresponds to a new signature introduced in Orthanc 
> 1.12.1
+static void ComputeMD5OfSet(std::string& result,
+                            const std::set<std::string>& data)
+{
+  std::string s;
+
+  for (std::set<std::string>::const_iterator it = data.begin(); it != 
data.end(); ++it)
+  {
+    s += *it;
+  }
+
+  Orthanc::Toolbox::ComputeMD5(result, s);
+}
+
 
 namespace
 {
@@ -1397,7 +1411,7 @@
   // save in attachments for future use
   Orthanc::IBufferCompressor::Compress(compressedSeriesMetadata, compressor, 
serializedSeriesMetadata);
   std::string instancesMd5;
-  Orthanc::Toolbox::ComputeMD5(instancesMd5, instancesIds);
+  ComputeMD5OfSet(instancesMd5, instancesIds);
 
   std::string cacheContent = "2;" + instancesMd5 + ";" + 
compressedSeriesMetadata; 
 
@@ -1520,7 +1534,7 @@
         std::set<std::string> currentInstancesIds;
         Orthanc::SerializationToolbox::ReadSetOfStrings(currentInstancesIds, 
seriesInfo, "Instances");
         std::string currentInstancesMd5;
-        Orthanc::Toolbox::ComputeMD5(currentInstancesMd5, currentInstancesIds);
+        ComputeMD5OfSet(currentInstancesMd5, currentInstancesIds);
 
         if (currentInstancesMd5 == instancesMd5InCache)
         {

Reply via email to