[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