comparison libervia/backend/bridge/dbus_bridge.py @ 4071:4b842c1fb686

refactoring: renamed `sat` package to `libervia.backend`
author Goffi <goffi@goffi.org>
date Fri, 02 Jun 2023 11:49:51 +0200
parents sat/bridge/dbus_bridge.py@2594e1951cf7
children 02f0adc745c6
comparison
equal deleted inserted replaced
4070:d10748475025 4071:4b842c1fb686
1 #!/usr/bin/env python3
2
3 # Libervia communication bridge
4 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
5
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Affero General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Affero General Public License for more details.
15
16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 from types import MethodType
20 from functools import partialmethod
21 from twisted.internet import defer, reactor
22 from libervia.backend.core.i18n import _
23 from libervia.backend.core.log import getLogger
24 from libervia.backend.core.exceptions import BridgeInitError
25 from libervia.backend.tools import config
26 from txdbus import client, objects, error
27 from txdbus.interface import DBusInterface, Method, Signal
28
29
30 log = getLogger(__name__)
31
32 # Interface prefix
33 const_INT_PREFIX = config.config_get(
34 config.parse_main_conf(),
35 "",
36 "bridge_dbus_int_prefix",
37 "org.libervia.Libervia")
38 const_ERROR_PREFIX = const_INT_PREFIX + ".error"
39 const_OBJ_PATH = "/org/libervia/Libervia/bridge"
40 const_CORE_SUFFIX = ".core"
41 const_PLUGIN_SUFFIX = ".plugin"
42
43
44 class ParseError(Exception):
45 pass
46
47
48 class DBusException(Exception):
49 pass
50
51
52 class MethodNotRegistered(DBusException):
53 dbusErrorName = const_ERROR_PREFIX + ".MethodNotRegistered"
54
55
56 class GenericException(DBusException):
57 def __init__(self, twisted_error):
58 """
59
60 @param twisted_error (Failure): instance of twisted Failure
61 error message is used to store a repr of message and condition in a tuple,
62 so it can be evaluated by the frontend bridge.
63 """
64 try:
65 # twisted_error.value is a class
66 class_ = twisted_error.value().__class__
67 except TypeError:
68 # twisted_error.value is an instance
69 class_ = twisted_error.value.__class__
70 data = twisted_error.getErrorMessage()
71 try:
72 data = (data, twisted_error.value.condition)
73 except AttributeError:
74 data = (data,)
75 else:
76 data = (str(twisted_error),)
77 self.dbusErrorName = ".".join(
78 (const_ERROR_PREFIX, class_.__module__, class_.__name__)
79 )
80 super(GenericException, self).__init__(repr(data))
81
82 @classmethod
83 def create_and_raise(cls, exc):
84 raise cls(exc)
85
86
87 class DBusObject(objects.DBusObject):
88
89 core_iface = DBusInterface(
90 const_INT_PREFIX + const_CORE_SUFFIX,
91 Method('action_launch', arguments='sss', returns='s'),
92 Method('actions_get', arguments='s', returns='a(ssi)'),
93 Method('config_get', arguments='ss', returns='s'),
94 Method('connect', arguments='ssa{ss}', returns='b'),
95 Method('contact_add', arguments='ss', returns=''),
96 Method('contact_del', arguments='ss', returns=''),
97 Method('contact_get', arguments='ss', returns='(a{ss}as)'),
98 Method('contact_update', arguments='ssass', returns=''),
99 Method('contacts_get', arguments='s', returns='a(sa{ss}as)'),
100 Method('contacts_get_from_group', arguments='ss', returns='as'),
101 Method('devices_infos_get', arguments='ss', returns='s'),
102 Method('disco_find_by_features', arguments='asa(ss)bbbbbs', returns='(a{sa(sss)}a{sa(sss)}a{sa(sss)})'),
103 Method('disco_infos', arguments='ssbs', returns='(asa(sss)a{sa(a{ss}as)})'),
104 Method('disco_items', arguments='ssbs', returns='a(sss)'),
105 Method('disconnect', arguments='s', returns=''),
106 Method('encryption_namespace_get', arguments='s', returns='s'),
107 Method('encryption_plugins_get', arguments='', returns='s'),
108 Method('encryption_trust_ui_get', arguments='sss', returns='s'),
109 Method('entities_data_get', arguments='asass', returns='a{sa{ss}}'),
110 Method('entity_data_get', arguments='sass', returns='a{ss}'),
111 Method('features_get', arguments='s', returns='a{sa{ss}}'),
112 Method('history_get', arguments='ssiba{ss}s', returns='a(sdssa{ss}a{ss}ss)'),
113 Method('image_check', arguments='s', returns='s'),
114 Method('image_convert', arguments='ssss', returns='s'),
115 Method('image_generate_preview', arguments='ss', returns='s'),
116 Method('image_resize', arguments='sii', returns='s'),
117 Method('is_connected', arguments='s', returns='b'),
118 Method('main_resource_get', arguments='ss', returns='s'),
119 Method('menu_help_get', arguments='ss', returns='s'),
120 Method('menu_launch', arguments='sasa{ss}is', returns='a{ss}'),
121 Method('menus_get', arguments='si', returns='a(ssasasa{ss})'),
122 Method('message_encryption_get', arguments='ss', returns='s'),
123 Method('message_encryption_start', arguments='ssbs', returns=''),
124 Method('message_encryption_stop', arguments='ss', returns=''),
125 Method('message_send', arguments='sa{ss}a{ss}sss', returns=''),
126 Method('namespaces_get', arguments='', returns='a{ss}'),
127 Method('param_get_a', arguments='ssss', returns='s'),
128 Method('param_get_a_async', arguments='sssis', returns='s'),
129 Method('param_set', arguments='sssis', returns=''),
130 Method('param_ui_get', arguments='isss', returns='s'),
131 Method('params_categories_get', arguments='', returns='as'),
132 Method('params_register_app', arguments='sis', returns=''),
133 Method('params_template_load', arguments='s', returns='b'),
134 Method('params_template_save', arguments='s', returns='b'),
135 Method('params_values_from_category_get_async', arguments='sisss', returns='a{ss}'),
136 Method('presence_set', arguments='ssa{ss}s', returns=''),
137 Method('presence_statuses_get', arguments='s', returns='a{sa{s(sia{ss})}}'),
138 Method('private_data_delete', arguments='sss', returns=''),
139 Method('private_data_get', arguments='sss', returns='s'),
140 Method('private_data_set', arguments='ssss', returns=''),
141 Method('profile_create', arguments='sss', returns=''),
142 Method('profile_delete_async', arguments='s', returns=''),
143 Method('profile_is_session_started', arguments='s', returns='b'),
144 Method('profile_name_get', arguments='s', returns='s'),
145 Method('profile_set_default', arguments='s', returns=''),
146 Method('profile_start_session', arguments='ss', returns='b'),
147 Method('profiles_list_get', arguments='bb', returns='as'),
148 Method('progress_get', arguments='ss', returns='a{ss}'),
149 Method('progress_get_all', arguments='s', returns='a{sa{sa{ss}}}'),
150 Method('progress_get_all_metadata', arguments='s', returns='a{sa{sa{ss}}}'),
151 Method('ready_get', arguments='', returns=''),
152 Method('roster_resync', arguments='s', returns=''),
153 Method('session_infos_get', arguments='s', returns='a{ss}'),
154 Method('sub_waiting_get', arguments='s', returns='a{ss}'),
155 Method('subscription', arguments='sss', returns=''),
156 Method('version_get', arguments='', returns='s'),
157 Signal('_debug', 'sa{ss}s'),
158 Signal('action_new', 'ssis'),
159 Signal('connected', 'ss'),
160 Signal('contact_deleted', 'ss'),
161 Signal('contact_new', 'sa{ss}ass'),
162 Signal('disconnected', 's'),
163 Signal('entity_data_updated', 'ssss'),
164 Signal('message_encryption_started', 'sss'),
165 Signal('message_encryption_stopped', 'sa{ss}s'),
166 Signal('message_new', 'sdssa{ss}a{ss}sss'),
167 Signal('param_update', 'ssss'),
168 Signal('presence_update', 'ssia{ss}s'),
169 Signal('progress_error', 'sss'),
170 Signal('progress_finished', 'sa{ss}s'),
171 Signal('progress_started', 'sa{ss}s'),
172 Signal('subscribe', 'sss'),
173 )
174 plugin_iface = DBusInterface(
175 const_INT_PREFIX + const_PLUGIN_SUFFIX
176 )
177
178 dbusInterfaces = [core_iface, plugin_iface]
179
180 def __init__(self, path):
181 super().__init__(path)
182 log.debug("Init DBusObject...")
183 self.cb = {}
184
185 def register_method(self, name, cb):
186 self.cb[name] = cb
187
188 def _callback(self, name, *args, **kwargs):
189 """Call the callback if it exists, raise an exception else"""
190 try:
191 cb = self.cb[name]
192 except KeyError:
193 raise MethodNotRegistered
194 else:
195 d = defer.maybeDeferred(cb, *args, **kwargs)
196 d.addErrback(GenericException.create_and_raise)
197 return d
198
199 def dbus_action_launch(self, callback_id, data, profile_key="@DEFAULT@"):
200 return self._callback("action_launch", callback_id, data, profile_key)
201
202 def dbus_actions_get(self, profile_key="@DEFAULT@"):
203 return self._callback("actions_get", profile_key)
204
205 def dbus_config_get(self, section, name):
206 return self._callback("config_get", section, name)
207
208 def dbus_connect(self, profile_key="@DEFAULT@", password='', options={}):
209 return self._callback("connect", profile_key, password, options)
210
211 def dbus_contact_add(self, entity_jid, profile_key="@DEFAULT@"):
212 return self._callback("contact_add", entity_jid, profile_key)
213
214 def dbus_contact_del(self, entity_jid, profile_key="@DEFAULT@"):
215 return self._callback("contact_del", entity_jid, profile_key)
216
217 def dbus_contact_get(self, arg_0, profile_key="@DEFAULT@"):
218 return self._callback("contact_get", arg_0, profile_key)
219
220 def dbus_contact_update(self, entity_jid, name, groups, profile_key="@DEFAULT@"):
221 return self._callback("contact_update", entity_jid, name, groups, profile_key)
222
223 def dbus_contacts_get(self, profile_key="@DEFAULT@"):
224 return self._callback("contacts_get", profile_key)
225
226 def dbus_contacts_get_from_group(self, group, profile_key="@DEFAULT@"):
227 return self._callback("contacts_get_from_group", group, profile_key)
228
229 def dbus_devices_infos_get(self, bare_jid, profile_key):
230 return self._callback("devices_infos_get", bare_jid, profile_key)
231
232 def dbus_disco_find_by_features(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key="@DEFAULT@"):
233 return self._callback("disco_find_by_features", namespaces, identities, bare_jid, service, roster, own_jid, local_device, profile_key)
234
235 def dbus_disco_infos(self, entity_jid, node=u'', use_cache=True, profile_key="@DEFAULT@"):
236 return self._callback("disco_infos", entity_jid, node, use_cache, profile_key)
237
238 def dbus_disco_items(self, entity_jid, node=u'', use_cache=True, profile_key="@DEFAULT@"):
239 return self._callback("disco_items", entity_jid, node, use_cache, profile_key)
240
241 def dbus_disconnect(self, profile_key="@DEFAULT@"):
242 return self._callback("disconnect", profile_key)
243
244 def dbus_encryption_namespace_get(self, arg_0):
245 return self._callback("encryption_namespace_get", arg_0)
246
247 def dbus_encryption_plugins_get(self, ):
248 return self._callback("encryption_plugins_get", )
249
250 def dbus_encryption_trust_ui_get(self, to_jid, namespace, profile_key):
251 return self._callback("encryption_trust_ui_get", to_jid, namespace, profile_key)
252
253 def dbus_entities_data_get(self, jids, keys, profile):
254 return self._callback("entities_data_get", jids, keys, profile)
255
256 def dbus_entity_data_get(self, jid, keys, profile):
257 return self._callback("entity_data_get", jid, keys, profile)
258
259 def dbus_features_get(self, profile_key):
260 return self._callback("features_get", profile_key)
261
262 def dbus_history_get(self, from_jid, to_jid, limit, between=True, filters='', profile="@NONE@"):
263 return self._callback("history_get", from_jid, to_jid, limit, between, filters, profile)
264
265 def dbus_image_check(self, arg_0):
266 return self._callback("image_check", arg_0)
267
268 def dbus_image_convert(self, source, dest, arg_2, extra):
269 return self._callback("image_convert", source, dest, arg_2, extra)
270
271 def dbus_image_generate_preview(self, image_path, profile_key):
272 return self._callback("image_generate_preview", image_path, profile_key)
273
274 def dbus_image_resize(self, image_path, width, height):
275 return self._callback("image_resize", image_path, width, height)
276
277 def dbus_is_connected(self, profile_key="@DEFAULT@"):
278 return self._callback("is_connected", profile_key)
279
280 def dbus_main_resource_get(self, contact_jid, profile_key="@DEFAULT@"):
281 return self._callback("main_resource_get", contact_jid, profile_key)
282
283 def dbus_menu_help_get(self, menu_id, language):
284 return self._callback("menu_help_get", menu_id, language)
285
286 def dbus_menu_launch(self, menu_type, path, data, security_limit, profile_key):
287 return self._callback("menu_launch", menu_type, path, data, security_limit, profile_key)
288
289 def dbus_menus_get(self, language, security_limit):
290 return self._callback("menus_get", language, security_limit)
291
292 def dbus_message_encryption_get(self, to_jid, profile_key):
293 return self._callback("message_encryption_get", to_jid, profile_key)
294
295 def dbus_message_encryption_start(self, to_jid, namespace='', replace=False, profile_key="@NONE@"):
296 return self._callback("message_encryption_start", to_jid, namespace, replace, profile_key)
297
298 def dbus_message_encryption_stop(self, to_jid, profile_key):
299 return self._callback("message_encryption_stop", to_jid, profile_key)
300
301 def dbus_message_send(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@"):
302 return self._callback("message_send", to_jid, message, subject, mess_type, extra, profile_key)
303
304 def dbus_namespaces_get(self, ):
305 return self._callback("namespaces_get", )
306
307 def dbus_param_get_a(self, name, category, attribute="value", profile_key="@DEFAULT@"):
308 return self._callback("param_get_a", name, category, attribute, profile_key)
309
310 def dbus_param_get_a_async(self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@"):
311 return self._callback("param_get_a_async", name, category, attribute, security_limit, profile_key)
312
313 def dbus_param_set(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@"):
314 return self._callback("param_set", name, value, category, security_limit, profile_key)
315
316 def dbus_param_ui_get(self, security_limit=-1, app='', extra='', profile_key="@DEFAULT@"):
317 return self._callback("param_ui_get", security_limit, app, extra, profile_key)
318
319 def dbus_params_categories_get(self, ):
320 return self._callback("params_categories_get", )
321
322 def dbus_params_register_app(self, xml, security_limit=-1, app=''):
323 return self._callback("params_register_app", xml, security_limit, app)
324
325 def dbus_params_template_load(self, filename):
326 return self._callback("params_template_load", filename)
327
328 def dbus_params_template_save(self, filename):
329 return self._callback("params_template_save", filename)
330
331 def dbus_params_values_from_category_get_async(self, category, security_limit=-1, app="", extra="", profile_key="@DEFAULT@"):
332 return self._callback("params_values_from_category_get_async", category, security_limit, app, extra, profile_key)
333
334 def dbus_presence_set(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@"):
335 return self._callback("presence_set", to_jid, show, statuses, profile_key)
336
337 def dbus_presence_statuses_get(self, profile_key="@DEFAULT@"):
338 return self._callback("presence_statuses_get", profile_key)
339
340 def dbus_private_data_delete(self, namespace, key, arg_2):
341 return self._callback("private_data_delete", namespace, key, arg_2)
342
343 def dbus_private_data_get(self, namespace, key, profile_key):
344 return self._callback("private_data_get", namespace, key, profile_key)
345
346 def dbus_private_data_set(self, namespace, key, data, profile_key):
347 return self._callback("private_data_set", namespace, key, data, profile_key)
348
349 def dbus_profile_create(self, profile, password='', component=''):
350 return self._callback("profile_create", profile, password, component)
351
352 def dbus_profile_delete_async(self, profile):
353 return self._callback("profile_delete_async", profile)
354
355 def dbus_profile_is_session_started(self, profile_key="@DEFAULT@"):
356 return self._callback("profile_is_session_started", profile_key)
357
358 def dbus_profile_name_get(self, profile_key="@DEFAULT@"):
359 return self._callback("profile_name_get", profile_key)
360
361 def dbus_profile_set_default(self, profile):
362 return self._callback("profile_set_default", profile)
363
364 def dbus_profile_start_session(self, password='', profile_key="@DEFAULT@"):
365 return self._callback("profile_start_session", password, profile_key)
366
367 def dbus_profiles_list_get(self, clients=True, components=False):
368 return self._callback("profiles_list_get", clients, components)
369
370 def dbus_progress_get(self, id, profile):
371 return self._callback("progress_get", id, profile)
372
373 def dbus_progress_get_all(self, profile):
374 return self._callback("progress_get_all", profile)
375
376 def dbus_progress_get_all_metadata(self, profile):
377 return self._callback("progress_get_all_metadata", profile)
378
379 def dbus_ready_get(self, ):
380 return self._callback("ready_get", )
381
382 def dbus_roster_resync(self, profile_key="@DEFAULT@"):
383 return self._callback("roster_resync", profile_key)
384
385 def dbus_session_infos_get(self, profile_key):
386 return self._callback("session_infos_get", profile_key)
387
388 def dbus_sub_waiting_get(self, profile_key="@DEFAULT@"):
389 return self._callback("sub_waiting_get", profile_key)
390
391 def dbus_subscription(self, sub_type, entity, profile_key="@DEFAULT@"):
392 return self._callback("subscription", sub_type, entity, profile_key)
393
394 def dbus_version_get(self, ):
395 return self._callback("version_get", )
396
397
398 class bridge:
399
400 def __init__(self):
401 log.info("Init DBus...")
402 self._obj = DBusObject(const_OBJ_PATH)
403
404 async def post_init(self):
405 try:
406 conn = await client.connect(reactor)
407 except error.DBusException as e:
408 if e.errName == "org.freedesktop.DBus.Error.NotSupported":
409 log.error(
410 _(
411 "D-Bus is not launched, please see README to see instructions on "
412 "how to launch it"
413 )
414 )
415 raise BridgeInitError(str(e))
416
417 conn.exportObject(self._obj)
418 await conn.requestBusName(const_INT_PREFIX)
419
420 def _debug(self, action, params, profile):
421 self._obj.emitSignal("_debug", action, params, profile)
422
423 def action_new(self, action_data, id, security_limit, profile):
424 self._obj.emitSignal("action_new", action_data, id, security_limit, profile)
425
426 def connected(self, jid_s, profile):
427 self._obj.emitSignal("connected", jid_s, profile)
428
429 def contact_deleted(self, entity_jid, profile):
430 self._obj.emitSignal("contact_deleted", entity_jid, profile)
431
432 def contact_new(self, contact_jid, attributes, groups, profile):
433 self._obj.emitSignal("contact_new", contact_jid, attributes, groups, profile)
434
435 def disconnected(self, profile):
436 self._obj.emitSignal("disconnected", profile)
437
438 def entity_data_updated(self, jid, name, value, profile):
439 self._obj.emitSignal("entity_data_updated", jid, name, value, profile)
440
441 def message_encryption_started(self, to_jid, encryption_data, profile_key):
442 self._obj.emitSignal("message_encryption_started", to_jid, encryption_data, profile_key)
443
444 def message_encryption_stopped(self, to_jid, encryption_data, profile_key):
445 self._obj.emitSignal("message_encryption_stopped", to_jid, encryption_data, profile_key)
446
447 def message_new(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile):
448 self._obj.emitSignal("message_new", uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile)
449
450 def param_update(self, name, value, category, profile):
451 self._obj.emitSignal("param_update", name, value, category, profile)
452
453 def presence_update(self, entity_jid, show, priority, statuses, profile):
454 self._obj.emitSignal("presence_update", entity_jid, show, priority, statuses, profile)
455
456 def progress_error(self, id, error, profile):
457 self._obj.emitSignal("progress_error", id, error, profile)
458
459 def progress_finished(self, id, metadata, profile):
460 self._obj.emitSignal("progress_finished", id, metadata, profile)
461
462 def progress_started(self, id, metadata, profile):
463 self._obj.emitSignal("progress_started", id, metadata, profile)
464
465 def subscribe(self, sub_type, entity_jid, profile):
466 self._obj.emitSignal("subscribe", sub_type, entity_jid, profile)
467
468 def register_method(self, name, callback):
469 log.debug(f"registering DBus bridge method [{name}]")
470 self._obj.register_method(name, callback)
471
472 def emit_signal(self, name, *args):
473 self._obj.emitSignal(name, *args)
474
475 def add_method(
476 self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={}
477 ):
478 """Dynamically add a method to D-Bus bridge"""
479 # FIXME: doc parameter is kept only temporary, the time to remove it from calls
480 log.debug(f"Adding method {name!r} to D-Bus bridge")
481 self._obj.plugin_iface.addMethod(
482 Method(name, arguments=in_sign, returns=out_sign)
483 )
484 # we have to create a method here instead of using partialmethod, because txdbus
485 # uses __func__ which doesn't work with partialmethod
486 def caller(self_, *args, **kwargs):
487 return self_._callback(name, *args, **kwargs)
488 setattr(self._obj, f"dbus_{name}", MethodType(caller, self._obj))
489 self.register_method(name, method)
490
491 def add_signal(self, name, int_suffix, signature, doc={}):
492 """Dynamically add a signal to D-Bus bridge"""
493 log.debug(f"Adding signal {name!r} to D-Bus bridge")
494 self._obj.plugin_iface.addSignal(Signal(name, signature))
495 setattr(bridge, name, partialmethod(bridge.emit_signal, name))