From ec2a637847ec653ca4c1509f5eea34b57ad3f373 Mon Sep 17 00:00:00 2001 From: Goffi Date: Fri, 1 Dec 2023 15:22:55 +0100 Subject: [PATCH] plugin ad-hoc D-Bus: fix deprecated use of python-dbus: the plugin was using python-dbus which is deprecated in the backend in the favor of TxDBus. Methods calls have been updated, and the plugin works again, but there seems to be still some issues (warnings in the logs). Those will be fixed later. --- libervia/backend/plugins/plugin_adhoc_dbus.py | 154 +++++++++--------- libervia/cli/cmd_adhoc.py | 2 +- 2 files changed, 80 insertions(+), 76 deletions(-) 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",