comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:7ed502b17b8f
1 /**
2 * Python plugin for Orthanc
3 * Copyright (C) 2017-2020 Osimis S.A., Belgium
4 *
5 * This program is free software: you can redistribute it and/or
6 * modify it under the terms of the GNU Affero General Public License
7 * as published by the Free Software Foundation, either version 3 of
8 * the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 **/
18
19
20 // http://edcjones.tripod.com/refcount.html
21 // https://docs.python.org/3/extending/extending.html
22
23 // https://www.codevate.com/blog/7-concurrency-with-embedded-python-in-a-multi-threaded-c-application
24 // https://fr.slideshare.net/YiLungTsai/embed-python
25
26
27 #include "OnStoredInstanceCallback.h"
28 #include "OnChangeCallback.h"
29 #include "RestCallbacks.h"
30 #include "PythonModule.h"
31
32 #include "Autogenerated/sdk.h"
33
34 #include <OrthancPluginCppWrapper.h>
35
36 #include <boost/algorithm/string/predicate.hpp>
37 #include <boost/filesystem.hpp>
38
39 #if !defined(_WIN32)
40 # include <dlfcn.h>
41 #endif
42
43
44
45 static bool pythonEnabled_ = false;
46 static std::string userScriptName_;
47 static std::vector<PyMethodDef> globalFunctions_;
48
49
50 static void InstallClasses(PyObject* module)
51 {
52 RegisterOrthancSdk(module);
53 }
54
55
56 static void SetupGlobalFunctions()
57 {
58 if (!globalFunctions_.empty())
59 {
60 ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
61 }
62
63 /**
64 * Add all the manual global functions
65 **/
66
67 std::list<PyMethodDef> functions;
68
69 {
70 PyMethodDef f = { "RegisterRestCallback", RegisterRestCallback, METH_VARARGS, "" };
71 functions.push_back(f);
72 }
73
74 {
75 PyMethodDef f = { "RegisterOnChangeCallback", RegisterOnChangeCallback, METH_VARARGS, "" };
76 functions.push_back(f);
77 }
78
79 {
80 PyMethodDef f = { "RegisterOnStoredInstanceCallback", RegisterOnStoredInstanceCallback,
81 METH_VARARGS, "" };
82 functions.push_back(f);
83 }
84
85 /**
86 * Append all the global functions that were automatically generated
87 **/
88
89 const PyMethodDef* sdk = GetOrthancSdkFunctions();
90
91 for (size_t i = 0; sdk[i].ml_name != NULL; i++)
92 {
93 functions.push_back(sdk[i]);
94 }
95
96 /**
97 * Flatten the list of functions into the vector
98 **/
99
100 globalFunctions_.resize(functions.size());
101 std::copy(functions.begin(), functions.end(), globalFunctions_.begin());
102
103 PyMethodDef sentinel = { NULL };
104 globalFunctions_.push_back(sentinel);
105 }
106
107
108 static PyMethodDef* GetGlobalFunctions()
109 {
110 if (globalFunctions_.empty())
111 {
112 // "SetupGlobalFunctions()" should have been called
113 ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
114 }
115 else
116 {
117 return &globalFunctions_[0];
118 }
119 }
120
121
122
123
124
125 #if !defined(_WIN32)
126
127 #include <link.h> // For dl_phdr_info
128
129 static int ForceImportCallback(struct dl_phdr_info *info, size_t size, void *data)
130 {
131 /**
132 * The following line solves the error: "ImportError:
133 * /usr/lib/python2.7/dist-packages/PIL/_imaging.x86_64-linux-gnu.so:
134 * undefined symbol: PyExc_SystemError"
135 * https://stackoverflow.com/a/48517485/881731
136 *
137 * dlopen("/usr/lib/x86_64-linux-gnu/libpython2.7.so", RTLD_NOW | RTLD_LAZY | RTLD_GLOBAL);
138 *
139 * Another fix consists in using LD_PRELOAD as follows:
140 * LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libpython2.7.so ~/Subversion/orthanc/i/Orthanc tutu.json
141 **/
142
143 std::string module(info->dlpi_name);
144
145 if (module.find("python") != std::string::npos)
146 {
147 OrthancPlugins::LogWarning("Force global loading of Python shared library: " + module);
148 dlopen(module.c_str(), RTLD_NOW | RTLD_LAZY | RTLD_GLOBAL);
149 }
150
151 return 0;
152 }
153
154 #endif
155
156
157 extern "C"
158 {
159 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
160 {
161 OrthancPlugins::SetGlobalContext(c);
162
163 OrthancPlugins::LogWarning("Python plugin is initializing");
164
165
166 /* Check the version of the Orthanc core */
167 if (OrthancPluginCheckVersion(c) == 0)
168 {
169 char info[1024];
170 sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
171 c->orthancVersion,
172 ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
173 ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
174 ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
175 OrthancPluginLogError(c, info);
176 return -1;
177 }
178
179
180 try
181 {
182 /**
183 * Detection of the user script
184 **/
185
186 OrthancPlugins::OrthancConfiguration config;
187
188 static const char* const OPTION = "PythonScript";
189
190 std::string script;
191 if (!config.LookupStringValue(script, OPTION))
192 {
193 pythonEnabled_ = false;
194
195 OrthancPlugins::LogWarning("The option \"" + std::string(OPTION) + "\" is not provided: " +
196 "Python scripting is disabled");
197 }
198 else
199 {
200 pythonEnabled_ = true;
201
202 /**
203 * Installation of the user script
204 **/
205
206 const boost::filesystem::path path(script);
207 if (!boost::iequals(path.extension().string(), ".py"))
208 {
209 OrthancPlugins::LogError("Python script must have the \".py\" file extension: " +
210 path.string());
211 return -1;
212 }
213
214 if (!boost::filesystem::is_regular_file(path))
215 {
216 OrthancPlugins::LogError("Inexistent directory for the Python script: " +
217 path.string());
218 return -1;
219 }
220
221 boost::filesystem::path userScriptDirectory = boost::filesystem::absolute(path).parent_path();
222
223 {
224 boost::filesystem::path module = path.filename().replace_extension("");
225 userScriptName_ = module.string();
226 }
227
228 OrthancPlugins::LogWarning("Using Python script \"" + userScriptName_ +
229 ".py\" from directory: " + userScriptDirectory.string());
230
231
232 /**
233 * Initialization of Python
234 **/
235
236 #if !defined(_WIN32)
237 dl_iterate_phdr(ForceImportCallback, NULL);
238 #endif
239
240 SetupGlobalFunctions();
241 PythonLock::GlobalInitialize("orthanc", "Exception",
242 GetGlobalFunctions, InstallClasses,
243 config.GetBooleanValue("PythonVerbose", false));
244 PythonLock::AddSysPath(userScriptDirectory.string());
245
246
247 /**
248 * Force loading the declarations in the user script
249 **/
250
251 PythonLock lock;
252
253 {
254 PythonModule module(lock, userScriptName_);
255 }
256
257 std::string traceback;
258 if (lock.HasErrorOccurred(traceback))
259 {
260 OrthancPlugins::LogError("Error during the installation of the Python script, "
261 "traceback:\n" + traceback);
262 return -1;
263 }
264 }
265 }
266 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
267 {
268 OrthancPlugins::LogError("Exception while starting the Python plugin: " +
269 std::string(e.What(c)));
270 return -1;
271 }
272
273 return 0;
274 }
275
276
277 ORTHANC_PLUGINS_API void OrthancPluginFinalize()
278 {
279 OrthancPlugins::LogWarning("Python plugin is finalizing");
280
281 if (pythonEnabled_)
282 {
283 FinalizeOnChangeCallback();
284 FinalizeRestCallbacks();
285 FinalizeOnStoredInstanceCallback();
286
287 PythonLock::GlobalFinalize();
288 }
289 }
290
291
292 ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
293 {
294 return "python";
295 }
296
297
298 ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
299 {
300 return PLUGIN_VERSION;
301 }
302 }