Move signal matching machinery into superclasses
[dbus-python-phuang.git] / dbus / connection.py
blob0ba64abd2b0046b37e8b7303b7a51e9427dea15b
1 # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
3 # Licensed under the Academic Free License version 2.1
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published
7 # by the Free Software Foundation; either version 2.1 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 __all__ = ('Connection', 'SignalMatch')
20 __docformat__ = 'reStructuredText'
22 import logging
23 try:
24 import thread
25 except ImportError:
26 import dummy_thread as thread
27 import weakref
29 from _dbus_bindings import Connection as _Connection, ErrorMessage, \
30 MethodCallMessage, MethodReturnMessage, \
31 DBusException, LOCAL_PATH, LOCAL_IFACE, \
32 validate_interface_name, validate_member_name,\
33 validate_bus_name, validate_object_path,\
34 validate_error_name, \
35 HANDLER_RESULT_NOT_YET_HANDLED, \
36 UTF8String, SignalMessage
37 from dbus.proxies import ProxyObject
40 _logger = logging.getLogger('dbus.connection')
43 def _noop(*args, **kwargs):
44 pass
47 class SignalMatch(object):
48 __slots__ = ('sender_unique', '_member', '_interface', '_sender',
49 '_path', '_handler', '_args_match', '_rule',
50 '_utf8_strings', '_byte_arrays', '_conn_weakref',
51 '_destination_keyword', '_interface_keyword',
52 '_message_keyword', '_member_keyword',
53 '_sender_keyword', '_path_keyword', '_int_args_match')
55 def __init__(self, conn, sender, object_path, dbus_interface,
56 member, handler, utf8_strings=False, byte_arrays=False,
57 sender_keyword=None, path_keyword=None,
58 interface_keyword=None, member_keyword=None,
59 message_keyword=None, destination_keyword=None,
60 **kwargs):
61 if member is not None:
62 validate_member_name(member)
63 if dbus_interface is not None:
64 validate_interface_name(dbus_interface)
65 if sender is not None:
66 validate_bus_name(sender)
67 if object_path is not None:
68 validate_object_path(object_path)
70 self._conn_weakref = weakref.ref(conn)
71 self._sender = sender
72 self._interface = dbus_interface
73 self._member = member
74 self._path = object_path
75 self._handler = handler
76 self.sender_unique = sender
77 self._utf8_strings = utf8_strings
78 self._byte_arrays = byte_arrays
79 self._sender_keyword = sender_keyword
80 self._path_keyword = path_keyword
81 self._member_keyword = member_keyword
82 self._interface_keyword = interface_keyword
83 self._message_keyword = message_keyword
84 self._destination_keyword = destination_keyword
86 self._args_match = kwargs
87 if not kwargs:
88 self._int_args_match = None
89 else:
90 self._int_args_match = {}
91 for kwarg in kwargs:
92 if not kwarg.startswith('arg'):
93 raise TypeError('SignalMatch: unknown keyword argument %s'
94 % kwarg)
95 try:
96 index = int(kwarg[3:])
97 except ValueError:
98 raise TypeError('SignalMatch: unknown keyword argument %s'
99 % kwarg)
100 if index < 0 or index > 63:
101 raise TypeError('SignalMatch: arg match index must be in '
102 'range(64), not %d' % index)
103 self._int_args_match[index] = kwargs[kwarg]
105 # we're probably going to have to calculate the match rule for
106 # the Bus's benefit, so this constructor might as well do the work
107 rule = ["type='signal'"]
108 if self._sender is not None:
109 rule.append("sender='%s'" % self._sender)
110 if self._path is not None:
111 rule.append("path='%s'" % self._path)
112 if self._interface is not None:
113 rule.append("interface='%s'" % self._interface)
114 if self._member is not None:
115 rule.append("member='%s'" % self._member)
116 for kwarg, value in kwargs.iteritems():
117 rule.append("%s='%s'" % (kwarg, value))
119 self._rule = ','.join(rule)
121 sender = property(lambda self: self._sender)
123 def __str__(self):
124 return self._rule
126 def __repr__(self):
127 return ('<%s at %x "%s" on conn %r>'
128 % (self.__class__, id(self), self._rule, self._conn_weakref()))
130 def matches_removal_spec(self, sender, object_path,
131 dbus_interface, member, handler, **kwargs):
132 if handler not in (None, self._handler):
133 return False
134 if sender != self._sender:
135 return False
136 if object_path != self._path:
137 return False
138 if dbus_interface != self._interface:
139 return False
140 if member != self._member:
141 return False
142 if kwargs != self._args_match:
143 return False
144 return True
146 def maybe_handle_message(self, message):
147 args = None
149 # these haven't been checked yet by the match tree
150 if self.sender_unique not in (None, message.get_sender()):
151 return False
152 if self._int_args_match is not None:
153 # extracting args with utf8_strings and byte_arrays is less work
154 args = message.get_args_list(utf8_strings=True, byte_arrays=True)
155 for index, value in self._int_args_match.iteritems():
156 if (index >= len(args)
157 or not isinstance(args[index], UTF8String)
158 or args[index] != value):
159 return False
161 # these have likely already been checked by the match tree
162 if self._member not in (None, message.get_member()):
163 return False
164 if self._interface not in (None, message.get_interface()):
165 return False
166 if self._path not in (None, message.get_path()):
167 return False
169 try:
170 # minor optimization: if we already extracted the args with the
171 # right calling convention to do the args match, don't bother
172 # doing so again
173 if args is None or not self._utf8_strings or not self._byte_arrays:
174 args = message.get_args_list(utf8_strings=self._utf8_strings,
175 byte_arrays=self._byte_arrays)
176 kwargs = {}
177 if self._sender_keyword is not None:
178 kwargs[self._sender_keyword] = message.get_sender()
179 if self._destination_keyword is not None:
180 kwargs[self._destination_keyword] = message.get_destination()
181 if self._path_keyword is not None:
182 kwargs[self._path_keyword] = message.get_path()
183 if self._member_keyword is not None:
184 kwargs[self._member_keyword] = message.get_member()
185 if self._interface_keyword is not None:
186 kwargs[self._interface_keyword] = message.get_interface()
187 if self._message_keyword is not None:
188 kwargs[self._message_keyword] = message
189 self._handler(*args, **kwargs)
190 except:
191 # basicConfig is a no-op if logging is already configured
192 logging.basicConfig()
193 _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
195 return True
197 def remove(self):
198 conn = self._conn_weakref()
199 # do nothing if the connection has already vanished
200 if conn is not None:
201 conn.remove_signal_receiver(self, self._member,
202 self._interface, self._sender,
203 self._path,
204 **self._args_match)
207 class Connection(_Connection):
208 """A connection to another application. In this base class there is
209 assumed to be no bus daemon.
212 ProxyObjectClass = ProxyObject
214 def __init__(self, *args, **kwargs):
215 super(Connection, self).__init__(*args, **kwargs)
217 # this if-block is needed because shared bus connections can be
218 # __init__'ed more than once
219 if not hasattr(self, '_dbus_Connection_initialized'):
220 self._dbus_Connection_initialized = 1
222 self._signal_recipients_by_object_path = {}
223 """Map from object path to dict mapping dbus_interface to dict
224 mapping member to list of SignalMatch objects."""
226 self._signals_lock = thread.allocate_lock()
227 """Lock used to protect signal data structures if doing two
228 removals at the same time (everything else is atomic, thanks to
229 the GIL)"""
231 self.add_message_filter(self.__class__._signal_func)
233 def activate_name_owner(self, bus_name):
234 """Return the unique name for the given bus name, activating it
235 if necessary and possible.
237 If the name is already unique or this connection is not to a
238 bus daemon, just return it.
240 :Returns: a bus name. If the given `bus_name` exists, the returned
241 name identifies its current owner; otherwise the returned name
242 does not exist.
243 :Raises DBusException: if the implementation has failed
244 to activate the given bus name.
246 return bus_name
248 def get_object(self, named_service, object_path, introspect=True):
249 """Return a local proxy for the given remote object.
251 Method calls on the proxy are translated into method calls on the
252 remote object.
254 :Parameters:
255 `named_service` : str
256 A bus name (either the unique name or a well-known name)
257 of the application owning the object
258 `object_path` : str
259 The object path of the desired object
260 `introspect` : bool
261 If true (default), attempt to introspect the remote
262 object to find out supported methods and their signatures
264 :Returns: a `dbus.proxies.ProxyObject`
266 return self.ProxyObjectClass(self, named_service, object_path,
267 introspect=introspect)
269 def add_signal_receiver(self, handler_function,
270 signal_name=None,
271 dbus_interface=None,
272 named_service=None,
273 path=None,
274 **keywords):
275 """Arrange for the given function to be called when a signal matching
276 the parameters is received.
278 :Parameters:
279 `handler_function` : callable
280 The function to be called. Its positional arguments will
281 be the arguments of the signal. By default it will receive
282 no keyword arguments, but see the description of
283 the optional keyword arguments below.
284 `signal_name` : str
285 The signal name; None (the default) matches all names
286 `dbus_interface` : str
287 The D-Bus interface name with which to qualify the signal;
288 None (the default) matches all interface names
289 `named_service` : str
290 A bus name for the sender, which will be resolved to a
291 unique name if it is not already; None (the default) matches
292 any sender
293 `path` : str
294 The object path of the object which must have emitted the
295 signal; None (the default) matches any object path
296 :Keywords:
297 `utf8_strings` : bool
298 If True, the handler function will receive any string
299 arguments as dbus.UTF8String objects (a subclass of str
300 guaranteed to be UTF-8). If False (default) it will receive
301 any string arguments as dbus.String objects (a subclass of
302 unicode).
303 `byte_arrays` : bool
304 If True, the handler function will receive any byte-array
305 arguments as dbus.ByteArray objects (a subclass of str).
306 If False (default) it will receive any byte-array
307 arguments as a dbus.Array of dbus.Byte (subclasses of:
308 a list of ints).
309 `sender_keyword` : str
310 If not None (the default), the handler function will receive
311 the unique name of the sending endpoint as a keyword
312 argument with this name.
313 `destination_keyword` : str
314 If not None (the default), the handler function will receive
315 the bus name of the destination (or None if the signal is a
316 broadcast, as is usual) as a keyword argument with this name.
317 `interface_keyword` : str
318 If not None (the default), the handler function will receive
319 the signal interface as a keyword argument with this name.
320 `member_keyword` : str
321 If not None (the default), the handler function will receive
322 the signal name as a keyword argument with this name.
323 `path_keyword` : str
324 If not None (the default), the handler function will receive
325 the object-path of the sending object as a keyword argument
326 with this name.
327 `message_keyword` : str
328 If not None (the default), the handler function will receive
329 the `dbus.lowlevel.SignalMessage` as a keyword argument with
330 this name.
331 `arg...` : unicode or UTF-8 str
332 If there are additional keyword parameters of the form
333 ``arg``\ *n*, match only signals where the *n*\ th argument
334 is the value given for that keyword parameter. As of this
335 time only string arguments can be matched (in particular,
336 object paths and signatures can't).
338 self._require_main_loop()
340 match = SignalMatch(self, named_service, path, dbus_interface,
341 signal_name, handler_function, **keywords)
342 by_interface = self._signal_recipients_by_object_path.setdefault(path,
344 by_member = by_interface.setdefault(dbus_interface, {})
345 matches = by_member.setdefault(signal_name, [])
346 self._signals_lock.acquire()
347 try:
348 matches.append(match)
349 finally:
350 self._signals_lock.release()
351 return match
353 def _iter_easy_matches(self, path, dbus_interface, member):
354 if path is not None:
355 path_keys = (None, path)
356 else:
357 path_keys = (None,)
358 if dbus_interface is not None:
359 interface_keys = (None, dbus_interface)
360 else:
361 interface_keys = (None,)
362 if member is not None:
363 member_keys = (None, member)
364 else:
365 member_keys = (None,)
367 for path in path_keys:
368 by_interface = self._signal_recipients_by_object_path.get(path,
369 None)
370 if by_interface is None:
371 continue
372 for dbus_interface in interface_keys:
373 by_member = by_interface.get(dbus_interface, None)
374 if by_member is None:
375 continue
376 for member in member_keys:
377 matches = by_member.get(member, None)
378 if matches is None:
379 continue
380 for m in matches:
381 yield m
383 def remove_signal_receiver(self, handler_or_match,
384 signal_name=None,
385 dbus_interface=None,
386 named_service=None,
387 path=None,
388 **keywords):
389 by_interface = self._signal_recipients_by_object_path.get(path, None)
390 if by_interface is None:
391 return
392 by_member = by_interface.get(dbus_interface, None)
393 if by_member is None:
394 return
395 matches = by_member.get(signal_name, None)
396 if matches is None:
397 return
398 self._signals_lock.acquire()
399 try:
400 new = []
401 for match in matches:
402 if (handler_or_match is match
403 or match.matches_removal_spec(named_service,
404 path,
405 dbus_interface,
406 signal_name,
407 handler_or_match,
408 **keywords)):
409 self._clean_up_signal_match(match)
410 else:
411 new.append(match)
412 by_member[signal_name] = new
413 finally:
414 self._signals_lock.release()
416 def _clean_up_signal_match(self, match):
417 # Called with the signals lock held
418 pass
420 def _signal_func(self, message):
421 """D-Bus filter function. Handle signals by dispatching to Python
422 callbacks kept in the match-rule tree.
425 if not isinstance(message, SignalMessage):
426 return HANDLER_RESULT_NOT_YET_HANDLED
428 dbus_interface = message.get_interface()
429 path = message.get_path()
430 signal_name = message.get_member()
432 for match in self._iter_easy_matches(path, dbus_interface,
433 signal_name):
434 match.maybe_handle_message(message)
435 return HANDLER_RESULT_NOT_YET_HANDLED
437 def call_async(self, bus_name, object_path, dbus_interface, method,
438 signature, args, reply_handler, error_handler,
439 timeout=-1.0, utf8_strings=False, byte_arrays=False,
440 require_main_loop=True):
441 """Call the given method, asynchronously.
443 If the reply_handler is None, successful replies will be ignored.
444 If the error_handler is None, failures will be ignored. If both
445 are None, the implementation may request that no reply is sent.
447 :Returns: The dbus.lowlevel.PendingCall.
449 if object_path == LOCAL_PATH:
450 raise DBusException('Methods may not be called on the reserved '
451 'path %s' % LOCAL_PATH)
452 if dbus_interface == LOCAL_IFACE:
453 raise DBusException('Methods may not be called on the reserved '
454 'interface %s' % LOCAL_IFACE)
455 # no need to validate other args - MethodCallMessage ctor will do
457 get_args_opts = {'utf8_strings': utf8_strings,
458 'byte_arrays': byte_arrays}
460 message = MethodCallMessage(destination=bus_name,
461 path=object_path,
462 interface=dbus_interface,
463 method=method)
464 # Add the arguments to the function
465 try:
466 message.append(signature=signature, *args)
467 except Exception, e:
468 logging.basicConfig()
469 _logger.error('Unable to set arguments %r according to '
470 'signature %r: %s: %s',
471 args, signature, e.__class__, e)
472 raise
474 if reply_handler is None and error_handler is None:
475 # we don't care what happens, so just send it
476 self.send_message(message)
477 return
479 if reply_handler is None:
480 reply_handler = _noop
481 if error_handler is None:
482 error_handler = _noop
484 def msg_reply_handler(message):
485 if isinstance(message, MethodReturnMessage):
486 reply_handler(*message.get_args_list(**get_args_opts))
487 elif isinstance(message, ErrorMessage):
488 args = message.get_args_list()
489 # FIXME: should we do something with the rest?
490 if len(args) > 0:
491 error_handler(DBusException(args[0]))
492 else:
493 error_handler(DBusException())
494 else:
495 error_handler(TypeError('Unexpected type for reply '
496 'message: %r' % message))
497 return self.send_message_with_reply(message, msg_reply_handler,
498 timeout/1000.0,
499 require_main_loop=require_main_loop)
501 def call_blocking(self, bus_name, object_path, dbus_interface, method,
502 signature, args, timeout=-1.0, utf8_strings=False,
503 byte_arrays=False):
504 """Call the given method, synchronously.
506 if object_path == LOCAL_PATH:
507 raise DBusException('Methods may not be called on the reserved '
508 'path %s' % LOCAL_PATH)
509 if dbus_interface == LOCAL_IFACE:
510 raise DBusException('Methods may not be called on the reserved '
511 'interface %s' % LOCAL_IFACE)
512 # no need to validate other args - MethodCallMessage ctor will do
514 get_args_opts = {'utf8_strings': utf8_strings,
515 'byte_arrays': byte_arrays}
517 message = MethodCallMessage(destination=bus_name,
518 path=object_path,
519 interface=dbus_interface,
520 method=method)
521 # Add the arguments to the function
522 try:
523 message.append(signature=signature, *args)
524 except Exception, e:
525 logging.basicConfig()
526 _logger.error('Unable to set arguments %r according to '
527 'signature %r: %s: %s',
528 args, signature, e.__class__, e)
529 raise
531 # make a blocking call
532 reply_message = self.send_message_with_reply_and_block(
533 message, timeout)
534 args_list = reply_message.get_args_list(**get_args_opts)
535 if len(args_list) == 0:
536 return None
537 elif len(args_list) == 1:
538 return args_list[0]
539 else:
540 return tuple(args_list)