diff Sources/Plugin.cpp @ 0:7ed502b17b8f

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 26 Mar 2020 18:47:01 +0100
parents
children 88d47c1c458d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/Plugin.cpp	Thu Mar 26 18:47:01 2020 +0100
@@ -0,0 +1,302 @@
+/**
+ * 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/>.
+ **/
+
+
+// http://edcjones.tripod.com/refcount.html
+// https://docs.python.org/3/extending/extending.html
+
+// https://www.codevate.com/blog/7-concurrency-with-embedded-python-in-a-multi-threaded-c-application
+// https://fr.slideshare.net/YiLungTsai/embed-python
+
+
+#include "OnStoredInstanceCallback.h"
+#include "OnChangeCallback.h"
+#include "RestCallbacks.h"
+#include "PythonModule.h"
+
+#include "Autogenerated/sdk.h"
+
+#include <OrthancPluginCppWrapper.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/filesystem.hpp>
+
+#if !defined(_WIN32)
+#  include <dlfcn.h>
+#endif
+
+
+
+static bool pythonEnabled_ = false;
+static std::string userScriptName_;
+static std::vector<PyMethodDef>  globalFunctions_;
+
+
+static void InstallClasses(PyObject* module)
+{
+  RegisterOrthancSdk(module);
+}
+
+
+static void SetupGlobalFunctions()
+{
+  if (!globalFunctions_.empty())
+  {
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+  }
+
+  /**
+   * Add all the manual global functions
+   **/
+
+  std::list<PyMethodDef> functions;
+
+  {
+    PyMethodDef f = { "RegisterRestCallback", RegisterRestCallback, METH_VARARGS, "" };
+    functions.push_back(f);
+  }
+
+  {
+    PyMethodDef f = { "RegisterOnChangeCallback", RegisterOnChangeCallback, METH_VARARGS, "" };
+    functions.push_back(f);
+  }
+
+  {
+    PyMethodDef f = { "RegisterOnStoredInstanceCallback", RegisterOnStoredInstanceCallback,
+                      METH_VARARGS, "" };
+    functions.push_back(f);
+  }
+  
+  /**
+   * Append all the global functions that were automatically generated
+   **/
+  
+  const PyMethodDef* sdk = GetOrthancSdkFunctions();
+
+  for (size_t i = 0; sdk[i].ml_name != NULL; i++)
+  {
+    functions.push_back(sdk[i]);
+  }
+
+  /**
+   * Flatten the list of functions into the vector
+   **/
+
+  globalFunctions_.resize(functions.size());
+  std::copy(functions.begin(), functions.end(), globalFunctions_.begin());
+
+  PyMethodDef sentinel = { NULL };
+  globalFunctions_.push_back(sentinel);
+}
+
+  
+static PyMethodDef* GetGlobalFunctions()
+{
+  if (globalFunctions_.empty())
+  {
+    // "SetupGlobalFunctions()" should have been called
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
+  }
+  else
+  {
+    return &globalFunctions_[0];
+  }
+}
+
+
+
+
+
+#if !defined(_WIN32)
+
+#include <link.h>  // For dl_phdr_info
+
+static int ForceImportCallback(struct dl_phdr_info *info, size_t size, void *data)
+{ 
+  /**
+   * The following line solves the error: "ImportError:
+   * /usr/lib/python2.7/dist-packages/PIL/_imaging.x86_64-linux-gnu.so:
+   * undefined symbol: PyExc_SystemError"
+   * https://stackoverflow.com/a/48517485/881731
+   * 
+   * dlopen("/usr/lib/x86_64-linux-gnu/libpython2.7.so", RTLD_NOW | RTLD_LAZY | RTLD_GLOBAL);
+   * 
+   * Another fix consists in using LD_PRELOAD as follows:
+   * LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libpython2.7.so ~/Subversion/orthanc/i/Orthanc tutu.json
+   **/
+    
+  std::string module(info->dlpi_name);
+
+  if (module.find("python") != std::string::npos)
+  {
+    OrthancPlugins::LogWarning("Force global loading of Python shared library: " + module);
+    dlopen(module.c_str(), RTLD_NOW | RTLD_LAZY | RTLD_GLOBAL);
+  }
+  
+  return 0;
+}
+
+#endif
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
+  {
+    OrthancPlugins::SetGlobalContext(c);
+    
+    OrthancPlugins::LogWarning("Python plugin is initializing");
+    
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(c) == 0)
+    {
+      char info[1024];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              c->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(c, info);
+      return -1;
+    }
+
+
+    try
+    {
+      /**
+       * Detection of the user script
+       **/
+
+      OrthancPlugins::OrthancConfiguration config;
+
+      static const char* const OPTION = "PythonScript";
+    
+      std::string script;
+      if (!config.LookupStringValue(script, OPTION))
+      {
+        pythonEnabled_ = false;
+      
+        OrthancPlugins::LogWarning("The option \"" + std::string(OPTION) + "\" is not provided: " +
+                                   "Python scripting is disabled");
+      }
+      else
+      {
+        pythonEnabled_ = true;
+      
+        /**
+         * Installation of the user script
+         **/
+
+        const boost::filesystem::path path(script);
+        if (!boost::iequals(path.extension().string(), ".py"))
+        {
+          OrthancPlugins::LogError("Python script must have the \".py\" file extension: " +
+                                   path.string());
+          return -1;
+        }
+
+        if (!boost::filesystem::is_regular_file(path))
+        {
+          OrthancPlugins::LogError("Inexistent directory for the Python script: " +
+                                   path.string());
+          return -1;
+        }
+
+        boost::filesystem::path userScriptDirectory = boost::filesystem::absolute(path).parent_path();
+
+        {
+          boost::filesystem::path module = path.filename().replace_extension("");
+          userScriptName_ = module.string();
+        }
+
+        OrthancPlugins::LogWarning("Using Python script \"" + userScriptName_ +
+                                   ".py\" from directory: " + userScriptDirectory.string());
+    
+    
+        /**
+         * Initialization of Python
+         **/
+
+#if !defined(_WIN32)
+        dl_iterate_phdr(ForceImportCallback, NULL);
+#endif
+
+        SetupGlobalFunctions();
+        PythonLock::GlobalInitialize("orthanc", "Exception",
+                                     GetGlobalFunctions, InstallClasses,
+                                     config.GetBooleanValue("PythonVerbose", false));
+        PythonLock::AddSysPath(userScriptDirectory.string());
+
+      
+        /**
+         * Force loading the declarations in the user script
+         **/
+
+        PythonLock lock;
+
+        {
+          PythonModule module(lock, userScriptName_);
+        }
+      
+        std::string traceback;
+        if (lock.HasErrorOccurred(traceback))
+        {
+          OrthancPlugins::LogError("Error during the installation of the Python script, "
+                                   "traceback:\n" + traceback);
+          return -1;
+        }
+      }
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      OrthancPlugins::LogError("Exception while starting the Python plugin: " +
+                               std::string(e.What(c)));
+      return -1;
+    }
+    
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    OrthancPlugins::LogWarning("Python plugin is finalizing");
+
+    if (pythonEnabled_)
+    {
+      FinalizeOnChangeCallback();
+      FinalizeRestCallbacks();
+      FinalizeOnStoredInstanceCallback();
+      
+      PythonLock::GlobalFinalize();
+    }
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "python";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return PLUGIN_VERSION;
+  }
+}