diff options
| author | John (J5) Palmieri <johnp@redhat.com> | 2005-05-24 00:21:07 +0000 | 
|---|---|---|
| committer | John (J5) Palmieri <johnp@redhat.com> | 2005-05-24 00:21:07 +0000 | 
| commit | e43a353d27f1b9643c21ccff53a7759fc3dcdffe (patch) | |
| tree | ad51cf8b4ec9801fe11e012b7453ddb52c51ab44 | |
| parent | 8af9f11e54317698424bc15ca566e46268646d73 (diff) | |
* python/decorators.py: import dbus_bindings
* python/matchrules.py (SignalMatchRule, SignalMatchTree,
  SignalMatchNode): new classes that implement wildcard signal
  callback matching using a tree lookup. Heavily modified from a
  patch sent by Celso Pinto (fd.o bug #3241)
* _dbus.py (add_signal_receiver, remove_signal_receiver, _signal_func):
  use new match classes to handle signals.
| -rw-r--r-- | ChangeLog | 12 | ||||
| -rw-r--r-- | python/Makefile.am | 2 | ||||
| -rw-r--r-- | python/_dbus.py | 69 | ||||
| -rw-r--r-- | python/decorators.py | 10 | ||||
| -rw-r--r-- | python/examples/example-signal-emitter.py | 8 | ||||
| -rw-r--r-- | python/examples/example-signal-recipient.py | 4 | ||||
| -rw-r--r-- | python/matchrules.py | 170 | 
7 files changed, 223 insertions, 52 deletions
| @@ -1,3 +1,15 @@ +2005-05-23  John (J5) Palmieri  <johnp@redhat.com> + +	* python/decorators.py: import dbus_bindings + +	* python/matchrules.py (SignalMatchRule, SignalMatchTree,  +	SignalMatchNode): new classes that implement wildcard signal +	callback matching using a tree lookup. Heavily modified from a +	patch sent by Celso Pinto (fd.o bug #3241) + +	* _dbus.py (add_signal_receiver, remove_signal_receiver, _signal_func): +	use new match classes to handle signals. +  2005-05-19  John (J5) Palmieri  <johnp@redhat.com>  	* python/dbus_bindings.pyx.in: s/TYPE_PATH/TYPE_OBJECT_PATH diff --git a/python/Makefile.am b/python/Makefile.am index 1cd40ee3..23e9cea4 100644 --- a/python/Makefile.am +++ b/python/Makefile.am @@ -3,7 +3,7 @@ SUBDIRS=examples  INCLUDES=-I$(top_builddir) -I$(top_builddir)/dbus $(DBUS_CLIENT_CFLAGS) $(DBUS_GLIB_CFLAGS) $(DBUS_GLIB_TOOL_CFLAGS) $(PYTHON_INCLUDES)  dbusdir = $(pythondir)/dbus -dbus_PYTHON = __init__.py _dbus.py decorators.py exceptions.py services.py proxies.py _util.py types.py +dbus_PYTHON = __init__.py _dbus.py decorators.py exceptions.py services.py proxies.py _util.py types.py matchrules.py  dbusbindingsdir = $(pyexecdir)/dbus  dbusbindings_LTLIBRARIES = dbus_bindings.la diff --git a/python/_dbus.py b/python/_dbus.py index ca7a156a..d408704a 100644 --- a/python/_dbus.py +++ b/python/_dbus.py @@ -46,6 +46,7 @@ from decorators import *  from proxies import *  from exceptions import *  from services import * +from matchrules import *  import re  import inspect @@ -79,7 +80,7 @@ class Bus:          self._connection = dbus_bindings.bus_get(bus_type)          self._connection.add_filter(self._signal_func) -        self._match_rule_to_receivers = { } +        self._match_rule_tree = SignalMatchTree()          if (glib_mainloop):              self._connection.setup_with_g_main() @@ -111,61 +112,47 @@ class Bus:          return self.ProxyObjectClass(self, named_service, object_path)      def add_signal_receiver(self, handler_function, signal_name=None, dbus_interface=None, named_service=None, path=None): -        match_rule = self._get_match_rule(signal_name, dbus_interface, named_service, path) +        if (named_service and named_service[0] != ':'): +            bus_object = self.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') +            named_service = bus_object.GetNameOwner(named_service, dbus_interface='org.freedesktop.DBus') +         +        match_rule = SignalMatchRule(signal_name, dbus_interface, named_service, path) +        match_rule.add_handler(handler_function) -        if (not self._match_rule_to_receivers.has_key(match_rule)): -            self._match_rule_to_receivers[match_rule] = [handler_function] -	else: -	    self._match_rule_to_receivers[match_rule].append(handler_function) +        self._match_rule_tree.add(match_rule) -        dbus_bindings.bus_add_match(self._connection, match_rule) +        dbus_bindings.bus_add_match(self._connection, str(match_rule))      def remove_signal_receiver(self, handler_function, signal_name=None, dbus_interface=None, named_service=None, path=None): -        match_rule = self._get_match_rule(signal_name, dbus_interface, named_service, path) - -        if self._match_rule_to_receivers.has_key(match_rule): -            if self._match_rule_to_receivers[match_rule].__contains__(handler_function): -                self._match_rule_to_receivers[match_rule].pop(handler_function) -                dbus_bindings.bus_remove_match(self._connection, match_rule) +        if (named_service and named_service[0] != ':'): +            bus_object = self.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') +            named_service = bus_object.GetNameOwner(named_service, dbus_interface='org.freedesktop.DBus') +         +        match_rule = SignalMatchRule(signal_name, dbus_interface, named_service, path) +        match_rule.add_handler(handler_function) +         +        self._match_rule_tree.remove(match_rule) +         +        #TODO we leak match rules in the lower level bindings.  We need to ref count them      def get_unix_user(self, named_service):          """Get the unix user for the given named_service on this Bus"""          return dbus_bindings.bus_get_unix_user(self._connection, named_service) - -    #TODO: Rethink match rules.  Right now matches have to be exact. -    def _get_match_rule(self, signal_name, dbus_interface, named_service, path): -        match_rule = "type='signal'" -        if (dbus_interface): -            match_rule = match_rule + ",interface='%s'" % (dbus_interface) -        if (named_service): -            if (named_service[0] != ':' and named_service != "org.freedesktop.DBus"): -                bus_object = self.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') -                named_service = bus_object.GetNameOwner(named_service, dbus_interface='org.freedesktop.DBus') - -            match_rule = match_rule + ",sender='%s'" % (named_service) -        if (path): -            match_rule = match_rule + ",path='%s'" % (path) -        if (signal_name): -            match_rule = match_rule + ",member='%s'" % (signal_name) -        return match_rule      def _signal_func(self, connection, message):          if (message.get_type() != dbus_bindings.MESSAGE_TYPE_SIGNAL):              return dbus_bindings.HANDLER_RESULT_NOT_YET_HANDLED          dbus_interface      = message.get_interface() -        named_service  = message.get_sender() -        path           = message.get_path() -        signal_name    = message.get_member() +        named_service     = message.get_sender() +        path                      = message.get_path() +        signal_name         = message.get_member() -        match_rule = self._get_match_rule(signal_name, dbus_interface, named_service, path) +        match_rule = SignalMatchRule(signal_name, dbus_interface, named_service, path) -        if (self._match_rule_to_receivers.has_key(match_rule)): -            receivers = self._match_rule_to_receivers[match_rule] +        args = message.get_args_list() -            for receiver in receivers: -	        args = message.get_args_list() -		receiver(*args) +        self._match_rule_tree.exec_matches(match_rule, *args)      def start_service_by_name(self, named_service):          return dbus_bindings.bus_start_service_by_name(self._connection, named_service) @@ -203,8 +190,8 @@ class Interface:      def connect_to_signal(self, signal_name, handler_function, dbus_interface = None):          if not dbus_interface: -	    dbus_interface = self._dbus_interface -		 +            dbus_interface = self._dbus_interface +                      self._obj.connect_to_signal(signal_name, handler_function, dbus_interface)      def __getattr__(self, member, **keywords): diff --git a/python/decorators.py b/python/decorators.py index 4be6e207..7847b3a2 100644 --- a/python/decorators.py +++ b/python/decorators.py @@ -1,5 +1,6 @@  import _util   import inspect +import dbus_bindings  def method(dbus_interface): @@ -18,14 +19,15 @@ def signal(dbus_interface):      _util._validate_interface_or_name(dbus_interface)      def decorator(func):          def emit_signal(self, *args, **keywords): -	    func(self, *args, **keywords) -	    message = dbus_bindings.Signal(self._object_path, dbus_interface, func.__name__) +            func(self, *args, **keywords)            +            message = dbus_bindings.Signal(self._object_path, dbus_interface, func.__name__)              iter = message.get_iter(True) +                         for arg in args:                  iter.append(arg) -         +                    self._connection.send(message) -	 +          emit_signal._dbus_is_signal = True          emit_signal._dbus_interface = dbus_interface          emit_signal.__name__ = func.__name__ diff --git a/python/examples/example-signal-emitter.py b/python/examples/example-signal-emitter.py index 662c2bc7..428f1440 100644 --- a/python/examples/example-signal-emitter.py +++ b/python/examples/example-signal-emitter.py @@ -10,15 +10,15 @@ class TestObject(dbus.Object):      @dbus.signal('org.designfu.TestService')      def HelloSignal(self, message):          # The signal is emitted when this method exits -	# You can have code here if you wish +        # You can have code here if you wish          pass      @dbus.method('org.designfu.TestService')      def emitHelloSignal(self):          #you emit signals by calling the signal's skeleton method -	self.HelloSignal("Hello") -	return "Signal emitted" -	 +        self.HelloSignal("Hello") +        return "Signal emitted" +  session_bus = dbus.SessionBus()  service = dbus.Service("org.designfu.TestService", bus=session_bus)  object = TestObject(service) diff --git a/python/examples/example-signal-recipient.py b/python/examples/example-signal-recipient.py index 1dbf6a65..2f932a9b 100644 --- a/python/examples/example-signal-recipient.py +++ b/python/examples/example-signal-recipient.py @@ -12,8 +12,8 @@ def handle_error(e):  def emit_signal():     #call the emitHelloSignal method async -   object.emitHelloSignal(dbus_interface="org.designfu.TestService",  -                          reply_handler = handle_reply, error_handler = handle_error) +   object.emitHelloSignal(dbus_interface="org.designfu.TestService") +                          #reply_handler = handle_reply, error_handler = handle_error)     return True  bus = dbus.SessionBus() diff --git a/python/matchrules.py b/python/matchrules.py new file mode 100644 index 00000000..e27c4c8f --- /dev/null +++ b/python/matchrules.py @@ -0,0 +1,170 @@ +from exceptions import * + +class SignalMatchNode: +    def __init__(self): +        self.wildcard = None +        self.finite = {} +        self.rules = [] +         +    def add(self, key, leaf=None): +        node = None +         +        if key: +            if self.finite.has_key(key): +                node = self.finite[key] +            else: +                node = SignalMatchNode() +                self.finite[key]  = node +        else: +            if self.wildcard: +                node = self.wildcard +            else: +                node = SignalMatchNode() +                self.wildcard = node +          +        node.rules.append(leaf) +        return node +     +    def get_matches(self, key): +        result = [] +        if self.wildcard: +            result.append(self.wildcard) +         +        if self.finite.has_key(key): +            result.append(self.finite[key]) +             +        return result +         +    def get_match(self, key): +        if key: +           if self.finite.has_key(key): +               return self.finite[key] +           else: +               return None +         +        return self.wildcard +         +    def has_children(self): +        if self.wildcard or len(self.finite.iterkeys()) > 0: +            return True +        return False + +    def remove_child(self, child, key=None): +        if self.wildcard == child: +            self.wildcard = None +        elif self.finite.had_key(key): +            del self.finite[key] + +class SignalMatchTree: +    """This class creates an ordered tree of SignalMatchRules +        to speed searchs.  Left branches are wildcard elements +        and all other branches are concreet elements. +    """ +    def __init__(self): +        self._tree = SignalMatchNode() +     +    def add(self, rule): +        interface = self._tree.add(rule.sender) +        signal = interface.add(rule.dbus_interface) +        path = signal.add(rule.signal_name) +        path.add(rule.path, leaf=rule) +        +    def exec_matches(self, match_rule, *args): +        sender_matches = self._tree.get_matches(match_rule.sender) +        for sender_node in sender_matches: +            interface_matches = sender_node.get_matches(match_rule.dbus_interface) +            for interface_node in interface_matches: +                signal_matches = interface_node.get_matches(match_rule.signal_name) +                for signal_node in signal_matches: +                    path_matches = signal_node.get_matches(match_rule.path) +                    for path_node in path_matches: +                        if(path_node.rules): +                            for rule in path_node.rules: +                                rule.execute(*args) +             +    def remove(self, rule): +        try: +            sender = self._tree.get_match(rule.sender) +            interface = sender.get_match(rule.dbus_interface) +            signal = interface.get_match(rule.signal_name) +            path = signal.get_match(rule.path) +             +            rule_matches = [] +            for _rule in path.rules: +                if _rule.is_match(rule): +                    rule_matches.append(_rule) +                     +            for _rule in rule_matches: +                path.rules.remove(_rule) +                 +            #clean up tree +            if len(path.rules) == 0: +                signal.remove_child(path, key = rule.path) +                if not signal.has_children(): +                    interface.remove_child(signal, key = rule.signal_name) +                    if not interface.has_children(): +                        sender.remove_child(interface, key = rule.dbus_interface) +                        if not sender.has_children(): +                            self._tree.remove_child(sender, key = rule.sender) +             +        except: +            raise DBusException ("Trying to remove unkown rule: %s"%str(rule)) + +class SignalMatchRule: +    """This class represents a dbus rule used to filter signals. +        When a rule matches a filter, the signal is propagated to the handler_funtions +    """ +    def __init__(self, signal_name, dbus_interface, sender, path): +        self.handler_functions = [] + +        self.signal_name = signal_name +        self.dbus_interface = dbus_interface +        self.sender = sender +        self.path = path + +    def execute(self, *args): +        for handler in self.handler_functions: +            handler(*args) + +    def add_handler(self, handler): +        self.handler_functions.append(handler) +         +    def is_match(self, rule): +        if (self.signal_name == rule.signal_name and +            self.dbus_interface == rule.dbus_interface and +            self.sender == rule.sender and +            self.path == rule.path): +                _funcs_copy_a = self.dbus.handler_functions[0:] +                _funcs_copy_b = rule.dbus.handler_functions[0:] +                _funcs_copy_a.sort() +                _funcs_copy_b.sort() +                return a == b +  +        return False + +    def __repr__(self): +        """Returns a custom representation of this DBusMatchRule that can +            be used with dbus_bindings +        """ +        repr = "type='signal'" +        if (self.dbus_interface): +            repr = repr + ",interface='%s'" % (self.dbus_interface) +        else: +            repr = repr + ",interface='*'" + +        if (self.sender):      +            repr = repr + ",sender='%s'" % (self.sender) +        else: +            repr = repr + ",sender='*'" +     +        if (self.path): +            repr = repr + ",path='%s'" % (self.path) +        else: +            repr = repr + ",path='*'"  +             +        if (self.signal_name): +            repr = repr + ",member='%s'" % (self.signal_name) +        else: +            repr = repr + ",member='*'" +     +        return repr | 
