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