service.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. # Copyright (C) 2003-2006 Red Hat Inc. <http://www.redhat.com/>
  2. # Copyright (C) 2003 David Zeuthen
  3. # Copyright (C) 2004 Rob Taylor
  4. # Copyright (C) 2005-2006 Collabora Ltd. <http://www.collabora.co.uk/>
  5. #
  6. # Permission is hereby granted, free of charge, to any person
  7. # obtaining a copy of this software and associated documentation
  8. # files (the "Software"), to deal in the Software without
  9. # restriction, including without limitation the rights to use, copy,
  10. # modify, merge, publish, distribute, sublicense, and/or sell copies
  11. # of the Software, and to permit persons to whom the Software is
  12. # furnished to do so, subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be
  15. # included in all copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  18. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  19. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  20. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  21. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  22. # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  24. # DEALINGS IN THE SOFTWARE.
  25. __all__ = ('BusName', 'Object', 'FallbackObject', 'method', 'signal')
  26. __docformat__ = 'restructuredtext'
  27. import sys
  28. import logging
  29. import threading
  30. import traceback
  31. from collections import Sequence
  32. import _dbus_bindings
  33. from dbus import (
  34. INTROSPECTABLE_IFACE, ObjectPath, SessionBus, Signature, Struct,
  35. validate_bus_name, validate_object_path)
  36. from dbus.decorators import method, signal
  37. from dbus.exceptions import (
  38. DBusException, NameExistsException, UnknownMethodException)
  39. from dbus.lowlevel import ErrorMessage, MethodReturnMessage, MethodCallMessage
  40. from dbus.proxies import LOCAL_PATH
  41. from dbus._compat import is_py2
  42. _logger = logging.getLogger('dbus.service')
  43. class _VariantSignature(object):
  44. """A fake method signature which, when iterated, yields an endless stream
  45. of 'v' characters representing variants (handy with zip()).
  46. It has no string representation.
  47. """
  48. def __iter__(self):
  49. """Return self."""
  50. return self
  51. def __next__(self):
  52. """Return 'v' whenever called."""
  53. return 'v'
  54. if is_py2:
  55. next = __next__
  56. class BusName(object):
  57. """A base class for exporting your own Named Services across the Bus.
  58. When instantiated, objects of this class attempt to claim the given
  59. well-known name on the given bus for the current process. The name is
  60. released when the BusName object becomes unreferenced.
  61. If a well-known name is requested multiple times, multiple references
  62. to the same BusName object will be returned.
  63. Caveats
  64. -------
  65. - Assumes that named services are only ever requested using this class -
  66. if you request names from the bus directly, confusion may occur.
  67. - Does not handle queueing.
  68. """
  69. def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
  70. """Constructor, which may either return an existing cached object
  71. or a new object.
  72. :Parameters:
  73. `name` : str
  74. The well-known name to be advertised
  75. `bus` : dbus.Bus
  76. A Bus on which this service will be advertised.
  77. Omitting this parameter or setting it to None has been
  78. deprecated since version 0.82.1. For backwards compatibility,
  79. if this is done, the global shared connection to the session
  80. bus will be used.
  81. `allow_replacement` : bool
  82. If True, other processes trying to claim the same well-known
  83. name will take precedence over this one.
  84. `replace_existing` : bool
  85. If True, this process can take over the well-known name
  86. from other processes already holding it.
  87. `do_not_queue` : bool
  88. If True, this service will not be placed in the queue of
  89. services waiting for the requested name if another service
  90. already holds it.
  91. """
  92. validate_bus_name(name, allow_well_known=True, allow_unique=False)
  93. # if necessary, get default bus (deprecated)
  94. if bus is None:
  95. import warnings
  96. warnings.warn('Omitting the "bus" parameter to '
  97. 'dbus.service.BusName.__init__ is deprecated',
  98. DeprecationWarning, stacklevel=2)
  99. bus = SessionBus()
  100. # see if this name is already defined, return it if so
  101. # FIXME: accessing internals of Bus
  102. if name in bus._bus_names:
  103. return bus._bus_names[name]
  104. # otherwise register the name
  105. name_flags = (
  106. (allow_replacement and _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT or 0) |
  107. (replace_existing and _dbus_bindings.NAME_FLAG_REPLACE_EXISTING or 0) |
  108. (do_not_queue and _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE or 0))
  109. retval = bus.request_name(name, name_flags)
  110. # TODO: more intelligent tracking of bus name states?
  111. if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
  112. pass
  113. elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
  114. # queueing can happen by default, maybe we should
  115. # track this better or let the user know if they're
  116. # queued or not?
  117. pass
  118. elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
  119. raise NameExistsException(name)
  120. elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
  121. # if this is a shared bus which is being used by someone
  122. # else in this process, this can happen legitimately
  123. pass
  124. else:
  125. raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
  126. # and create the object
  127. bus_name = object.__new__(cls)
  128. bus_name._bus = bus
  129. bus_name._name = name
  130. # cache instance (weak ref only)
  131. # FIXME: accessing Bus internals again
  132. bus._bus_names[name] = bus_name
  133. return bus_name
  134. # do nothing because this is called whether or not the bus name
  135. # object was retrieved from the cache or created new
  136. def __init__(self, *args, **keywords):
  137. pass
  138. # we can delete the low-level name here because these objects
  139. # are guaranteed to exist only once for each bus name
  140. def __del__(self):
  141. self._bus.release_name(self._name)
  142. pass
  143. def get_bus(self):
  144. """Get the Bus this Service is on"""
  145. return self._bus
  146. def get_name(self):
  147. """Get the name of this service"""
  148. return self._name
  149. def __repr__(self):
  150. return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
  151. __str__ = __repr__
  152. def _method_lookup(self, method_name, dbus_interface):
  153. """Walks the Python MRO of the given class to find the method to invoke.
  154. Returns two methods, the one to call, and the one it inherits from which
  155. defines its D-Bus interface name, signature, and attributes.
  156. """
  157. parent_method = None
  158. candidate_class = None
  159. successful = False
  160. # split up the cases when we do and don't have an interface because the
  161. # latter is much simpler
  162. if dbus_interface:
  163. # search through the class hierarchy in python MRO order
  164. for cls in self.__class__.__mro__:
  165. # if we haven't got a candidate class yet, and we find a class with a
  166. # suitably named member, save this as a candidate class
  167. if (not candidate_class and method_name in cls.__dict__):
  168. if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
  169. and "_dbus_interface" in cls.__dict__[method_name].__dict__):
  170. # however if it is annotated for a different interface
  171. # than we are looking for, it cannot be a candidate
  172. if cls.__dict__[method_name]._dbus_interface == dbus_interface:
  173. candidate_class = cls
  174. parent_method = cls.__dict__[method_name]
  175. successful = True
  176. break
  177. else:
  178. pass
  179. else:
  180. candidate_class = cls
  181. # if we have a candidate class, carry on checking this and all
  182. # superclasses for a method annoated as a dbus method
  183. # on the correct interface
  184. if (candidate_class and method_name in cls.__dict__
  185. and "_dbus_is_method" in cls.__dict__[method_name].__dict__
  186. and "_dbus_interface" in cls.__dict__[method_name].__dict__
  187. and cls.__dict__[method_name]._dbus_interface == dbus_interface):
  188. # the candidate class has a dbus method on the correct interface,
  189. # or overrides a method that is, success!
  190. parent_method = cls.__dict__[method_name]
  191. successful = True
  192. break
  193. else:
  194. # simpler version of above
  195. for cls in self.__class__.__mro__:
  196. if (not candidate_class and method_name in cls.__dict__):
  197. candidate_class = cls
  198. if (candidate_class and method_name in cls.__dict__
  199. and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
  200. parent_method = cls.__dict__[method_name]
  201. successful = True
  202. break
  203. if successful:
  204. return (candidate_class.__dict__[method_name], parent_method)
  205. else:
  206. if dbus_interface:
  207. raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
  208. else:
  209. raise UnknownMethodException('%s is not a valid method' % method_name)
  210. def _method_reply_return(connection, message, method_name, signature, *retval):
  211. reply = MethodReturnMessage(message)
  212. try:
  213. reply.append(signature=signature, *retval)
  214. except Exception as e:
  215. logging.basicConfig()
  216. if signature is None:
  217. try:
  218. signature = reply.guess_signature(retval) + ' (guessed)'
  219. except Exception as e:
  220. _logger.error('Unable to guess signature for arguments %r: '
  221. '%s: %s', retval, e.__class__, e)
  222. raise
  223. _logger.error('Unable to append %r to message with signature %s: '
  224. '%s: %s', retval, signature, e.__class__, e)
  225. raise
  226. connection.send_message(reply)
  227. def _method_reply_error(connection, message, exception):
  228. name = getattr(exception, '_dbus_error_name', None)
  229. if name is not None:
  230. pass
  231. elif getattr(exception, '__module__', '') in ('', '__main__'):
  232. name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
  233. else:
  234. name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
  235. et, ev, etb = sys.exc_info()
  236. if isinstance(exception, DBusException) and not exception.include_traceback:
  237. # We don't actually want the traceback anyway
  238. contents = exception.get_dbus_message()
  239. elif ev is exception:
  240. # The exception was actually thrown, so we can get a traceback
  241. contents = ''.join(traceback.format_exception(et, ev, etb))
  242. else:
  243. # We don't have any traceback for it, e.g.
  244. # async_err_cb(MyException('Failed to badger the mushroom'))
  245. # see also https://bugs.freedesktop.org/show_bug.cgi?id=12403
  246. contents = ''.join(traceback.format_exception_only(exception.__class__,
  247. exception))
  248. reply = ErrorMessage(message, name, contents)
  249. connection.send_message(reply)
  250. class InterfaceType(type):
  251. def __init__(cls, name, bases, dct):
  252. # these attributes are shared between all instances of the Interface
  253. # object, so this has to be a dictionary that maps class names to
  254. # the per-class introspection/interface data
  255. class_table = getattr(cls, '_dbus_class_table', {})
  256. cls._dbus_class_table = class_table
  257. interface_table = class_table[cls.__module__ + '.' + name] = {}
  258. # merge all the name -> method tables for all the interfaces
  259. # implemented by our base classes into our own
  260. for b in bases:
  261. base_name = b.__module__ + '.' + b.__name__
  262. if getattr(b, '_dbus_class_table', False):
  263. for (interface, method_table) in class_table[base_name].items():
  264. our_method_table = interface_table.setdefault(interface, {})
  265. our_method_table.update(method_table)
  266. # add in all the name -> method entries for our own methods/signals
  267. for func in dct.values():
  268. if getattr(func, '_dbus_interface', False):
  269. method_table = interface_table.setdefault(func._dbus_interface, {})
  270. method_table[func.__name__] = func
  271. super(InterfaceType, cls).__init__(name, bases, dct)
  272. # methods are different to signals, so we have two functions... :)
  273. def _reflect_on_method(cls, func):
  274. args = func._dbus_args
  275. if func._dbus_in_signature:
  276. # convert signature into a tuple so length refers to number of
  277. # types, not number of characters. the length is checked by
  278. # the decorator to make sure it matches the length of args.
  279. in_sig = tuple(Signature(func._dbus_in_signature))
  280. else:
  281. # magic iterator which returns as many v's as we need
  282. in_sig = _VariantSignature()
  283. if func._dbus_out_signature:
  284. out_sig = Signature(func._dbus_out_signature)
  285. else:
  286. # its tempting to default to Signature('v'), but
  287. # for methods that return nothing, providing incorrect
  288. # introspection data is worse than providing none at all
  289. out_sig = []
  290. reflection_data = ' <method name="%s">\n' % (func.__name__)
  291. for pair in zip(in_sig, args):
  292. reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
  293. for type in out_sig:
  294. reflection_data += ' <arg direction="out" type="%s" />\n' % type
  295. reflection_data += ' </method>\n'
  296. return reflection_data
  297. def _reflect_on_signal(cls, func):
  298. args = func._dbus_args
  299. if func._dbus_signature:
  300. # convert signature into a tuple so length refers to number of
  301. # types, not number of characters
  302. sig = tuple(Signature(func._dbus_signature))
  303. else:
  304. # magic iterator which returns as many v's as we need
  305. sig = _VariantSignature()
  306. reflection_data = ' <signal name="%s">\n' % (func.__name__)
  307. for pair in zip(sig, args):
  308. reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
  309. reflection_data = reflection_data + ' </signal>\n'
  310. return reflection_data
  311. # Define Interface as an instance of the metaclass InterfaceType, in a way
  312. # that is compatible across both Python 2 and Python 3.
  313. Interface = InterfaceType('Interface', (object,), {})
  314. #: A unique object used as the value of Object._object_path and
  315. #: Object._connection if it's actually in more than one place
  316. _MANY = object()
  317. class Object(Interface):
  318. r"""A base class for exporting your own Objects across the Bus.
  319. Just inherit from Object and mark exported methods with the
  320. @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
  321. Example::
  322. class Example(dbus.service.object):
  323. def __init__(self, object_path):
  324. dbus.service.Object.__init__(self, dbus.SessionBus(), path)
  325. self._last_input = None
  326. @dbus.service.method(interface='com.example.Sample',
  327. in_signature='v', out_signature='s')
  328. def StringifyVariant(self, var):
  329. self.LastInputChanged(var) # emits the signal
  330. return str(var)
  331. @dbus.service.signal(interface='com.example.Sample',
  332. signature='v')
  333. def LastInputChanged(self, var):
  334. # run just before the signal is actually emitted
  335. # just put "pass" if nothing should happen
  336. self._last_input = var
  337. @dbus.service.method(interface='com.example.Sample',
  338. in_signature='', out_signature='v')
  339. def GetLastInput(self):
  340. return self._last_input
  341. """
  342. #: If True, this object can be made available at more than one object path.
  343. #: If True but `SUPPORTS_MULTIPLE_CONNECTIONS` is False, the object may
  344. #: handle more than one object path, but they must all be on the same
  345. #: connection.
  346. SUPPORTS_MULTIPLE_OBJECT_PATHS = False
  347. #: If True, this object can be made available on more than one connection.
  348. #: If True but `SUPPORTS_MULTIPLE_OBJECT_PATHS` is False, the object must
  349. #: have the same object path on all its connections.
  350. SUPPORTS_MULTIPLE_CONNECTIONS = False
  351. def __init__(self, conn=None, object_path=None, bus_name=None):
  352. """Constructor. Either conn or bus_name is required; object_path
  353. is also required.
  354. :Parameters:
  355. `conn` : dbus.connection.Connection or None
  356. The connection on which to export this object.
  357. If None, use the Bus associated with the given ``bus_name``.
  358. If there is no ``bus_name`` either, the object is not
  359. initially available on any Connection.
  360. For backwards compatibility, if an instance of
  361. dbus.service.BusName is passed as the first parameter,
  362. this is equivalent to passing its associated Bus as
  363. ``conn``, and passing the BusName itself as ``bus_name``.
  364. `object_path` : str or None
  365. A D-Bus object path at which to make this Object available
  366. immediately. If this is not None, a `conn` or `bus_name` must
  367. also be provided.
  368. `bus_name` : dbus.service.BusName or None
  369. Represents a well-known name claimed by this process. A
  370. reference to the BusName object will be held by this
  371. Object, preventing the name from being released during this
  372. Object's lifetime (unless it's released manually).
  373. """
  374. if object_path is not None:
  375. validate_object_path(object_path)
  376. if isinstance(conn, BusName):
  377. # someone's using the old API; don't gratuitously break them
  378. bus_name = conn
  379. conn = bus_name.get_bus()
  380. elif conn is None:
  381. if bus_name is not None:
  382. # someone's using the old API but naming arguments, probably
  383. conn = bus_name.get_bus()
  384. #: Either an object path, None or _MANY
  385. self._object_path = None
  386. #: Either a dbus.connection.Connection, None or _MANY
  387. self._connection = None
  388. #: A list of tuples (Connection, object path, False) where the False
  389. #: is for future expansion (to support fallback paths)
  390. self._locations = []
  391. #: Lock protecting `_locations`, `_connection` and `_object_path`
  392. self._locations_lock = threading.Lock()
  393. #: True if this is a fallback object handling a whole subtree.
  394. self._fallback = False
  395. self._name = bus_name
  396. if conn is None and object_path is not None:
  397. raise TypeError('If object_path is given, either conn or bus_name '
  398. 'is required')
  399. if conn is not None and object_path is not None:
  400. self.add_to_connection(conn, object_path)
  401. @property
  402. def __dbus_object_path__(self):
  403. """The object-path at which this object is available.
  404. Access raises AttributeError if there is no object path, or more than
  405. one object path.
  406. Changed in 0.82.0: AttributeError can be raised.
  407. """
  408. if self._object_path is _MANY:
  409. raise AttributeError('Object %r has more than one object path: '
  410. 'use Object.locations instead' % self)
  411. elif self._object_path is None:
  412. raise AttributeError('Object %r has no object path yet' % self)
  413. else:
  414. return self._object_path
  415. @property
  416. def connection(self):
  417. """The Connection on which this object is available.
  418. Access raises AttributeError if there is no Connection, or more than
  419. one Connection.
  420. Changed in 0.82.0: AttributeError can be raised.
  421. """
  422. if self._connection is _MANY:
  423. raise AttributeError('Object %r is on more than one Connection: '
  424. 'use Object.locations instead' % self)
  425. elif self._connection is None:
  426. raise AttributeError('Object %r has no Connection yet' % self)
  427. else:
  428. return self._connection
  429. @property
  430. def locations(self):
  431. """An iterable over tuples representing locations at which this
  432. object is available.
  433. Each tuple has at least two items, but may have more in future
  434. versions of dbus-python, so do not rely on their exact length.
  435. The first two items are the dbus.connection.Connection and the object
  436. path.
  437. :Since: 0.82.0
  438. """
  439. return iter(self._locations)
  440. def add_to_connection(self, connection, path):
  441. """Make this object accessible via the given D-Bus connection and
  442. object path.
  443. :Parameters:
  444. `connection` : dbus.connection.Connection
  445. Export the object on this connection. If the class attribute
  446. SUPPORTS_MULTIPLE_CONNECTIONS is False (default), this object
  447. can only be made available on one connection; if the class
  448. attribute is set True by a subclass, the object can be made
  449. available on more than one connection.
  450. `path` : dbus.ObjectPath or other str
  451. Place the object at this object path. If the class attribute
  452. SUPPORTS_MULTIPLE_OBJECT_PATHS is False (default), this object
  453. can only be made available at one object path; if the class
  454. attribute is set True by a subclass, the object can be made
  455. available with more than one object path.
  456. :Raises ValueError: if the object's class attributes do not allow the
  457. object to be exported in the desired way.
  458. :Since: 0.82.0
  459. """
  460. if path == LOCAL_PATH:
  461. raise ValueError('Objects may not be exported on the reserved '
  462. 'path %s' % LOCAL_PATH)
  463. self._locations_lock.acquire()
  464. try:
  465. if (self._connection is not None and
  466. self._connection is not connection and
  467. not self.SUPPORTS_MULTIPLE_CONNECTIONS):
  468. raise ValueError('%r is already exported on '
  469. 'connection %r' % (self, self._connection))
  470. if (self._object_path is not None and
  471. not self.SUPPORTS_MULTIPLE_OBJECT_PATHS and
  472. self._object_path != path):
  473. raise ValueError('%r is already exported at object '
  474. 'path %s' % (self, self._object_path))
  475. connection._register_object_path(path, self._message_cb,
  476. self._unregister_cb,
  477. self._fallback)
  478. if self._connection is None:
  479. self._connection = connection
  480. elif self._connection is not connection:
  481. self._connection = _MANY
  482. if self._object_path is None:
  483. self._object_path = path
  484. elif self._object_path != path:
  485. self._object_path = _MANY
  486. self._locations.append((connection, path, self._fallback))
  487. finally:
  488. self._locations_lock.release()
  489. def remove_from_connection(self, connection=None, path=None):
  490. """Make this object inaccessible via the given D-Bus connection
  491. and object path. If no connection or path is specified,
  492. the object ceases to be accessible via any connection or path.
  493. :Parameters:
  494. `connection` : dbus.connection.Connection or None
  495. Only remove the object from this Connection. If None,
  496. remove from all Connections on which it's exported.
  497. `path` : dbus.ObjectPath or other str, or None
  498. Only remove the object from this object path. If None,
  499. remove from all object paths.
  500. :Raises LookupError:
  501. if the object was not exported on the requested connection
  502. or path, or (if both are None) was not exported at all.
  503. :Since: 0.81.1
  504. """
  505. self._locations_lock.acquire()
  506. try:
  507. if self._object_path is None or self._connection is None:
  508. raise LookupError('%r is not exported' % self)
  509. if connection is not None or path is not None:
  510. dropped = []
  511. for location in self._locations:
  512. if ((connection is None or location[0] is connection) and
  513. (path is None or location[1] == path)):
  514. dropped.append(location)
  515. else:
  516. dropped = self._locations
  517. self._locations = []
  518. if not dropped:
  519. raise LookupError('%r is not exported at a location matching '
  520. '(%r,%r)' % (self, connection, path))
  521. for location in dropped:
  522. try:
  523. location[0]._unregister_object_path(location[1])
  524. except LookupError:
  525. pass
  526. if self._locations:
  527. try:
  528. self._locations.remove(location)
  529. except ValueError:
  530. pass
  531. finally:
  532. self._locations_lock.release()
  533. def _unregister_cb(self, connection):
  534. # there's not really enough information to do anything useful here
  535. _logger.info('Unregistering exported object %r from some path '
  536. 'on %r', self, connection)
  537. def _message_cb(self, connection, message):
  538. if not isinstance(message, MethodCallMessage):
  539. return
  540. try:
  541. # lookup candidate method and parent method
  542. method_name = message.get_member()
  543. interface_name = message.get_interface()
  544. (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
  545. # set up method call parameters
  546. args = message.get_args_list(**parent_method._dbus_get_args_options)
  547. keywords = {}
  548. if parent_method._dbus_out_signature is not None:
  549. signature = Signature(parent_method._dbus_out_signature)
  550. else:
  551. signature = None
  552. # set up async callback functions
  553. if parent_method._dbus_async_callbacks:
  554. (return_callback, error_callback) = parent_method._dbus_async_callbacks
  555. keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
  556. keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
  557. # include the sender etc. if desired
  558. if parent_method._dbus_sender_keyword:
  559. keywords[parent_method._dbus_sender_keyword] = message.get_sender()
  560. if parent_method._dbus_path_keyword:
  561. keywords[parent_method._dbus_path_keyword] = message.get_path()
  562. if parent_method._dbus_rel_path_keyword:
  563. path = message.get_path()
  564. rel_path = path
  565. for exp in self._locations:
  566. # pathological case: if we're exported in two places,
  567. # one of which is a subtree of the other, then pick the
  568. # subtree by preference (i.e. minimize the length of
  569. # rel_path)
  570. if exp[0] is connection:
  571. if path == exp[1]:
  572. rel_path = '/'
  573. break
  574. if exp[1] == '/':
  575. # we already have rel_path == path at the beginning
  576. continue
  577. if path.startswith(exp[1] + '/'):
  578. # yes we're in this exported subtree
  579. suffix = path[len(exp[1]):]
  580. if len(suffix) < len(rel_path):
  581. rel_path = suffix
  582. rel_path = ObjectPath(rel_path)
  583. keywords[parent_method._dbus_rel_path_keyword] = rel_path
  584. if parent_method._dbus_destination_keyword:
  585. keywords[parent_method._dbus_destination_keyword] = message.get_destination()
  586. if parent_method._dbus_message_keyword:
  587. keywords[parent_method._dbus_message_keyword] = message
  588. if parent_method._dbus_connection_keyword:
  589. keywords[parent_method._dbus_connection_keyword] = connection
  590. # call method
  591. retval = candidate_method(self, *args, **keywords)
  592. # we're done - the method has got callback functions to reply with
  593. if parent_method._dbus_async_callbacks:
  594. return
  595. # otherwise we send the return values in a reply. if we have a
  596. # signature, use it to turn the return value into a tuple as
  597. # appropriate
  598. if signature is not None:
  599. signature_tuple = tuple(signature)
  600. # if we have zero or one return values we want make a tuple
  601. # for the _method_reply_return function, otherwise we need
  602. # to check we're passing it a sequence
  603. if len(signature_tuple) == 0:
  604. if retval == None:
  605. retval = ()
  606. else:
  607. raise TypeError('%s has an empty output signature but did not return None' %
  608. method_name)
  609. elif len(signature_tuple) == 1:
  610. retval = (retval,)
  611. else:
  612. if isinstance(retval, Sequence):
  613. # multi-value signature, multi-value return... proceed
  614. # unchanged
  615. pass
  616. else:
  617. raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
  618. (method_name, signature))
  619. # no signature, so just turn the return into a tuple and send it as normal
  620. else:
  621. if retval is None:
  622. retval = ()
  623. elif (isinstance(retval, tuple)
  624. and not isinstance(retval, Struct)):
  625. # If the return is a tuple that is not a Struct, we use it
  626. # as-is on the assumption that there are multiple return
  627. # values - this is the usual Python idiom. (fd.o #10174)
  628. pass
  629. else:
  630. retval = (retval,)
  631. _method_reply_return(connection, message, method_name, signature, *retval)
  632. except Exception as exception:
  633. # send error reply
  634. _method_reply_error(connection, message, exception)
  635. @method(INTROSPECTABLE_IFACE, in_signature='', out_signature='s',
  636. path_keyword='object_path', connection_keyword='connection')
  637. def Introspect(self, object_path, connection):
  638. """Return a string of XML encoding this object's supported interfaces,
  639. methods and signals.
  640. """
  641. reflection_data = _dbus_bindings.DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
  642. reflection_data += '<node name="%s">\n' % object_path
  643. interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
  644. for (name, funcs) in interfaces.items():
  645. reflection_data += ' <interface name="%s">\n' % (name)
  646. for func in funcs.values():
  647. if getattr(func, '_dbus_is_method', False):
  648. reflection_data += self.__class__._reflect_on_method(func)
  649. elif getattr(func, '_dbus_is_signal', False):
  650. reflection_data += self.__class__._reflect_on_signal(func)
  651. reflection_data += ' </interface>\n'
  652. for name in connection.list_exported_child_objects(object_path):
  653. reflection_data += ' <node name="%s"/>\n' % name
  654. reflection_data += '</node>\n'
  655. return reflection_data
  656. def __repr__(self):
  657. where = ''
  658. if (self._object_path is not _MANY
  659. and self._object_path is not None):
  660. where = ' at %s' % self._object_path
  661. return '<%s.%s%s at %#x>' % (self.__class__.__module__,
  662. self.__class__.__name__, where,
  663. id(self))
  664. __str__ = __repr__
  665. class FallbackObject(Object):
  666. """An object that implements an entire subtree of the object-path
  667. tree.
  668. :Since: 0.82.0
  669. """
  670. SUPPORTS_MULTIPLE_OBJECT_PATHS = True
  671. def __init__(self, conn=None, object_path=None):
  672. """Constructor.
  673. Note that the superclass' ``bus_name`` __init__ argument is not
  674. supported here.
  675. :Parameters:
  676. `conn` : dbus.connection.Connection or None
  677. The connection on which to export this object. If this is not
  678. None, an `object_path` must also be provided.
  679. If None, the object is not initially available on any
  680. Connection.
  681. `object_path` : str or None
  682. A D-Bus object path at which to make this Object available
  683. immediately. If this is not None, a `conn` must also be
  684. provided.
  685. This object will implements all object-paths in the subtree
  686. starting at this object-path, except where a more specific
  687. object has been added.
  688. """
  689. super(FallbackObject, self).__init__()
  690. self._fallback = True
  691. if conn is None:
  692. if object_path is not None:
  693. raise TypeError('If object_path is given, conn is required')
  694. elif object_path is None:
  695. raise TypeError('If conn is given, object_path is required')
  696. else:
  697. self.add_to_connection(conn, object_path)