0
|
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 OrthancPlugins::LogWarning("Python plugin is initializing");
|
|
163
|
|
164
|
|
165 /* Check the version of the Orthanc core */
|
|
166 if (OrthancPluginCheckVersion(c) == 0)
|
|
167 {
|
|
168 char info[1024];
|
|
169 sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
|
|
170 c->orthancVersion,
|
|
171 ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
|
|
172 ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
|
|
173 ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
|
|
174 OrthancPluginLogError(c, info);
|
|
175 return -1;
|
|
176 }
|
11
|
177
|
|
178 OrthancPluginSetDescription(c, "Run Python scripts as Orthanc plugins");
|
|
179
|
0
|
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 }
|