changeset 79:068551520123

New Python function: "orthanc.RegisterStorageArea()"
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 12 Aug 2021 17:52:17 +0200
parents 2b9d82366e3a
children ad31ac9a6fef
files CMakeLists.txt NEWS Sources/Plugin.cpp Sources/StorageArea.cpp Sources/StorageArea.h
diffstat 5 files changed, 283 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Aug 12 16:05:49 2021 +0200
+++ b/CMakeLists.txt	Thu Aug 12 17:52:17 2021 +0200
@@ -166,6 +166,7 @@
 add_library(OrthancPython SHARED
   Sources/Autogenerated/sdk.cpp
   Sources/DicomScpCallbacks.cpp
+  Sources/ICallbackRegistration.cpp
   Sources/IncomingHttpRequestFilter.cpp
   Sources/OnChangeCallback.cpp
   Sources/OnStoredInstanceCallback.cpp
@@ -176,7 +177,7 @@
   Sources/PythonObject.cpp
   Sources/PythonString.cpp
   Sources/RestCallbacks.cpp
-  Sources/ICallbackRegistration.cpp
+  Sources/StorageArea.cpp
 
   # Third-party sources
   ${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp
--- a/NEWS	Thu Aug 12 16:05:49 2021 +0200
+++ b/NEWS	Thu Aug 12 17:52:17 2021 +0200
@@ -1,6 +1,7 @@
 Pending changes in the mainline
 ===============================
 
+* New Python function: "orthanc.RegisterStorageArea()"
 * Custom exception "orthanc.OrthancException" is raised instead of "ValueError"
 
 
--- a/Sources/Plugin.cpp	Thu Aug 12 16:05:49 2021 +0200
+++ b/Sources/Plugin.cpp	Thu Aug 12 17:52:17 2021 +0200
@@ -28,6 +28,7 @@
 #include "IncomingHttpRequestFilter.h"
 #include "OnChangeCallback.h"
 #include "OnStoredInstanceCallback.h"
+#include "StorageArea.h"
 
 #include "RestCallbacks.h"
 #include "PythonModule.h"
@@ -363,6 +364,16 @@
   
   
   /**
+   * New in release 3.3
+   **/
+  
+  {
+    PyMethodDef f = { "RegisterStorageArea", RegisterStorageArea, METH_VARARGS, "" };
+    functions.push_back(f);
+  }
+
+
+  /**
    * Append all the global functions that were automatically generated
    **/
   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/StorageArea.cpp	Thu Aug 12 17:52:17 2021 +0200
@@ -0,0 +1,243 @@
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "StorageArea.h"
+
+#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
+#include "PythonString.h"
+
+
+static PyObject*  createCallback_ = NULL;
+static PyObject*  readCallback_ = NULL;
+static PyObject*  removeCallback_ = NULL;
+
+
+static OrthancPluginErrorCode RunCallback(PythonLock& lock,
+                                          PyObject* callback,
+                                          PythonObject& args,
+                                          const std::string& name)
+{
+  PythonObject result(lock, PyObject_CallObject(callback, args.GetPyObject()));
+
+  std::string traceback;
+  if (lock.HasErrorOccurred(traceback))
+  {
+    OrthancPlugins::LogError("Error in the Python " + name + " callback, traceback:\n" + traceback);
+    return OrthancPluginErrorCode_Plugin;
+  }
+  else
+  {
+    return OrthancPluginErrorCode_Success;
+  }
+}
+
+
+static OrthancPluginErrorCode StorageCreate(const char *uuid,
+                                            const void *content,
+                                            int64_t size,
+                                            OrthancPluginContentType type)
+{
+  try
+  {
+    if (createCallback_ == NULL)
+    {
+      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+    }
+    
+    PythonLock lock;
+
+    PythonObject args(lock, PyTuple_New(3));
+
+    PythonString str(lock, uuid);
+    PyTuple_SetItem(args.GetPyObject(), 0, str.Release());
+    PyTuple_SetItem(args.GetPyObject(), 1, PyLong_FromLong(type));
+    PyTuple_SetItem(args.GetPyObject(), 2, PyBytes_FromStringAndSize(reinterpret_cast<const char*>(content), size));
+
+    return RunCallback(lock, createCallback_, args, "StorageCreate");
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    return e.GetErrorCode();
+  }
+}
+
+
+static OrthancPluginErrorCode StorageRead(void **content,
+                                          int64_t *size,
+                                          const char *uuid,
+                                          OrthancPluginContentType type)
+{
+  try
+  {
+    if (readCallback_ == NULL)
+    {
+      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+    }
+    
+    PythonLock lock;
+
+    PythonObject args(lock, PyTuple_New(2));
+
+    PythonString str(lock, uuid);
+    PyTuple_SetItem(args.GetPyObject(), 0, str.Release());
+    PyTuple_SetItem(args.GetPyObject(), 1, PyLong_FromLong(type));
+    
+    PythonObject result(lock, PyObject_CallObject(readCallback_, args.GetPyObject()));
+    
+    std::string traceback;
+    if (lock.HasErrorOccurred(traceback))
+    {
+      OrthancPlugins::LogError("Error in the Python StorageRead callback, traceback:\n" + traceback);
+      return OrthancPluginErrorCode_Plugin;
+    }
+    else if (!PyBytes_Check(result.GetPyObject()))
+    {
+      OrthancPlugins::LogError("The Python StorageRead callback has not returned a byte array as expected");
+      return OrthancPluginErrorCode_Plugin;
+    }
+    else
+    {
+      char* pythonBuffer = NULL;
+      Py_ssize_t pythonSize = 0;
+      if (PyBytes_AsStringAndSize(result.GetPyObject(), &pythonBuffer, &pythonSize) == 1)
+      {
+        OrthancPlugins::LogError("Cannot access the byte buffer returned by the Python StorageRead callback");
+        return OrthancPluginErrorCode_Plugin;
+      }
+      else
+      {
+        if (pythonSize == 0)
+        {
+          *content = NULL;
+          *size = 0;
+        }
+        else
+        {
+          *content = malloc(pythonSize);
+          *size = pythonSize;
+          if (*content == NULL)
+          {
+            return OrthancPluginErrorCode_NotEnoughMemory;
+          }
+
+          memcpy(*content, pythonBuffer, pythonSize);
+        }
+        
+        return OrthancPluginErrorCode_Success;
+      }
+    }
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    return e.GetErrorCode();
+  }
+}
+
+
+static OrthancPluginErrorCode StorageRemove(const char *uuid,
+                                            OrthancPluginContentType type)
+{
+  try
+  {
+    if (removeCallback_ == NULL)
+    {
+      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+    }
+    
+    PythonLock lock;
+
+    PythonObject args(lock, PyTuple_New(2));
+
+    PythonString str(lock, uuid);
+    PyTuple_SetItem(args.GetPyObject(), 0, str.Release());
+    PyTuple_SetItem(args.GetPyObject(), 1, PyLong_FromLong(type));
+
+    return RunCallback(lock, removeCallback_, args, "StorageRemove");
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    return e.GetErrorCode();
+  }
+}
+
+
+PyObject* RegisterStorageArea(PyObject* module, PyObject* args)
+{
+  // The GIL is locked at this point (no need to create "PythonLock")
+
+  PyObject* a = NULL;
+  PyObject* b = NULL;
+  PyObject* c = NULL;
+
+  if (!PyArg_ParseTuple(args, "OOO", &a, &b, &c) ||
+      a == NULL ||
+      b == NULL ||
+      c == NULL)
+  {
+    PyErr_SetString(PyExc_ValueError, "Expected three callback functions to register a custom storage area");
+    return NULL;
+  }
+  else if (createCallback_ != NULL ||
+           readCallback_ != NULL ||
+           removeCallback_ != NULL)
+  {
+    PyErr_SetString(PyExc_RuntimeError, "Cannot register twice a custom storage area");
+    return NULL;
+  }
+  else
+  {
+    OrthancPlugins::LogInfo("Registering a custom storage area in Python");
+
+    OrthancPluginRegisterStorageArea(OrthancPlugins::GetGlobalContext(),
+                                     StorageCreate, StorageRead, StorageRemove);
+    
+    createCallback_ = a;
+    Py_XINCREF(createCallback_);
+    
+    readCallback_ = b;
+    Py_XINCREF(readCallback_);
+    
+    removeCallback_ = c;
+    Py_XINCREF(removeCallback_);
+    
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+}
+
+
+void FinalizeStorageArea()
+{
+  PythonLock lock;
+  
+  if (createCallback_ != NULL)
+  {
+    Py_XDECREF(createCallback_);
+  }
+  
+  if (readCallback_ != NULL)
+  {
+    Py_XDECREF(readCallback_);
+  }
+  
+  if (removeCallback_ != NULL)
+  {
+    Py_XDECREF(removeCallback_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/StorageArea.h	Thu Aug 12 17:52:17 2021 +0200
@@ -0,0 +1,26 @@
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "PythonHeaderWrapper.h"
+
+PyObject* RegisterStorageArea(PyObject* module, PyObject* args);
+
+void FinalizeStorageArea();