Mercurial > libervia-backend
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libervia/backend/bridge/dbus_bridge.py Fri Jun 02 11:49:51 2023 +0200 @@ -0,0 +1,495 @@ +#!/usr/bin/env python3 + +# Libervia communication bridge +# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from types import MethodType +from functools import partialmethod +from twisted.internet import defer, reactor +from libervia.backend.core.i18n import _ +from libervia.backend.core.log import getLogger +from libervia.backend.core.exceptions import BridgeInitError +from libervia.backend.tools import config +from txdbus import client, objects, error +from txdbus.interface import DBusInterface, Method, Signal + + +log = getLogger(__name__) + +# Interface prefix +const_INT_PREFIX = config.config_get( + config.parse_main_conf(), + "", + "bridge_dbus_int_prefix", + "org.libervia.Libervia") +const_ERROR_PREFIX = const_INT_PREFIX + ".error" +const_OBJ_PATH = "/org/libervia/Libervia/bridge" +const_CORE_SUFFIX = ".core" +const_PLUGIN_SUFFIX = ".plugin" + + +class ParseError(Exception): + pass + + +class DBusException(Exception): + pass + + +class MethodNotRegistered(DBusException): + dbusErrorName = const_ERROR_PREFIX + ".MethodNotRegistered" + + +class GenericException(DBusException): + def __init__(self, twisted_error): + """ + + @param twisted_error (Failure): instance of twisted Failure + error message is used to store a repr of message and condition in a tuple, + so it can be evaluated by the frontend bridge. + """ + try: + # twisted_error.value is a class + class_ = twisted_error.value().__class__ + except TypeError: + # twisted_error.value is an instance + class_ = twisted_error.value.__class__ + data = twisted_error.getErrorMessage() + try: + data = (data, twisted_error.value.condition) + except AttributeError: + data = (data,) + else: + data = (str(twisted_error),) + self.dbusErrorName = ".".join( + (const_ERROR_PREFIX, class_.__module__, class_.__name__) + ) + super(GenericException, self).__init__(repr(data)) + + @classmethod + def create_and_raise(cls, exc): + raise cls(exc) + + +class DBusObject(objects.DBusObject): + + core_iface = DBusInterface( + const_INT_PREFIX + const_CORE_SUFFIX, + Method('action_launch', arguments='sss', returns='s'), + Method('actions_get', arguments='s', returns='a(ssi)'), + Method('config_get', arguments='ss', returns='s'), + Method('connect', arguments='ssa{ss}', returns='b'), + Method('contact_add', arguments='ss', returns=''), + Method('contact_del', arguments='ss', returns=''), + Method('contact_get', arguments='ss', returns='(a{ss}as)'), + Method('contact_update', arguments='ssass', returns=''), + Method('contacts_get', arguments='s', returns='a(sa{ss}as)'), + Method('contacts_get_from_group', arguments='ss', returns='as'), + Method('devices_infos_get', arguments='ss', returns='s'), + Method('disco_find_by_features', arguments='asa(ss)bbbbbs', returns='(a{sa(sss)}a{sa(sss)}a{sa(sss)})'), + Method('disco_infos', arguments='ssbs', returns='(asa(sss)a{sa(a{ss}as)})'), + Method('disco_items', arguments='ssbs', returns='a(sss)'), + Method('disconnect', arguments='s', returns=''), + Method('encryption_namespace_get', arguments='s', returns='s'), + Method('encryption_plugins_get', arguments='', returns='s'), + Method('encryption_trust_ui_get', arguments='sss', returns='s'), + Method('entities_data_get', arguments='asass', returns='a{sa{ss}}'), + Method('entity_data_get', arguments='sass', returns='a{ss}'), + Method('features_get', arguments='s', returns='a{sa{ss}}'), + Method('history_get', arguments='ssiba{ss}s', returns='a(sdssa{ss}a{ss}ss)'), + Method('image_check', arguments='s', returns='s'), + Method('image_convert', arguments='ssss', returns='s'), + Method('image_generate_preview', arguments='ss', returns='s'), + Method('image_resize', arguments='sii', returns='s'), + Method('is_connected', arguments='s', returns='b'), + Method('main_resource_get', arguments='ss', returns='s'), + Method('menu_help_get', arguments='ss', returns='s'), + Method('menu_launch', arguments='sasa{ss}is', returns='a{ss}'), + Method('menus_get', arguments='si', returns='a(ssasasa{ss})'), + Method('message_encryption_get', arguments='ss', returns='s'), + Method('message_encryption_start', arguments='ssbs', returns=''), + Method('message_encryption_stop', arguments='ss', returns=''), + Method('message_send', arguments='sa{ss}a{ss}sss', returns=''), + Method('namespaces_get', arguments='', returns='a{ss}'), + Method('param_get_a', arguments='ssss', returns='s'), + Method('param_get_a_async', arguments='sssis', returns='s'), + Method('param_set', arguments='sssis', returns=''), + Method('param_ui_get', arguments='isss', returns='s'), + Method('params_categories_get', arguments='', returns='as'), + Method('params_register_app', arguments='sis', returns=''), + Method('params_template_load', arguments='s', returns='b'), + Method('params_template_save', arguments='s', returns='b'), + Method('params_values_from_category_get_async', arguments='sisss', returns='a{ss}'), + Method('presence_set', arguments='ssa{ss}s', returns=''), + Method('presence_statuses_get', arguments='s', returns='a{sa{s(sia{ss})}}'), + Method('private_data_delete', arguments='sss', returns=''), + Method('private_data_get', arguments='sss', returns='s'), + Method('private_data_set', arguments='ssss', returns=''), + Method('profile_create', arguments='sss', returns=''), + Method('profile_delete_async', arguments='s', returns=''), + Method('profile_is_session_started', arguments='s', returns='b'), + Method('profile_name_get', arguments='s', returns='s'), + Method('profile_set_default', arguments='s', returns=''), + Method('profile_start_session', arguments='ss', returns='b'), + Method('profiles_list_get', arguments='bb', returns='as'), + Method('progress_get', arguments='ss', returns='a{ss}'), + Method('progress_get_all', arguments='s', returns='a{sa{sa{ss}}}'), + Method('progress_get_all_metadata', arguments='s', returns='a{sa{sa{ss}}}'), + Method('ready_get', arguments='', returns=''), + Method('roster_resync', arguments='s', returns=''), + Method('session_infos_get', arguments='s', returns='a{ss}'), + Method('sub_waiting_get', arguments='s', returns='a{ss}'), + Method('subscription', arguments='sss', returns=''), + Method('version_get', arguments='', returns='s'), + Signal('_debug', 'sa{ss}s'), + Signal('action_new', 'ssis'), + Signal('connected', 'ss'), + Signal('contact_deleted', 'ss'), + Signal('contact_new', 'sa{ss}ass'), + Signal('disconnected', 's'), + Signal('entity_data_updated', 'ssss'), + Signal('message_encryption_started', 'sss'), + Signal('message_encryption_stopped', 'sa{ss}s'), + Signal('message_new', 'sdssa{ss}a{ss}sss'), + Signal('param_update', 'ssss'), + Signal('presence_update', 'ssia{ss}s'), + Signal('progress_error', 'sss'), + Signal('progress_finished', 'sa{ss}s'), + Signal('progress_started', 'sa{ss}s'), + Signal('subscribe', 'sss'), + ) + plugin_iface = DBusInterface( + const_INT_PREFIX + const_PLUGIN_SUFFIX + ) + + dbusInterfaces = [core_iface, plugin_iface] + + def __init__(self, path): + super().__init__(path) + log.debug("Init DBusObject...") + self.cb = {} + + def register_method(self, name, cb): + self.cb[name] = cb + + def _callback(self, name, *args, **kwargs): + """Call the callback if it exists, raise an exception else""" + try: + cb = self.cb[name] + except KeyError: + raise MethodNotRegistered + else: + d = defer.maybeDeferred(cb, *args, **kwargs) + d.addErrback(GenericException.create_and_raise) + return d + + def dbus_action_launch(self, callback_id, data, profile_key="@DEFAULT@"): + return self._callback("action_launch", callback_id, data, profile_key) + + def dbus_actions_get(self, profile_key="@DEFAULT@"): + return self._callback("actions_get", profile_key) + + def dbus_config_get(self, section, name): + return self._callback("config_get", section, name) + + def dbus_connect(self, profile_key="@DEFAULT@", password='', options={}): + return self._callback("connect", profile_key, password, options) + + def dbus_contact_add(self, entity_jid, profile_key="@DEFAULT@"): + return self._callback("contact_add", entity_jid, profile_key) + + def dbus_contact_del(self, entity_jid, profile_key="@DEFAULT@"): + return self._callback("contact_del", entity_jid, profile_key) + + def dbus_contact_get(self, arg_0, profile_key="@DEFAULT@"): + return self._callback("contact_get", arg_0, profile_key) + + def dbus_contact_update(self, entity_jid, name, groups, profile_key="@DEFAULT@"): + return self._callback("contact_update", entity_jid, name, groups, profile_key) + + def dbus_contacts_get(self, profile_key="@DEFAULT@"): + return self._callback("contacts_get", profile_key) + + def dbus_contacts_get_from_group(self, group, profile_key="@DEFAULT@"): + return self._callback("contacts_get_from_group", group, profile_key) + + def dbus_devices_infos_get(self, bare_jid, profile_key): + return self._callback("devices_infos_get", bare_jid, profile_key) + + 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@"): + return self._callback("disco_find_by_features", namespaces, identities, bare_jid, service, roster, own_jid, local_device, profile_key) + + def dbus_disco_infos(self, entity_jid, node=u'', use_cache=True, profile_key="@DEFAULT@"): + return self._callback("disco_infos", entity_jid, node, use_cache, profile_key) + + def dbus_disco_items(self, entity_jid, node=u'', use_cache=True, profile_key="@DEFAULT@"): + return self._callback("disco_items", entity_jid, node, use_cache, profile_key) + + def dbus_disconnect(self, profile_key="@DEFAULT@"): + return self._callback("disconnect", profile_key) + + def dbus_encryption_namespace_get(self, arg_0): + return self._callback("encryption_namespace_get", arg_0) + + def dbus_encryption_plugins_get(self, ): + return self._callback("encryption_plugins_get", ) + + def dbus_encryption_trust_ui_get(self, to_jid, namespace, profile_key): + return self._callback("encryption_trust_ui_get", to_jid, namespace, profile_key) + + def dbus_entities_data_get(self, jids, keys, profile): + return self._callback("entities_data_get", jids, keys, profile) + + def dbus_entity_data_get(self, jid, keys, profile): + return self._callback("entity_data_get", jid, keys, profile) + + def dbus_features_get(self, profile_key): + return self._callback("features_get", profile_key) + + def dbus_history_get(self, from_jid, to_jid, limit, between=True, filters='', profile="@NONE@"): + return self._callback("history_get", from_jid, to_jid, limit, between, filters, profile) + + def dbus_image_check(self, arg_0): + return self._callback("image_check", arg_0) + + def dbus_image_convert(self, source, dest, arg_2, extra): + return self._callback("image_convert", source, dest, arg_2, extra) + + def dbus_image_generate_preview(self, image_path, profile_key): + return self._callback("image_generate_preview", image_path, profile_key) + + def dbus_image_resize(self, image_path, width, height): + return self._callback("image_resize", image_path, width, height) + + def dbus_is_connected(self, profile_key="@DEFAULT@"): + return self._callback("is_connected", profile_key) + + def dbus_main_resource_get(self, contact_jid, profile_key="@DEFAULT@"): + return self._callback("main_resource_get", contact_jid, profile_key) + + def dbus_menu_help_get(self, menu_id, language): + return self._callback("menu_help_get", menu_id, language) + + def dbus_menu_launch(self, menu_type, path, data, security_limit, profile_key): + return self._callback("menu_launch", menu_type, path, data, security_limit, profile_key) + + def dbus_menus_get(self, language, security_limit): + return self._callback("menus_get", language, security_limit) + + def dbus_message_encryption_get(self, to_jid, profile_key): + return self._callback("message_encryption_get", to_jid, profile_key) + + def dbus_message_encryption_start(self, to_jid, namespace='', replace=False, profile_key="@NONE@"): + return self._callback("message_encryption_start", to_jid, namespace, replace, profile_key) + + def dbus_message_encryption_stop(self, to_jid, profile_key): + return self._callback("message_encryption_stop", to_jid, profile_key) + + def dbus_message_send(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@"): + return self._callback("message_send", to_jid, message, subject, mess_type, extra, profile_key) + + def dbus_namespaces_get(self, ): + return self._callback("namespaces_get", ) + + def dbus_param_get_a(self, name, category, attribute="value", profile_key="@DEFAULT@"): + return self._callback("param_get_a", name, category, attribute, profile_key) + + def dbus_param_get_a_async(self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@"): + return self._callback("param_get_a_async", name, category, attribute, security_limit, profile_key) + + def dbus_param_set(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@"): + return self._callback("param_set", name, value, category, security_limit, profile_key) + + def dbus_param_ui_get(self, security_limit=-1, app='', extra='', profile_key="@DEFAULT@"): + return self._callback("param_ui_get", security_limit, app, extra, profile_key) + + def dbus_params_categories_get(self, ): + return self._callback("params_categories_get", ) + + def dbus_params_register_app(self, xml, security_limit=-1, app=''): + return self._callback("params_register_app", xml, security_limit, app) + + def dbus_params_template_load(self, filename): + return self._callback("params_template_load", filename) + + def dbus_params_template_save(self, filename): + return self._callback("params_template_save", filename) + + def dbus_params_values_from_category_get_async(self, category, security_limit=-1, app="", extra="", profile_key="@DEFAULT@"): + return self._callback("params_values_from_category_get_async", category, security_limit, app, extra, profile_key) + + def dbus_presence_set(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@"): + return self._callback("presence_set", to_jid, show, statuses, profile_key) + + def dbus_presence_statuses_get(self, profile_key="@DEFAULT@"): + return self._callback("presence_statuses_get", profile_key) + + def dbus_private_data_delete(self, namespace, key, arg_2): + return self._callback("private_data_delete", namespace, key, arg_2) + + def dbus_private_data_get(self, namespace, key, profile_key): + return self._callback("private_data_get", namespace, key, profile_key) + + def dbus_private_data_set(self, namespace, key, data, profile_key): + return self._callback("private_data_set", namespace, key, data, profile_key) + + def dbus_profile_create(self, profile, password='', component=''): + return self._callback("profile_create", profile, password, component) + + def dbus_profile_delete_async(self, profile): + return self._callback("profile_delete_async", profile) + + def dbus_profile_is_session_started(self, profile_key="@DEFAULT@"): + return self._callback("profile_is_session_started", profile_key) + + def dbus_profile_name_get(self, profile_key="@DEFAULT@"): + return self._callback("profile_name_get", profile_key) + + def dbus_profile_set_default(self, profile): + return self._callback("profile_set_default", profile) + + def dbus_profile_start_session(self, password='', profile_key="@DEFAULT@"): + return self._callback("profile_start_session", password, profile_key) + + def dbus_profiles_list_get(self, clients=True, components=False): + return self._callback("profiles_list_get", clients, components) + + def dbus_progress_get(self, id, profile): + return self._callback("progress_get", id, profile) + + def dbus_progress_get_all(self, profile): + return self._callback("progress_get_all", profile) + + def dbus_progress_get_all_metadata(self, profile): + return self._callback("progress_get_all_metadata", profile) + + def dbus_ready_get(self, ): + return self._callback("ready_get", ) + + def dbus_roster_resync(self, profile_key="@DEFAULT@"): + return self._callback("roster_resync", profile_key) + + def dbus_session_infos_get(self, profile_key): + return self._callback("session_infos_get", profile_key) + + def dbus_sub_waiting_get(self, profile_key="@DEFAULT@"): + return self._callback("sub_waiting_get", profile_key) + + def dbus_subscription(self, sub_type, entity, profile_key="@DEFAULT@"): + return self._callback("subscription", sub_type, entity, profile_key) + + def dbus_version_get(self, ): + return self._callback("version_get", ) + + +class bridge: + + def __init__(self): + log.info("Init DBus...") + self._obj = DBusObject(const_OBJ_PATH) + + async def post_init(self): + try: + conn = await client.connect(reactor) + except error.DBusException as e: + if e.errName == "org.freedesktop.DBus.Error.NotSupported": + log.error( + _( + "D-Bus is not launched, please see README to see instructions on " + "how to launch it" + ) + ) + raise BridgeInitError(str(e)) + + conn.exportObject(self._obj) + await conn.requestBusName(const_INT_PREFIX) + + def _debug(self, action, params, profile): + self._obj.emitSignal("_debug", action, params, profile) + + def action_new(self, action_data, id, security_limit, profile): + self._obj.emitSignal("action_new", action_data, id, security_limit, profile) + + def connected(self, jid_s, profile): + self._obj.emitSignal("connected", jid_s, profile) + + def contact_deleted(self, entity_jid, profile): + self._obj.emitSignal("contact_deleted", entity_jid, profile) + + def contact_new(self, contact_jid, attributes, groups, profile): + self._obj.emitSignal("contact_new", contact_jid, attributes, groups, profile) + + def disconnected(self, profile): + self._obj.emitSignal("disconnected", profile) + + def entity_data_updated(self, jid, name, value, profile): + self._obj.emitSignal("entity_data_updated", jid, name, value, profile) + + def message_encryption_started(self, to_jid, encryption_data, profile_key): + self._obj.emitSignal("message_encryption_started", to_jid, encryption_data, profile_key) + + def message_encryption_stopped(self, to_jid, encryption_data, profile_key): + self._obj.emitSignal("message_encryption_stopped", to_jid, encryption_data, profile_key) + + def message_new(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile): + self._obj.emitSignal("message_new", uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile) + + def param_update(self, name, value, category, profile): + self._obj.emitSignal("param_update", name, value, category, profile) + + def presence_update(self, entity_jid, show, priority, statuses, profile): + self._obj.emitSignal("presence_update", entity_jid, show, priority, statuses, profile) + + def progress_error(self, id, error, profile): + self._obj.emitSignal("progress_error", id, error, profile) + + def progress_finished(self, id, metadata, profile): + self._obj.emitSignal("progress_finished", id, metadata, profile) + + def progress_started(self, id, metadata, profile): + self._obj.emitSignal("progress_started", id, metadata, profile) + + def subscribe(self, sub_type, entity_jid, profile): + self._obj.emitSignal("subscribe", sub_type, entity_jid, profile) + + def register_method(self, name, callback): + log.debug(f"registering DBus bridge method [{name}]") + self._obj.register_method(name, callback) + + def emit_signal(self, name, *args): + self._obj.emitSignal(name, *args) + + def add_method( + self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={} + ): + """Dynamically add a method to D-Bus bridge""" + # FIXME: doc parameter is kept only temporary, the time to remove it from calls + log.debug(f"Adding method {name!r} to D-Bus bridge") + self._obj.plugin_iface.addMethod( + Method(name, arguments=in_sign, returns=out_sign) + ) + # we have to create a method here instead of using partialmethod, because txdbus + # uses __func__ which doesn't work with partialmethod + def caller(self_, *args, **kwargs): + return self_._callback(name, *args, **kwargs) + setattr(self._obj, f"dbus_{name}", MethodType(caller, self._obj)) + self.register_method(name, method) + + def add_signal(self, name, int_suffix, signature, doc={}): + """Dynamically add a signal to D-Bus bridge""" + log.debug(f"Adding signal {name!r} to D-Bus bridge") + self._obj.plugin_iface.addSignal(Signal(name, signature)) + setattr(bridge, name, partialmethod(bridge.emit_signal, name)) \ No newline at end of file