diff Sources/OnChangeCallback.cpp @ 0:7ed502b17b8f

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 26 Mar 2020 18:47:01 +0100
parents
children 26762eb9d704
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/OnChangeCallback.cpp	Thu Mar 26 18:47:01 2020 +0100
@@ -0,0 +1,234 @@
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2017-2020 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 "OnChangeCallback.h"
+
+#include "PythonObject.h"
+
+#include <OrthancPluginCppWrapper.h>
+
+#include <boost/thread.hpp>
+
+
+class PendingChange : public boost::noncopyable
+{
+private:
+  OrthancPluginChangeType    changeType_;
+  OrthancPluginResourceType  resourceType_;
+  std::string                resourceId_;
+
+public:
+  PendingChange(OrthancPluginChangeType changeType,
+                OrthancPluginResourceType resourceType,
+                const char* resourceId) :
+    changeType_(changeType),
+    resourceType_(resourceType)
+  {
+    if (resourceId == NULL)
+    {
+      resourceId_.clear();
+    }
+    else
+    {
+      resourceId_.assign(resourceId);
+    }
+  }
+
+  OrthancPluginChangeType  GetChangeType() const
+  {
+    return changeType_;
+  }
+
+  OrthancPluginResourceType  GetResourceType() const
+  {
+    return resourceType_;
+  }
+
+  const std::string& GetResourceId() const
+  {
+    return resourceId_;
+  }
+};
+
+
+
+class PendingChanges : public boost::noncopyable
+{
+private:
+  typedef std::list<PendingChange*>  Queue;
+  
+  boost::mutex               mutex_;
+  Queue                      queue_;
+  boost::condition_variable  elementAvailable_;
+
+public:
+  ~PendingChanges()
+  {
+    for (Queue::iterator it = queue_.begin(); it != queue_.end(); ++it)
+    {
+      assert(*it != NULL);
+      delete *it;
+    }
+  }
+  
+  void Enqueue(OrthancPluginChangeType changeType,
+               OrthancPluginResourceType resourceType,
+               const char* resourceId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    queue_.push_back(new PendingChange(changeType, resourceType, resourceId));
+    elementAvailable_.notify_one();
+  }
+
+  PendingChange* Dequeue(unsigned int millisecondsTimeout)
+  {
+    if (millisecondsTimeout <= 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+    
+    boost::mutex::scoped_lock lock(mutex_);
+
+    // Wait for a message to arrive in the FIFO queue
+    while (queue_.empty())
+    {
+      bool success = elementAvailable_.timed_wait
+        (lock, boost::posix_time::milliseconds(millisecondsTimeout));
+      if (!success)
+      {
+        return NULL;
+      }
+    }
+
+    std::auto_ptr<PendingChange> change(queue_.front());
+    queue_.pop_front();
+
+    return change.release();
+  }
+};
+
+
+
+static PendingChanges  pendingChanges_;
+static bool            stopping_ = false;
+static boost::thread   changesThread_;
+static PyObject*       changesCallback_ = NULL;
+
+
+static void ChangesWorker()
+{
+  while (!stopping_)
+  {
+    for (;;)
+    {
+      std::unique_ptr<PendingChange> change(pendingChanges_.Dequeue(100));
+      if (change.get() == NULL)
+      {
+        break;
+      }
+      else if (changesCallback_ != NULL)
+      {
+        try
+        {
+          PythonLock lock;
+          PythonObject args(lock, PyTuple_New(3));
+          PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromLong(change->GetChangeType()));
+          PyTuple_SetItem(args.GetPyObject(), 1, PyLong_FromLong(change->GetResourceType()));
+          PyTuple_SetItem(args.GetPyObject(), 2, PyUnicode_FromString(change->GetResourceId().c_str()));
+          PythonObject result(lock, PyObject_CallObject(changesCallback_, args.GetPyObject()));
+        }
+        catch (OrthancPlugins::PluginException& e)
+        {
+          OrthancPlugins::LogError("Error during Python on-change callback: " +
+                                   std::string(e.What(OrthancPlugins::GetGlobalContext())));
+        }
+      }
+    }
+  }
+}
+
+
+static OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType,
+                                               OrthancPluginResourceType resourceType,
+                                               const char* resourceId)
+{
+  {
+    PythonLock lock;
+    pendingChanges_.Enqueue(changeType, resourceType, resourceId);
+  }
+  
+  return OrthancPluginErrorCode_Success;
+}
+
+
+PyObject* RegisterOnChangeCallback(PyObject* module, PyObject* args)
+{
+  // The GIL is locked at this point (no need to create "PythonLock")
+  
+  // https://docs.python.org/3/extending/extending.html#calling-python-functions-from-c
+  PyObject* callback = NULL;
+
+  if (!PyArg_ParseTuple(args, "O", &callback) ||
+      callback == NULL)
+  {
+    PyErr_SetString(PyExc_ValueError, "Expected a callback function");
+    return NULL;
+  }
+
+  if (changesCallback_ != NULL)
+  {
+    PyErr_SetString(PyExc_RuntimeError, "Can only register one Python on-changes callback");
+    return NULL;
+  }
+  
+  OrthancPlugins::LogInfo("Registering a Python on-changes callback");
+
+  OrthancPluginRegisterOnChangeCallback(OrthancPlugins::GetGlobalContext(), OnChangeCallback);
+
+  stopping_ = false;
+  changesThread_ = boost::thread(ChangesWorker);
+
+  changesCallback_ = callback;
+  Py_XINCREF(changesCallback_);
+  
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+
+
+void FinalizeOnChangeCallback()
+{
+  stopping_ = true;
+
+  if (changesThread_.joinable())
+  {
+    changesThread_.join();
+  }
+
+  {
+    PythonLock lock;
+    
+    if (changesCallback_ != NULL)
+    {
+      Py_XDECREF(changesCallback_);
+    }
+  }
+}