[PATCH 14/17] Move signal matching machinery into superclasses
John (J5) Palmieri
johnp at redhat.com
Mon Apr 30 15:08:55 PDT 2007
Subscribe!!!
On Mon, 2007-04-30 at 13:15 +0100, Simon McVittie wrote:
> diff --git a/dbus/_dbus.py b/dbus/_dbus.py
> index bab195a..12aff2c 100644
> --- a/dbus/_dbus.py
> +++ b/dbus/_dbus.py
> @@ -27,7 +27,6 @@ __all__ = ('Bus', 'SystemBus', 'SessionBus', 'StarterBus')
> __docformat__ = 'reStructuredText'
>
> import os
> -import logging
> import sys
> import weakref
> from traceback import print_exc
> @@ -43,188 +42,12 @@ from _dbus_bindings import BUS_DAEMON_NAME, BUS_DAEMON_PATH,\
> HANDLER_RESULT_NOT_YET_HANDLED,\
> HANDLER_RESULT_HANDLED
> from dbus.bus import BusConnection
> -from dbus.proxies import ProxyObject
>
> try:
> import thread
> except ImportError:
> import dummy_thread as thread
>
> -logger = logging.getLogger('dbus._dbus')
> -
> -_NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
> - "interface='%s',member='NameOwnerChanged',"
> - "path='%s',arg0='%%s'"
> - % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
> - BUS_DAEMON_PATH))
> -"""(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
> -messages"""
> -
> -
> -class SignalMatch(object):
> - __slots__ = ('sender_unique', '_member', '_interface', '_sender',
> - '_path', '_handler', '_args_match', '_rule',
> - '_utf8_strings', '_byte_arrays', '_conn_weakref',
> - '_destination_keyword', '_interface_keyword',
> - '_message_keyword', '_member_keyword',
> - '_sender_keyword', '_path_keyword', '_int_args_match')
> -
> - def __init__(self, conn, sender, object_path, dbus_interface,
> - member, handler, utf8_strings=False, byte_arrays=False,
> - sender_keyword=None, path_keyword=None,
> - interface_keyword=None, member_keyword=None,
> - message_keyword=None, destination_keyword=None,
> - **kwargs):
> - if member is not None:
> - validate_member_name(member)
> - if dbus_interface is not None:
> - validate_interface_name(dbus_interface)
> - if sender is not None:
> - validate_bus_name(sender)
> - if object_path is not None:
> - validate_object_path(object_path)
> -
> - self._conn_weakref = weakref.ref(conn)
> - self._sender = sender
> - self._interface = dbus_interface
> - self._member = member
> - self._path = object_path
> - self._handler = handler
> - if (sender is not None and sender[:1] != ':'
> - and sender != BUS_DAEMON_NAME):
> - self.sender_unique = conn.get_name_owner(sender)
> - else:
> - self.sender_unique = sender
> - self._utf8_strings = utf8_strings
> - self._byte_arrays = byte_arrays
> - self._sender_keyword = sender_keyword
> - self._path_keyword = path_keyword
> - self._member_keyword = member_keyword
> - self._interface_keyword = interface_keyword
> - self._message_keyword = message_keyword
> - self._destination_keyword = destination_keyword
> -
> - self._args_match = kwargs
> - if not kwargs:
> - self._int_args_match = None
> - else:
> - self._int_args_match = {}
> - for kwarg in kwargs:
> - if not kwarg.startswith('arg'):
> - raise TypeError('SignalMatch: unknown keyword argument %s'
> - % kwarg)
> - try:
> - index = int(kwarg[3:])
> - except ValueError:
> - raise TypeError('SignalMatch: unknown keyword argument %s'
> - % kwarg)
> - if index < 0 or index > 63:
> - raise TypeError('SignalMatch: arg match index must be in '
> - 'range(64), not %d' % index)
> - self._int_args_match[index] = kwargs[kwarg]
> -
> - # we're always going to have to calculate the match rule for
> - # the Bus's benefit, so this constructor might as well do the work
> - rule = ["type='signal'"]
> - if self._sender is not None:
> - rule.append("sender='%s'" % self._sender)
> - if self._path is not None:
> - rule.append("path='%s'" % self._path)
> - if self._interface is not None:
> - rule.append("interface='%s'" % self._interface)
> - if self._member is not None:
> - rule.append("member='%s'" % self._member)
> - for kwarg, value in kwargs.iteritems():
> - rule.append("%s='%s'" % (kwarg, value))
> -
> - self._rule = ','.join(rule)
> -
> - def __str__(self):
> - return self._rule
> -
> - def __repr__(self):
> - return ('<%s at %x "%s" on conn %r>'
> - % (self.__class__, id(self), self._rule, self._conn_weakref()))
> -
> - def matches_removal_spec(self, sender, object_path,
> - dbus_interface, member, handler, **kwargs):
> - if handler not in (None, self._handler):
> - return False
> - if sender != self._sender:
> - return False
> - if object_path != self._path:
> - return False
> - if dbus_interface != self._interface:
> - return False
> - if member != self._member:
> - return False
> - if kwargs != self._args_match:
> - return False
> - return True
> -
> - def maybe_handle_message(self, message):
> - args = None
> -
> - # these haven't been checked yet by the match tree
> - if self.sender_unique not in (None, message.get_sender()):
> - return False
> - if self._int_args_match is not None:
> - # extracting args with utf8_strings and byte_arrays is less work
> - args = message.get_args_list(utf8_strings=True, byte_arrays=True)
> - for index, value in self._int_args_match.iteritems():
> - if (index >= len(args)
> - or not isinstance(args[index], UTF8String)
> - or args[index] != value):
> - return False
> -
> - # these have likely already been checked by the match tree
> - if self._member not in (None, message.get_member()):
> - return False
> - if self._interface not in (None, message.get_interface()):
> - return False
> - if self._path not in (None, message.get_path()):
> - return False
> -
> - try:
> - # minor optimization: if we already extracted the args with the
> - # right calling convention to do the args match, don't bother
> - # doing so again
> - if args is None or not self._utf8_strings or not self._byte_arrays:
> - args = message.get_args_list(utf8_strings=self._utf8_strings,
> - byte_arrays=self._byte_arrays)
> - kwargs = {}
> - if self._sender_keyword is not None:
> - kwargs[self._sender_keyword] = message.get_sender()
> - if self._destination_keyword is not None:
> - kwargs[self._destination_keyword] = message.get_destination()
> - if self._path_keyword is not None:
> - kwargs[self._path_keyword] = message.get_path()
> - if self._member_keyword is not None:
> - kwargs[self._member_keyword] = message.get_member()
> - if self._interface_keyword is not None:
> - kwargs[self._interface_keyword] = message.get_interface()
> - if self._message_keyword is not None:
> - kwargs[self._message_keyword] = message
> - self._handler(*args, **kwargs)
> - except:
> - # FIXME: need to decide whether dbus-python uses logging, or
> - # stderr, or what, and make it consistent
> - sys.stderr.write('Exception in handler for D-Bus signal:\n')
> - print_exc()
> -
> - return True
> -
> - def remove(self):
> - #logger.debug('%r: removing', self)
> - conn = self._conn_weakref()
> - # do nothing if the connection has already vanished
> - if conn is not None:
> - #logger.debug('%r: removing from connection %r', self, conn)
> - conn.remove_signal_receiver(self, self._member,
> - self._interface, self._sender,
> - self._path,
> - **self._args_match)
> -
>
> class Bus(BusConnection):
> """A connection to one of three possible standard buses, the SESSION,
> @@ -235,9 +58,6 @@ class Bus(BusConnection):
> `BusConnection`, which doesn't have all this magic.
> """
>
> - START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
> - START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
> -
> _shared_instances = {}
>
> def __new__(cls, bus_type=BusConnection.TYPE_SESSION, private=False,
> @@ -283,26 +103,9 @@ class Bus(BusConnection):
> else:
> raise ValueError('invalid bus_type %s' % bus_type)
>
> - bus = subclass._new_for_bus(bus_type, mainloop=mainloop)
> + bus = BusConnection.__new__(subclass, bus_type, mainloop=mainloop)
>
> bus._bus_type = bus_type
> - # _bus_names is used by dbus.service.BusName!
> - bus._bus_names = weakref.WeakValueDictionary()
> -
> - bus._signal_recipients_by_object_path = {}
> - """Map from object path to dict mapping dbus_interface to dict
> - mapping member to list of SignalMatch objects."""
> -
> - bus._signal_sender_matches = {}
> - """Map from sender well-known name to list of match rules for all
> - signal handlers that match on sender well-known name."""
> -
> - bus._signals_lock = thread.allocate_lock()
> - """Lock used to protect signal data structures if doing two
> - removals at the same time (everything else is atomic, thanks to
> - the GIL)"""
> -
> - bus.add_message_filter(bus.__class__._signal_func)
>
> if not private:
> cls._shared_instances[bus_type] = bus
> @@ -313,7 +116,7 @@ class Bus(BusConnection):
> t = self._bus_type
> if self.__class__._shared_instances[t] is self:
> del self.__class__._shared_instances[t]
> - super(BusConnection, self).close()
> + super(Bus, self).close()
>
> def get_connection(self):
> """(Deprecated - in new code, just use self)
> @@ -361,217 +164,6 @@ class Bus(BusConnection):
>
> get_starter = staticmethod(get_starter)
>
> - def add_signal_receiver(self, handler_function,
> - signal_name=None,
> - dbus_interface=None,
> - named_service=None,
> - path=None,
> - **keywords):
> - """Arrange for the given function to be called when a signal matching
> - the parameters is received.
> -
> - :Parameters:
> - `handler_function` : callable
> - The function to be called. Its positional arguments will
> - be the arguments of the signal. By default it will receive
> - no keyword arguments, but see the description of
> - the optional keyword arguments below.
> - `signal_name` : str
> - The signal name; None (the default) matches all names
> - `dbus_interface` : str
> - The D-Bus interface name with which to qualify the signal;
> - None (the default) matches all interface names
> - `named_service` : str
> - A bus name for the sender, which will be resolved to a
> - unique name if it is not already; None (the default) matches
> - any sender
> - `path` : str
> - The object path of the object which must have emitted the
> - signal; None (the default) matches any object path
> - :Keywords:
> - `utf8_strings` : bool
> - If True, the handler function will receive any string
> - arguments as dbus.UTF8String objects (a subclass of str
> - guaranteed to be UTF-8). If False (default) it will receive
> - any string arguments as dbus.String objects (a subclass of
> - unicode).
> - `byte_arrays` : bool
> - If True, the handler function will receive any byte-array
> - arguments as dbus.ByteArray objects (a subclass of str).
> - If False (default) it will receive any byte-array
> - arguments as a dbus.Array of dbus.Byte (subclasses of:
> - a list of ints).
> - `sender_keyword` : str
> - If not None (the default), the handler function will receive
> - the unique name of the sending endpoint as a keyword
> - argument with this name.
> - `destination_keyword` : str
> - If not None (the default), the handler function will receive
> - the bus name of the destination (or None if the signal is a
> - broadcast, as is usual) as a keyword argument with this name.
> - `interface_keyword` : str
> - If not None (the default), the handler function will receive
> - the signal interface as a keyword argument with this name.
> - `member_keyword` : str
> - If not None (the default), the handler function will receive
> - the signal name as a keyword argument with this name.
> - `path_keyword` : str
> - If not None (the default), the handler function will receive
> - the object-path of the sending object as a keyword argument
> - with this name.
> - `message_keyword` : str
> - If not None (the default), the handler function will receive
> - the `dbus.lowlevel.SignalMessage` as a keyword argument with
> - this name.
> - `arg...` : unicode or UTF-8 str
> - If there are additional keyword parameters of the form
> - ``arg``\ *n*, match only signals where the *n*\ th argument
> - is the value given for that keyword parameter. As of this
> - time only string arguments can be matched (in particular,
> - object paths and signatures can't).
> - """
> - self._require_main_loop()
> -
> - match = SignalMatch(self, named_service, path, dbus_interface,
> - signal_name, handler_function, **keywords)
> - by_interface = self._signal_recipients_by_object_path.setdefault(path,
> - {})
> - by_member = by_interface.setdefault(dbus_interface, {})
> - matches = by_member.setdefault(signal_name, [])
> - # The bus daemon is special - its unique-name is org.freedesktop.DBus
> - # rather than starting with :
> - if (named_service is not None and named_service[:1] != ':'
> - and named_service != BUS_DAEMON_NAME):
> - notification = self._signal_sender_matches.setdefault(named_service,
> - [])
> - if not notification:
> - self.add_match_string(_NAME_OWNER_CHANGE_MATCH % named_service)
> - notification.append(match)
> - # make sure nobody is currently manipulating the list
> - self._signals_lock.acquire()
> - try:
> - matches.append(match)
> - finally:
> - self._signals_lock.release()
> - self.add_match_string(str(match))
> - return match
> -
> - def _iter_easy_matches(self, path, dbus_interface, member):
> - if path is not None:
> - path_keys = (None, path)
> - else:
> - path_keys = (None,)
> - if dbus_interface is not None:
> - interface_keys = (None, dbus_interface)
> - else:
> - interface_keys = (None,)
> - if member is not None:
> - member_keys = (None, member)
> - else:
> - member_keys = (None,)
> -
> - for path in path_keys:
> - by_interface = self._signal_recipients_by_object_path.get(path,
> - None)
> - if by_interface is None:
> - continue
> - for dbus_interface in interface_keys:
> - by_member = by_interface.get(dbus_interface, None)
> - if by_member is None:
> - continue
> - for member in member_keys:
> - matches = by_member.get(member, None)
> - if matches is None:
> - continue
> - for m in matches:
> - yield m
> -
> - def _remove_name_owner_changed_for_match(self, named_service, match):
> - # The signals lock must be held.
> - notification = self._signal_sender_matches.get(named_service, False)
> - if notification:
> - try:
> - notification.remove(match)
> - except LookupError:
> - pass
> - if not notification:
> - self.remove_match_string(_NAME_OWNER_CHANGE_MATCH
> - % named_service)
> -
> - def remove_signal_receiver(self, handler_or_match,
> - signal_name=None,
> - dbus_interface=None,
> - named_service=None,
> - path=None,
> - **keywords):
> - #logger.debug('%r: removing signal receiver %r: member=%s, '
> - #'iface=%s, sender=%s, path=%s, kwargs=%r',
> - #self, handler_or_match, signal_name,
> - #dbus_interface, named_service, path, keywords)
> - #logger.debug('%r', self._signal_recipients_by_object_path)
> - by_interface = self._signal_recipients_by_object_path.get(path, None)
> - if by_interface is None:
> - return
> - by_member = by_interface.get(dbus_interface, None)
> - if by_member is None:
> - return
> - matches = by_member.get(signal_name, None)
> - if matches is None:
> - return
> - self._signals_lock.acquire()
> - #logger.debug(matches)
> - try:
> - new = []
> - for match in matches:
> - if (handler_or_match is match
> - or match.matches_removal_spec(named_service,
> - path,
> - dbus_interface,
> - signal_name,
> - handler_or_match,
> - **keywords)):
> - #logger.debug('Removing match string: %s', match)
> - self.remove_match_string(str(match))
> - self._remove_name_owner_changed_for_match(named_service,
> - match)
> - else:
> - new.append(match)
> - by_member[signal_name] = new
> - finally:
> - self._signals_lock.release()
> -
> - def _signal_func(self, message):
> - """D-Bus filter function. Handle signals by dispatching to Python
> - callbacks kept in the match-rule tree.
> - """
> -
> - #logger.debug('Incoming message %r with args %r', message,
> - #message.get_args_list())
> -
> - if not isinstance(message, SignalMessage):
> - return HANDLER_RESULT_NOT_YET_HANDLED
> -
> - # If it's NameOwnerChanged, we'll need to update our
> - # sender well-known name -> sender unique name mappings
> - if (message.is_signal(BUS_DAEMON_IFACE, 'NameOwnerChanged')
> - and message.has_sender(BUS_DAEMON_NAME)
> - and message.has_path(BUS_DAEMON_PATH)):
> - name, unused, new = message.get_args_list()
> - for match in self._signal_sender_matches.get(name, (None,))[1:]:
> - match.sender_unique = new
> -
> - # See if anyone else wants to know
> - dbus_interface = message.get_interface()
> - path = message.get_path()
> - signal_name = message.get_member()
> -
> - ret = HANDLER_RESULT_NOT_YET_HANDLED
> - for match in self._iter_easy_matches(path, dbus_interface,
> - signal_name):
> - if match.maybe_handle_message(message):
> - ret = HANDLER_RESULT_HANDLED
> - return ret
> -
> def __repr__(self):
> if self._bus_type == BUS_SESSION:
> name = 'SESSION'
> diff --git a/dbus/bus.py b/dbus/bus.py
> index 45c8505..d981726 100644
> --- a/dbus/bus.py
> +++ b/dbus/bus.py
> @@ -19,14 +19,31 @@
> __all__ = ('BusConnection',)
> __docformat__ = 'reStructuredText'
>
> +import logging
> +import weakref
> +
> from _dbus_bindings import validate_interface_name, validate_member_name,\
> validate_bus_name, validate_object_path,\
> validate_error_name,\
> DBusException, \
> BUS_SESSION, BUS_STARTER, BUS_SYSTEM, \
> - BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE
> + DBUS_START_REPLY_SUCCESS, \
> + DBUS_START_REPLY_ALREADY_RUNNING, \
> + BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
> + HANDLER_RESULT_NOT_YET_HANDLED
> from dbus.connection import Connection
>
> +_NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
> + "interface='%s',member='NameOwnerChanged',"
> + "path='%s',arg0='%%s'"
> + % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
> + BUS_DAEMON_PATH))
> +"""(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
> +messages"""
> +
> +
> +_logger = logging.getLogger('dbus.connection')
> +
>
> class BusConnection(Connection):
> """A connection to a D-Bus daemon that implements the
> @@ -43,6 +60,77 @@ class BusConnection(Connection):
> """Represents the bus that started this service by activation (same as
> the global dbus.BUS_STARTER)"""
>
> + START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
> + START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
> +
> + def __new__(cls, address_or_type=TYPE_SESSION, mainloop=None):
> + bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
> +
> + # _bus_names is used by dbus.service.BusName!
> + bus._bus_names = weakref.WeakValueDictionary()
> +
> + bus._signal_sender_matches = {}
> + """Map from sender well-known name to list of match rules for all
> + signal handlers that match on sender well-known name."""
> +
> + bus.add_message_filter(bus.__class__._noc_signal_func)
> +
> + return bus
> +
> + def _noc_signal_func(self, message):
> + # If it's NameOwnerChanged, we'll need to update our
> + # sender well-known name -> sender unique name mappings
> + if (message.is_signal(BUS_DAEMON_IFACE, 'NameOwnerChanged')
> + and message.has_sender(BUS_DAEMON_NAME)
> + and message.has_path(BUS_DAEMON_PATH)):
> + name, unused, new = message.get_args_list()
> + for match in self._signal_sender_matches.get(name, (None,)):
> + match.sender_unique = new
> +
> + return HANDLER_RESULT_NOT_YET_HANDLED
> +
> + def add_signal_receiver(self, handler_function, signal_name=None,
> + dbus_interface=None, named_service=None,
> + path=None, **keywords):
> + match = super(BusConnection, self).add_signal_receiver(
> + handler_function, signal_name, dbus_interface, named_service,
> + path, **keywords)
> +
> + # The bus daemon is special - its unique-name is org.freedesktop.DBus
> + # rather than starting with :
> + if (named_service is not None
> + and named_service[:1] != ':'
> + and named_service != BUS_DAEMON_NAME):
> + try:
> + match.sender_unique = self.get_name_owner(named_service)
> + except DBusException:
> + # if the desired sender isn't actually running, we'll get
> + # notified by NameOwnerChanged when it appears
> + pass
> + notification = self._signal_sender_matches.setdefault(
> + named_service, [])
> + if not notification:
> + self.add_match_string(_NAME_OWNER_CHANGE_MATCH % named_service)
> + notification.append(match)
> +
> + self.add_match_string(str(match))
> +
> + return match
> +
> + def _clean_up_signal_match(self, match):
> + # The signals lock must be held.
> + self.remove_match_string(str(match))
> + notification = self._signal_sender_matches.get(match.sender, False)
> + if notification:
> + try:
> + notification.remove(match)
> + except LookupError:
> + pass
> + if not notification:
> + # nobody cares any more, so remove the match rule from the bus
> + self.remove_match_string(_NAME_OWNER_CHANGE_MATCH
> + % match.sender)
> +
> def activate_name_owner(self, bus_name):
> if (bus_name is not None and bus_name[:1] != ':'
> and bus_name != BUS_DAEMON_NAME):
> diff --git a/dbus/connection.py b/dbus/connection.py
> index 1de5ec4..0ba64ab 100644
> --- a/dbus/connection.py
> +++ b/dbus/connection.py
> @@ -16,24 +16,194 @@
> # along with this program; if not, write to the Free Software
> # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
>
> -__all__ = ('Connection',)
> +__all__ = ('Connection', 'SignalMatch')
> __docformat__ = 'reStructuredText'
>
> import logging
> +try:
> + import thread
> +except ImportError:
> + import dummy_thread as thread
> +import weakref
>
> from _dbus_bindings import Connection as _Connection, ErrorMessage, \
> MethodCallMessage, MethodReturnMessage, \
> - DBusException, LOCAL_PATH, LOCAL_IFACE
> + DBusException, LOCAL_PATH, LOCAL_IFACE, \
> + validate_interface_name, validate_member_name,\
> + validate_bus_name, validate_object_path,\
> + validate_error_name, \
> + HANDLER_RESULT_NOT_YET_HANDLED, \
> + UTF8String, SignalMessage
> from dbus.proxies import ProxyObject
>
>
> -_logger = logging.getLogger('dbus.methods')
> +_logger = logging.getLogger('dbus.connection')
>
>
> def _noop(*args, **kwargs):
> pass
>
>
> +class SignalMatch(object):
> + __slots__ = ('sender_unique', '_member', '_interface', '_sender',
> + '_path', '_handler', '_args_match', '_rule',
> + '_utf8_strings', '_byte_arrays', '_conn_weakref',
> + '_destination_keyword', '_interface_keyword',
> + '_message_keyword', '_member_keyword',
> + '_sender_keyword', '_path_keyword', '_int_args_match')
> +
> + def __init__(self, conn, sender, object_path, dbus_interface,
> + member, handler, utf8_strings=False, byte_arrays=False,
> + sender_keyword=None, path_keyword=None,
> + interface_keyword=None, member_keyword=None,
> + message_keyword=None, destination_keyword=None,
> + **kwargs):
> + if member is not None:
> + validate_member_name(member)
> + if dbus_interface is not None:
> + validate_interface_name(dbus_interface)
> + if sender is not None:
> + validate_bus_name(sender)
> + if object_path is not None:
> + validate_object_path(object_path)
> +
> + self._conn_weakref = weakref.ref(conn)
> + self._sender = sender
> + self._interface = dbus_interface
> + self._member = member
> + self._path = object_path
> + self._handler = handler
> + self.sender_unique = sender
> + self._utf8_strings = utf8_strings
> + self._byte_arrays = byte_arrays
> + self._sender_keyword = sender_keyword
> + self._path_keyword = path_keyword
> + self._member_keyword = member_keyword
> + self._interface_keyword = interface_keyword
> + self._message_keyword = message_keyword
> + self._destination_keyword = destination_keyword
> +
> + self._args_match = kwargs
> + if not kwargs:
> + self._int_args_match = None
> + else:
> + self._int_args_match = {}
> + for kwarg in kwargs:
> + if not kwarg.startswith('arg'):
> + raise TypeError('SignalMatch: unknown keyword argument %s'
> + % kwarg)
> + try:
> + index = int(kwarg[3:])
> + except ValueError:
> + raise TypeError('SignalMatch: unknown keyword argument %s'
> + % kwarg)
> + if index < 0 or index > 63:
> + raise TypeError('SignalMatch: arg match index must be in '
> + 'range(64), not %d' % index)
> + self._int_args_match[index] = kwargs[kwarg]
> +
> + # we're probably going to have to calculate the match rule for
> + # the Bus's benefit, so this constructor might as well do the work
> + rule = ["type='signal'"]
> + if self._sender is not None:
> + rule.append("sender='%s'" % self._sender)
> + if self._path is not None:
> + rule.append("path='%s'" % self._path)
> + if self._interface is not None:
> + rule.append("interface='%s'" % self._interface)
> + if self._member is not None:
> + rule.append("member='%s'" % self._member)
> + for kwarg, value in kwargs.iteritems():
> + rule.append("%s='%s'" % (kwarg, value))
> +
> + self._rule = ','.join(rule)
> +
> + sender = property(lambda self: self._sender)
> +
> + def __str__(self):
> + return self._rule
> +
> + def __repr__(self):
> + return ('<%s at %x "%s" on conn %r>'
> + % (self.__class__, id(self), self._rule, self._conn_weakref()))
> +
> + def matches_removal_spec(self, sender, object_path,
> + dbus_interface, member, handler, **kwargs):
> + if handler not in (None, self._handler):
> + return False
> + if sender != self._sender:
> + return False
> + if object_path != self._path:
> + return False
> + if dbus_interface != self._interface:
> + return False
> + if member != self._member:
> + return False
> + if kwargs != self._args_match:
> + return False
> + return True
> +
> + def maybe_handle_message(self, message):
> + args = None
> +
> + # these haven't been checked yet by the match tree
> + if self.sender_unique not in (None, message.get_sender()):
> + return False
> + if self._int_args_match is not None:
> + # extracting args with utf8_strings and byte_arrays is less work
> + args = message.get_args_list(utf8_strings=True, byte_arrays=True)
> + for index, value in self._int_args_match.iteritems():
> + if (index >= len(args)
> + or not isinstance(args[index], UTF8String)
> + or args[index] != value):
> + return False
> +
> + # these have likely already been checked by the match tree
> + if self._member not in (None, message.get_member()):
> + return False
> + if self._interface not in (None, message.get_interface()):
> + return False
> + if self._path not in (None, message.get_path()):
> + return False
> +
> + try:
> + # minor optimization: if we already extracted the args with the
> + # right calling convention to do the args match, don't bother
> + # doing so again
> + if args is None or not self._utf8_strings or not self._byte_arrays:
> + args = message.get_args_list(utf8_strings=self._utf8_strings,
> + byte_arrays=self._byte_arrays)
> + kwargs = {}
> + if self._sender_keyword is not None:
> + kwargs[self._sender_keyword] = message.get_sender()
> + if self._destination_keyword is not None:
> + kwargs[self._destination_keyword] = message.get_destination()
> + if self._path_keyword is not None:
> + kwargs[self._path_keyword] = message.get_path()
> + if self._member_keyword is not None:
> + kwargs[self._member_keyword] = message.get_member()
> + if self._interface_keyword is not None:
> + kwargs[self._interface_keyword] = message.get_interface()
> + if self._message_keyword is not None:
> + kwargs[self._message_keyword] = message
> + self._handler(*args, **kwargs)
> + except:
> + # basicConfig is a no-op if logging is already configured
> + logging.basicConfig()
> + _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
> +
> + return True
> +
> + def remove(self):
> + conn = self._conn_weakref()
> + # do nothing if the connection has already vanished
> + if conn is not None:
> + conn.remove_signal_receiver(self, self._member,
> + self._interface, self._sender,
> + self._path,
> + **self._args_match)
> +
> +
> class Connection(_Connection):
> """A connection to another application. In this base class there is
> assumed to be no bus daemon.
> @@ -41,6 +211,40 @@ class Connection(_Connection):
>
> ProxyObjectClass = ProxyObject
>
> + def __init__(self, *args, **kwargs):
> + super(Connection, self).__init__(*args, **kwargs)
> +
> + # this if-block is needed because shared bus connections can be
> + # __init__'ed more than once
> + if not hasattr(self, '_dbus_Connection_initialized'):
> + self._dbus_Connection_initialized = 1
> +
> + self._signal_recipients_by_object_path = {}
> + """Map from object path to dict mapping dbus_interface to dict
> + mapping member to list of SignalMatch objects."""
> +
> + self._signals_lock = thread.allocate_lock()
> + """Lock used to protect signal data structures if doing two
> + removals at the same time (everything else is atomic, thanks to
> + the GIL)"""
> +
> + self.add_message_filter(self.__class__._signal_func)
> +
> + def activate_name_owner(self, bus_name):
> + """Return the unique name for the given bus name, activating it
> + if necessary and possible.
> +
> + If the name is already unique or this connection is not to a
> + bus daemon, just return it.
> +
> + :Returns: a bus name. If the given `bus_name` exists, the returned
> + name identifies its current owner; otherwise the returned name
> + does not exist.
> + :Raises DBusException: if the implementation has failed
> + to activate the given bus name.
> + """
> + return bus_name
> +
> def get_object(self, named_service, object_path, introspect=True):
> """Return a local proxy for the given remote object.
>
> @@ -62,20 +266,173 @@ class Connection(_Connection):
> return self.ProxyObjectClass(self, named_service, object_path,
> introspect=introspect)
>
> - def activate_name_owner(self, bus_name):
> - """Return the unique name for the given bus name, activating it
> - if necessary and possible.
> + def add_signal_receiver(self, handler_function,
> + signal_name=None,
> + dbus_interface=None,
> + named_service=None,
> + path=None,
> + **keywords):
> + """Arrange for the given function to be called when a signal matching
> + the parameters is received.
>
> - If the name is already unique or this connection is not to a
> - bus daemon, just return it.
> + :Parameters:
> + `handler_function` : callable
> + The function to be called. Its positional arguments will
> + be the arguments of the signal. By default it will receive
> + no keyword arguments, but see the description of
> + the optional keyword arguments below.
> + `signal_name` : str
> + The signal name; None (the default) matches all names
> + `dbus_interface` : str
> + The D-Bus interface name with which to qualify the signal;
> + None (the default) matches all interface names
> + `named_service` : str
> + A bus name for the sender, which will be resolved to a
> + unique name if it is not already; None (the default) matches
> + any sender
> + `path` : str
> + The object path of the object which must have emitted the
> + signal; None (the default) matches any object path
> + :Keywords:
> + `utf8_strings` : bool
> + If True, the handler function will receive any string
> + arguments as dbus.UTF8String objects (a subclass of str
> + guaranteed to be UTF-8). If False (default) it will receive
> + any string arguments as dbus.String objects (a subclass of
> + unicode).
> + `byte_arrays` : bool
> + If True, the handler function will receive any byte-array
> + arguments as dbus.ByteArray objects (a subclass of str).
> + If False (default) it will receive any byte-array
> + arguments as a dbus.Array of dbus.Byte (subclasses of:
> + a list of ints).
> + `sender_keyword` : str
> + If not None (the default), the handler function will receive
> + the unique name of the sending endpoint as a keyword
> + argument with this name.
> + `destination_keyword` : str
> + If not None (the default), the handler function will receive
> + the bus name of the destination (or None if the signal is a
> + broadcast, as is usual) as a keyword argument with this name.
> + `interface_keyword` : str
> + If not None (the default), the handler function will receive
> + the signal interface as a keyword argument with this name.
> + `member_keyword` : str
> + If not None (the default), the handler function will receive
> + the signal name as a keyword argument with this name.
> + `path_keyword` : str
> + If not None (the default), the handler function will receive
> + the object-path of the sending object as a keyword argument
> + with this name.
> + `message_keyword` : str
> + If not None (the default), the handler function will receive
> + the `dbus.lowlevel.SignalMessage` as a keyword argument with
> + this name.
> + `arg...` : unicode or UTF-8 str
> + If there are additional keyword parameters of the form
> + ``arg``\ *n*, match only signals where the *n*\ th argument
> + is the value given for that keyword parameter. As of this
> + time only string arguments can be matched (in particular,
> + object paths and signatures can't).
> + """
> + self._require_main_loop()
>
> - :Returns: a bus name. If the given `bus_name` exists, the returned
> - name identifies its current owner; otherwise the returned name
> - does not exist.
> - :Raises DBusException: if the implementation has failed
> - to activate the given bus name.
> + match = SignalMatch(self, named_service, path, dbus_interface,
> + signal_name, handler_function, **keywords)
> + by_interface = self._signal_recipients_by_object_path.setdefault(path,
> + {})
> + by_member = by_interface.setdefault(dbus_interface, {})
> + matches = by_member.setdefault(signal_name, [])
> + self._signals_lock.acquire()
> + try:
> + matches.append(match)
> + finally:
> + self._signals_lock.release()
> + return match
> +
> + def _iter_easy_matches(self, path, dbus_interface, member):
> + if path is not None:
> + path_keys = (None, path)
> + else:
> + path_keys = (None,)
> + if dbus_interface is not None:
> + interface_keys = (None, dbus_interface)
> + else:
> + interface_keys = (None,)
> + if member is not None:
> + member_keys = (None, member)
> + else:
> + member_keys = (None,)
> +
> + for path in path_keys:
> + by_interface = self._signal_recipients_by_object_path.get(path,
> + None)
> + if by_interface is None:
> + continue
> + for dbus_interface in interface_keys:
> + by_member = by_interface.get(dbus_interface, None)
> + if by_member is None:
> + continue
> + for member in member_keys:
> + matches = by_member.get(member, None)
> + if matches is None:
> + continue
> + for m in matches:
> + yield m
> +
> + def remove_signal_receiver(self, handler_or_match,
> + signal_name=None,
> + dbus_interface=None,
> + named_service=None,
> + path=None,
> + **keywords):
> + by_interface = self._signal_recipients_by_object_path.get(path, None)
> + if by_interface is None:
> + return
> + by_member = by_interface.get(dbus_interface, None)
> + if by_member is None:
> + return
> + matches = by_member.get(signal_name, None)
> + if matches is None:
> + return
> + self._signals_lock.acquire()
> + try:
> + new = []
> + for match in matches:
> + if (handler_or_match is match
> + or match.matches_removal_spec(named_service,
> + path,
> + dbus_interface,
> + signal_name,
> + handler_or_match,
> + **keywords)):
> + self._clean_up_signal_match(match)
> + else:
> + new.append(match)
> + by_member[signal_name] = new
> + finally:
> + self._signals_lock.release()
> +
> + def _clean_up_signal_match(self, match):
> + # Called with the signals lock held
> + pass
> +
> + def _signal_func(self, message):
> + """D-Bus filter function. Handle signals by dispatching to Python
> + callbacks kept in the match-rule tree.
> """
> - return bus_name
> +
> + if not isinstance(message, SignalMessage):
> + return HANDLER_RESULT_NOT_YET_HANDLED
> +
> + dbus_interface = message.get_interface()
> + path = message.get_path()
> + signal_name = message.get_member()
> +
> + for match in self._iter_easy_matches(path, dbus_interface,
> + signal_name):
> + match.maybe_handle_message(message)
> + return HANDLER_RESULT_NOT_YET_HANDLED
>
> def call_async(self, bus_name, object_path, dbus_interface, method,
> signature, args, reply_handler, error_handler,
> @@ -108,6 +465,7 @@ class Connection(_Connection):
> try:
> message.append(signature=signature, *args)
> except Exception, e:
> + logging.basicConfig()
> _logger.error('Unable to set arguments %r according to '
> 'signature %r: %s: %s',
> args, signature, e.__class__, e)
> @@ -164,6 +522,7 @@ class Connection(_Connection):
> try:
> message.append(signature=signature, *args)
> except Exception, e:
> + logging.basicConfig()
> _logger.error('Unable to set arguments %r according to '
> 'signature %r: %s: %s',
> args, signature, e.__class__, e)
> _______________________________________________
> dbus mailing list
> dbus at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dbus
More information about the dbus
mailing list