changeset 119:cf6decdf9e15

wrapped new SDK callback: orthanc.RegisterStorageCommitmentScpCallback()
author Alain Mazy <am@osimis.io>
date Mon, 28 Aug 2023 18:30:42 +0200
parents 7f8f26ef5006
children a3f77cf16396
files CMakeLists.txt NEWS Sources/ICallbackRegistration.cpp Sources/ICallbackRegistration.h Sources/Plugin.cpp Sources/StorageCommitmentScpCallback.cpp Sources/StorageCommitmentScpCallback.h
diffstat 7 files changed, 274 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Mon Aug 28 16:44:41 2023 +0200
+++ b/CMakeLists.txt	Mon Aug 28 18:30:42 2023 +0200
@@ -186,6 +186,7 @@
   Sources/ReceivedInstanceCallback.cpp
   Sources/RestCallbacks.cpp
   Sources/StorageArea.cpp
+  Sources/StorageCommitmentScpCallback.cpp
 
   # Third-party sources
   ${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp
--- a/NEWS	Mon Aug 28 16:44:41 2023 +0200
+++ b/NEWS	Mon Aug 28 18:30:42 2023 +0200
@@ -1,6 +1,9 @@
 Pending changes in the mainline
 ===============================
 
+* New functions from the SDK wrapped in Python:
+  - orthanc.RegisterStorageCommitmentScpCallback()
+
 Maintenance:
 * Fix memory leaks when a python script calls orthanc.RestApiPost() and sibling methods,
   in IncomingHttpRequestFilter and in the CMove callback.
--- a/Sources/ICallbackRegistration.cpp	Mon Aug 28 16:44:41 2023 +0200
+++ b/Sources/ICallbackRegistration.cpp	Mon Aug 28 18:30:42 2023 +0200
@@ -58,6 +58,46 @@
 }
 
 
+PyObject *ICallbackRegistration::Apply2(ICallbackRegistration& registration,
+                                        PyObject* args,
+                                        PyObject*& singletonCallback1,
+                                        PyObject*& singletonCallback2,
+                                        const std::string& details)
+{
+  // https://docs.python.org/3/extending/extending.html#calling-python-functions-from-c
+  PyObject* callback1 = NULL;
+  PyObject* callback2 = NULL;
+
+  if (!PyArg_ParseTuple(args, "OO", &callback1, &callback2) ||
+      callback1 == NULL || callback2 == NULL)
+  {
+    const std::string message = "Expected two callback functions to register " + details;
+    PyErr_SetString(PyExc_ValueError, message.c_str());
+    return NULL;
+  }
+  else if (singletonCallback1 != NULL || singletonCallback2 != NULL)
+  {
+    const std::string message = "Can only register once for " + details;
+    PyErr_SetString(PyExc_RuntimeError, message.c_str());
+    return NULL;
+  }
+  else
+  {
+    OrthancPlugins::LogInfo("Registering callbacks " + details);
+    registration.Register();
+
+    singletonCallback1 = callback1;
+    Py_XINCREF(singletonCallback1);
+
+    singletonCallback2 = callback2;
+    Py_XINCREF(singletonCallback2);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+}
+
+
 void ICallbackRegistration::Unregister(PyObject*& singletonCallback)
 {
   PythonLock lock;
--- a/Sources/ICallbackRegistration.h	Mon Aug 28 16:44:41 2023 +0200
+++ b/Sources/ICallbackRegistration.h	Mon Aug 28 18:30:42 2023 +0200
@@ -40,5 +40,12 @@
                          PyObject*& singletonCallback,
                          const std::string& details);
 
+  // The GIL must be locked
+  static PyObject *Apply2(ICallbackRegistration& registration,
+                          PyObject* args,
+                          PyObject*& singletonCallback1,
+                          PyObject*& singletonCallback2,
+                          const std::string& details);
+
   static void Unregister(PyObject*& singletonCallback);
 };
--- a/Sources/Plugin.cpp	Mon Aug 28 16:44:41 2023 +0200
+++ b/Sources/Plugin.cpp	Mon Aug 28 18:30:42 2023 +0200
@@ -32,6 +32,7 @@
 #include "IncomingInstanceFilter.h"
 #include "ReceivedInstanceCallback.h"
 #include "StorageArea.h"
+#include "StorageCommitmentScpCallback.h"
 
 #include "RestCallbacks.h"
 #include "PythonModule.h"
@@ -391,6 +392,15 @@
   }
 
   /**
+   * New in release 4.1
+   **/
+
+  {
+    PyMethodDef f = { "RegisterStorageCommitmentScpCallback", RegisterStorageCommitmentScpCallback, 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/StorageCommitmentScpCallback.cpp	Mon Aug 28 18:30:42 2023 +0200
@@ -0,0 +1,186 @@
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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 "StorageCommitmentScpCallback.h"
+
+#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
+#include "ICallbackRegistration.h"
+#include "PythonString.h"
+
+
+static PyObject*   storageCommitmentScpCallback_ = NULL;
+static PyObject*   storageCommitmentLookupCallback_ = NULL;
+
+
+static OrthancPluginErrorCode StorageCommitmentSCPCallback(
+  void**              handler /* out */,
+  const char*         jobId,
+  const char*         transactionUid,
+  const char* const*  sopClassUids,
+  const char* const*  sopInstanceUids,
+  uint32_t            countInstances,
+  const char*         remoteAet,
+  const char*         calledAet)
+{
+  try
+  {
+    PythonLock lock;
+
+    PythonObject args(lock, PyTuple_New(6));
+    {
+      PythonString str(lock, jobId);
+      PyTuple_SetItem(args.GetPyObject(), 0, str.Release());
+    }
+    {
+      PythonString str(lock, transactionUid);
+      PyTuple_SetItem(args.GetPyObject(), 1, str.Release());
+    }
+    {
+      PythonObject sopClassUidList(lock, PyList_New(countInstances));
+      for (uint32_t i = 0; i < countInstances; i++)
+      {
+        PythonString str(lock, sopClassUids[i]);
+        PyList_SetItem(sopClassUidList.GetPyObject(), i, str.Release());
+      }
+      PyTuple_SetItem(args.GetPyObject(), 2, sopClassUidList.Release());
+      PythonObject sopInstanceUidList(lock, PyList_New(countInstances));
+      for (uint32_t i = 0; i < countInstances; i++)
+      {
+        PythonString str(lock, sopInstanceUids[i]);
+        PyList_SetItem(sopInstanceUidList.GetPyObject(), i, str.Release());
+      }
+      PyTuple_SetItem(args.GetPyObject(), 3, sopInstanceUidList.Release());
+    }
+    {
+      PythonString str(lock, remoteAet);
+      PyTuple_SetItem(args.GetPyObject(), 4, str.Release());
+    }
+    {
+      PythonString str(lock, calledAet);
+      PyTuple_SetItem(args.GetPyObject(), 5, str.Release());
+    }
+
+    PythonObject result(lock, PyObject_CallObject(storageCommitmentScpCallback_, args.GetPyObject()));
+    *handler = result.Release();
+
+    std::string traceback;
+    if (lock.HasErrorOccurred(traceback))
+    {
+      OrthancPlugins::LogError("Error in the Python storage commitment SCP callback, "
+                               "traceback:\n" + traceback);
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    OrthancPlugins::LogError("Error in the Python storage commitment SCP callback: " +
+                             std::string(e.What(OrthancPlugins::GetGlobalContext())));
+  }
+  return OrthancPluginErrorCode_Success;
+}
+
+static OrthancPluginErrorCode StorageCommitmentLookupCallback(
+  OrthancPluginStorageCommitmentFailureReason* target /* out */,
+  void*                                        handler,
+  const char*                                  sopClassUid,
+  const char*                                  sopInstanceUid)
+{
+  try
+  {
+    PythonLock lock;
+
+    PythonObject args(lock, PyTuple_New(3));
+    {
+      PythonString str(lock, sopClassUid);
+      PyTuple_SetItem(args.GetPyObject(), 0, str.Release());
+    }
+    {
+      PythonString str(lock, sopInstanceUid);
+      PyTuple_SetItem(args.GetPyObject(), 1, str.Release());
+    }
+    {
+      PyObject* data = (PyObject*) handler;
+      Py_INCREF(data);  // Keep a reference before it was stolen by PyTuple_SetItem.
+      PyTuple_SetItem(args.GetPyObject(), 2, data);
+    }
+
+    PythonObject result(lock, PyObject_CallObject(storageCommitmentLookupCallback_, args.GetPyObject()));
+
+    if (!PyLong_Check(result.GetPyObject()))
+    {
+      OrthancPlugins::LogError("The Python storage commitment Lookup callback has not returned an int as the return value");
+      return OrthancPluginErrorCode_Plugin;
+    }
+
+    *target = static_cast<OrthancPluginStorageCommitmentFailureReason>(PyLong_AsLong(result.GetPyObject()));
+
+    std::string traceback;
+    if (lock.HasErrorOccurred(traceback))
+    {
+      OrthancPlugins::LogError("Error in the Python storage commitment Lookup callback, "
+                               "traceback:\n" + traceback);
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    OrthancPlugins::LogError("Error in the Python storage commitment Lookup callback: " +
+                             std::string(e.What(OrthancPlugins::GetGlobalContext())));
+  }
+  return OrthancPluginErrorCode_Success;
+}
+
+static void StorageCommitmentDestructor(void *handler)
+{
+  PythonLock lock;
+  Py_DECREF((PyObject*)handler);  // Release the reference
+}
+
+PyObject* RegisterStorageCommitmentScpCallback(PyObject* module, PyObject* args)
+{
+  // The GIL is locked at this point (no need to create "PythonLock")
+
+  class Registration : public ICallbackRegistration
+  {
+  public:
+    virtual void Register() ORTHANC_OVERRIDE
+    {
+      OrthancPluginRegisterStorageCommitmentScpCallback(
+        OrthancPlugins::GetGlobalContext(),
+        StorageCommitmentSCPCallback,
+        StorageCommitmentDestructor,
+        StorageCommitmentLookupCallback);
+    }
+  };
+
+  {
+    Registration registration;
+    return ICallbackRegistration::Apply2(registration, args,
+      storageCommitmentScpCallback_,
+      storageCommitmentLookupCallback_,
+      "Python storage commitment SCP & Lookup callback");
+  }
+}
+
+void FinalizeStorageCommitmentScpCallback()
+{
+  ICallbackRegistration::Unregister(storageCommitmentScpCallback_);
+  ICallbackRegistration::Unregister(storageCommitmentLookupCallback_);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/StorageCommitmentScpCallback.h	Mon Aug 28 18:30:42 2023 +0200
@@ -0,0 +1,27 @@
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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* RegisterStorageCommitmentScpCallback(PyObject* module, PyObject* args);
+
+void FinalizeStorageCommitmentScpCallback();
\ No newline at end of file