From e43a353d27f1b9643c21ccff53a7759fc3dcdffe Mon Sep 17 00:00:00 2001 From: "John (J5) Palmieri" Date: Tue, 24 May 2005 00:21:07 +0000 Subject: * 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. --- python/Makefile.am | 2 +- python/_dbus.py | 69 +++++------ python/decorators.py | 10 +- python/examples/example-signal-emitter.py | 8 +- python/examples/example-signal-recipient.py | 4 +- python/matchrules.py | 170 ++++++++++++++++++++++++++++ 6 files changed, 211 insertions(+), 52 deletions(-) create mode 100644 python/matchrules.py (limited to 'python') 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 -- cgit