changeset 554:3dc4b2e04615 annotations

added a cache for features
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 01 Sep 2026 14:28:04 +0200
parents 55b48abb2994
children 304d7b366580
files ViewerPlugin/Annotations/AnnotationsRestApi.cpp ViewerPlugin/ViewerConfiguration.cpp ViewerPlugin/ViewerConfiguration.h
diffstat 3 files changed, 131 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- a/ViewerPlugin/Annotations/AnnotationsRestApi.cpp	Tue Sep 01 11:42:14 2026 +0200
+++ b/ViewerPlugin/Annotations/AnnotationsRestApi.cpp	Tue Sep 01 14:28:04 2026 +0200
@@ -1137,7 +1137,7 @@
   class CachedAnnotations : public boost::noncopyable
   {
   private:
-    boost::shared_ptr<Orthanc::IDynamicObject>  content_;
+    boost::shared_ptr<Orthanc::IDynamicObject>  cached_;
 
     static Orthanc::SharedObjectCache& GetCache()
     {
@@ -1161,26 +1161,18 @@
     {
       const std::string key = id.GetKey();
 
-      content_ = GetCache().GetCachedValue(key);
+      cached_ = GetCache().GetCachedValue(key);
 
-      if (content_.get() == NULL)
+      if (cached_.get() == NULL)
       {
-        content_.reset(new Annotations(id));
-        GetCache().Store(key, content_, 1);
+        cached_.reset(new Annotations(id));
+        GetCache().Store(key, cached_, 1);
       }
     }
 
-
-    Annotations& GetContent()
+    Annotations& GetContent() const
     {
-      return dynamic_cast<Annotations&>(*content_);
-    }
-
-
-    static void Invalidate(const AnnotationsId& id)
-    {
-      const std::string key = id.GetKey();
-      GetCache().Invalidate(key);
+      return dynamic_cast<Annotations&>(*cached_);
     }
   };
 
@@ -1234,22 +1226,12 @@
       return *user_;
     }
 
-    bool IsRootUser() const
-    {
-      return GetUser().GetAnnotatingId().GetType() == UserId::Type_Root;
-    }
-
     const AnnotationsId& GetAnnotationsId() const
     {
       assert(annotationsId_.get() != NULL);
       return *annotationsId_;
     }
 
-    std::string GetFeaturesKey() const
-    {
-      return ::OrthancWSI::GetFeaturesKey(GetAnnotationsId(), GetUser().GetAnnotatingId());
-    }
-
     std::string GetBodyString(const char* field) const
     {
       return Orthanc::SerializationToolbox::ReadString(body_, field);
@@ -1395,23 +1377,19 @@
   }
 
 
-  void LoadUserFeatures(OrthancPluginRestOutput* output,
-                        const char* url,
-                        const OrthancPluginHttpRequest* request)
+  class UserFeatures : public Orthanc::IDynamicObject
   {
-    if (request->method != OrthancPluginHttpMethod_Post)
+  private:
+    Orthanc::ReaderWriterLock  mutex_;
+    std::string                key_;
+    Json::Value                content_;
+
+    void Load()
     {
-      OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "POST");
-    }
-    else
-    {
-      AnnotationsCommandContext context(request);
-
-      Json::Value answer;
-      answer[KEY_FEATURES] = Json::arrayValue;
+      content_ = Json::arrayValue;
 
       std::string compressed;
-      if (LookupKeyValueStore(compressed, context.GetFeaturesKey()))
+      if (LookupKeyValueStore(compressed, key_))
       {
         std::string uncompressed;
         Orthanc::GzipCompressor compressor;
@@ -1430,7 +1408,7 @@
 
         if (version == ORTHANC_WSI_ANNOTATIONS_VERSION)
         {
-          answer[KEY_FEATURES] = unserialized[KEY_FEATURES];
+          content_ = unserialized[KEY_FEATURES];
         }
         else
         {
@@ -1444,6 +1422,108 @@
           }
         }
       }
+    }
+
+    void Save() const
+    {
+      Json::Value unserialized;
+      unserialized[KEY_VERSION] = static_cast<unsigned int>(ORTHANC_WSI_ANNOTATIONS_VERSION);
+      unserialized[KEY_FEATURES] = content_;
+
+      std::string serialized;
+      Orthanc::Toolbox::WriteFastJson(serialized, unserialized);
+
+      std::string compressed;
+      Orthanc::GzipCompressor compressor;
+      Orthanc::IBufferCompressor::Compress(compressed, compressor, serialized);
+
+      SetKeyValueStore(key_, compressed);
+    }
+
+  public:
+    UserFeatures(const std::string& key) :
+      key_(key)
+    {
+      Load();
+    }
+
+    void GetContent(Json::Value& target)
+    {
+      Orthanc::ReaderWriterLock::ReadLock lock(mutex_);
+      target = content_;
+    }
+
+    void SetContent(const Json::Value& content)
+    {
+      Orthanc::ReaderWriterLock::WriteLock lock(mutex_);
+      content_ = content;
+      Save();
+    }
+  };
+
+
+  class CachedUserFeatures : public boost::noncopyable
+  {
+  private:
+    boost::shared_ptr<Orthanc::IDynamicObject>  cached_;
+
+    static Orthanc::SharedObjectCache& GetCache()
+    {
+      static boost::mutex  mutex;
+      static std::unique_ptr<Orthanc::SharedObjectCache>  cache;
+
+      {
+        boost::mutex::scoped_lock lock(mutex);
+
+        if (cache.get() == NULL)
+        {
+          cache.reset(new Orthanc::SharedObjectCache(ViewerConfiguration::GetInstance().GetFeaturesCacheSize()));
+        }
+
+        return *cache;
+      }
+    }
+
+  public:
+    CachedUserFeatures(const AnnotationsId& annotations,
+                       const UserId& user)
+    {
+      const std::string key = GetFeaturesKey(annotations, user);
+
+      cached_ = GetCache().GetCachedValue(key);
+
+      if (cached_.get() == NULL)
+      {
+        cached_.reset(new UserFeatures(key));
+        GetCache().Store(key, cached_, 1);
+      }
+    }
+
+    UserFeatures& GetFeatures() const
+    {
+      return dynamic_cast<UserFeatures&>(*cached_);
+    }
+  };
+
+
+  void LoadUserFeatures(OrthancPluginRestOutput* output,
+                        const char* url,
+                        const OrthancPluginHttpRequest* request)
+  {
+    if (request->method != OrthancPluginHttpMethod_Post)
+    {
+      OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "POST");
+    }
+    else
+    {
+      AnnotationsCommandContext context(request);
+
+      Json::Value answer;
+
+      {
+        CachedUserFeatures cached(context.GetAnnotationsId(), context.GetUser().GetAnnotatingId());
+        cached.GetFeatures().GetContent(answer[KEY_FEATURES]);
+      }
 
       ViewerToolbox::AnswerJson(output, answer);
     }
@@ -1462,18 +1542,10 @@
     {
       AnnotationsCommandContext context(request);
 
-      Json::Value content;
-      content[KEY_VERSION] = static_cast<unsigned int>(ORTHANC_WSI_ANNOTATIONS_VERSION);
-      content[KEY_FEATURES] = context.GetBodyField(KEY_FEATURES);
-
-      std::string serialized;
-      Orthanc::Toolbox::WriteFastJson(serialized, content);
-
-      std::string compressed;
-      Orthanc::GzipCompressor compressor;
-      Orthanc::IBufferCompressor::Compress(compressed, compressor, serialized);
-
-      SetKeyValueStore(context.GetFeaturesKey(), compressed);
+      {
+        CachedUserFeatures cached(context.GetAnnotationsId(), context.GetUser().GetAnnotatingId());
+        cached.GetFeatures().SetContent(context.GetBodyField(KEY_FEATURES));
+      }
 
       ViewerToolbox::AnswerEmpty(output);
     }
--- a/ViewerPlugin/ViewerConfiguration.cpp	Tue Sep 01 11:42:14 2026 +0200
+++ b/ViewerPlugin/ViewerConfiguration.cpp	Tue Sep 01 14:28:04 2026 +0200
@@ -202,6 +202,12 @@
   }
 
 
+  unsigned int ViewerConfiguration::GetFeaturesCacheSize() const
+  {
+    return 100;   // TODO - Configuration option?
+  }
+
+
   bool ViewerConfiguration::IsAnnotationsSharingEnabled() const
   {
     return wsiConfiguration_.GetBooleanValue("EnableAnnotationsSharing", false);
--- a/ViewerPlugin/ViewerConfiguration.h	Tue Sep 01 11:42:14 2026 +0200
+++ b/ViewerPlugin/ViewerConfiguration.h	Tue Sep 01 14:28:04 2026 +0200
@@ -73,6 +73,8 @@
 
     unsigned int GetAnnotationsCacheSize() const;
 
+    unsigned int GetFeaturesCacheSize() const;
+
     bool IsAnnotationsSharingEnabled() const;
   };
 }