Mercurial > libervia-backend
view sat/bridge/dbus_bridge.py @ 3733:6cc39a3b8c14
tests (unit): AP gateway unit tests:
are covered:
- AP actor handle to XMPP JID/pubsub node
- XMPP JID/pubsub node to AP actor handle
- AP request to JID/pubsub node (AP collection to items/RSM metadata conversion)
- pubsub request to AP actor (pubsub request with RSM to AP collection/pagination requests
conversion)
ticket 363
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 31 Jan 2022 18:35:52 +0100 |
parents | 60d3861e5996 |
children | 524856bd7b19 |
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 sat.core.i18n import _ from sat.core.log import getLogger from sat.core.exceptions import BridgeInitError from sat.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.getConfig( config.parseMainConf(), "", "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('actionsGet', arguments='s', returns='a(a{ss}si)'), Method('addContact', arguments='ss', returns=''), Method('asyncDeleteProfile', arguments='s', returns=''), Method('asyncGetParamA', arguments='sssis', returns='s'), Method('asyncGetParamsValuesFromCategory', arguments='sisss', returns='a{ss}'), Method('connect', arguments='ssa{ss}', returns='b'), Method('contactGet', arguments='ss', returns='(a{ss}as)'), Method('delContact', arguments='ss', returns=''), Method('devicesInfosGet', arguments='ss', returns='s'), Method('discoFindByFeatures', arguments='asa(ss)bbbbbs', returns='(a{sa(sss)}a{sa(sss)}a{sa(sss)})'), Method('discoInfos', arguments='ssbs', returns='(asa(sss)a{sa(a{ss}as)})'), Method('discoItems', arguments='ssbs', returns='a(sss)'), Method('disconnect', arguments='s', returns=''), Method('encryptionNamespaceGet', arguments='s', returns='s'), Method('encryptionPluginsGet', arguments='', returns='s'), Method('encryptionTrustUIGet', arguments='sss', returns='s'), Method('getConfig', arguments='ss', returns='s'), Method('getContacts', arguments='s', returns='a(sa{ss}as)'), Method('getContactsFromGroup', arguments='ss', returns='as'), Method('getEntitiesData', arguments='asass', returns='a{sa{ss}}'), Method('getEntityData', arguments='sass', returns='a{ss}'), Method('getFeatures', arguments='s', returns='a{sa{ss}}'), Method('getMainResource', arguments='ss', returns='s'), Method('getParamA', arguments='ssss', returns='s'), Method('getParamsCategories', arguments='', returns='as'), Method('getParamsUI', arguments='isss', returns='s'), Method('getPresenceStatuses', arguments='s', returns='a{sa{s(sia{ss})}}'), Method('getReady', arguments='', returns=''), Method('getVersion', arguments='', returns='s'), Method('getWaitingSub', arguments='s', returns='a{ss}'), Method('historyGet', arguments='ssiba{ss}s', returns='a(sdssa{ss}a{ss}ss)'), Method('imageCheck', arguments='s', returns='s'), Method('imageConvert', arguments='ssss', returns='s'), Method('imageGeneratePreview', arguments='ss', returns='s'), Method('imageResize', arguments='sii', returns='s'), Method('isConnected', arguments='s', returns='b'), Method('launchAction', arguments='sa{ss}s', returns='a{ss}'), Method('loadParamsTemplate', arguments='s', returns='b'), Method('menuHelpGet', arguments='ss', returns='s'), Method('menuLaunch', arguments='sasa{ss}is', returns='a{ss}'), Method('menusGet', arguments='si', returns='a(ssasasa{ss})'), Method('messageEncryptionGet', arguments='ss', returns='s'), Method('messageEncryptionStart', arguments='ssbs', returns=''), Method('messageEncryptionStop', arguments='ss', returns=''), Method('messageSend', arguments='sa{ss}a{ss}sss', returns=''), Method('namespacesGet', arguments='', returns='a{ss}'), Method('paramsRegisterApp', arguments='sis', returns=''), Method('privateDataDelete', arguments='sss', returns=''), Method('privateDataGet', arguments='sss', returns='s'), Method('privateDataSet', arguments='ssss', returns=''), Method('profileCreate', arguments='sss', returns=''), Method('profileIsSessionStarted', arguments='s', returns='b'), Method('profileNameGet', arguments='s', returns='s'), Method('profileSetDefault', arguments='s', returns=''), Method('profileStartSession', arguments='ss', returns='b'), Method('profilesListGet', arguments='bb', returns='as'), Method('progressGet', arguments='ss', returns='a{ss}'), Method('progressGetAll', arguments='s', returns='a{sa{sa{ss}}}'), Method('progressGetAllMetadata', arguments='s', returns='a{sa{sa{ss}}}'), Method('rosterResync', arguments='s', returns=''), Method('saveParamsTemplate', arguments='s', returns='b'), Method('sessionInfosGet', arguments='s', returns='a{ss}'), Method('setParam', arguments='sssis', returns=''), Method('setPresence', arguments='ssa{ss}s', returns=''), Method('subscription', arguments='sss', returns=''), Method('updateContact', arguments='ssass', returns=''), Signal('_debug', 'sa{ss}s'), Signal('actionNew', 'a{ss}sis'), Signal('connected', 'ss'), Signal('contactDeleted', 'ss'), Signal('disconnected', 's'), Signal('entityDataUpdated', 'ssss'), Signal('messageEncryptionStarted', 'sss'), Signal('messageEncryptionStopped', 'sa{ss}s'), Signal('messageNew', 'sdssa{ss}a{ss}sss'), Signal('newContact', 'sa{ss}ass'), Signal('paramUpdate', 'ssss'), Signal('presenceUpdate', 'ssia{ss}s'), Signal('progressError', 'sss'), Signal('progressFinished', 'sa{ss}s'), Signal('progressStarted', '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_actionsGet(self, profile_key="@DEFAULT@"): return self._callback("actionsGet", profile_key) def dbus_addContact(self, entity_jid, profile_key="@DEFAULT@"): return self._callback("addContact", entity_jid, profile_key) def dbus_asyncDeleteProfile(self, profile): return self._callback("asyncDeleteProfile", profile) def dbus_asyncGetParamA(self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@"): return self._callback("asyncGetParamA", name, category, attribute, security_limit, profile_key) def dbus_asyncGetParamsValuesFromCategory(self, category, security_limit=-1, app="", extra="", profile_key="@DEFAULT@"): return self._callback("asyncGetParamsValuesFromCategory", category, security_limit, app, extra, profile_key) def dbus_connect(self, profile_key="@DEFAULT@", password='', options={}): return self._callback("connect", profile_key, password, options) def dbus_contactGet(self, arg_0, profile_key="@DEFAULT@"): return self._callback("contactGet", arg_0, profile_key) def dbus_delContact(self, entity_jid, profile_key="@DEFAULT@"): return self._callback("delContact", entity_jid, profile_key) def dbus_devicesInfosGet(self, bare_jid, profile_key): return self._callback("devicesInfosGet", bare_jid, profile_key) def dbus_discoFindByFeatures(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key="@DEFAULT@"): return self._callback("discoFindByFeatures", namespaces, identities, bare_jid, service, roster, own_jid, local_device, profile_key) def dbus_discoInfos(self, entity_jid, node=u'', use_cache=True, profile_key="@DEFAULT@"): return self._callback("discoInfos", entity_jid, node, use_cache, profile_key) def dbus_discoItems(self, entity_jid, node=u'', use_cache=True, profile_key="@DEFAULT@"): return self._callback("discoItems", entity_jid, node, use_cache, profile_key) def dbus_disconnect(self, profile_key="@DEFAULT@"): return self._callback("disconnect", profile_key) def dbus_encryptionNamespaceGet(self, arg_0): return self._callback("encryptionNamespaceGet", arg_0) def dbus_encryptionPluginsGet(self, ): return self._callback("encryptionPluginsGet", ) def dbus_encryptionTrustUIGet(self, to_jid, namespace, profile_key): return self._callback("encryptionTrustUIGet", to_jid, namespace, profile_key) def dbus_getConfig(self, section, name): return self._callback("getConfig", section, name) def dbus_getContacts(self, profile_key="@DEFAULT@"): return self._callback("getContacts", profile_key) def dbus_getContactsFromGroup(self, group, profile_key="@DEFAULT@"): return self._callback("getContactsFromGroup", group, profile_key) def dbus_getEntitiesData(self, jids, keys, profile): return self._callback("getEntitiesData", jids, keys, profile) def dbus_getEntityData(self, jid, keys, profile): return self._callback("getEntityData", jid, keys, profile) def dbus_getFeatures(self, profile_key): return self._callback("getFeatures", profile_key) def dbus_getMainResource(self, contact_jid, profile_key="@DEFAULT@"): return self._callback("getMainResource", contact_jid, profile_key) def dbus_getParamA(self, name, category, attribute="value", profile_key="@DEFAULT@"): return self._callback("getParamA", name, category, attribute, profile_key) def dbus_getParamsCategories(self, ): return self._callback("getParamsCategories", ) def dbus_getParamsUI(self, security_limit=-1, app='', extra='', profile_key="@DEFAULT@"): return self._callback("getParamsUI", security_limit, app, extra, profile_key) def dbus_getPresenceStatuses(self, profile_key="@DEFAULT@"): return self._callback("getPresenceStatuses", profile_key) def dbus_getReady(self, ): return self._callback("getReady", ) def dbus_getVersion(self, ): return self._callback("getVersion", ) def dbus_getWaitingSub(self, profile_key="@DEFAULT@"): return self._callback("getWaitingSub", profile_key) def dbus_historyGet(self, from_jid, to_jid, limit, between=True, filters='', profile="@NONE@"): return self._callback("historyGet", from_jid, to_jid, limit, between, filters, profile) def dbus_imageCheck(self, arg_0): return self._callback("imageCheck", arg_0) def dbus_imageConvert(self, source, dest, arg_2, extra): return self._callback("imageConvert", source, dest, arg_2, extra) def dbus_imageGeneratePreview(self, image_path, profile_key): return self._callback("imageGeneratePreview", image_path, profile_key) def dbus_imageResize(self, image_path, width, height): return self._callback("imageResize", image_path, width, height) def dbus_isConnected(self, profile_key="@DEFAULT@"): return self._callback("isConnected", profile_key) def dbus_launchAction(self, callback_id, data, profile_key="@DEFAULT@"): return self._callback("launchAction", callback_id, data, profile_key) def dbus_loadParamsTemplate(self, filename): return self._callback("loadParamsTemplate", filename) def dbus_menuHelpGet(self, menu_id, language): return self._callback("menuHelpGet", menu_id, language) def dbus_menuLaunch(self, menu_type, path, data, security_limit, profile_key): return self._callback("menuLaunch", menu_type, path, data, security_limit, profile_key) def dbus_menusGet(self, language, security_limit): return self._callback("menusGet", language, security_limit) def dbus_messageEncryptionGet(self, to_jid, profile_key): return self._callback("messageEncryptionGet", to_jid, profile_key) def dbus_messageEncryptionStart(self, to_jid, namespace='', replace=False, profile_key="@NONE@"): return self._callback("messageEncryptionStart", to_jid, namespace, replace, profile_key) def dbus_messageEncryptionStop(self, to_jid, profile_key): return self._callback("messageEncryptionStop", to_jid, profile_key) def dbus_messageSend(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@"): return self._callback("messageSend", to_jid, message, subject, mess_type, extra, profile_key) def dbus_namespacesGet(self, ): return self._callback("namespacesGet", ) def dbus_paramsRegisterApp(self, xml, security_limit=-1, app=''): return self._callback("paramsRegisterApp", xml, security_limit, app) def dbus_privateDataDelete(self, namespace, key, arg_2): return self._callback("privateDataDelete", namespace, key, arg_2) def dbus_privateDataGet(self, namespace, key, profile_key): return self._callback("privateDataGet", namespace, key, profile_key) def dbus_privateDataSet(self, namespace, key, data, profile_key): return self._callback("privateDataSet", namespace, key, data, profile_key) def dbus_profileCreate(self, profile, password='', component=''): return self._callback("profileCreate", profile, password, component) def dbus_profileIsSessionStarted(self, profile_key="@DEFAULT@"): return self._callback("profileIsSessionStarted", profile_key) def dbus_profileNameGet(self, profile_key="@DEFAULT@"): return self._callback("profileNameGet", profile_key) def dbus_profileSetDefault(self, profile): return self._callback("profileSetDefault", profile) def dbus_profileStartSession(self, password='', profile_key="@DEFAULT@"): return self._callback("profileStartSession", password, profile_key) def dbus_profilesListGet(self, clients=True, components=False): return self._callback("profilesListGet", clients, components) def dbus_progressGet(self, id, profile): return self._callback("progressGet", id, profile) def dbus_progressGetAll(self, profile): return self._callback("progressGetAll", profile) def dbus_progressGetAllMetadata(self, profile): return self._callback("progressGetAllMetadata", profile) def dbus_rosterResync(self, profile_key="@DEFAULT@"): return self._callback("rosterResync", profile_key) def dbus_saveParamsTemplate(self, filename): return self._callback("saveParamsTemplate", filename) def dbus_sessionInfosGet(self, profile_key): return self._callback("sessionInfosGet", profile_key) def dbus_setParam(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@"): return self._callback("setParam", name, value, category, security_limit, profile_key) def dbus_setPresence(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@"): return self._callback("setPresence", to_jid, show, statuses, profile_key) def dbus_subscription(self, sub_type, entity, profile_key="@DEFAULT@"): return self._callback("subscription", sub_type, entity, profile_key) def dbus_updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@"): return self._callback("updateContact", entity_jid, name, groups, profile_key) class Bridge: def __init__(self): log.info("Init DBus...") self._obj = DBusObject(const_OBJ_PATH) async def postInit(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 actionNew(self, action_data, id, security_limit, profile): self._obj.emitSignal("actionNew", action_data, id, security_limit, profile) def connected(self, jid_s, profile): self._obj.emitSignal("connected", jid_s, profile) def contactDeleted(self, entity_jid, profile): self._obj.emitSignal("contactDeleted", entity_jid, profile) def disconnected(self, profile): self._obj.emitSignal("disconnected", profile) def entityDataUpdated(self, jid, name, value, profile): self._obj.emitSignal("entityDataUpdated", jid, name, value, profile) def messageEncryptionStarted(self, to_jid, encryption_data, profile_key): self._obj.emitSignal("messageEncryptionStarted", to_jid, encryption_data, profile_key) def messageEncryptionStopped(self, to_jid, encryption_data, profile_key): self._obj.emitSignal("messageEncryptionStopped", to_jid, encryption_data, profile_key) def messageNew(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile): self._obj.emitSignal("messageNew", uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile) def newContact(self, contact_jid, attributes, groups, profile): self._obj.emitSignal("newContact", contact_jid, attributes, groups, profile) def paramUpdate(self, name, value, category, profile): self._obj.emitSignal("paramUpdate", name, value, category, profile) def presenceUpdate(self, entity_jid, show, priority, statuses, profile): self._obj.emitSignal("presenceUpdate", entity_jid, show, priority, statuses, profile) def progressError(self, id, error, profile): self._obj.emitSignal("progressError", id, error, profile) def progressFinished(self, id, metadata, profile): self._obj.emitSignal("progressFinished", id, metadata, profile) def progressStarted(self, id, metadata, profile): self._obj.emitSignal("progressStarted", 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 emitSignal(self, name, *args): self._obj.emitSignal(name, *args) def addMethod( 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 addSignal(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.emitSignal, name))