comparison Sources/OnChangeCallback.cpp @ 0:7ed502b17b8f

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 26 Mar 2020 18:47:01 +0100
parents
children 26762eb9d704
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 #include "OnChangeCallback.h"
21
22 #include "PythonObject.h"
23
24 #include <OrthancPluginCppWrapper.h>
25
26 #include <boost/thread.hpp>
27
28
29 class PendingChange : public boost::noncopyable
30 {
31 private:
32 OrthancPluginChangeType changeType_;
33 OrthancPluginResourceType resourceType_;
34 std::string resourceId_;
35
36 public:
37 PendingChange(OrthancPluginChangeType changeType,
38 OrthancPluginResourceType resourceType,
39 const char* resourceId) :
40 changeType_(changeType),
41 resourceType_(resourceType)
42 {
43 if (resourceId == NULL)
44 {
45 resourceId_.clear();
46 }
47 else
48 {
49 resourceId_.assign(resourceId);
50 }
51 }
52
53 OrthancPluginChangeType GetChangeType() const
54 {
55 return changeType_;
56 }
57
58 OrthancPluginResourceType GetResourceType() const
59 {
60 return resourceType_;
61 }
62
63 const std::string& GetResourceId() const
64 {
65 return resourceId_;
66 }
67 };
68
69
70
71 class PendingChanges : public boost::noncopyable
72 {
73 private:
74 typedef std::list<PendingChange*> Queue;
75
76 boost::mutex mutex_;
77 Queue queue_;
78 boost::condition_variable elementAvailable_;
79
80 public:
81 ~PendingChanges()
82 {
83 for (Queue::iterator it = queue_.begin(); it != queue_.end(); ++it)
84 {
85 assert(*it != NULL);
86 delete *it;
87 }
88 }
89
90 void Enqueue(OrthancPluginChangeType changeType,
91 OrthancPluginResourceType resourceType,
92 const char* resourceId)
93 {
94 boost::mutex::scoped_lock lock(mutex_);
95 queue_.push_back(new PendingChange(changeType, resourceType, resourceId));
96 elementAvailable_.notify_one();
97 }
98
99 PendingChange* Dequeue(unsigned int millisecondsTimeout)
100 {
101 if (millisecondsTimeout <= 0)
102 {
103 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
104 }
105
106 boost::mutex::scoped_lock lock(mutex_);
107
108 // Wait for a message to arrive in the FIFO queue
109 while (queue_.empty())
110 {
111 bool success = elementAvailable_.timed_wait
112 (lock, boost::posix_time::milliseconds(millisecondsTimeout));
113 if (!success)
114 {
115 return NULL;
116 }
117 }
118
119 std::auto_ptr<PendingChange> change(queue_.front());
120 queue_.pop_front();
121
122 return change.release();
123 }
124 };
125
126
127
128 static PendingChanges pendingChanges_;
129 static bool stopping_ = false;
130 static boost::thread changesThread_;
131 static PyObject* changesCallback_ = NULL;
132
133
134 static void ChangesWorker()
135 {
136 while (!stopping_)
137 {
138 for (;;)
139 {
140 std::unique_ptr<PendingChange> change(pendingChanges_.Dequeue(100));
141 if (change.get() == NULL)
142 {
143 break;
144 }
145 else if (changesCallback_ != NULL)
146 {
147 try
148 {
149 PythonLock lock;
150 PythonObject args(lock, PyTuple_New(3));
151 PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromLong(change->GetChangeType()));
152 PyTuple_SetItem(args.GetPyObject(), 1, PyLong_FromLong(change->GetResourceType()));
153 PyTuple_SetItem(args.GetPyObject(), 2, PyUnicode_FromString(change->GetResourceId().c_str()));
154 PythonObject result(lock, PyObject_CallObject(changesCallback_, args.GetPyObject()));
155 }
156 catch (OrthancPlugins::PluginException& e)
157 {
158 OrthancPlugins::LogError("Error during Python on-change callback: " +
159 std::string(e.What(OrthancPlugins::GetGlobalContext())));
160 }
161 }
162 }
163 }
164 }
165
166
167 static OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType,
168 OrthancPluginResourceType resourceType,
169 const char* resourceId)
170 {
171 {
172 PythonLock lock;
173 pendingChanges_.Enqueue(changeType, resourceType, resourceId);
174 }
175
176 return OrthancPluginErrorCode_Success;
177 }
178
179
180 PyObject* RegisterOnChangeCallback(PyObject* module, PyObject* args)
181 {
182 // The GIL is locked at this point (no need to create "PythonLock")
183
184 // https://docs.python.org/3/extending/extending.html#calling-python-functions-from-c
185 PyObject* callback = NULL;
186
187 if (!PyArg_ParseTuple(args, "O", &callback) ||
188 callback == NULL)
189 {
190 PyErr_SetString(PyExc_ValueError, "Expected a callback function");
191 return NULL;
192 }
193
194 if (changesCallback_ != NULL)
195 {
196 PyErr_SetString(PyExc_RuntimeError, "Can only register one Python on-changes callback");
197 return NULL;
198 }
199
200 OrthancPlugins::LogInfo("Registering a Python on-changes callback");
201
202 OrthancPluginRegisterOnChangeCallback(OrthancPlugins::GetGlobalContext(), OnChangeCallback);
203
204 stopping_ = false;
205 changesThread_ = boost::thread(ChangesWorker);
206
207 changesCallback_ = callback;
208 Py_XINCREF(changesCallback_);
209
210 Py_INCREF(Py_None);
211 return Py_None;
212 }
213
214
215
216
217 void FinalizeOnChangeCallback()
218 {
219 stopping_ = true;
220
221 if (changesThread_.joinable())
222 {
223 changesThread_.join();
224 }
225
226 {
227 PythonLock lock;
228
229 if (changesCallback_ != NULL)
230 {
231 Py_XDECREF(changesCallback_);
232 }
233 }
234 }