comparison sat/plugins/plugin_adhoc_dbus.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents b6abf8af87db
children fee60f17ebac
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT plugin for adding D-Bus to Ad-Hoc Commands 4 # SAT plugin for adding D-Bus to Ad-Hoc Commands
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
28 28
29 try: 29 try:
30 from lxml import etree 30 from lxml import etree
31 except ImportError: 31 except ImportError:
32 etree = None 32 etree = None
33 log.warning(u"Missing module lxml, please download/install it from http://lxml.de/ ." 33 log.warning("Missing module lxml, please download/install it from http://lxml.de/ ."
34 u"Auto D-Bus discovery will be disabled") 34 "Auto D-Bus discovery will be disabled")
35 from collections import OrderedDict 35 from collections import OrderedDict
36 import os.path 36 import os.path
37 import uuid 37 import uuid
38 try: 38 try:
39 import dbus 39 import dbus
40 from dbus.mainloop.glib import DBusGMainLoop 40 from dbus.mainloop.glib import DBusGMainLoop
41 except ImportError: 41 except ImportError:
42 dbus = None 42 dbus = None
43 log.warning(u"Missing module dbus, please download/install it" 43 log.warning("Missing module dbus, please download/install it"
44 u"auto D-Bus discovery will be disabled") 44 "auto D-Bus discovery will be disabled")
45 45
46 else: 46 else:
47 DBusGMainLoop(set_as_default=True) 47 DBusGMainLoop(set_as_default=True)
48 48
49 NS_MEDIA_PLAYER = "org.salutatoi.mediaplayer" 49 NS_MEDIA_PLAYER = "org.salutatoi.mediaplayer"
50 FD_NAME = "org.freedesktop.DBus" 50 FD_NAME = "org.freedesktop.DBus"
51 FD_PATH = "/org/freedekstop/DBus" 51 FD_PATH = "/org/freedekstop/DBus"
52 INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable" 52 INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable"
53 MPRIS_PREFIX = u"org.mpris.MediaPlayer2" 53 MPRIS_PREFIX = "org.mpris.MediaPlayer2"
54 CMD_GO_BACK = u"GoBack" 54 CMD_GO_BACK = "GoBack"
55 CMD_GO_FWD = u"GoFW" 55 CMD_GO_FWD = "GoFW"
56 SEEK_OFFSET = 5 * 1000 * 1000 56 SEEK_OFFSET = 5 * 1000 * 1000
57 MPRIS_COMMANDS = [u"org.mpris.MediaPlayer2.Player." + cmd for cmd in ( 57 MPRIS_COMMANDS = ["org.mpris.MediaPlayer2.Player." + cmd for cmd in (
58 u"Previous", CMD_GO_BACK, u"PlayPause", CMD_GO_FWD, u"Next")] 58 "Previous", CMD_GO_BACK, "PlayPause", CMD_GO_FWD, "Next")]
59 MPRIS_PATH = u"/org/mpris/MediaPlayer2" 59 MPRIS_PATH = "/org/mpris/MediaPlayer2"
60 MPRIS_PROPERTIES = OrderedDict(( 60 MPRIS_PROPERTIES = OrderedDict((
61 (u"org.mpris.MediaPlayer2", ( 61 ("org.mpris.MediaPlayer2", (
62 "Identity", 62 "Identity",
63 )), 63 )),
64 (u"org.mpris.MediaPlayer2.Player", ( 64 ("org.mpris.MediaPlayer2.Player", (
65 "Metadata", 65 "Metadata",
66 "PlaybackStatus", 66 "PlaybackStatus",
67 "Volume", 67 "Volume",
68 )), 68 )),
69 )) 69 ))
70 MPRIS_METADATA_KEY = "Metadata" 70 MPRIS_METADATA_KEY = "Metadata"
71 MPRIS_METADATA_MAP = OrderedDict(( 71 MPRIS_METADATA_MAP = OrderedDict((
72 ("xesam:title", u"Title"), 72 ("xesam:title", "Title"),
73 )) 73 ))
74 74
75 INTROSPECT_METHOD = "Introspect" 75 INTROSPECT_METHOD = "Introspect"
76 IGNORED_IFACES_START = ( 76 IGNORED_IFACES_START = (
77 "org.freedesktop", 77 "org.freedesktop",
86 C.PI_TYPE: "Misc", 86 C.PI_TYPE: "Misc",
87 C.PI_PROTOCOLS: [], 87 C.PI_PROTOCOLS: [],
88 C.PI_DEPENDENCIES: ["XEP-0050"], 88 C.PI_DEPENDENCIES: ["XEP-0050"],
89 C.PI_MAIN: "AdHocDBus", 89 C.PI_MAIN: "AdHocDBus",
90 C.PI_HANDLER: "no", 90 C.PI_HANDLER: "no",
91 C.PI_DESCRIPTION: _(u"""Add D-Bus management to Ad-Hoc commands"""), 91 C.PI_DESCRIPTION: _("""Add D-Bus management to Ad-Hoc commands"""),
92 } 92 }
93 93
94 94
95 class AdHocDBus(object): 95 class AdHocDBus(object):
96 96
102 "adHocDBusAddAuto", 102 "adHocDBusAddAuto",
103 ".plugin", 103 ".plugin",
104 in_sign="sasasasasasass", 104 in_sign="sasasasasasass",
105 out_sign="(sa(sss))", 105 out_sign="(sa(sss))",
106 method=self._adHocDBusAddAuto, 106 method=self._adHocDBusAddAuto,
107 async=True, 107 async_=True,
108 ) 108 )
109 host.bridge.addMethod( 109 host.bridge.addMethod(
110 "adHocRemotesGet", 110 "adHocRemotesGet",
111 ".plugin", 111 ".plugin",
112 in_sign="s", 112 in_sign="s",
113 out_sign="a(sss)", 113 out_sign="a(sss)",
114 method=self._adHocRemotesGet, 114 method=self._adHocRemotesGet,
115 async=True, 115 async_=True,
116 ) 116 )
117 self._c = host.plugins["XEP-0050"] 117 self._c = host.plugins["XEP-0050"]
118 host.registerNamespace(u"mediaplayer", NS_MEDIA_PLAYER) 118 host.registerNamespace("mediaplayer", NS_MEDIA_PLAYER)
119 if dbus is not None: 119 if dbus is not None:
120 self.session_bus = dbus.SessionBus() 120 self.session_bus = dbus.SessionBus()
121 self.fd_object = self.session_bus.get_object( 121 self.fd_object = self.session_bus.get_object(
122 FD_NAME, FD_PATH, introspect=False) 122 FD_NAME, FD_PATH, introspect=False)
123 123
124 def profileConnected(self, client): 124 def profileConnected(self, client):
125 if dbus is not None: 125 if dbus is not None:
126 self._c.addAdHocCommand( 126 self._c.addAdHocCommand(
127 client, self.localMediaCb, D_(u"Media Players"), 127 client, self.localMediaCb, D_("Media Players"),
128 node=NS_MEDIA_PLAYER, 128 node=NS_MEDIA_PLAYER,
129 timeout=60*60*6 # 6 hours timeout, to avoid breaking remote 129 timeout=60*60*6 # 6 hours timeout, to avoid breaking remote
130 # in the middle of a movie 130 # in the middle of a movie
131 ) 131 )
132 132
149 proxy.get_dbus_method(method, dbus_interface=interface)(*args, **kwargs) 149 proxy.get_dbus_method(method, dbus_interface=interface)(*args, **kwargs)
150 return d 150 return d
151 151
152 def _DBusGetProperty(self, proxy, interface, name): 152 def _DBusGetProperty(self, proxy, interface, name):
153 return self._DBusAsyncCall( 153 return self._DBusAsyncCall(
154 proxy, u"Get", interface, name, interface=u"org.freedesktop.DBus.Properties") 154 proxy, "Get", interface, name, interface="org.freedesktop.DBus.Properties")
155 155
156 156
157 def _DBusListNames(self): 157 def _DBusListNames(self):
158 return self._DBusAsyncCall(self.fd_object, "ListNames") 158 return self._DBusAsyncCall(self.fd_object, "ListNames")
159 159
269 note = None 269 note = None
270 270
271 elif len(actions) == 2: 271 elif len(actions) == 2:
272 # we should have the answer here 272 # we should have the answer here
273 try: 273 try:
274 x_elt = command_elt.elements(data_form.NS_X_DATA, "x").next() 274 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x"))
275 answer_form = data_form.Form.fromElement(x_elt) 275 answer_form = data_form.Form.fromElement(x_elt)
276 command = answer_form["command"] 276 command = answer_form["command"]
277 except (KeyError, StopIteration): 277 except (KeyError, StopIteration):
278 raise self._c.AdHocError(self._c.ERROR.BAD_PAYLOAD) 278 raise self._c.AdHocError(self._c.ERROR.BAD_PAYLOAD)
279 279
293 del actions[:] 293 del actions[:]
294 names_map.clear() 294 names_map.clear()
295 return DBusCallback( 295 return DBusCallback(
296 client, None, session_data, self._c.ACTION.EXECUTE, node 296 client, None, session_data, self._c.ACTION.EXECUTE, node
297 ) 297 )
298 form = data_form.Form("form", title=_(u"Updated")) 298 form = data_form.Form("form", title=_("Updated"))
299 form.addField(data_form.Field("fixed", u"Command sent")) 299 form.addField(data_form.Field("fixed", "Command sent"))
300 status = self._c.STATUS.COMPLETED 300 status = self._c.STATUS.COMPLETED
301 payload = None 301 payload = None
302 note = (self._c.NOTE.INFO, _(u"Command sent")) 302 note = (self._c.NOTE.INFO, _("Command sent"))
303 else: 303 else:
304 raise self._c.AdHocError(self._c.ERROR.INTERNAL) 304 raise self._c.AdHocError(self._c.ERROR.INTERNAL)
305 305
306 return (payload, status, None, note) 306 return (payload, status, None, note)
307 307
361 remotes.append((device_jid_s, 361 remotes.append((device_jid_s,
362 opt.value, 362 opt.value,
363 opt.label or opt.value)) 363 opt.label or opt.value))
364 except Exception as e: 364 except Exception as e:
365 log.warning(_( 365 log.warning(_(
366 u"Can't retrieve remote controllers on {device_jid}: " 366 "Can't retrieve remote controllers on {device_jid}: "
367 u"{reason}".format(device_jid=device_jid, reason=e))) 367 "{reason}".format(device_jid=device_jid, reason=e)))
368 break 368 break
369 defer.returnValue(remotes) 369 defer.returnValue(remotes)
370 370
371 def doMPRISCommand(self, proxy, command): 371 def doMPRISCommand(self, proxy, command):
372 iface, command = command.rsplit(u".", 1) 372 iface, command = command.rsplit(".", 1)
373 if command == CMD_GO_BACK: 373 if command == CMD_GO_BACK:
374 command = u'Seek' 374 command = 'Seek'
375 args = [-SEEK_OFFSET] 375 args = [-SEEK_OFFSET]
376 elif command == CMD_GO_FWD: 376 elif command == CMD_GO_FWD:
377 command = u'Seek' 377 command = 'Seek'
378 args = [SEEK_OFFSET] 378 args = [SEEK_OFFSET]
379 else: 379 else:
380 args = [] 380 args = []
381 return self._DBusAsyncCall(proxy, command, *args, interface=iface) 381 return self._DBusAsyncCall(proxy, command, *args, interface=iface)
382 382
383 def addMPRISMetadata(self, form, metadata): 383 def addMPRISMetadata(self, form, metadata):
384 """Serialise MRPIS Metadata according to MPRIS_METADATA_MAP""" 384 """Serialise MRPIS Metadata according to MPRIS_METADATA_MAP"""
385 for mpris_key, name in MPRIS_METADATA_MAP.iteritems(): 385 for mpris_key, name in MPRIS_METADATA_MAP.items():
386 if mpris_key in metadata: 386 if mpris_key in metadata:
387 value = unicode(metadata[mpris_key]) 387 value = str(metadata[mpris_key])
388 form.addField(data_form.Field(fieldType=u"fixed", 388 form.addField(data_form.Field(fieldType="fixed",
389 var=name, 389 var=name,
390 value=value)) 390 value=value))
391 391
392 @defer.inlineCallbacks 392 @defer.inlineCallbacks
393 def localMediaCb(self, client, command_elt, session_data, action, node): 393 def localMediaCb(self, client, command_elt, session_data, action, node):
394 try: 394 try:
395 x_elt = command_elt.elements(data_form.NS_X_DATA, "x").next() 395 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x"))
396 command_form = data_form.Form.fromElement(x_elt) 396 command_form = data_form.Form.fromElement(x_elt)
397 except StopIteration: 397 except StopIteration:
398 command_form = None 398 command_form = None
399 399
400 if command_form is None or len(command_form.fields) == 0: 400 if command_form is None or len(command_form.fields) == 0:
401 # root request, we looks for media players 401 # root request, we looks for media players
402 bus_names = yield self._DBusListNames() 402 bus_names = yield self._DBusListNames()
403 bus_names = [b for b in bus_names if b.startswith(MPRIS_PREFIX)] 403 bus_names = [b for b in bus_names if b.startswith(MPRIS_PREFIX)]
404 if len(bus_names) == 0: 404 if len(bus_names) == 0:
405 note = (self._c.NOTE.INFO, D_(u"No media player found.")) 405 note = (self._c.NOTE.INFO, D_("No media player found."))
406 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) 406 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note))
407 options = [] 407 options = []
408 status = self._c.STATUS.EXECUTING 408 status = self._c.STATUS.EXECUTING
409 form = data_form.Form("form", title=D_(u"Media Player Selection"), 409 form = data_form.Form("form", title=D_("Media Player Selection"),
410 formNamespace=NS_MEDIA_PLAYER) 410 formNamespace=NS_MEDIA_PLAYER)
411 for bus in bus_names: 411 for bus in bus_names:
412 player_name = bus[len(MPRIS_PREFIX)+1:] 412 player_name = bus[len(MPRIS_PREFIX)+1:]
413 if not player_name: 413 if not player_name:
414 log.warning(_(u"Ignoring MPRIS bus without suffix")) 414 log.warning(_("Ignoring MPRIS bus without suffix"))
415 continue 415 continue
416 options.append(data_form.Option(bus, player_name)) 416 options.append(data_form.Option(bus, player_name))
417 field = data_form.Field( 417 field = data_form.Field(
418 "list-single", "media_player", options=options, required=True 418 "list-single", "media_player", options=options, required=True
419 ) 419 )
421 payload = form.toElement() 421 payload = form.toElement()
422 defer.returnValue((payload, status, None, None)) 422 defer.returnValue((payload, status, None, None))
423 else: 423 else:
424 # player request 424 # player request
425 try: 425 try:
426 bus_name = command_form[u"media_player"] 426 bus_name = command_form["media_player"]
427 except KeyError: 427 except KeyError:
428 raise ValueError(_(u"missing media_player value")) 428 raise ValueError(_("missing media_player value"))
429 429
430 if not bus_name.startswith(MPRIS_PREFIX): 430 if not bus_name.startswith(MPRIS_PREFIX):
431 log.warning(_(u"Media player ad-hoc command trying to use non MPRIS bus. " 431 log.warning(_("Media player ad-hoc command trying to use non MPRIS bus. "
432 u"Hack attempt? Refused bus: {bus_name}").format( 432 "Hack attempt? Refused bus: {bus_name}").format(
433 bus_name=bus_name)) 433 bus_name=bus_name))
434 note = (self._c.NOTE.ERROR, D_(u"Invalid player name.")) 434 note = (self._c.NOTE.ERROR, D_("Invalid player name."))
435 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) 435 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note))
436 436
437 try: 437 try:
438 proxy = self.session_bus.get_object(bus_name, MPRIS_PATH) 438 proxy = self.session_bus.get_object(bus_name, MPRIS_PATH)
439 except dbus.exceptions.DBusException as e: 439 except dbus.exceptions.DBusException as e:
440 log.warning(_(u"Can't get D-Bus proxy: {reason}").format(reason=e)) 440 log.warning(_("Can't get D-Bus proxy: {reason}").format(reason=e))
441 note = (self._c.NOTE.ERROR, D_(u"Media player is not available anymore")) 441 note = (self._c.NOTE.ERROR, D_("Media player is not available anymore"))
442 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) 442 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note))
443 try: 443 try:
444 command = command_form[u"command"] 444 command = command_form["command"]
445 except KeyError: 445 except KeyError:
446 pass 446 pass
447 else: 447 else:
448 yield self.doMPRISCommand(proxy, command) 448 yield self.doMPRISCommand(proxy, command)
449 449
450 # we construct the remote control form 450 # we construct the remote control form
451 form = data_form.Form("form", title=D_(u"Media Player Selection")) 451 form = data_form.Form("form", title=D_("Media Player Selection"))
452 form.addField(data_form.Field(fieldType=u"hidden", 452 form.addField(data_form.Field(fieldType="hidden",
453 var=u"media_player", 453 var="media_player",
454 value=bus_name)) 454 value=bus_name))
455 for iface, properties_names in MPRIS_PROPERTIES.iteritems(): 455 for iface, properties_names in MPRIS_PROPERTIES.items():
456 for name in properties_names: 456 for name in properties_names:
457 try: 457 try:
458 value = yield self._DBusGetProperty(proxy, iface, name) 458 value = yield self._DBusGetProperty(proxy, iface, name)
459 except Exception as e: 459 except Exception as e:
460 log.warning(_(u"Can't retrieve attribute {name}: {reason}") 460 log.warning(_("Can't retrieve attribute {name}: {reason}")
461 .format(name=name, reason=e)) 461 .format(name=name, reason=e))
462 continue 462 continue
463 if name == MPRIS_METADATA_KEY: 463 if name == MPRIS_METADATA_KEY:
464 self.addMPRISMetadata(form, value) 464 self.addMPRISMetadata(form, value)
465 else: 465 else:
466 form.addField(data_form.Field(fieldType=u"fixed", 466 form.addField(data_form.Field(fieldType="fixed",
467 var=name, 467 var=name,
468 value=unicode(value))) 468 value=str(value)))
469 469
470 commands = [data_form.Option(c, c.rsplit(u".", 1)[1]) for c in MPRIS_COMMANDS] 470 commands = [data_form.Option(c, c.rsplit(".", 1)[1]) for c in MPRIS_COMMANDS]
471 form.addField(data_form.Field(fieldType=u"list-single", 471 form.addField(data_form.Field(fieldType="list-single",
472 var=u"command", 472 var="command",
473 options=commands, 473 options=commands,
474 required=True)) 474 required=True))
475 475
476 payload = form.toElement() 476 payload = form.toElement()
477 status = self._c.STATUS.EXECUTING 477 status = self._c.STATUS.EXECUTING