Mercurial > libervia-backend
view libervia/backend/bridge/dbus_bridge.py @ 4318:27bb22eace65
tests (unit/email gateway): add test for XEP-0131 handling:
rel 451
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 28 Sep 2024 15:59:48 +0200 |
parents | 3a550e9a2b55 |
children |
line wrap: on
line source
#!/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("init_pre_script", arguments="", returns=""), 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("notification_add", arguments="ssssbbsdss", returns=""), Method("notification_delete", arguments="sbs", returns=""), Method("notifications_expired_clean", arguments="ds", returns=""), Method("notifications_get", arguments="ss", returns="s"), 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("message_update", "ssss"), Signal("notification_deleted", "ss"), Signal("notification_new", "sdssssbidss"), 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="", 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="", 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_init_pre_script( self, ): return self._callback( "init_pre_script", ) 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_notification_add( self, type_, body_plain, body_rich, title, is_global, requires_action, arg_6, priority, expire_at, extra, ): return self._callback( "notification_add", type_, body_plain, body_rich, title, is_global, requires_action, arg_6, priority, expire_at, extra, ) def dbus_notification_delete(self, id_, is_global, profile_key): return self._callback("notification_delete", id_, is_global, profile_key) def dbus_notifications_expired_clean(self, limit_timestamp, profile_key): return self._callback("notifications_expired_clean", limit_timestamp, profile_key) def dbus_notifications_get(self, filters, profile_key): return self._callback("notifications_get", filters, profile_key) 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 message_update(self, uid, message_type, message_data, profile): self._obj.emitSignal("message_update", uid, message_type, message_data, profile) def notification_deleted(self, id, profile): self._obj.emitSignal("notification_deleted", id, profile) def notification_new( self, id, timestamp, type, body_plain, body_rich, title, requires_action, priority, expire_at, extra, profile, ): self._obj.emitSignal( "notification_new", id, timestamp, type, body_plain, body_rich, title, requires_action, priority, expire_at, 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))