diff --git a/libervia/backend/plugins/plugin_adhoc_dbus.py b/libervia/backend/plugins/plugin_adhoc_dbus.py index 58ac0ce1..06a3c3c4 100644 --- a/libervia/backend/plugins/plugin_adhoc_dbus.py +++ b/libervia/backend/plugins/plugin_adhoc_dbus.py @@ -17,14 +17,21 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from libervia.backend.core.i18n import D_, _ +from collections import OrderedDict +import os.path +import uuid + +from twisted.internet import defer +from twisted.internet import reactor +from twisted.words.protocols.jabber import jid +from wokkel import data_form + from libervia.backend.core.constants import Const as C +from libervia.backend.core.i18n import D_, _ from libervia.backend.core.log import getLogger log = getLogger(__name__) -from twisted.internet import defer -from twisted.words.protocols.jabber import jid -from wokkel import data_form + try: from lxml import etree @@ -32,19 +39,17 @@ except ImportError: etree = None log.warning("Missing module lxml, please download/install it from http://lxml.de/ ." "Auto D-Bus discovery will be disabled") -from collections import OrderedDict -import os.path -import uuid -try: - import dbus - from dbus.mainloop.glib import DBusGMainLoop -except ImportError: - dbus = None - log.warning("Missing module dbus, please download/install it, " - "auto D-Bus discovery will be disabled") -else: - DBusGMainLoop(set_as_default=True) +try: + import txdbus + from txdbus import client as dbus_client +except ImportError: + txdbus = None + log.warning( + "Missing module txdbus, please download/install it, auto D-Bus discovery will be " + "disabled" + ) + NS_MEDIA_PLAYER = "org.libervia.mediaplayer" FD_NAME = "org.freedesktop.DBus" @@ -92,10 +97,10 @@ PLUGIN_INFO = { } -class AdHocDBus(object): +class AdHocDBus: def __init__(self, host): - log.info(_("plugin Ad-Hoc D-Bus initialization")) + log.info(f"plugin {PLUGIN_INFO[C.PI_NAME]!r} initialization") self.host = host if etree is not None: host.bridge.add_method( @@ -116,13 +121,14 @@ class AdHocDBus(object): ) self._c = host.plugins["XEP-0050"] host.register_namespace("mediaplayer", NS_MEDIA_PLAYER) - if dbus is not None: - self.session_bus = dbus.SessionBus() - self.fd_object = self.session_bus.get_object( - FD_NAME, FD_PATH, introspect=False) + self.session_con = None + + async def profile_connected(self, client): + if txdbus is not None: + if self.session_con is None: + self.session_con = await dbus_client.connect(reactor, 'session') + self.fd_object = await self.session_con.getRemoteObject(FD_NAME, FD_PATH) - def profile_connected(self, client): - if dbus is not None: self._c.add_ad_hoc_command( client, self.local_media_cb, D_("Media Players"), node=NS_MEDIA_PLAYER, @@ -130,7 +136,7 @@ class AdHocDBus(object): # in the middle of a movie ) - def _dbus_async_call(self, proxy, method, *args, **kwargs): + async def _dbus_async_call(self, proxy, method, *args, **kwargs): """ Call a DBus method asynchronously and return a deferred @param proxy: DBus object proxy, as returner by get_object @@ -142,23 +148,18 @@ class AdHocDBus(object): @return: a deferred """ - d = defer.Deferred() - interface = kwargs.pop("interface", None) - kwargs["reply_handler"] = lambda ret=None: d.callback(ret) - kwargs["error_handler"] = d.errback - proxy.get_dbus_method(method, dbus_interface=interface)(*args, **kwargs) - return d + return await proxy.callRemote(method, *args, **kwargs) - def _dbus_get_property(self, proxy, interface, name): - return self._dbus_async_call( + async def _dbus_get_property(self, proxy, interface, name): + return await self._dbus_async_call( proxy, "Get", interface, name, interface="org.freedesktop.DBus.Properties") - def _dbus_list_names(self): - return self._dbus_async_call(self.fd_object, "ListNames") + async def _dbus_list_names(self): + return await self.fd_object.callRemote("ListNames") - def _dbus_introspect(self, proxy): - return self._dbus_async_call(proxy, INTROSPECT_METHOD, interface=INTROSPECT_IFACE) + async def _dbus_introspect(self, proxy): + return await self._dbus_async_call(proxy, INTROSPECT_METHOD, interface=INTROSPECT_IFACE) def _accept_method(self, method): """ Return True if we accept the method for a command @@ -172,18 +173,18 @@ class AdHocDBus(object): return False return True - @defer.inlineCallbacks - def _introspect(self, methods, bus_name, proxy): + async def _introspect(self, methods, bus_name, proxy): log.debug("introspecting path [%s]" % proxy.object_path) - introspect_xml = yield self._dbus_introspect(proxy) + assert etree is not None + introspect_xml = await self._dbus_introspect(proxy) el = etree.fromstring(introspect_xml) for node in el.iterchildren("node", "interface"): if node.tag == "node": new_path = os.path.join(proxy.object_path, node.get("name")) - new_proxy = self.session_bus.get_object( - bus_name, new_path, introspect=False + new_proxy = await self.session_con.getRemoteObject( + bus_name, new_path ) - yield self._introspect(methods, bus_name, new_proxy) + await self._introspect(methods, bus_name, new_proxy) elif node.tag == "interface": name = node.get("name") if any(name.startswith(ignored) for ignored in IGNORED_IFACES_START): @@ -203,24 +204,26 @@ class AdHocDBus(object): client, prog_name, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, flags) - @defer.inlineCallbacks - def ad_hoc_dbus_add_auto(self, client, prog_name, allowed_jids=None, allowed_groups=None, + async def ad_hoc_dbus_add_auto(self, client, prog_name, allowed_jids=None, allowed_groups=None, allowed_magics=None, forbidden_jids=None, forbidden_groups=None, flags=None): - bus_names = yield self._dbus_list_names() + bus_names = await self._dbus_list_names() bus_names = [bus_name for bus_name in bus_names if "." + prog_name in bus_name] if not bus_names: log.info("Can't find any bus for [%s]" % prog_name) - defer.returnValue(("", [])) + return ("", []) bus_names.sort() for bus_name in bus_names: if bus_name.endswith(prog_name): break - log.info("bus name found: [%s]" % bus_name) - proxy = self.session_bus.get_object(bus_name, "/", introspect=False) + else: + log.info(f"Can't find any command for {prog_name}") + return ("", []) + log.info(f"bus name found: {bus_name}") + proxy = await self.session_con.getRemoteObject(bus_name, "/") methods = set() - yield self._introspect(methods, bus_name, proxy) + await self._introspect(methods, bus_name, proxy) if methods: self._add_command( @@ -236,7 +239,7 @@ class AdHocDBus(object): flags=flags, ) - defer.returnValue((str(bus_name), methods)) + return (str(bus_name), methods) def _add_command(self, client, adhoc_name, bus_name, methods, allowed_jids=None, allowed_groups=None, allowed_magics=None, forbidden_jids=None, @@ -244,7 +247,7 @@ class AdHocDBus(object): if flags is None: flags = set() - def d_bus_callback(client, command_elt, session_data, action, node): + async def d_bus_callback(client, command_elt, session_data, action, node): actions = session_data.setdefault("actions", []) names_map = session_data.setdefault("names_map", {}) actions.append(action) @@ -281,9 +284,9 @@ class AdHocDBus(object): raise self._c.AdHocError(self._c.ERROR.BAD_PAYLOAD) path, iface, command = names_map[command] - proxy = self.session_bus.get_object(bus_name, path) + proxy = await self.session_con.getRemoteObject(bus_name, path) - self._dbus_async_call(proxy, command, interface=iface) + await self._dbus_async_call(proxy, command, interface=iface) # job done, we can end the session, except if we have FLAG_LOOP if FLAG_LOOP in flags: @@ -292,7 +295,7 @@ class AdHocDBus(object): # is OK) del actions[:] names_map.clear() - return d_bus_callback( + return await d_bus_callback( client, None, session_data, self._c.ACTION.EXECUTE, node ) form = data_form.Form("form", title=_("Updated")) @@ -321,15 +324,14 @@ class AdHocDBus(object): def _ad_hoc_remotes_get(self, profile): return self.ad_hoc_remotes_get(self.host.get_client(profile)) - @defer.inlineCallbacks - def ad_hoc_remotes_get(self, client): + async def ad_hoc_remotes_get(self, client): """Retrieve available remote media controlers in our devices @return (list[tuple[unicode, unicode, unicode]]): list of devices with: - entity full jid - device name - device label """ - found_data = yield defer.ensureDeferred(self.host.find_by_features( + found_data = await defer.ensureDeferred(self.host.find_by_features( client, [self.host.ns_map['commands']], service=False, roster=False, own_jid=True, local_device=True)) @@ -338,11 +340,11 @@ class AdHocDBus(object): for found in found_data: for device_jid_s in found: device_jid = jid.JID(device_jid_s) - cmd_list = yield self._c.list(client, device_jid) + cmd_list = await self._c.list(client, device_jid) for cmd in cmd_list: if cmd.nodeIdentifier == NS_MEDIA_PLAYER: try: - result_elt = yield self._c.do(client, device_jid, + result_elt = await self._c.do(client, device_jid, NS_MEDIA_PLAYER, timeout=5) command_elt = self._c.get_command_elt(result_elt) form = data_form.findForm(command_elt, NS_MEDIA_PLAYER) @@ -366,9 +368,9 @@ class AdHocDBus(object): "Can't retrieve remote controllers on {device_jid}: " "{reason}".format(device_jid=device_jid, reason=e))) break - defer.returnValue(remotes) + return remotes - def do_mpris_command(self, proxy, command): + async def do_mpris_command(self, proxy, command): iface, command = command.rsplit(".", 1) if command == CMD_GO_BACK: command = 'Seek' @@ -378,7 +380,7 @@ class AdHocDBus(object): args = [SEEK_OFFSET] else: args = [] - return self._dbus_async_call(proxy, command, *args, interface=iface) + return await self._dbus_async_call(proxy, command, *args, interface=iface) def add_mpris_metadata(self, form, metadata): """Serialise MRPIS Metadata according to MPRIS_METADATA_MAP""" @@ -389,8 +391,8 @@ class AdHocDBus(object): var=name, value=value)) - @defer.inlineCallbacks - def local_media_cb(self, client, command_elt, session_data, action, node): + async def local_media_cb(self, client, command_elt, session_data, action, node): + assert txdbus is not None try: x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x")) command_form = data_form.Form.fromElement(x_elt) @@ -399,11 +401,11 @@ class AdHocDBus(object): if command_form is None or len(command_form.fields) == 0: # root request, we looks for media players - bus_names = yield self._dbus_list_names() + bus_names = await self._dbus_list_names() bus_names = [b for b in bus_names if b.startswith(MPRIS_PREFIX)] if len(bus_names) == 0: note = (self._c.NOTE.INFO, D_("No media player found.")) - defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) + return (None, self._c.STATUS.COMPLETED, None, note) options = [] status = self._c.STATUS.EXECUTING form = data_form.Form("form", title=D_("Media Player Selection"), @@ -419,7 +421,7 @@ class AdHocDBus(object): ) form.addField(field) payload = form.toElement() - defer.returnValue((payload, status, None, None)) + return (payload, status, None, None) else: # player request try: @@ -432,20 +434,22 @@ class AdHocDBus(object): "Hack attempt? Refused bus: {bus_name}").format( bus_name=bus_name)) note = (self._c.NOTE.ERROR, D_("Invalid player name.")) - defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) + return (None, self._c.STATUS.COMPLETED, None, note) try: - proxy = self.session_bus.get_object(bus_name, MPRIS_PATH) - except dbus.exceptions.DBusException as e: + proxy = await self.session_con.getRemoteObject( + bus_name, MPRIS_PATH, "org.mpris.MediaPlayer2.Player" + ) + except Exception as e: log.warning(_("Can't get D-Bus proxy: {reason}").format(reason=e)) note = (self._c.NOTE.ERROR, D_("Media player is not available anymore")) - defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) + return (None, self._c.STATUS.COMPLETED, None, note) try: command = command_form["command"] except KeyError: pass else: - yield self.do_mpris_command(proxy, command) + await self.do_mpris_command(proxy, command) # we construct the remote control form form = data_form.Form("form", title=D_("Media Player Selection")) @@ -455,7 +459,7 @@ class AdHocDBus(object): for iface, properties_names in MPRIS_PROPERTIES.items(): for name in properties_names: try: - value = yield self._dbus_get_property(proxy, iface, name) + value = await self._dbus_get_property(proxy, iface, name) except Exception as e: log.warning(_("Can't retrieve attribute {name}: {reason}") .format(name=name, reason=e)) @@ -475,4 +479,4 @@ class AdHocDBus(object): payload = form.toElement() status = self._c.STATUS.EXECUTING - defer.returnValue((payload, status, None, None)) + return (payload, status, None, None) diff --git a/libervia/cli/cmd_adhoc.py b/libervia/cli/cmd_adhoc.py index facc617a..5e580f41 100644 --- a/libervia/cli/cmd_adhoc.py +++ b/libervia/cli/cmd_adhoc.py @@ -117,7 +117,7 @@ class Run(base.CommandBase): "-j", "--jid", default="", - help=_("jid of the service (default: profile's server"), + help=_("jid of the service (default: profile's server)"), ) self.parser.add_argument( "-S",