comparison libervia/backend/plugins/plugin_adhoc_dbus.py @ 4167:319a0e47dc8b

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.
author Goffi <goffi@goffi.org>
date Fri, 01 Dec 2023 15:22:55 +0100
parents 4b842c1fb686
children 50c919dfe61b
comparison
equal deleted inserted replaced
4166:a1f7040b5a15 4167:319a0e47dc8b
15 # GNU Affero General Public License for more details. 15 # GNU Affero General Public License for more details.
16 16
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from libervia.backend.core.i18n import D_, _ 20 from collections import OrderedDict
21 from libervia.backend.core.constants import Const as C 21 import os.path
22 from libervia.backend.core.log import getLogger 22 import uuid
23 23
24 log = getLogger(__name__)
25 from twisted.internet import defer 24 from twisted.internet import defer
25 from twisted.internet import reactor
26 from twisted.words.protocols.jabber import jid 26 from twisted.words.protocols.jabber import jid
27 from wokkel import data_form 27 from wokkel import data_form
28
29 from libervia.backend.core.constants import Const as C
30 from libervia.backend.core.i18n import D_, _
31 from libervia.backend.core.log import getLogger
32
33 log = getLogger(__name__)
34
28 35
29 try: 36 try:
30 from lxml import etree 37 from lxml import etree
31 except ImportError: 38 except ImportError:
32 etree = None 39 etree = None
33 log.warning("Missing module lxml, please download/install it from http://lxml.de/ ." 40 log.warning("Missing module lxml, please download/install it from http://lxml.de/ ."
34 "Auto D-Bus discovery will be disabled") 41 "Auto D-Bus discovery will be disabled")
35 from collections import OrderedDict 42
36 import os.path
37 import uuid
38 try: 43 try:
39 import dbus 44 import txdbus
40 from dbus.mainloop.glib import DBusGMainLoop 45 from txdbus import client as dbus_client
41 except ImportError: 46 except ImportError:
42 dbus = None 47 txdbus = None
43 log.warning("Missing module dbus, please download/install it, " 48 log.warning(
44 "auto D-Bus discovery will be disabled") 49 "Missing module txdbus, please download/install it, auto D-Bus discovery will be "
45 50 "disabled"
46 else: 51 )
47 DBusGMainLoop(set_as_default=True) 52
48 53
49 NS_MEDIA_PLAYER = "org.libervia.mediaplayer" 54 NS_MEDIA_PLAYER = "org.libervia.mediaplayer"
50 FD_NAME = "org.freedesktop.DBus" 55 FD_NAME = "org.freedesktop.DBus"
51 FD_PATH = "/org/freedekstop/DBus" 56 FD_PATH = "/org/freedekstop/DBus"
52 INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable" 57 INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable"
90 C.PI_HANDLER: "no", 95 C.PI_HANDLER: "no",
91 C.PI_DESCRIPTION: _("""Add D-Bus management to Ad-Hoc commands"""), 96 C.PI_DESCRIPTION: _("""Add D-Bus management to Ad-Hoc commands"""),
92 } 97 }
93 98
94 99
95 class AdHocDBus(object): 100 class AdHocDBus:
96 101
97 def __init__(self, host): 102 def __init__(self, host):
98 log.info(_("plugin Ad-Hoc D-Bus initialization")) 103 log.info(f"plugin {PLUGIN_INFO[C.PI_NAME]!r} initialization")
99 self.host = host 104 self.host = host
100 if etree is not None: 105 if etree is not None:
101 host.bridge.add_method( 106 host.bridge.add_method(
102 "ad_hoc_dbus_add_auto", 107 "ad_hoc_dbus_add_auto",
103 ".plugin", 108 ".plugin",
114 method=self._ad_hoc_remotes_get, 119 method=self._ad_hoc_remotes_get,
115 async_=True, 120 async_=True,
116 ) 121 )
117 self._c = host.plugins["XEP-0050"] 122 self._c = host.plugins["XEP-0050"]
118 host.register_namespace("mediaplayer", NS_MEDIA_PLAYER) 123 host.register_namespace("mediaplayer", NS_MEDIA_PLAYER)
119 if dbus is not None: 124 self.session_con = None
120 self.session_bus = dbus.SessionBus() 125
121 self.fd_object = self.session_bus.get_object( 126 async def profile_connected(self, client):
122 FD_NAME, FD_PATH, introspect=False) 127 if txdbus is not None:
123 128 if self.session_con is None:
124 def profile_connected(self, client): 129 self.session_con = await dbus_client.connect(reactor, 'session')
125 if dbus is not None: 130 self.fd_object = await self.session_con.getRemoteObject(FD_NAME, FD_PATH)
131
126 self._c.add_ad_hoc_command( 132 self._c.add_ad_hoc_command(
127 client, self.local_media_cb, D_("Media Players"), 133 client, self.local_media_cb, D_("Media Players"),
128 node=NS_MEDIA_PLAYER, 134 node=NS_MEDIA_PLAYER,
129 timeout=60*60*6 # 6 hours timeout, to avoid breaking remote 135 timeout=60*60*6 # 6 hours timeout, to avoid breaking remote
130 # in the middle of a movie 136 # in the middle of a movie
131 ) 137 )
132 138
133 def _dbus_async_call(self, proxy, method, *args, **kwargs): 139 async def _dbus_async_call(self, proxy, method, *args, **kwargs):
134 """ Call a DBus method asynchronously and return a deferred 140 """ Call a DBus method asynchronously and return a deferred
135 141
136 @param proxy: DBus object proxy, as returner by get_object 142 @param proxy: DBus object proxy, as returner by get_object
137 @param method: name of the method to call 143 @param method: name of the method to call
138 @param args: will be transmitted to the method 144 @param args: will be transmitted to the method
140 values: 146 values:
141 - interface: name of the interface to use 147 - interface: name of the interface to use
142 @return: a deferred 148 @return: a deferred
143 149
144 """ 150 """
145 d = defer.Deferred() 151 return await proxy.callRemote(method, *args, **kwargs)
146 interface = kwargs.pop("interface", None) 152
147 kwargs["reply_handler"] = lambda ret=None: d.callback(ret) 153 async def _dbus_get_property(self, proxy, interface, name):
148 kwargs["error_handler"] = d.errback 154 return await self._dbus_async_call(
149 proxy.get_dbus_method(method, dbus_interface=interface)(*args, **kwargs)
150 return d
151
152 def _dbus_get_property(self, proxy, interface, name):
153 return self._dbus_async_call(
154 proxy, "Get", interface, name, interface="org.freedesktop.DBus.Properties") 155 proxy, "Get", interface, name, interface="org.freedesktop.DBus.Properties")
155 156
156 157
157 def _dbus_list_names(self): 158 async def _dbus_list_names(self):
158 return self._dbus_async_call(self.fd_object, "ListNames") 159 return await self.fd_object.callRemote("ListNames")
159 160
160 def _dbus_introspect(self, proxy): 161 async def _dbus_introspect(self, proxy):
161 return self._dbus_async_call(proxy, INTROSPECT_METHOD, interface=INTROSPECT_IFACE) 162 return await self._dbus_async_call(proxy, INTROSPECT_METHOD, interface=INTROSPECT_IFACE)
162 163
163 def _accept_method(self, method): 164 def _accept_method(self, method):
164 """ Return True if we accept the method for a command 165 """ Return True if we accept the method for a command
165 @param method: etree.Element 166 @param method: etree.Element
166 @return: True if the method is acceptable 167 @return: True if the method is acceptable
170 "arg[@direction='in']" 171 "arg[@direction='in']"
171 ): # we don't accept method with argument for the moment 172 ): # we don't accept method with argument for the moment
172 return False 173 return False
173 return True 174 return True
174 175
175 @defer.inlineCallbacks 176 async def _introspect(self, methods, bus_name, proxy):
176 def _introspect(self, methods, bus_name, proxy):
177 log.debug("introspecting path [%s]" % proxy.object_path) 177 log.debug("introspecting path [%s]" % proxy.object_path)
178 introspect_xml = yield self._dbus_introspect(proxy) 178 assert etree is not None
179 introspect_xml = await self._dbus_introspect(proxy)
179 el = etree.fromstring(introspect_xml) 180 el = etree.fromstring(introspect_xml)
180 for node in el.iterchildren("node", "interface"): 181 for node in el.iterchildren("node", "interface"):
181 if node.tag == "node": 182 if node.tag == "node":
182 new_path = os.path.join(proxy.object_path, node.get("name")) 183 new_path = os.path.join(proxy.object_path, node.get("name"))
183 new_proxy = self.session_bus.get_object( 184 new_proxy = await self.session_con.getRemoteObject(
184 bus_name, new_path, introspect=False 185 bus_name, new_path
185 ) 186 )
186 yield self._introspect(methods, bus_name, new_proxy) 187 await self._introspect(methods, bus_name, new_proxy)
187 elif node.tag == "interface": 188 elif node.tag == "interface":
188 name = node.get("name") 189 name = node.get("name")
189 if any(name.startswith(ignored) for ignored in IGNORED_IFACES_START): 190 if any(name.startswith(ignored) for ignored in IGNORED_IFACES_START):
190 log.debug("interface [%s] is ignored" % name) 191 log.debug("interface [%s] is ignored" % name)
191 continue 192 continue
201 client = self.host.get_client(profile_key) 202 client = self.host.get_client(profile_key)
202 return self.ad_hoc_dbus_add_auto( 203 return self.ad_hoc_dbus_add_auto(
203 client, prog_name, allowed_jids, allowed_groups, allowed_magics, 204 client, prog_name, allowed_jids, allowed_groups, allowed_magics,
204 forbidden_jids, forbidden_groups, flags) 205 forbidden_jids, forbidden_groups, flags)
205 206
206 @defer.inlineCallbacks 207 async def ad_hoc_dbus_add_auto(self, client, prog_name, allowed_jids=None, allowed_groups=None,
207 def ad_hoc_dbus_add_auto(self, client, prog_name, allowed_jids=None, allowed_groups=None,
208 allowed_magics=None, forbidden_jids=None, forbidden_groups=None, 208 allowed_magics=None, forbidden_jids=None, forbidden_groups=None,
209 flags=None): 209 flags=None):
210 bus_names = yield self._dbus_list_names() 210 bus_names = await self._dbus_list_names()
211 bus_names = [bus_name for bus_name in bus_names if "." + prog_name in bus_name] 211 bus_names = [bus_name for bus_name in bus_names if "." + prog_name in bus_name]
212 if not bus_names: 212 if not bus_names:
213 log.info("Can't find any bus for [%s]" % prog_name) 213 log.info("Can't find any bus for [%s]" % prog_name)
214 defer.returnValue(("", [])) 214 return ("", [])
215 bus_names.sort() 215 bus_names.sort()
216 for bus_name in bus_names: 216 for bus_name in bus_names:
217 if bus_name.endswith(prog_name): 217 if bus_name.endswith(prog_name):
218 break 218 break
219 log.info("bus name found: [%s]" % bus_name) 219 else:
220 proxy = self.session_bus.get_object(bus_name, "/", introspect=False) 220 log.info(f"Can't find any command for {prog_name}")
221 return ("", [])
222 log.info(f"bus name found: {bus_name}")
223 proxy = await self.session_con.getRemoteObject(bus_name, "/")
221 methods = set() 224 methods = set()
222 225
223 yield self._introspect(methods, bus_name, proxy) 226 await self._introspect(methods, bus_name, proxy)
224 227
225 if methods: 228 if methods:
226 self._add_command( 229 self._add_command(
227 client, 230 client,
228 prog_name, 231 prog_name,
234 forbidden_jids=forbidden_jids, 237 forbidden_jids=forbidden_jids,
235 forbidden_groups=forbidden_groups, 238 forbidden_groups=forbidden_groups,
236 flags=flags, 239 flags=flags,
237 ) 240 )
238 241
239 defer.returnValue((str(bus_name), methods)) 242 return (str(bus_name), methods)
240 243
241 def _add_command(self, client, adhoc_name, bus_name, methods, allowed_jids=None, 244 def _add_command(self, client, adhoc_name, bus_name, methods, allowed_jids=None,
242 allowed_groups=None, allowed_magics=None, forbidden_jids=None, 245 allowed_groups=None, allowed_magics=None, forbidden_jids=None,
243 forbidden_groups=None, flags=None): 246 forbidden_groups=None, flags=None):
244 if flags is None: 247 if flags is None:
245 flags = set() 248 flags = set()
246 249
247 def d_bus_callback(client, command_elt, session_data, action, node): 250 async def d_bus_callback(client, command_elt, session_data, action, node):
248 actions = session_data.setdefault("actions", []) 251 actions = session_data.setdefault("actions", [])
249 names_map = session_data.setdefault("names_map", {}) 252 names_map = session_data.setdefault("names_map", {})
250 actions.append(action) 253 actions.append(action)
251 254
252 if len(actions) == 1: 255 if len(actions) == 1:
279 282
280 if command not in names_map: 283 if command not in names_map:
281 raise self._c.AdHocError(self._c.ERROR.BAD_PAYLOAD) 284 raise self._c.AdHocError(self._c.ERROR.BAD_PAYLOAD)
282 285
283 path, iface, command = names_map[command] 286 path, iface, command = names_map[command]
284 proxy = self.session_bus.get_object(bus_name, path) 287 proxy = await self.session_con.getRemoteObject(bus_name, path)
285 288
286 self._dbus_async_call(proxy, command, interface=iface) 289 await self._dbus_async_call(proxy, command, interface=iface)
287 290
288 # job done, we can end the session, except if we have FLAG_LOOP 291 # job done, we can end the session, except if we have FLAG_LOOP
289 if FLAG_LOOP in flags: 292 if FLAG_LOOP in flags:
290 # We have a loop, so we clear everything and we execute again the 293 # We have a loop, so we clear everything and we execute again the
291 # command as we had a first call (command_elt is not used, so None 294 # command as we had a first call (command_elt is not used, so None
292 # is OK) 295 # is OK)
293 del actions[:] 296 del actions[:]
294 names_map.clear() 297 names_map.clear()
295 return d_bus_callback( 298 return await d_bus_callback(
296 client, None, session_data, self._c.ACTION.EXECUTE, node 299 client, None, session_data, self._c.ACTION.EXECUTE, node
297 ) 300 )
298 form = data_form.Form("form", title=_("Updated")) 301 form = data_form.Form("form", title=_("Updated"))
299 form.addField(data_form.Field("fixed", "Command sent")) 302 form.addField(data_form.Field("fixed", "Command sent"))
300 status = self._c.STATUS.COMPLETED 303 status = self._c.STATUS.COMPLETED
319 ## Local media ## 322 ## Local media ##
320 323
321 def _ad_hoc_remotes_get(self, profile): 324 def _ad_hoc_remotes_get(self, profile):
322 return self.ad_hoc_remotes_get(self.host.get_client(profile)) 325 return self.ad_hoc_remotes_get(self.host.get_client(profile))
323 326
324 @defer.inlineCallbacks 327 async def ad_hoc_remotes_get(self, client):
325 def ad_hoc_remotes_get(self, client):
326 """Retrieve available remote media controlers in our devices 328 """Retrieve available remote media controlers in our devices
327 @return (list[tuple[unicode, unicode, unicode]]): list of devices with: 329 @return (list[tuple[unicode, unicode, unicode]]): list of devices with:
328 - entity full jid 330 - entity full jid
329 - device name 331 - device name
330 - device label 332 - device label
331 """ 333 """
332 found_data = yield defer.ensureDeferred(self.host.find_by_features( 334 found_data = await defer.ensureDeferred(self.host.find_by_features(
333 client, [self.host.ns_map['commands']], service=False, roster=False, 335 client, [self.host.ns_map['commands']], service=False, roster=False,
334 own_jid=True, local_device=True)) 336 own_jid=True, local_device=True))
335 337
336 remotes = [] 338 remotes = []
337 339
338 for found in found_data: 340 for found in found_data:
339 for device_jid_s in found: 341 for device_jid_s in found:
340 device_jid = jid.JID(device_jid_s) 342 device_jid = jid.JID(device_jid_s)
341 cmd_list = yield self._c.list(client, device_jid) 343 cmd_list = await self._c.list(client, device_jid)
342 for cmd in cmd_list: 344 for cmd in cmd_list:
343 if cmd.nodeIdentifier == NS_MEDIA_PLAYER: 345 if cmd.nodeIdentifier == NS_MEDIA_PLAYER:
344 try: 346 try:
345 result_elt = yield self._c.do(client, device_jid, 347 result_elt = await self._c.do(client, device_jid,
346 NS_MEDIA_PLAYER, timeout=5) 348 NS_MEDIA_PLAYER, timeout=5)
347 command_elt = self._c.get_command_elt(result_elt) 349 command_elt = self._c.get_command_elt(result_elt)
348 form = data_form.findForm(command_elt, NS_MEDIA_PLAYER) 350 form = data_form.findForm(command_elt, NS_MEDIA_PLAYER)
349 if form is None: 351 if form is None:
350 continue 352 continue
364 except Exception as e: 366 except Exception as e:
365 log.warning(_( 367 log.warning(_(
366 "Can't retrieve remote controllers on {device_jid}: " 368 "Can't retrieve remote controllers on {device_jid}: "
367 "{reason}".format(device_jid=device_jid, reason=e))) 369 "{reason}".format(device_jid=device_jid, reason=e)))
368 break 370 break
369 defer.returnValue(remotes) 371 return remotes
370 372
371 def do_mpris_command(self, proxy, command): 373 async def do_mpris_command(self, proxy, command):
372 iface, command = command.rsplit(".", 1) 374 iface, command = command.rsplit(".", 1)
373 if command == CMD_GO_BACK: 375 if command == CMD_GO_BACK:
374 command = 'Seek' 376 command = 'Seek'
375 args = [-SEEK_OFFSET] 377 args = [-SEEK_OFFSET]
376 elif command == CMD_GO_FWD: 378 elif command == CMD_GO_FWD:
377 command = 'Seek' 379 command = 'Seek'
378 args = [SEEK_OFFSET] 380 args = [SEEK_OFFSET]
379 else: 381 else:
380 args = [] 382 args = []
381 return self._dbus_async_call(proxy, command, *args, interface=iface) 383 return await self._dbus_async_call(proxy, command, *args, interface=iface)
382 384
383 def add_mpris_metadata(self, form, metadata): 385 def add_mpris_metadata(self, form, metadata):
384 """Serialise MRPIS Metadata according to MPRIS_METADATA_MAP""" 386 """Serialise MRPIS Metadata according to MPRIS_METADATA_MAP"""
385 for mpris_key, name in MPRIS_METADATA_MAP.items(): 387 for mpris_key, name in MPRIS_METADATA_MAP.items():
386 if mpris_key in metadata: 388 if mpris_key in metadata:
387 value = str(metadata[mpris_key]) 389 value = str(metadata[mpris_key])
388 form.addField(data_form.Field(fieldType="fixed", 390 form.addField(data_form.Field(fieldType="fixed",
389 var=name, 391 var=name,
390 value=value)) 392 value=value))
391 393
392 @defer.inlineCallbacks 394 async def local_media_cb(self, client, command_elt, session_data, action, node):
393 def local_media_cb(self, client, command_elt, session_data, action, node): 395 assert txdbus is not None
394 try: 396 try:
395 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x")) 397 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x"))
396 command_form = data_form.Form.fromElement(x_elt) 398 command_form = data_form.Form.fromElement(x_elt)
397 except StopIteration: 399 except StopIteration:
398 command_form = None 400 command_form = None
399 401
400 if command_form is None or len(command_form.fields) == 0: 402 if command_form is None or len(command_form.fields) == 0:
401 # root request, we looks for media players 403 # root request, we looks for media players
402 bus_names = yield self._dbus_list_names() 404 bus_names = await self._dbus_list_names()
403 bus_names = [b for b in bus_names if b.startswith(MPRIS_PREFIX)] 405 bus_names = [b for b in bus_names if b.startswith(MPRIS_PREFIX)]
404 if len(bus_names) == 0: 406 if len(bus_names) == 0:
405 note = (self._c.NOTE.INFO, D_("No media player found.")) 407 note = (self._c.NOTE.INFO, D_("No media player found."))
406 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) 408 return (None, self._c.STATUS.COMPLETED, None, note)
407 options = [] 409 options = []
408 status = self._c.STATUS.EXECUTING 410 status = self._c.STATUS.EXECUTING
409 form = data_form.Form("form", title=D_("Media Player Selection"), 411 form = data_form.Form("form", title=D_("Media Player Selection"),
410 formNamespace=NS_MEDIA_PLAYER) 412 formNamespace=NS_MEDIA_PLAYER)
411 for bus in bus_names: 413 for bus in bus_names:
417 field = data_form.Field( 419 field = data_form.Field(
418 "list-single", "media_player", options=options, required=True 420 "list-single", "media_player", options=options, required=True
419 ) 421 )
420 form.addField(field) 422 form.addField(field)
421 payload = form.toElement() 423 payload = form.toElement()
422 defer.returnValue((payload, status, None, None)) 424 return (payload, status, None, None)
423 else: 425 else:
424 # player request 426 # player request
425 try: 427 try:
426 bus_name = command_form["media_player"] 428 bus_name = command_form["media_player"]
427 except KeyError: 429 except KeyError:
430 if not bus_name.startswith(MPRIS_PREFIX): 432 if not bus_name.startswith(MPRIS_PREFIX):
431 log.warning(_("Media player ad-hoc command trying to use non MPRIS bus. " 433 log.warning(_("Media player ad-hoc command trying to use non MPRIS bus. "
432 "Hack attempt? Refused bus: {bus_name}").format( 434 "Hack attempt? Refused bus: {bus_name}").format(
433 bus_name=bus_name)) 435 bus_name=bus_name))
434 note = (self._c.NOTE.ERROR, D_("Invalid player name.")) 436 note = (self._c.NOTE.ERROR, D_("Invalid player name."))
435 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) 437 return (None, self._c.STATUS.COMPLETED, None, note)
436 438
437 try: 439 try:
438 proxy = self.session_bus.get_object(bus_name, MPRIS_PATH) 440 proxy = await self.session_con.getRemoteObject(
439 except dbus.exceptions.DBusException as e: 441 bus_name, MPRIS_PATH, "org.mpris.MediaPlayer2.Player"
442 )
443 except Exception as e:
440 log.warning(_("Can't get D-Bus proxy: {reason}").format(reason=e)) 444 log.warning(_("Can't get D-Bus proxy: {reason}").format(reason=e))
441 note = (self._c.NOTE.ERROR, D_("Media player is not available anymore")) 445 note = (self._c.NOTE.ERROR, D_("Media player is not available anymore"))
442 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) 446 return (None, self._c.STATUS.COMPLETED, None, note)
443 try: 447 try:
444 command = command_form["command"] 448 command = command_form["command"]
445 except KeyError: 449 except KeyError:
446 pass 450 pass
447 else: 451 else:
448 yield self.do_mpris_command(proxy, command) 452 await self.do_mpris_command(proxy, command)
449 453
450 # we construct the remote control form 454 # we construct the remote control form
451 form = data_form.Form("form", title=D_("Media Player Selection")) 455 form = data_form.Form("form", title=D_("Media Player Selection"))
452 form.addField(data_form.Field(fieldType="hidden", 456 form.addField(data_form.Field(fieldType="hidden",
453 var="media_player", 457 var="media_player",
454 value=bus_name)) 458 value=bus_name))
455 for iface, properties_names in MPRIS_PROPERTIES.items(): 459 for iface, properties_names in MPRIS_PROPERTIES.items():
456 for name in properties_names: 460 for name in properties_names:
457 try: 461 try:
458 value = yield self._dbus_get_property(proxy, iface, name) 462 value = await self._dbus_get_property(proxy, iface, name)
459 except Exception as e: 463 except Exception as e:
460 log.warning(_("Can't retrieve attribute {name}: {reason}") 464 log.warning(_("Can't retrieve attribute {name}: {reason}")
461 .format(name=name, reason=e)) 465 .format(name=name, reason=e))
462 continue 466 continue
463 if name == MPRIS_METADATA_KEY: 467 if name == MPRIS_METADATA_KEY:
473 options=commands, 477 options=commands,
474 required=True)) 478 required=True))
475 479
476 payload = form.toElement() 480 payload = form.toElement()
477 status = self._c.STATUS.EXECUTING 481 status = self._c.STATUS.EXECUTING
478 defer.returnValue((payload, status, None, None)) 482 return (payload, status, None, None)