diff OrthancServer/Plugins/Samples/DelayedDeletion/LargeDeleteJob.cpp @ 5024:c2ebc47f4f18 delayed-deletion

wip: adding DelayedDeletion plugin
author Alain Mazy <am@osimis.io>
date Mon, 20 Jun 2022 16:53:21 +0200
parents
children 99751c5a7cfe
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Plugins/Samples/DelayedDeletion/LargeDeleteJob.cpp	Mon Jun 20 16:53:21 2022 +0200
@@ -0,0 +1,301 @@
+#include "LargeDeleteJob.h"
+
+#include "../../../../OrthancFramework/Sources/Logging.h"
+#include "../../../../OrthancFramework/Sources/OrthancException.h"
+
+#include <json/reader.h>
+
+void LargeDeleteJob::UpdateDeleteProgress()
+{
+  size_t total = 2 * resources_.size() + instances_.size() + series_.size();
+
+  float progress;
+  if (total == 0)
+  {
+    progress = 1;
+  }
+  else
+  {
+    progress = (static_cast<float>(posResources_ + posInstances_ + posSeries_ + posDelete_) /
+                static_cast<float>(total));
+  }
+
+  UpdateProgress(progress);
+}
+
+
+void LargeDeleteJob::ScheduleChildrenResources(std::vector<std::string>& target,
+                                               const std::string& uri)
+{
+  Json::Value items;
+  if (OrthancPlugins::RestApiGet(items, uri, false))
+  {
+    if (items.type() != Json::arrayValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    for (Json::Value::ArrayIndex i = 0; i < items.size(); i++)
+    {
+      if (items[i].type() != Json::objectValue ||
+          !items[i].isMember("ID") ||
+          items[i]["ID"].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      else
+      {
+        target.push_back(items[i]["ID"].asString());
+      }
+    }
+  }
+}
+  
+
+void LargeDeleteJob::ScheduleResource(Orthanc::ResourceType level,
+                                      const std::string& id)
+{
+#if 0
+  // Instance-level granularity => very slow!
+  switch (level)
+  {
+    case Orthanc::ResourceType_Patient:
+      ScheduleChildrenResources(instances_, "/patients/" + id + "/instances");
+      break;
+            
+    case Orthanc::ResourceType_Study:
+      ScheduleChildrenResources(instances_, "/studies/" + id + "/instances");
+      break;
+            
+    case Orthanc::ResourceType_Series:
+      ScheduleChildrenResources(instances_, "/series/" + id + "/instances");
+      break;
+
+    case Orthanc::ResourceType_Instance:
+      instances_.push_back(id);
+      break;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+#else
+  /**
+   * Series-level granularity => looks like a good compromise between
+   * having the Orthanc mutex locked during all the study, and very
+   * slow instance-level granularity.
+   **/
+  switch (level)
+  {
+    case Orthanc::ResourceType_Patient:
+      ScheduleChildrenResources(series_, "/patients/" + id + "/series");
+      break;
+            
+    case Orthanc::ResourceType_Study:
+      ScheduleChildrenResources(series_, "/studies/" + id + "/series");
+      break;
+            
+    case Orthanc::ResourceType_Series:
+      series_.push_back(id);
+      break;
+
+    case Orthanc::ResourceType_Instance:
+      instances_.push_back(id);
+      break;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+#endif
+}
+
+
+void LargeDeleteJob::DeleteResource(Orthanc::ResourceType level,
+                                    const std::string& id)
+{
+  std::string uri;      
+  switch (level)
+  {
+    case Orthanc::ResourceType_Patient:
+      uri = "/patients/" + id;
+      break;
+          
+    case Orthanc::ResourceType_Study:
+      uri = "/studies/" + id;
+      break;
+          
+    case Orthanc::ResourceType_Series:
+      uri = "/series/" + id;
+      break;
+          
+    case Orthanc::ResourceType_Instance:
+      uri = "/instances/" + id;
+      break;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+  OrthancPlugins::RestApiDelete(uri, false);
+}
+
+  
+LargeDeleteJob::LargeDeleteJob(const std::vector<std::string>& resources,
+                               const std::vector<Orthanc::ResourceType>& levels) :
+  OrthancJob("LargeDelete"),
+  resources_(resources),
+  levels_(levels),
+  posResources_(0),
+  posInstances_(0),
+  posDelete_(0)
+{
+  if (resources.size() != levels.size())
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+  }
+}
+
+  
+OrthancPluginJobStepStatus LargeDeleteJob::Step()
+{
+  if (posResources_ == 0)
+  {
+    if (resources_.size() == 1)
+    {
+      // LOG(WARNING) << "LargeDeleteJob has started on resource: " << resources_[0];
+    }
+    else
+    {
+      // LOG(WARNING) << "LargeDeleteJob has started";
+    }
+  }
+  
+  if (posResources_ < resources_.size())
+  {
+    // First step: Discovering all the instances of the resources
+
+    ScheduleResource(levels_[posResources_], resources_[posResources_]);
+      
+    posResources_ += 1;
+    UpdateDeleteProgress();
+    return OrthancPluginJobStepStatus_Continue;
+  }    
+  else if (posInstances_ < instances_.size())
+  {
+    // Second step: Deleting the instances one by one
+
+    DeleteResource(Orthanc::ResourceType_Instance, instances_[posInstances_]);
+
+    posInstances_ += 1;
+    UpdateDeleteProgress();
+    return OrthancPluginJobStepStatus_Continue;
+  }
+  else if (posSeries_ < series_.size())
+  {
+    // Third step: Deleting the series one by one
+
+    DeleteResource(Orthanc::ResourceType_Series, series_[posSeries_]);
+
+    posSeries_ += 1;
+    UpdateDeleteProgress();
+    return OrthancPluginJobStepStatus_Continue;
+  }
+  else if (posDelete_ < resources_.size())
+  {
+    // Fourth step: Make sure the resources where fully deleted
+    // (instances might have been received since the beginning of
+    // the job)
+
+    DeleteResource(levels_[posDelete_], resources_[posDelete_]);
+
+    posDelete_ += 1;
+    UpdateDeleteProgress();
+    return OrthancPluginJobStepStatus_Continue;
+  }
+  else
+  {
+    if (resources_.size() == 1)
+    {
+      // LOG(WARNING) << "LargeDeleteJob has completed on resource: " << resources_[0];
+    }
+    else
+    {
+      // LOG(WARNING) << "LargeDeleteJob has completed";
+    }
+
+    UpdateProgress(1);
+    return OrthancPluginJobStepStatus_Success;
+  }                   
+}
+
+
+void LargeDeleteJob::Reset()
+{
+  posResources_ = 0;
+  posInstances_ = 0;
+  posDelete_ = 0;
+  instances_.clear();
+}
+
+
+void LargeDeleteJob::RestHandler(OrthancPluginRestOutput* output,
+                                 const char* url,
+                                 const OrthancPluginHttpRequest* request)
+{
+  static const char* KEY_RESOURCES = "Resources";
+  
+  if (request->method != OrthancPluginHttpMethod_Post)
+  {
+    OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "POST");
+    return;
+  }
+
+  Json::Value body;
+  Json::Reader reader;
+  if (!reader.parse(reinterpret_cast<const char*>(request->body),
+                    reinterpret_cast<const char*>(request->body) + request->bodySize, body))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "JSON body is expected");
+  }
+
+  if (body.type() != Json::objectValue)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                    "Expected a JSON object in the body");
+  }
+
+  if (!body.isMember(KEY_RESOURCES) ||
+      body[KEY_RESOURCES].type() != Json::arrayValue)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                    "The JSON object must contain an array in \"" +
+                                    std::string(KEY_RESOURCES) + "\"");
+  }
+
+  std::vector<std::string> resources;
+  std::vector<Orthanc::ResourceType>  levels;
+
+  resources.reserve(body.size());
+  levels.reserve(body.size());
+
+  const Json::Value& arr = body[KEY_RESOURCES];
+  for (Json::Value::ArrayIndex i = 0; i < arr.size(); i++)
+  {
+    if (arr[i].type() != Json::arrayValue ||
+        arr[i].size() != 2u ||
+        arr[i][0].type() != Json::stringValue ||
+        arr[i][1].type() != Json::stringValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Each entry must be an array containing 2 strings, "
+                                      "the resource level and its ID");
+    }
+    else
+    {
+      levels.push_back(Orthanc::StringToResourceType(arr[i][0].asCString()));
+      resources.push_back(arr[i][1].asString());
+    }
+  }
+  
+  OrthancPlugins::OrthancJob::SubmitFromRestApiPost(
+    output, body, new LargeDeleteJob(resources, levels));
+}