]> git.ipfire.org Git - thirdparty/dbus.git/commitdiff
* python/decorators.py: import dbus_bindings
authorJohn (J5) Palmieri <johnp@redhat.com>
Tue, 24 May 2005 00:21:07 +0000 (00:21 +0000)
committerJohn (J5) Palmieri <johnp@redhat.com>
Tue, 24 May 2005 00:21:07 +0000 (00:21 +0000)
* 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.

ChangeLog
python/Makefile.am
python/_dbus.py
python/decorators.py
python/examples/example-signal-emitter.py
python/examples/example-signal-recipient.py
python/matchrules.py [new file with mode: 0644]

index 8fcd7bc9daa1466c151664e95079bbbdbe9ef402..48214c75ce99203332b45d9235960d4bc92d3123 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
index 1cd40ee303da36bf0dad36463593deb189bb263c..23e9cea428a42363e1b58446d2bf1bc730cb8d9e 100644 (file)
@@ -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
index ca7a156a207d411de074482c1414a391298443da..d408704a03d94dea5694b67add8b414079064c8b 100644 (file)
@@ -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):
index 4be6e2078bb35d777109b83bd31bde9f46c80940..7847b3a2e75a7d8e61b5b21e419c41bb2b420a49 100644 (file)
@@ -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__
index 662c2bc7478afdca2cd6261d1b6a3ce8322ecb73..428f1440d44614e8b17da5bd7d5a429a8a58b6c7 100644 (file)
@@ -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)
index 1dbf6a65a3ff06ce3bd9b2e8c1dff98b3eb4740f..2f932a9b41dd1f1ef67ef0f361dd8249f34e7efb 100644 (file)
@@ -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 (file)
index 0000000..e27c4c8
--- /dev/null
@@ -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