Mercurial > hg > orthanc-python
annotate Sources/Plugin.cpp @ 56:23f3099bed47
upgrade to year 2021
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 06 Jan 2021 18:17:53 +0100 |
parents | 70abe3ebbbfc |
children | 32de70a1e4c7 |
rev | line source |
---|---|
0 | 1 /** |
2 * Python plugin for Orthanc | |
56
23f3099bed47
upgrade to year 2021
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
50
diff
changeset
|
3 * Copyright (C) 2020-2021 Osimis S.A., Belgium |
0 | 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 | |
50
70abe3ebbbfc
New Python function: "orthanc.RegisterIncomingHttpRequestFilter()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
47
diff
changeset
|
27 #include "IncomingHttpRequestFilter.h" |
70abe3ebbbfc
New Python function: "orthanc.RegisterIncomingHttpRequestFilter()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
47
diff
changeset
|
28 #include "OnChangeCallback.h" |
0 | 29 #include "OnStoredInstanceCallback.h" |
50
70abe3ebbbfc
New Python function: "orthanc.RegisterIncomingHttpRequestFilter()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
47
diff
changeset
|
30 |
0 | 31 #include "RestCallbacks.h" |
32 #include "PythonModule.h" | |
33 | |
34 #include "Autogenerated/sdk.h" | |
35 | |
36
fd58eb5749ed
CMake simplification using DownloadOrthancFramework.cmake
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
11
diff
changeset
|
36 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" |
0 | 37 |
38 #include <boost/algorithm/string/predicate.hpp> | |
39 #include <boost/filesystem.hpp> | |
40 | |
47
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
41 |
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
42 // The "dl_iterate_phdr()" function (to walk through shared libraries) |
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
43 // is not available on Microsoft Windows and Apple OS X |
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
44 #if defined(_WIN32) |
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
45 # define HAS_DL_ITERATE 0 |
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
46 #elif defined(__APPLE__) && defined(__MACH__) |
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
47 # define HAS_DL_ITERATE 0 |
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
48 #else |
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
49 # define HAS_DL_ITERATE 1 |
0 | 50 #endif |
51 | |
52 | |
53 | |
54 static bool pythonEnabled_ = false; | |
55 static std::string userScriptName_; | |
56 static std::vector<PyMethodDef> globalFunctions_; | |
57 | |
58 | |
59 static void InstallClasses(PyObject* module) | |
60 { | |
61 RegisterOrthancSdk(module); | |
62 } | |
63 | |
64 | |
65 static void SetupGlobalFunctions() | |
66 { | |
67 if (!globalFunctions_.empty()) | |
68 { | |
69 ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); | |
70 } | |
71 | |
72 /** | |
73 * Add all the manual global functions | |
74 **/ | |
75 | |
76 std::list<PyMethodDef> functions; | |
77 | |
78 { | |
79 PyMethodDef f = { "RegisterRestCallback", RegisterRestCallback, METH_VARARGS, "" }; | |
80 functions.push_back(f); | |
81 } | |
82 | |
83 { | |
84 PyMethodDef f = { "RegisterOnChangeCallback", RegisterOnChangeCallback, METH_VARARGS, "" }; | |
85 functions.push_back(f); | |
86 } | |
87 | |
88 { | |
89 PyMethodDef f = { "RegisterOnStoredInstanceCallback", RegisterOnStoredInstanceCallback, | |
90 METH_VARARGS, "" }; | |
91 functions.push_back(f); | |
92 } | |
50
70abe3ebbbfc
New Python function: "orthanc.RegisterIncomingHttpRequestFilter()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
47
diff
changeset
|
93 |
70abe3ebbbfc
New Python function: "orthanc.RegisterIncomingHttpRequestFilter()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
47
diff
changeset
|
94 { |
70abe3ebbbfc
New Python function: "orthanc.RegisterIncomingHttpRequestFilter()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
47
diff
changeset
|
95 // New in release 3.0 |
70abe3ebbbfc
New Python function: "orthanc.RegisterIncomingHttpRequestFilter()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
47
diff
changeset
|
96 PyMethodDef f = { "RegisterIncomingHttpRequestFilter", RegisterIncomingHttpRequestFilter, METH_VARARGS, "" }; |
70abe3ebbbfc
New Python function: "orthanc.RegisterIncomingHttpRequestFilter()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
47
diff
changeset
|
97 functions.push_back(f); |
70abe3ebbbfc
New Python function: "orthanc.RegisterIncomingHttpRequestFilter()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
47
diff
changeset
|
98 } |
0 | 99 |
100 /** | |
101 * Append all the global functions that were automatically generated | |
102 **/ | |
103 | |
104 const PyMethodDef* sdk = GetOrthancSdkFunctions(); | |
105 | |
106 for (size_t i = 0; sdk[i].ml_name != NULL; i++) | |
107 { | |
108 functions.push_back(sdk[i]); | |
109 } | |
110 | |
111 /** | |
112 * Flatten the list of functions into the vector | |
113 **/ | |
114 | |
115 globalFunctions_.resize(functions.size()); | |
116 std::copy(functions.begin(), functions.end(), globalFunctions_.begin()); | |
117 | |
118 PyMethodDef sentinel = { NULL }; | |
119 globalFunctions_.push_back(sentinel); | |
120 } | |
121 | |
122 | |
123 static PyMethodDef* GetGlobalFunctions() | |
124 { | |
125 if (globalFunctions_.empty()) | |
126 { | |
127 // "SetupGlobalFunctions()" should have been called | |
128 ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); | |
129 } | |
130 else | |
131 { | |
132 return &globalFunctions_[0]; | |
133 } | |
134 } | |
135 | |
136 | |
137 | |
47
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
138 #if HAS_DL_ITERATE == 1 |
0 | 139 |
47
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
140 #include <dlfcn.h> |
0 | 141 #include <link.h> // For dl_phdr_info |
142 | |
143 static int ForceImportCallback(struct dl_phdr_info *info, size_t size, void *data) | |
144 { | |
145 /** | |
146 * The following line solves the error: "ImportError: | |
147 * /usr/lib/python2.7/dist-packages/PIL/_imaging.x86_64-linux-gnu.so: | |
148 * undefined symbol: PyExc_SystemError" | |
149 * https://stackoverflow.com/a/48517485/881731 | |
150 * | |
151 * dlopen("/usr/lib/x86_64-linux-gnu/libpython2.7.so", RTLD_NOW | RTLD_LAZY | RTLD_GLOBAL); | |
152 * | |
153 * Another fix consists in using LD_PRELOAD as follows: | |
154 * LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libpython2.7.so ~/Subversion/orthanc/i/Orthanc tutu.json | |
155 **/ | |
156 | |
157 std::string module(info->dlpi_name); | |
158 | |
159 if (module.find("python") != std::string::npos) | |
160 { | |
161 OrthancPlugins::LogWarning("Force global loading of Python shared library: " + module); | |
162 dlopen(module.c_str(), RTLD_NOW | RTLD_LAZY | RTLD_GLOBAL); | |
163 } | |
164 | |
165 return 0; | |
166 } | |
167 | |
168 #endif | |
169 | |
170 | |
171 extern "C" | |
172 { | |
173 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c) | |
174 { | |
175 OrthancPlugins::SetGlobalContext(c); | |
176 OrthancPlugins::LogWarning("Python plugin is initializing"); | |
177 | |
178 | |
179 /* Check the version of the Orthanc core */ | |
180 if (OrthancPluginCheckVersion(c) == 0) | |
181 { | |
182 char info[1024]; | |
183 sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", | |
184 c->orthancVersion, | |
185 ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, | |
186 ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, | |
187 ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); | |
188 OrthancPluginLogError(c, info); | |
189 return -1; | |
190 } | |
11 | 191 |
192 OrthancPluginSetDescription(c, "Run Python scripts as Orthanc plugins"); | |
193 | |
0 | 194 try |
195 { | |
196 /** | |
197 * Detection of the user script | |
198 **/ | |
199 | |
200 OrthancPlugins::OrthancConfiguration config; | |
201 | |
202 static const char* const OPTION = "PythonScript"; | |
203 | |
204 std::string script; | |
205 if (!config.LookupStringValue(script, OPTION)) | |
206 { | |
207 pythonEnabled_ = false; | |
208 | |
209 OrthancPlugins::LogWarning("The option \"" + std::string(OPTION) + "\" is not provided: " + | |
210 "Python scripting is disabled"); | |
211 } | |
212 else | |
213 { | |
214 pythonEnabled_ = true; | |
215 | |
216 /** | |
217 * Installation of the user script | |
218 **/ | |
219 | |
220 const boost::filesystem::path path(script); | |
221 if (!boost::iequals(path.extension().string(), ".py")) | |
222 { | |
223 OrthancPlugins::LogError("Python script must have the \".py\" file extension: " + | |
224 path.string()); | |
225 return -1; | |
226 } | |
227 | |
228 if (!boost::filesystem::is_regular_file(path)) | |
229 { | |
230 OrthancPlugins::LogError("Inexistent directory for the Python script: " + | |
231 path.string()); | |
232 return -1; | |
233 } | |
234 | |
235 boost::filesystem::path userScriptDirectory = boost::filesystem::absolute(path).parent_path(); | |
236 | |
237 { | |
238 boost::filesystem::path module = path.filename().replace_extension(""); | |
239 userScriptName_ = module.string(); | |
240 } | |
241 | |
242 OrthancPlugins::LogWarning("Using Python script \"" + userScriptName_ + | |
243 ".py\" from directory: " + userScriptDirectory.string()); | |
244 | |
245 | |
246 /** | |
247 * Initialization of Python | |
248 **/ | |
249 | |
47
42de8b600c0c
Support of Apple OS X
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
250 #if HAS_DL_ITERATE == 1 |
0 | 251 dl_iterate_phdr(ForceImportCallback, NULL); |
252 #endif | |
253 | |
254 SetupGlobalFunctions(); | |
255 PythonLock::GlobalInitialize("orthanc", "Exception", | |
256 GetGlobalFunctions, InstallClasses, | |
257 config.GetBooleanValue("PythonVerbose", false)); | |
258 PythonLock::AddSysPath(userScriptDirectory.string()); | |
259 | |
260 | |
261 /** | |
262 * Force loading the declarations in the user script | |
263 **/ | |
264 | |
265 PythonLock lock; | |
266 | |
267 { | |
268 PythonModule module(lock, userScriptName_); | |
269 } | |
270 | |
271 std::string traceback; | |
272 if (lock.HasErrorOccurred(traceback)) | |
273 { | |
274 OrthancPlugins::LogError("Error during the installation of the Python script, " | |
275 "traceback:\n" + traceback); | |
276 return -1; | |
277 } | |
278 } | |
279 } | |
280 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) | |
281 { | |
282 OrthancPlugins::LogError("Exception while starting the Python plugin: " + | |
283 std::string(e.What(c))); | |
284 return -1; | |
285 } | |
286 | |
287 return 0; | |
288 } | |
289 | |
290 | |
291 ORTHANC_PLUGINS_API void OrthancPluginFinalize() | |
292 { | |
293 OrthancPlugins::LogWarning("Python plugin is finalizing"); | |
294 | |
295 if (pythonEnabled_) | |
296 { | |
297 FinalizeOnChangeCallback(); | |
298 FinalizeRestCallbacks(); | |
299 FinalizeOnStoredInstanceCallback(); | |
50
70abe3ebbbfc
New Python function: "orthanc.RegisterIncomingHttpRequestFilter()"
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
47
diff
changeset
|
300 FinalizeIncomingHttpRequestFilter(); |
0 | 301 |
302 PythonLock::GlobalFinalize(); | |
303 } | |
304 } | |
305 | |
306 | |
307 ORTHANC_PLUGINS_API const char* OrthancPluginGetName() | |
308 { | |
309 return "python"; | |
310 } | |
311 | |
312 | |
313 ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() | |
314 { | |
315 return PLUGIN_VERSION; | |
316 } | |
317 } |