diff options
author | xiubuzhe <xiubuzhe@sina.com> | 2023-10-08 20:59:00 +0800 |
---|---|---|
committer | xiubuzhe <xiubuzhe@sina.com> | 2023-10-08 20:59:00 +0800 |
commit | 1dac2263372df2b85db5d029a45721fa158a5c9d (patch) | |
tree | 0365f9c57df04178a726d7584ca6a6b955a7ce6a /lib/dbus/service.py | |
parent | b494be364bb39e1de128ada7dc576a729d99907e (diff) | |
download | sunhpc-1dac2263372df2b85db5d029a45721fa158a5c9d.tar.gz sunhpc-1dac2263372df2b85db5d029a45721fa158a5c9d.tar.bz2 sunhpc-1dac2263372df2b85db5d029a45721fa158a5c9d.zip |
first add files
Diffstat (limited to 'lib/dbus/service.py')
-rw-r--r-- | lib/dbus/service.py | 840 |
1 files changed, 840 insertions, 0 deletions
diff --git a/lib/dbus/service.py b/lib/dbus/service.py new file mode 100644 index 0000000..2e13d3c --- /dev/null +++ b/lib/dbus/service.py @@ -0,0 +1,840 @@ +# Copyright (C) 2003-2006 Red Hat Inc. <http://www.redhat.com/> +# Copyright (C) 2003 David Zeuthen +# Copyright (C) 2004 Rob Taylor +# Copyright (C) 2005-2006 Collabora Ltd. <http://www.collabora.co.uk/> +# +# SPDX-License-Identifier: MIT +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, copy, +# modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +__all__ = ('BusName', 'Object', 'FallbackObject', 'method', 'signal') +__docformat__ = 'restructuredtext' + +import sys +import logging +import threading +import traceback +try: + from collections.abc import Sequence +except ImportError: + # Python 2 (and 3.x < 3.3, but we don't support those) + from collections import Sequence + +import _dbus_bindings +from dbus import ( + INTROSPECTABLE_IFACE, ObjectPath, SessionBus, Signature, Struct, + validate_bus_name, validate_object_path) +from dbus.decorators import method, signal +from dbus.exceptions import ( + DBusException, NameExistsException, UnknownMethodException) +from dbus.lowlevel import ErrorMessage, MethodReturnMessage, MethodCallMessage +from dbus.proxies import LOCAL_PATH +from dbus._compat import is_py2 + + +_logger = logging.getLogger('dbus.service') + + +class _VariantSignature(object): + """A fake method signature which, when iterated, yields an endless stream + of 'v' characters representing variants (handy with zip()). + + It has no string representation. + """ + def __iter__(self): + """Return self.""" + return self + + def __next__(self): + """Return 'v' whenever called.""" + return 'v' + + +class BusName(object): + """A base class for exporting your own Named Services across the Bus. + + When instantiated, objects of this class attempt to claim the given + well-known name on the given bus for the current process. The name is + released when the BusName object becomes unreferenced. + + If a well-known name is requested multiple times, multiple references + to the same BusName object will be returned. + + :Caveats: + + - Assumes that named services are only ever requested using this class - + if you request names from the bus directly, confusion may occur. + - Does not handle queueing. + """ + def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False): + """Constructor, which may either return an existing cached object + or a new object. + + :Parameters: + `name` : str + The well-known name to be advertised + `bus` : dbus.Bus + A Bus on which this service will be advertised. + + Omitting this parameter or setting it to None has been + deprecated since version 0.82.1. For backwards compatibility, + if this is done, the global shared connection to the session + bus will be used. + + `allow_replacement` : bool + If True, other processes trying to claim the same well-known + name will take precedence over this one. + `replace_existing` : bool + If True, this process can take over the well-known name + from other processes already holding it. + `do_not_queue` : bool + If True, this service will not be placed in the queue of + services waiting for the requested name if another service + already holds it. + """ + validate_bus_name(name, allow_well_known=True, allow_unique=False) + + # if necessary, get default bus (deprecated) + if bus is None: + import warnings + warnings.warn('Omitting the "bus" parameter to ' + 'dbus.service.BusName.__init__ is deprecated', + DeprecationWarning, stacklevel=2) + bus = SessionBus() + + # see if this name is already defined, return it if so + # FIXME: accessing internals of Bus + if name in bus._bus_names: + return bus._bus_names[name] + + # otherwise register the name + name_flags = ( + (allow_replacement and _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT or 0) | + (replace_existing and _dbus_bindings.NAME_FLAG_REPLACE_EXISTING or 0) | + (do_not_queue and _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE or 0)) + + retval = bus.request_name(name, name_flags) + + # TODO: more intelligent tracking of bus name states? + if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER: + pass + elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE: + # queueing can happen by default, maybe we should + # track this better or let the user know if they're + # queued or not? + pass + elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS: + raise NameExistsException(name) + elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER: + # if this is a shared bus which is being used by someone + # else in this process, this can happen legitimately + pass + else: + raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval)) + + # and create the object + bus_name = object.__new__(cls) + bus_name._bus = bus + bus_name._name = name + + # cache instance (weak ref only) + # FIXME: accessing Bus internals again + bus._bus_names[name] = bus_name + + return bus_name + + # do nothing because this is called whether or not the bus name + # object was retrieved from the cache or created new + def __init__(self, *args, **keywords): + pass + + # we can delete the low-level name here because these objects + # are guaranteed to exist only once for each bus name + def __del__(self): + self._bus.release_name(self._name) + pass + + def get_bus(self): + """Get the Bus this Service is on""" + return self._bus + + def get_name(self): + """Get the name of this service""" + return self._name + + def __repr__(self): + return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self)) + __str__ = __repr__ + + +def _method_lookup(self, method_name, dbus_interface): + """Walks the Python MRO of the given class to find the method to invoke. + + Returns two methods, the one to call, and the one it inherits from which + defines its D-Bus interface name, signature, and attributes. + """ + parent_method = None + candidate_class = None + successful = False + + # split up the cases when we do and don't have an interface because the + # latter is much simpler + if dbus_interface: + # search through the class hierarchy in python MRO order + for cls in self.__class__.__mro__: + # if we haven't got a candidate class yet, and we find a class with a + # suitably named member, save this as a candidate class + if (not candidate_class and method_name in cls.__dict__): + if ("_dbus_is_method" in cls.__dict__[method_name].__dict__ + and "_dbus_interface" in cls.__dict__[method_name].__dict__): + # however if it is annotated for a different interface + # than we are looking for, it cannot be a candidate + if cls.__dict__[method_name]._dbus_interface == dbus_interface: + candidate_class = cls + parent_method = cls.__dict__[method_name] + successful = True + break + else: + pass + else: + candidate_class = cls + + # if we have a candidate class, carry on checking this and all + # superclasses for a method annoated as a dbus method + # on the correct interface + if (candidate_class and method_name in cls.__dict__ + and "_dbus_is_method" in cls.__dict__[method_name].__dict__ + and "_dbus_interface" in cls.__dict__[method_name].__dict__ + and cls.__dict__[method_name]._dbus_interface == dbus_interface): + # the candidate class has a dbus method on the correct interface, + # or overrides a method that is, success! + parent_method = cls.__dict__[method_name] + successful = True + break + + else: + # simpler version of above + for cls in self.__class__.__mro__: + if (not candidate_class and method_name in cls.__dict__): + candidate_class = cls + + if (candidate_class and method_name in cls.__dict__ + and "_dbus_is_method" in cls.__dict__[method_name].__dict__): + parent_method = cls.__dict__[method_name] + successful = True + break + + if successful: + return (candidate_class.__dict__[method_name], parent_method) + else: + if dbus_interface: + raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface)) + else: + raise UnknownMethodException('%s is not a valid method' % method_name) + + +def _method_reply_return(connection, message, method_name, signature, *retval): + reply = MethodReturnMessage(message) + try: + reply.append(signature=signature, *retval) + except Exception as e: + logging.basicConfig() + if signature is None: + try: + signature = reply.guess_signature(retval) + ' (guessed)' + except Exception as e: + _logger.error('Unable to guess signature for arguments %r: ' + '%s: %s', retval, e.__class__, e) + raise + _logger.error('Unable to append %r to message with signature %s: ' + '%s: %s', retval, signature, e.__class__, e) + raise + + if not message.get_no_reply(): + connection.send_message(reply) + + +def _method_reply_error(connection, message, exception): + name = getattr(exception, '_dbus_error_name', None) + + if name is not None: + pass + elif getattr(exception, '__module__', '') in ('', '__main__'): + name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__ + else: + name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__) + + et, ev, etb = sys.exc_info() + if isinstance(exception, DBusException) and not exception.include_traceback: + # We don't actually want the traceback anyway + contents = exception.get_dbus_message() + elif ev is exception: + # The exception was actually thrown, so we can get a traceback + contents = ''.join(traceback.format_exception(et, ev, etb)) + else: + # We don't have any traceback for it, e.g. + # async_err_cb(MyException('Failed to badger the mushroom')) + # see also https://bugs.freedesktop.org/show_bug.cgi?id=12403 + contents = ''.join(traceback.format_exception_only(exception.__class__, + exception)) + reply = ErrorMessage(message, name, contents) + + if not message.get_no_reply(): + connection.send_message(reply) + + +class InterfaceType(type): + def __init__(cls, name, bases, dct): + # these attributes are shared between all instances of the Interface + # object, so this has to be a dictionary that maps class names to + # the per-class introspection/interface data + class_table = getattr(cls, '_dbus_class_table', {}) + cls._dbus_class_table = class_table + interface_table = class_table[cls.__module__ + '.' + name] = {} + + # merge all the name -> method tables for all the interfaces + # implemented by our base classes into our own + for b in bases: + base_name = b.__module__ + '.' + b.__name__ + if getattr(b, '_dbus_class_table', False): + for (interface, method_table) in class_table[base_name].items(): + our_method_table = interface_table.setdefault(interface, {}) + our_method_table.update(method_table) + + # add in all the name -> method entries for our own methods/signals + for func in dct.values(): + if getattr(func, '_dbus_interface', False): + method_table = interface_table.setdefault(func._dbus_interface, {}) + method_table[func.__name__] = func + + super(InterfaceType, cls).__init__(name, bases, dct) + + # methods are different to signals, so we have two functions... :) + def _reflect_on_method(cls, func): + args = func._dbus_args + + if func._dbus_in_signature: + # convert signature into a tuple so length refers to number of + # types, not number of characters. the length is checked by + # the decorator to make sure it matches the length of args. + in_sig = tuple(Signature(func._dbus_in_signature)) + else: + # magic iterator which returns as many v's as we need + in_sig = _VariantSignature() + + if func._dbus_out_signature: + out_sig = Signature(func._dbus_out_signature) + else: + # its tempting to default to Signature('v'), but + # for methods that return nothing, providing incorrect + # introspection data is worse than providing none at all + out_sig = [] + + reflection_data = ' <method name="%s">\n' % (func.__name__) + for pair in zip(in_sig, args): + reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair + for type in out_sig: + reflection_data += ' <arg direction="out" type="%s" />\n' % type + reflection_data += ' </method>\n' + + return reflection_data + + def _reflect_on_signal(cls, func): + args = func._dbus_args + + if func._dbus_signature: + # convert signature into a tuple so length refers to number of + # types, not number of characters + sig = tuple(Signature(func._dbus_signature)) + else: + # magic iterator which returns as many v's as we need + sig = _VariantSignature() + + reflection_data = ' <signal name="%s">\n' % (func.__name__) + for pair in zip(sig, args): + reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair + reflection_data = reflection_data + ' </signal>\n' + + return reflection_data + + +# Define Interface as an instance of the metaclass InterfaceType, in a way +# that is compatible across both Python 2 and Python 3. +Interface = InterfaceType('Interface', (object,), {}) + + +#: A unique object used as the value of Object._object_path and +#: Object._connection if it's actually in more than one place +_MANY = object() + +class Object(Interface): + r"""A base class for exporting your own Objects across the Bus. + + Just inherit from Object and mark exported methods with the + @\ `dbus.service.method` or @\ `dbus.service.signal` decorator. + + Example:: + + class Example(dbus.service.object): + def __init__(self, object_path): + dbus.service.Object.__init__(self, dbus.SessionBus(), path) + self._last_input = None + + @dbus.service.method(interface='com.example.Sample', + in_signature='v', out_signature='s') + def StringifyVariant(self, var): + self.LastInputChanged(var) # emits the signal + return str(var) + + @dbus.service.signal(interface='com.example.Sample', + signature='v') + def LastInputChanged(self, var): + # run just before the signal is actually emitted + # just put "pass" if nothing should happen + self._last_input = var + + @dbus.service.method(interface='com.example.Sample', + in_signature='', out_signature='v') + def GetLastInput(self): + return self._last_input + """ + + #: If True, this object can be made available at more than one object path. + #: If True but `SUPPORTS_MULTIPLE_CONNECTIONS` is False, the object may + #: handle more than one object path, but they must all be on the same + #: connection. + SUPPORTS_MULTIPLE_OBJECT_PATHS = False + + #: If True, this object can be made available on more than one connection. + #: If True but `SUPPORTS_MULTIPLE_OBJECT_PATHS` is False, the object must + #: have the same object path on all its connections. + SUPPORTS_MULTIPLE_CONNECTIONS = False + + def __init__(self, conn=None, object_path=None, bus_name=None): + """Constructor. Either conn or bus_name is required; object_path + is also required. + + :Parameters: + `conn` : dbus.connection.Connection or None + The connection on which to export this object. + + If None, use the Bus associated with the given ``bus_name``. + If there is no ``bus_name`` either, the object is not + initially available on any Connection. + + For backwards compatibility, if an instance of + dbus.service.BusName is passed as the first parameter, + this is equivalent to passing its associated Bus as + ``conn``, and passing the BusName itself as ``bus_name``. + + `object_path` : str or None + A D-Bus object path at which to make this Object available + immediately. If this is not None, a `conn` or `bus_name` must + also be provided. + + `bus_name` : dbus.service.BusName or None + Represents a well-known name claimed by this process. A + reference to the BusName object will be held by this + Object, preventing the name from being released during this + Object's lifetime (unless it's released manually). + """ + if object_path is not None: + validate_object_path(object_path) + + if isinstance(conn, BusName): + # someone's using the old API; don't gratuitously break them + bus_name = conn + conn = bus_name.get_bus() + elif conn is None: + if bus_name is not None: + # someone's using the old API but naming arguments, probably + conn = bus_name.get_bus() + + #: Either an object path, None or _MANY + self._object_path = None + #: Either a dbus.connection.Connection, None or _MANY + self._connection = None + #: A list of tuples (Connection, object path, False) where the False + #: is for future expansion (to support fallback paths) + self._locations = [] + #: Lock protecting `_locations`, `_connection` and `_object_path` + self._locations_lock = threading.Lock() + + #: True if this is a fallback object handling a whole subtree. + self._fallback = False + + self._name = bus_name + + if conn is None and object_path is not None: + raise TypeError('If object_path is given, either conn or bus_name ' + 'is required') + if conn is not None and object_path is not None: + self.add_to_connection(conn, object_path) + + @property + def __dbus_object_path__(self): + """The object-path at which this object is available. + Access raises AttributeError if there is no object path, or more than + one object path. + + Changed in 0.82.0: AttributeError can be raised. + """ + if self._object_path is _MANY: + raise AttributeError('Object %r has more than one object path: ' + 'use Object.locations instead' % self) + elif self._object_path is None: + raise AttributeError('Object %r has no object path yet' % self) + else: + return self._object_path + + @property + def connection(self): + """The Connection on which this object is available. + Access raises AttributeError if there is no Connection, or more than + one Connection. + + Changed in 0.82.0: AttributeError can be raised. + """ + if self._connection is _MANY: + raise AttributeError('Object %r is on more than one Connection: ' + 'use Object.locations instead' % self) + elif self._connection is None: + raise AttributeError('Object %r has no Connection yet' % self) + else: + return self._connection + + @property + def locations(self): + """An iterable over tuples representing locations at which this + object is available. + + Each tuple has at least two items, but may have more in future + versions of dbus-python, so do not rely on their exact length. + The first two items are the dbus.connection.Connection and the object + path. + + :Since: 0.82.0 + """ + return iter(self._locations) + + def add_to_connection(self, connection, path): + """Make this object accessible via the given D-Bus connection and + object path. + + :Parameters: + `connection` : dbus.connection.Connection + Export the object on this connection. If the class attribute + SUPPORTS_MULTIPLE_CONNECTIONS is False (default), this object + can only be made available on one connection; if the class + attribute is set True by a subclass, the object can be made + available on more than one connection. + + `path` : dbus.ObjectPath or other str + Place the object at this object path. If the class attribute + SUPPORTS_MULTIPLE_OBJECT_PATHS is False (default), this object + can only be made available at one object path; if the class + attribute is set True by a subclass, the object can be made + available with more than one object path. + + :Raises ValueError: if the object's class attributes do not allow the + object to be exported in the desired way. + :Since: 0.82.0 + """ + if path == LOCAL_PATH: + raise ValueError('Objects may not be exported on the reserved ' + 'path %s' % LOCAL_PATH) + + self._locations_lock.acquire() + try: + if (self._connection is not None and + self._connection is not connection and + not self.SUPPORTS_MULTIPLE_CONNECTIONS): + raise ValueError('%r is already exported on ' + 'connection %r' % (self, self._connection)) + + if (self._object_path is not None and + not self.SUPPORTS_MULTIPLE_OBJECT_PATHS and + self._object_path != path): + raise ValueError('%r is already exported at object ' + 'path %s' % (self, self._object_path)) + + connection._register_object_path(path, self._message_cb, + self._unregister_cb, + self._fallback) + + if self._connection is None: + self._connection = connection + elif self._connection is not connection: + self._connection = _MANY + + if self._object_path is None: + self._object_path = path + elif self._object_path != path: + self._object_path = _MANY + + self._locations.append((connection, path, self._fallback)) + finally: + self._locations_lock.release() + + def remove_from_connection(self, connection=None, path=None): + """Make this object inaccessible via the given D-Bus connection + and object path. If no connection or path is specified, + the object ceases to be accessible via any connection or path. + + :Parameters: + `connection` : dbus.connection.Connection or None + Only remove the object from this Connection. If None, + remove from all Connections on which it's exported. + `path` : dbus.ObjectPath or other str, or None + Only remove the object from this object path. If None, + remove from all object paths. + :Raises LookupError: + if the object was not exported on the requested connection + or path, or (if both are None) was not exported at all. + :Since: 0.81.1 + """ + self._locations_lock.acquire() + try: + if self._object_path is None or self._connection is None: + raise LookupError('%r is not exported' % self) + + if connection is not None or path is not None: + dropped = [] + for location in self._locations: + if ((connection is None or location[0] is connection) and + (path is None or location[1] == path)): + dropped.append(location) + else: + dropped = self._locations + self._locations = [] + + if not dropped: + raise LookupError('%r is not exported at a location matching ' + '(%r,%r)' % (self, connection, path)) + + for location in dropped: + try: + location[0]._unregister_object_path(location[1]) + except LookupError: + pass + if self._locations: + try: + self._locations.remove(location) + except ValueError: + pass + finally: + self._locations_lock.release() + + def _unregister_cb(self, connection): + # there's not really enough information to do anything useful here + _logger.info('Unregistering exported object %r from some path ' + 'on %r', self, connection) + + def _message_cb(self, connection, message): + if not isinstance(message, MethodCallMessage): + return + + try: + # lookup candidate method and parent method + method_name = message.get_member() + interface_name = message.get_interface() + (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name) + + # set up method call parameters + args = message.get_args_list(**parent_method._dbus_get_args_options) + keywords = {} + + if parent_method._dbus_out_signature is not None: + signature = Signature(parent_method._dbus_out_signature) + else: + signature = None + + # set up async callback functions + if parent_method._dbus_async_callbacks: + (return_callback, error_callback) = parent_method._dbus_async_callbacks + keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval) + keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception) + + # include the sender etc. if desired + if parent_method._dbus_sender_keyword: + keywords[parent_method._dbus_sender_keyword] = message.get_sender() + if parent_method._dbus_path_keyword: + keywords[parent_method._dbus_path_keyword] = message.get_path() + if parent_method._dbus_rel_path_keyword: + path = message.get_path() + rel_path = path + for exp in self._locations: + # pathological case: if we're exported in two places, + # one of which is a subtree of the other, then pick the + # subtree by preference (i.e. minimize the length of + # rel_path) + if exp[0] is connection: + if path == exp[1]: + rel_path = '/' + break + if exp[1] == '/': + # we already have rel_path == path at the beginning + continue + if path.startswith(exp[1] + '/'): + # yes we're in this exported subtree + suffix = path[len(exp[1]):] + if len(suffix) < len(rel_path): + rel_path = suffix + rel_path = ObjectPath(rel_path) + keywords[parent_method._dbus_rel_path_keyword] = rel_path + + if parent_method._dbus_destination_keyword: + keywords[parent_method._dbus_destination_keyword] = message.get_destination() + if parent_method._dbus_message_keyword: + keywords[parent_method._dbus_message_keyword] = message + if parent_method._dbus_connection_keyword: + keywords[parent_method._dbus_connection_keyword] = connection + + # call method + retval = candidate_method(self, *args, **keywords) + + # we're done - the method has got callback functions to reply with + if parent_method._dbus_async_callbacks: + return + + # otherwise we send the return values in a reply. if we have a + # signature, use it to turn the return value into a tuple as + # appropriate + if signature is not None: + signature_tuple = tuple(signature) + # if we have zero or one return values we want make a tuple + # for the _method_reply_return function, otherwise we need + # to check we're passing it a sequence + if len(signature_tuple) == 0: + if retval == None: + retval = () + else: + raise TypeError('%s has an empty output signature but did not return None' % + method_name) + elif len(signature_tuple) == 1: + retval = (retval,) + else: + if isinstance(retval, Sequence): + # multi-value signature, multi-value return... proceed + # unchanged + pass + else: + raise TypeError('%s has multiple output values in signature %s but did not return a sequence' % + (method_name, signature)) + + # no signature, so just turn the return into a tuple and send it as normal + else: + if retval is None: + retval = () + elif (isinstance(retval, tuple) + and not isinstance(retval, Struct)): + # If the return is a tuple that is not a Struct, we use it + # as-is on the assumption that there are multiple return + # values - this is the usual Python idiom. (fd.o #10174) + pass + else: + retval = (retval,) + + _method_reply_return(connection, message, method_name, signature, *retval) + except Exception as exception: + # send error reply + _method_reply_error(connection, message, exception) + + @method(INTROSPECTABLE_IFACE, in_signature='', out_signature='s', + path_keyword='object_path', connection_keyword='connection') + def Introspect(self, object_path, connection): + """Return a string of XML encoding this object's supported interfaces, + methods and signals. + """ + reflection_data = _dbus_bindings.DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + reflection_data += '<node name="%s">\n' % object_path + + interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__] + for (name, funcs) in interfaces.items(): + reflection_data += ' <interface name="%s">\n' % (name) + + for func in funcs.values(): + if getattr(func, '_dbus_is_method', False): + reflection_data += self.__class__._reflect_on_method(func) + elif getattr(func, '_dbus_is_signal', False): + reflection_data += self.__class__._reflect_on_signal(func) + + reflection_data += ' </interface>\n' + + for name in connection.list_exported_child_objects(object_path): + reflection_data += ' <node name="%s"/>\n' % name + + reflection_data += '</node>\n' + + return reflection_data + + def __repr__(self): + where = '' + if (self._object_path is not _MANY + and self._object_path is not None): + where = ' at %s' % self._object_path + return '<%s.%s%s at %#x>' % (self.__class__.__module__, + self.__class__.__name__, where, + id(self)) + __str__ = __repr__ + +class FallbackObject(Object): + """An object that implements an entire subtree of the object-path + tree. + + :Since: 0.82.0 + """ + + SUPPORTS_MULTIPLE_OBJECT_PATHS = True + + def __init__(self, conn=None, object_path=None): + """Constructor. + + Note that the superclass' ``bus_name`` __init__ argument is not + supported here. + + :Parameters: + `conn` : dbus.connection.Connection or None + The connection on which to export this object. If this is not + None, an `object_path` must also be provided. + + If None, the object is not initially available on any + Connection. + + `object_path` : str or None + A D-Bus object path at which to make this Object available + immediately. If this is not None, a `conn` must also be + provided. + + This object will implements all object-paths in the subtree + starting at this object-path, except where a more specific + object has been added. + """ + super(FallbackObject, self).__init__() + self._fallback = True + + if conn is None: + if object_path is not None: + raise TypeError('If object_path is given, conn is required') + elif object_path is None: + raise TypeError('If conn is given, object_path is required') + else: + self.add_to_connection(conn, object_path) |