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