dbus.connection: Release signals lock before calling _clean_up_signal_match().
[dbus-python-phuang.git] / dbus / connection.py
blob78ed28756d979a2a2487ff319176fa3fe75a9eaf
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 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.exceptions import DBusException
38 from dbus.proxies import ProxyObject
41 _logger = logging.getLogger('dbus.connection')
44 def _noop(*args, **kwargs):
45 pass
48 class SignalMatch(object):
49 __slots__ = ('_sender_name_owner', '_member', '_interface', '_sender',
50 '_path', '_handler', '_args_match', '_rule',
51 '_utf8_strings', '_byte_arrays', '_conn_weakref',
52 '_destination_keyword', '_interface_keyword',
53 '_message_keyword', '_member_keyword',
54 '_sender_keyword', '_path_keyword', '_int_args_match')
56 def __init__(self, conn, sender, object_path, dbus_interface,
57 member, handler, utf8_strings=False, byte_arrays=False,
58 sender_keyword=None, path_keyword=None,
59 interface_keyword=None, member_keyword=None,
60 message_keyword=None, destination_keyword=None,
61 **kwargs):
62 if member is not None:
63 validate_member_name(member)
64 if dbus_interface is not None:
65 validate_interface_name(dbus_interface)
66 if sender is not None:
67 validate_bus_name(sender)
68 if object_path is not None:
69 validate_object_path(object_path)
71 self._rule = None
72 self._conn_weakref = weakref.ref(conn)
73 self._sender = sender
74 self._interface = dbus_interface
75 self._member = member
76 self._path = object_path
77 self._handler = handler
79 # if the connection is actually a bus, it's responsible for changing
80 # this later
81 self._sender_name_owner = sender
83 self._utf8_strings = utf8_strings
84 self._byte_arrays = byte_arrays
85 self._sender_keyword = sender_keyword
86 self._path_keyword = path_keyword
87 self._member_keyword = member_keyword
88 self._interface_keyword = interface_keyword
89 self._message_keyword = message_keyword
90 self._destination_keyword = destination_keyword
92 self._args_match = kwargs
93 if not kwargs:
94 self._int_args_match = None
95 else:
96 self._int_args_match = {}
97 for kwarg in kwargs:
98 if not kwarg.startswith('arg'):
99 raise TypeError('SignalMatch: unknown keyword argument %s'
100 % kwarg)
101 try:
102 index = int(kwarg[3:])
103 except ValueError:
104 raise TypeError('SignalMatch: unknown keyword argument %s'
105 % kwarg)
106 if index < 0 or index > 63:
107 raise TypeError('SignalMatch: arg match index must be in '
108 'range(64), not %d' % index)
109 self._int_args_match[index] = kwargs[kwarg]
111 def __hash__(self):
112 """SignalMatch objects are compared by identity."""
113 return hash(id(self))
115 def __eq__(self, other):
116 """SignalMatch objects are compared by identity."""
117 return self is other
119 def __ne__(self, other):
120 """SignalMatch objects are compared by identity."""
121 return self is not other
123 sender = property(lambda self: self._sender)
125 def __str__(self):
126 if self._rule is None:
127 rule = ["type='signal'"]
128 if self._sender is not None:
129 rule.append("sender='%s'" % self._sender)
130 if self._path is not None:
131 rule.append("path='%s'" % self._path)
132 if self._interface is not None:
133 rule.append("interface='%s'" % self._interface)
134 if self._member is not None:
135 rule.append("member='%s'" % self._member)
136 if self._int_args_match is not None:
137 for index, value in self._int_args_match.iteritems():
138 rule.append("arg%d='%s'" % (index, value))
140 self._rule = ','.join(rule)
142 return self._rule
144 def __repr__(self):
145 return ('<%s at %x "%s" on conn %r>'
146 % (self.__class__, id(self), self._rule, self._conn_weakref()))
148 def set_sender_name_owner(self, new_name):
149 self._sender_name_owner = new_name
151 def matches_removal_spec(self, sender, object_path,
152 dbus_interface, member, handler, **kwargs):
153 if handler not in (None, self._handler):
154 return False
155 if sender != self._sender:
156 return False
157 if object_path != self._path:
158 return False
159 if dbus_interface != self._interface:
160 return False
161 if member != self._member:
162 return False
163 if kwargs != self._args_match:
164 return False
165 return True
167 def maybe_handle_message(self, message):
168 args = None
170 # these haven't been checked yet by the match tree
171 if self._sender_name_owner not in (None, message.get_sender()):
172 return False
173 if self._int_args_match is not None:
174 # extracting args with utf8_strings and byte_arrays is less work
175 args = message.get_args_list(utf8_strings=True, byte_arrays=True)
176 for index, value in self._int_args_match.iteritems():
177 if (index >= len(args)
178 or not isinstance(args[index], UTF8String)
179 or args[index] != value):
180 return False
182 # these have likely already been checked by the match tree
183 if self._member not in (None, message.get_member()):
184 return False
185 if self._interface not in (None, message.get_interface()):
186 return False
187 if self._path not in (None, message.get_path()):
188 return False
190 try:
191 # minor optimization: if we already extracted the args with the
192 # right calling convention to do the args match, don't bother
193 # doing so again
194 if args is None or not self._utf8_strings or not self._byte_arrays:
195 args = message.get_args_list(utf8_strings=self._utf8_strings,
196 byte_arrays=self._byte_arrays)
197 kwargs = {}
198 if self._sender_keyword is not None:
199 kwargs[self._sender_keyword] = message.get_sender()
200 if self._destination_keyword is not None:
201 kwargs[self._destination_keyword] = message.get_destination()
202 if self._path_keyword is not None:
203 kwargs[self._path_keyword] = message.get_path()
204 if self._member_keyword is not None:
205 kwargs[self._member_keyword] = message.get_member()
206 if self._interface_keyword is not None:
207 kwargs[self._interface_keyword] = message.get_interface()
208 if self._message_keyword is not None:
209 kwargs[self._message_keyword] = message
210 self._handler(*args, **kwargs)
211 except:
212 # basicConfig is a no-op if logging is already configured
213 logging.basicConfig()
214 _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
216 return True
218 def remove(self):
219 conn = self._conn_weakref()
220 # do nothing if the connection has already vanished
221 if conn is not None:
222 conn.remove_signal_receiver(self, self._member,
223 self._interface, self._sender,
224 self._path,
225 **self._args_match)
228 class Connection(_Connection):
229 """A connection to another application. In this base class there is
230 assumed to be no bus daemon.
233 ProxyObjectClass = ProxyObject
235 def __init__(self, *args, **kwargs):
236 super(Connection, self).__init__(*args, **kwargs)
238 # this if-block is needed because shared bus connections can be
239 # __init__'ed more than once
240 if not hasattr(self, '_dbus_Connection_initialized'):
241 self._dbus_Connection_initialized = 1
243 self._signal_recipients_by_object_path = {}
244 """Map from object path to dict mapping dbus_interface to dict
245 mapping member to list of SignalMatch objects."""
247 self._signals_lock = thread.allocate_lock()
248 """Lock used to protect signal data structures"""
250 self.add_message_filter(self.__class__._signal_func)
252 def activate_name_owner(self, bus_name):
253 """Return the unique name for the given bus name, activating it
254 if necessary and possible.
256 If the name is already unique or this connection is not to a
257 bus daemon, just return it.
259 :Returns: a bus name. If the given `bus_name` exists, the returned
260 name identifies its current owner; otherwise the returned name
261 does not exist.
262 :Raises DBusException: if the implementation has failed
263 to activate the given bus name.
265 return bus_name
267 def get_object(self, bus_name=None, object_path=None, introspect=True,
268 **kwargs):
269 """Return a local proxy for the given remote object.
271 Method calls on the proxy are translated into method calls on the
272 remote object.
274 :Parameters:
275 `bus_name` : str
276 A bus name (either the unique name or a well-known name)
277 of the application owning the object. The keyword argument
278 named_service is a deprecated alias for this.
279 `object_path` : str
280 The object path of the desired object
281 `introspect` : bool
282 If true (default), attempt to introspect the remote
283 object to find out supported methods and their signatures
285 :Returns: a `dbus.proxies.ProxyObject`
287 named_service = kwargs.pop('named_service', None)
288 if named_service is not None:
289 if bus_name is not None:
290 raise TypeError('bus_name and named_service cannot both '
291 'be specified')
292 from warnings import warn
293 warn('Passing the named_service parameter to get_object by name '
294 'is deprecated: please use positional parameters',
295 DeprecationWarning, stacklevel=2)
296 bus_name = named_service
297 if kwargs:
298 raise TypeError('get_object does not take these keyword '
299 'arguments: %s' % ', '.join(kwargs.iterkeys()))
301 return self.ProxyObjectClass(self, bus_name, object_path,
302 introspect=introspect)
304 def add_signal_receiver(self, handler_function,
305 signal_name=None,
306 dbus_interface=None,
307 bus_name=None,
308 path=None,
309 **keywords):
310 """Arrange for the given function to be called when a signal matching
311 the parameters is received.
313 :Parameters:
314 `handler_function` : callable
315 The function to be called. Its positional arguments will
316 be the arguments of the signal. By default it will receive
317 no keyword arguments, but see the description of
318 the optional keyword arguments below.
319 `signal_name` : str
320 The signal name; None (the default) matches all names
321 `dbus_interface` : str
322 The D-Bus interface name with which to qualify the signal;
323 None (the default) matches all interface names
324 `bus_name` : str
325 A bus name for the sender, which will be resolved to a
326 unique name if it is not already; None (the default) matches
327 any sender.
328 `path` : str
329 The object path of the object which must have emitted the
330 signal; None (the default) matches any object path
331 :Keywords:
332 `utf8_strings` : bool
333 If True, the handler function will receive any string
334 arguments as dbus.UTF8String objects (a subclass of str
335 guaranteed to be UTF-8). If False (default) it will receive
336 any string arguments as dbus.String objects (a subclass of
337 unicode).
338 `byte_arrays` : bool
339 If True, the handler function will receive any byte-array
340 arguments as dbus.ByteArray objects (a subclass of str).
341 If False (default) it will receive any byte-array
342 arguments as a dbus.Array of dbus.Byte (subclasses of:
343 a list of ints).
344 `sender_keyword` : str
345 If not None (the default), the handler function will receive
346 the unique name of the sending endpoint as a keyword
347 argument with this name.
348 `destination_keyword` : str
349 If not None (the default), the handler function will receive
350 the bus name of the destination (or None if the signal is a
351 broadcast, as is usual) as a keyword argument with this name.
352 `interface_keyword` : str
353 If not None (the default), the handler function will receive
354 the signal interface as a keyword argument with this name.
355 `member_keyword` : str
356 If not None (the default), the handler function will receive
357 the signal name as a keyword argument with this name.
358 `path_keyword` : str
359 If not None (the default), the handler function will receive
360 the object-path of the sending object as a keyword argument
361 with this name.
362 `message_keyword` : str
363 If not None (the default), the handler function will receive
364 the `dbus.lowlevel.SignalMessage` as a keyword argument with
365 this name.
366 `arg...` : unicode or UTF-8 str
367 If there are additional keyword parameters of the form
368 ``arg``\ *n*, match only signals where the *n*\ th argument
369 is the value given for that keyword parameter. As of this
370 time only string arguments can be matched (in particular,
371 object paths and signatures can't).
372 `named_service` : str
373 A deprecated alias for `bus_name`.
375 self._require_main_loop()
377 named_service = keywords.pop('named_service', None)
378 if named_service is not None:
379 if bus_name is not None:
380 raise TypeError('bus_name and named_service cannot both be '
381 'specified')
382 bus_name = named_service
383 from warnings import warn
384 warn('Passing the named_service parameter to add_signal_receiver '
385 'by name is deprecated: please use positional parameters',
386 DeprecationWarning, stacklevel=2)
388 match = SignalMatch(self, bus_name, path, dbus_interface,
389 signal_name, handler_function, **keywords)
391 self._signals_lock.acquire()
392 try:
393 by_interface = self._signal_recipients_by_object_path.setdefault(
394 path, {})
395 by_member = by_interface.setdefault(dbus_interface, {})
396 matches = by_member.setdefault(signal_name, [])
398 matches.append(match)
399 finally:
400 self._signals_lock.release()
402 return match
404 def _iter_easy_matches(self, path, dbus_interface, member):
405 if path is not None:
406 path_keys = (None, path)
407 else:
408 path_keys = (None,)
409 if dbus_interface is not None:
410 interface_keys = (None, dbus_interface)
411 else:
412 interface_keys = (None,)
413 if member is not None:
414 member_keys = (None, member)
415 else:
416 member_keys = (None,)
418 for path in path_keys:
419 by_interface = self._signal_recipients_by_object_path.get(path,
420 None)
421 if by_interface is None:
422 continue
423 for dbus_interface in interface_keys:
424 by_member = by_interface.get(dbus_interface, None)
425 if by_member is None:
426 continue
427 for member in member_keys:
428 matches = by_member.get(member, None)
429 if matches is None:
430 continue
431 for m in matches:
432 yield m
434 def remove_signal_receiver(self, handler_or_match,
435 signal_name=None,
436 dbus_interface=None,
437 bus_name=None,
438 path=None,
439 **keywords):
440 named_service = keywords.pop('named_service', None)
441 if named_service is not None:
442 if bus_name is not None:
443 raise TypeError('bus_name and named_service cannot both be '
444 'specified')
445 bus_name = named_service
446 from warnings import warn
447 warn('Passing the named_service parameter to '
448 'remove_signal_receiver by name is deprecated: please use '
449 'positional parameters',
450 DeprecationWarning, stacklevel=2)
452 new = []
453 deletions = []
454 self._signals_lock.acquire()
455 try:
456 by_interface = self._signal_recipients_by_object_path.get(path,
457 None)
458 if by_interface is None:
459 return
460 by_member = by_interface.get(dbus_interface, None)
461 if by_member is None:
462 return
463 matches = by_member.get(signal_name, None)
464 if matches is None:
465 return
467 for match in matches:
468 if (handler_or_match is match
469 or match.matches_removal_spec(bus_name,
470 path,
471 dbus_interface,
472 signal_name,
473 handler_or_match,
474 **keywords)):
475 deletions.append(match)
476 else:
477 new.append(match)
478 by_member[signal_name] = new
479 finally:
480 self._signals_lock.release()
482 for match in deletions:
483 self._clean_up_signal_match(match)
485 def _clean_up_signal_match(self, match):
486 # Now called without the signals lock held (it was held in <= 0.81.0)
487 pass
489 def _signal_func(self, message):
490 """D-Bus filter function. Handle signals by dispatching to Python
491 callbacks kept in the match-rule tree.
494 if not isinstance(message, SignalMessage):
495 return HANDLER_RESULT_NOT_YET_HANDLED
497 dbus_interface = message.get_interface()
498 path = message.get_path()
499 signal_name = message.get_member()
501 for match in self._iter_easy_matches(path, dbus_interface,
502 signal_name):
503 match.maybe_handle_message(message)
504 return HANDLER_RESULT_NOT_YET_HANDLED
506 def call_async(self, bus_name, object_path, dbus_interface, method,
507 signature, args, reply_handler, error_handler,
508 timeout=-1.0, utf8_strings=False, byte_arrays=False,
509 require_main_loop=True):
510 """Call the given method, asynchronously.
512 If the reply_handler is None, successful replies will be ignored.
513 If the error_handler is None, failures will be ignored. If both
514 are None, the implementation may request that no reply is sent.
516 :Returns: The dbus.lowlevel.PendingCall.
518 if object_path == LOCAL_PATH:
519 raise DBusException('Methods may not be called on the reserved '
520 'path %s' % LOCAL_PATH)
521 if dbus_interface == LOCAL_IFACE:
522 raise DBusException('Methods may not be called on the reserved '
523 'interface %s' % LOCAL_IFACE)
524 # no need to validate other args - MethodCallMessage ctor will do
526 get_args_opts = {'utf8_strings': utf8_strings,
527 'byte_arrays': byte_arrays}
529 message = MethodCallMessage(destination=bus_name,
530 path=object_path,
531 interface=dbus_interface,
532 method=method)
533 # Add the arguments to the function
534 try:
535 message.append(signature=signature, *args)
536 except Exception, e:
537 logging.basicConfig()
538 _logger.error('Unable to set arguments %r according to '
539 'signature %r: %s: %s',
540 args, signature, e.__class__, e)
541 raise
543 if reply_handler is None and error_handler is None:
544 # we don't care what happens, so just send it
545 self.send_message(message)
546 return
548 if reply_handler is None:
549 reply_handler = _noop
550 if error_handler is None:
551 error_handler = _noop
553 def msg_reply_handler(message):
554 if isinstance(message, MethodReturnMessage):
555 reply_handler(*message.get_args_list(**get_args_opts))
556 elif isinstance(message, ErrorMessage):
557 error_handler(DBusException(name=message.get_error_name(),
558 *message.get_args_list()))
559 else:
560 error_handler(TypeError('Unexpected type for reply '
561 'message: %r' % message))
562 return self.send_message_with_reply(message, msg_reply_handler,
563 timeout/1000.0,
564 require_main_loop=require_main_loop)
566 def call_blocking(self, bus_name, object_path, dbus_interface, method,
567 signature, args, timeout=-1.0, utf8_strings=False,
568 byte_arrays=False):
569 """Call the given method, synchronously.
571 if object_path == LOCAL_PATH:
572 raise DBusException('Methods may not be called on the reserved '
573 'path %s' % LOCAL_PATH)
574 if dbus_interface == LOCAL_IFACE:
575 raise DBusException('Methods may not be called on the reserved '
576 'interface %s' % LOCAL_IFACE)
577 # no need to validate other args - MethodCallMessage ctor will do
579 get_args_opts = {'utf8_strings': utf8_strings,
580 'byte_arrays': byte_arrays}
582 message = MethodCallMessage(destination=bus_name,
583 path=object_path,
584 interface=dbus_interface,
585 method=method)
586 # Add the arguments to the function
587 try:
588 message.append(signature=signature, *args)
589 except Exception, e:
590 logging.basicConfig()
591 _logger.error('Unable to set arguments %r according to '
592 'signature %r: %s: %s',
593 args, signature, e.__class__, e)
594 raise
596 # make a blocking call
597 reply_message = self.send_message_with_reply_and_block(
598 message, timeout)
599 args_list = reply_message.get_args_list(**get_args_opts)
600 if len(args_list) == 0:
601 return None
602 elif len(args_list) == 1:
603 return args_list[0]
604 else:
605 return tuple(args_list)