Mercurial > hg > orthanc-python
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; + } +}