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