Commit | Line | Data |
---|---|---|
81447b5b PP |
1 | # The MIT License (MIT) |
2 | # | |
3 | # Copyright (c) 2017 Philippe Proulx <pproulx@efficios.com> | |
4 | # | |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
6 | # of this software and associated documentation files (the "Software"), to deal | |
7 | # in the Software without restriction, including without limitation the rights | |
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
9 | # copies of the Software, and to permit persons to whom the Software is | |
10 | # furnished to do so, subject to the following conditions: | |
11 | # | |
12 | # The above copyright notice and this permission notice shall be included in | |
13 | # all copies or substantial portions of the Software. | |
14 | # | |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
21 | # THE SOFTWARE. | |
22 | ||
23 | from bt2 import native_bt, object, utils | |
24 | import bt2.notification_iterator | |
25 | import collections.abc | |
26 | import bt2 | |
27 | ||
28 | ||
29 | # This class wraps a component class pointer. This component class could | |
30 | # have been created by Python code, but since we only have the pointer, | |
31 | # we can only wrap it in a generic way and lose the original Python | |
32 | # class. | |
33 | class _GenericComponentClass(object._Object): | |
34 | @property | |
35 | def name(self): | |
36 | return native_bt.component_class_get_name(self._ptr) | |
37 | ||
38 | @property | |
39 | def description(self): | |
40 | return native_bt.component_class_get_description(self._ptr) | |
41 | ||
42 | def __call__(self, params=None, name=None): | |
43 | params = bt2.create_value(params) | |
44 | comp_ptr = native_bt.component_create_with_init_method_data(self._ptr, | |
45 | name, | |
46 | params._ptr, | |
47 | None) | |
48 | ||
49 | if comp_ptr is None: | |
50 | raise bt2.CreationError('cannot create component object') | |
51 | ||
52 | return _create_generic_component_from_ptr(comp_ptr) | |
53 | ||
54 | ||
55 | class _GenericSourceComponentClass(_GenericComponentClass): | |
56 | pass | |
57 | ||
58 | ||
59 | class _GenericFilterComponentClass(_GenericComponentClass): | |
60 | pass | |
61 | ||
62 | ||
63 | class _GenericSinkComponentClass(_GenericComponentClass): | |
64 | pass | |
65 | ||
66 | ||
67 | # This class holds the methods which are common to both generic | |
68 | # component objects and Python user component objects. They use the | |
69 | # internal native _ptr, however it was set, to call native API | |
70 | # functions. | |
71 | class _CommonComponentMethods: | |
72 | @property | |
73 | def name(self): | |
74 | return native_bt.component_get_name(self._ptr) | |
75 | ||
76 | @property | |
77 | def component_class(self): | |
78 | cc_ptr = native_bt.component_get_class(self._ptr) | |
79 | utils._handle_ptr(cc_ptr, "cannot get component object's class object") | |
80 | return _create_generic_component_class_from_ptr(cc_ptr) | |
81 | ||
82 | def _handle_status(self, status, gen_error_msg): | |
83 | if status == native_bt.COMPONENT_STATUS_END: | |
84 | raise bt2.Stop | |
85 | elif status == native_bt.COMPONENT_STATUS_AGAIN: | |
86 | raise bt2.TryAgain | |
87 | elif status == native_bt.COMPONENT_STATUS_UNSUPPORTED: | |
88 | raise bt2.UnsupportedFeature | |
89 | elif status < 0: | |
90 | raise bt2.Error(gen_error_msg) | |
91 | ||
92 | ||
93 | class _CommonSourceComponentMethods(_CommonComponentMethods): | |
94 | def create_notification_iterator(self): | |
95 | iter_ptr = native_bt.component_source_create_notification_iterator_with_init_method_data(self._ptr, None) | |
96 | ||
97 | if iter_ptr is None: | |
98 | raise bt2.CreationError('cannot create notification iterator object') | |
99 | ||
100 | return bt2.notification_iterator._GenericNotificationIterator._create_from_ptr(iter_ptr) | |
101 | ||
102 | ||
103 | class _CommonFilterComponentMethods(_CommonComponentMethods): | |
104 | def create_notification_iterator(self): | |
105 | iter_ptr = native_bt.component_filter_create_notification_iterator_with_init_method_data(self._ptr, None) | |
106 | ||
107 | if iter_ptr is None: | |
108 | raise bt2.CreationError('cannot create notification iterator object') | |
109 | ||
110 | return bt2.notification_iterator._GenericNotificationIterator._create_from_ptr(iter_ptr) | |
111 | ||
112 | def add_notification_iterator(self, notif_iter): | |
113 | utils._check_type(notif_iter, bt2.notification_iterator._GenericNotificationIteratorMethods) | |
114 | status = native_bt.component_filter_add_iterator(self._ptr, notif_iter._ptr) | |
115 | self._handle_status(status, 'unexpected error: cannot add notification iterator to filter component object') | |
116 | ||
117 | ||
118 | class _CommonSinkComponentMethods(_CommonComponentMethods): | |
119 | def add_notification_iterator(self, notif_iter): | |
120 | utils._check_type(notif_iter, bt2.notification_iterator._GenericNotificationIteratorMethods) | |
121 | status = native_bt.component_sink_add_iterator(self._ptr, notif_iter._ptr) | |
122 | self._handle_status(status, 'unexpected error: cannot add notification iterator to sink component object') | |
123 | ||
124 | def consume(self): | |
125 | status = native_bt.component_sink_consume(self._ptr) | |
126 | self._handle_status(status, 'unexpected error: cannot consume sink component object') | |
127 | ||
128 | ||
129 | # This is analogous to _GenericSourceComponentClass, but for source | |
130 | # component objects. | |
131 | class _GenericSourceComponent(object._Object, _CommonSourceComponentMethods): | |
132 | pass | |
133 | ||
134 | ||
135 | # This is analogous to _GenericFilterComponentClass, but for filter | |
136 | # component objects. | |
137 | class _GenericFilterComponent(object._Object, _CommonFilterComponentMethods): | |
138 | pass | |
139 | ||
140 | ||
141 | # This is analogous to _GenericSinkComponentClass, but for sink | |
142 | # component objects. | |
143 | class _GenericSinkComponent(object._Object, _CommonSinkComponentMethods): | |
144 | pass | |
145 | ||
146 | ||
147 | _COMP_CLS_TYPE_TO_GENERIC_COMP_CLS = { | |
148 | native_bt.COMPONENT_CLASS_TYPE_SOURCE: _GenericSourceComponent, | |
149 | native_bt.COMPONENT_CLASS_TYPE_FILTER: _GenericFilterComponent, | |
150 | native_bt.COMPONENT_CLASS_TYPE_SINK: _GenericSinkComponent, | |
151 | } | |
152 | ||
153 | ||
154 | _COMP_CLS_TYPE_TO_GENERIC_COMP_CLS_CLS = { | |
155 | native_bt.COMPONENT_CLASS_TYPE_SOURCE: _GenericSourceComponentClass, | |
156 | native_bt.COMPONENT_CLASS_TYPE_FILTER: _GenericFilterComponentClass, | |
157 | native_bt.COMPONENT_CLASS_TYPE_SINK: _GenericSinkComponentClass, | |
158 | } | |
159 | ||
160 | ||
161 | def _create_generic_component_from_ptr(ptr): | |
162 | comp_cls_type = native_bt.component_get_class_type(ptr) | |
163 | return _COMP_CLS_TYPE_TO_GENERIC_COMP_CLS[comp_cls_type]._create_from_ptr(ptr) | |
164 | ||
165 | ||
166 | def _create_generic_component_class_from_ptr(ptr): | |
167 | comp_cls_type = native_bt.component_class_get_type(ptr) | |
168 | return _COMP_CLS_TYPE_TO_GENERIC_COMP_CLS_CLS[comp_cls_type]._create_from_ptr(ptr) | |
169 | ||
170 | ||
171 | # Metaclass for component classes defined by Python code. | |
172 | # | |
173 | # The Python user can create a standard Python class which inherits one | |
174 | # of the three base classes (UserSourceComponent, UserFilterComponent, | |
175 | # or UserSinkComponent). Those base classes set this class | |
176 | # (_UserComponentType) as their metaclass. | |
177 | # | |
178 | # Once the body of a user-defined component class is executed, this | |
179 | # metaclass is used to create and initialize the class. The metaclass | |
180 | # creates a native BT component class of the corresponding type and | |
181 | # associates it with this user-defined class. The metaclass also defines | |
182 | # class methods like the `name` and `description` properties to match | |
183 | # the _GenericComponentClass interface. | |
184 | # | |
185 | # The component class name which is used is either: | |
186 | # | |
187 | # * The `name` parameter of the class: | |
188 | # | |
189 | # class MySink(bt2.SinkComponent, name='my-custom-sink'): | |
190 | # ... | |
191 | # | |
192 | # * If the `name` class parameter is not used: the name of the class | |
193 | # itself (`MySink` in the example above). | |
194 | # | |
195 | # The component class description which is used is the user-defined | |
196 | # class's docstring: | |
197 | # | |
198 | # class MySink(bt2.SinkComponent): | |
199 | # 'Description goes here' | |
200 | # ... | |
201 | # | |
202 | # A user-defined Python component class can have an __init__() method | |
203 | # which must at least accept the `params` and `name` arguments: | |
204 | # | |
205 | # def __init__(self, params, name, something_else): | |
206 | # ... | |
207 | # | |
208 | # The user-defined component class can also have a _destroy() method | |
209 | # (do NOT use __del__()) to be notified when the component object is | |
210 | # (really) destroyed. | |
211 | # | |
212 | # User-defined source and filter component classes must use the | |
213 | # `notification_iterator_class` class parameter to specify the | |
214 | # notification iterator class to use for this component class: | |
215 | # | |
216 | # class MyNotificationIterator(bt2.UserNotificationIterator): | |
217 | # ... | |
218 | # | |
219 | # class MySource(bt2.UserSourceComponent, | |
220 | # notification_iterator_class=MyNotificationIterator): | |
221 | # ... | |
222 | # | |
223 | # This notification iterator class must inherit | |
224 | # bt2.UserNotificationIterator, and it must define the _get() and | |
225 | # _next() methods. The notification iterator class can also define an | |
226 | # __init__() method: this method has access to the original Python | |
227 | # component object which was used to create it as the `component` | |
228 | # property. The notification iterator class can also define a _destroy() | |
229 | # method (again, do NOT use __del__()): this is called when the | |
230 | # notification iterator is (really) destroyed. | |
231 | # | |
232 | # When the user-defined class is destroyed, this metaclass's __del__() | |
233 | # method is called: the native BT component class pointer is put (not | |
234 | # needed anymore, at least not by any Python code since all references | |
235 | # are dropped for __del__() to be called). | |
236 | class _UserComponentType(type): | |
237 | # __new__() is used to catch custom class parameters | |
238 | def __new__(meta_cls, class_name, bases, attrs, **kwargs): | |
239 | return super().__new__(meta_cls, class_name, bases, attrs) | |
240 | ||
241 | def __init__(cls, class_name, bases, namespace, **kwargs): | |
242 | super().__init__(class_name, bases, namespace) | |
243 | ||
244 | # skip our own bases; they are never directly instantiated by the user | |
245 | if class_name in ('_UserComponent', 'UserSourceComponent', 'UserFilterComponent', 'UserSinkComponent'): | |
246 | return | |
247 | ||
248 | comp_cls_name = kwargs.get('name', class_name) | |
249 | comp_cls_descr = getattr(cls, '__doc__', None) | |
250 | iter_cls = kwargs.get('notification_iterator_class') | |
251 | ||
252 | if UserSourceComponent in bases: | |
253 | _UserComponentType._set_iterator_class(cls, iter_cls) | |
254 | has_seek_time = _UserComponentType._has_seek_to_time_method(cls._iter_cls) | |
255 | cc_ptr = native_bt.py3_component_class_source_create(cls, | |
256 | comp_cls_name, | |
257 | comp_cls_descr, | |
258 | has_seek_time) | |
259 | elif UserFilterComponent in bases: | |
260 | _UserComponentType._set_iterator_class(cls, iter_cls) | |
261 | has_seek_time = _UserComponentType._has_seek_to_time_method(cls._iter_cls) | |
262 | cc_ptr = native_bt.py3_component_class_filter_create(cls, | |
263 | comp_cls_name, | |
264 | comp_cls_descr, | |
265 | has_seek_time) | |
266 | elif UserSinkComponent in bases: | |
267 | if not hasattr(cls, '_consume'): | |
268 | raise bt2.IncompleteUserClassError("cannot create component class '{}': missing a _consume() method".format(class_name)) | |
269 | ||
270 | cc_ptr = native_bt.py3_component_class_sink_create(cls, | |
271 | comp_cls_name, | |
272 | comp_cls_descr) | |
273 | else: | |
274 | raise bt2.IncompleteUserClassError("cannot find a known component class base in the bases of '{}'".format(class_name)) | |
275 | ||
276 | if cc_ptr is None: | |
277 | raise bt2.CreationError("cannot create component class '{}'".format(class_name)) | |
278 | ||
279 | cls._cc_ptr = cc_ptr | |
280 | ||
281 | def __call__(cls, *args, **kwargs): | |
282 | # create instance | |
283 | self = cls.__new__(cls) | |
284 | ||
285 | # assign native component pointer received from caller | |
286 | self._ptr = kwargs.get('__comp_ptr') | |
287 | name = kwargs.get('name') | |
288 | ||
289 | if self._ptr is None: | |
290 | # called from Python code | |
291 | self._belongs_to_native_component = False | |
292 | ||
293 | # py3_component_create() will call self.__init__() with the | |
294 | # desired arguments and keyword arguments. This is needed | |
295 | # because functions such as | |
296 | # bt_component_sink_set_minimum_input_count() can only be | |
297 | # called _during_ the bt_component_create() call (in | |
298 | # Python words: during self.__init__()). | |
299 | # | |
300 | # The arguments and keyword arguments to use for | |
301 | # self.__init__() are put in the object itself to find them | |
302 | # from the bt_component_create() function. | |
303 | self._init_args = args | |
304 | self._init_kwargs = kwargs | |
305 | native_bt.py3_component_create(cls._cc_ptr, self, name) | |
306 | ||
307 | # At this point, self._ptr should be set to non-None. If | |
308 | # it's not, an error occured during the | |
309 | # native_bt.py3_component_create() call. We consider this a | |
310 | # creation error. | |
311 | if self._ptr is None: | |
312 | raise bt2.CreationError("cannot create component object from component class '{}'".format(cls.__name__)) | |
313 | else: | |
314 | # Called from non-Python code (within | |
315 | # bt_component_create()): call __init__() here, after | |
316 | # removing the __comp_ptr keyword argument which is just for | |
317 | # this __call__() method. | |
318 | self._belongs_to_native_component = True | |
319 | del kwargs['__comp_ptr'] | |
320 | ||
321 | # inject `name` into the keyword arguments | |
322 | kwargs['name'] = self.name | |
323 | self.__init__(*args, **kwargs) | |
324 | ||
325 | return self | |
326 | ||
327 | @staticmethod | |
328 | def _has_seek_to_time_method(iter_cls): | |
329 | return hasattr(iter_cls, '_seek_to_time') | |
330 | ||
331 | @staticmethod | |
332 | def _set_iterator_class(cls, iter_cls): | |
333 | if iter_cls is None: | |
334 | raise bt2.IncompleteUserClassError("cannot create component class '{}': missing notification iterator class".format(cls.__name__)) | |
335 | ||
336 | if not issubclass(iter_cls, bt2.notification_iterator.UserNotificationIterator): | |
337 | raise bt2.IncompleteUserClassError("cannot create component class '{}': notification iterator class does not inherit bt2.UserNotificationIterator".format(cls.__name__)) | |
338 | ||
339 | if not hasattr(iter_cls, '_get'): | |
340 | raise bt2.IncompleteUserClassError("cannot create component class '{}': notification iterator class is missing a _get() method".format(cls.__name__)) | |
341 | ||
342 | if not hasattr(iter_cls, '_next'): | |
343 | raise bt2.IncompleteUserClassError("cannot create component class '{}': notification iterator class is missing a _next() method".format(cls.__name__)) | |
344 | ||
345 | cls._iter_cls = iter_cls | |
346 | ||
347 | @property | |
348 | def name(cls): | |
349 | return native_bt.component_class_get_name(cls._cc_ptr) | |
350 | ||
351 | @property | |
352 | def description(cls): | |
353 | return native_bt.component_class_get_description(cls._cc_ptr) | |
354 | ||
355 | @property | |
356 | def addr(cls): | |
357 | return int(cls._cc_ptr) | |
358 | ||
359 | def __del__(cls): | |
360 | if hasattr(cls, '_cc_ptr'): | |
361 | native_bt.put(cls._cc_ptr) | |
362 | ||
363 | ||
364 | class _ComponentInputNotificationIterators(collections.abc.Sequence): | |
365 | def __init__(self, comp, count_func, get_func): | |
366 | self._comp = comp | |
367 | self._count_func = count_func | |
368 | self._get_func = get_func | |
369 | ||
370 | def __len__(self): | |
371 | status, count = self._count_func(self._comp._ptr) | |
372 | utils._handle_ret(status, "cannot get component object's input notification iterator count") | |
373 | return count | |
374 | ||
375 | def __getitem__(self, index): | |
376 | utils._check_uint64(index) | |
377 | ||
378 | if index >= len(self): | |
379 | raise IndexError | |
380 | ||
381 | notif_iter_ptr = self._get_func(self._comp._ptr, index) | |
382 | utils._handle_ptr(notif_iter_ptr, "cannot get component object's input notification iterator") | |
383 | return bt2.notification_iterator._GenericNotificationIterator._create_from_ptr(notif_iter_ptr) | |
384 | ||
385 | ||
386 | class _UserComponent(metaclass=_UserComponentType): | |
387 | @property | |
388 | def addr(self): | |
389 | return int(self._ptr) | |
390 | ||
391 | def __init__(self, *args, **kwargs): | |
392 | pass | |
393 | ||
394 | def _destroy(self): | |
395 | pass | |
396 | ||
397 | def __del__(self): | |
398 | if not self._belongs_to_native_component: | |
399 | self._belongs_to_native_component = True | |
400 | native_bt.py3_component_on_del(self) | |
401 | native_bt.put(self._ptr) | |
402 | ||
403 | ||
404 | class UserSourceComponent(_UserComponent, _CommonSourceComponentMethods): | |
405 | pass | |
406 | ||
407 | ||
408 | class UserFilterComponent(_UserComponent, _CommonFilterComponentMethods): | |
409 | def _set_minimum_input_notification_iterator_count(self, count): | |
410 | utils._check_uint64(count) | |
411 | status = native_bt.component_filter_set_minimum_input_count(self._ptr, count) | |
412 | self._handle_status(status, "unexpected error: cannot set filter component object's minimum input notification iterator count") | |
413 | ||
414 | _minimum_input_notification_iterator_count = property(fset=_set_minimum_input_notification_iterator_count) | |
415 | ||
416 | def _set_maximum_input_notification_iterator_count(self, count): | |
417 | utils._check_uint64(count) | |
418 | status = native_bt.component_filter_set_maximum_input_count(self._ptr, count) | |
419 | self._handle_status(status, "unexpected error: cannot set filter component object's maximum input notification iterator count") | |
420 | ||
421 | _maximum_input_notification_iterator_count = property(fset=_set_maximum_input_notification_iterator_count) | |
422 | ||
423 | @property | |
424 | def _input_notification_iterators(self): | |
425 | return _ComponentInputNotificationIterators(self, | |
426 | native_bt.component_filter_get_input_count, | |
427 | native_bt.component_filter_get_input_iterator_private) | |
428 | ||
429 | def _add_iterator_from_bt(self, notif_iter_ptr): | |
430 | notif_iter = bt2.notification_iterator._GenericNotificationIterator._create_from_ptr(notif_iter_ptr) | |
431 | self._add_notification_iterator(notif_iter) | |
432 | ||
433 | def _add_notification_iterator(self, notif_iter): | |
434 | pass | |
435 | ||
436 | ||
437 | class UserSinkComponent(_UserComponent, _CommonSinkComponentMethods): | |
438 | def _set_minimum_input_notification_iterator_count(self, count): | |
439 | utils._check_uint64(count) | |
440 | status = native_bt.component_sink_set_minimum_input_count(self._ptr, count) | |
441 | self._handle_status(status, "unexpected error: cannot set sink component object's minimum input notification iterator count") | |
442 | ||
443 | _minimum_input_notification_iterator_count = property(fset=_set_minimum_input_notification_iterator_count) | |
444 | ||
445 | def _set_maximum_input_notification_iterator_count(self, count): | |
446 | utils._check_uint64(count) | |
447 | status = native_bt.component_sink_set_maximum_input_count(self._ptr, count) | |
448 | self._handle_status(status, "unexpected error: cannot set sink component object's maximum input notification iterator count") | |
449 | ||
450 | _maximum_input_notification_iterator_count = property(fset=_set_maximum_input_notification_iterator_count) | |
451 | ||
452 | @property | |
453 | def _input_notification_iterators(self): | |
454 | return _ComponentInputNotificationIterators(self, | |
455 | native_bt.component_sink_get_input_count, | |
456 | native_bt.component_sink_get_input_iterator_private) | |
457 | ||
458 | def _add_iterator_from_bt(self, notif_iter_ptr): | |
459 | notif_iter = bt2.notification_iterator._GenericNotificationIterator._create_from_ptr(notif_iter_ptr) | |
460 | self._add_notification_iterator(notif_iter) | |
461 | ||
462 | def _add_notification_iterator(self, notif_iter): | |
463 | pass |