test/test-p2p.py: Added. Test "peer-to-peer" connections.
[dbus-python-phuang.git] / dbus / connection.py
blob4dc4ead1d18856993440d83e2e6da41b2ea0bd2f
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
77 # if the connection is actually a bus, it's responsible for changing
78 # this
79 self.sender_unique = sender
81 self._utf8_strings = utf8_strings
82 self._byte_arrays = byte_arrays
83 self._sender_keyword = sender_keyword
84 self._path_keyword = path_keyword
85 self._member_keyword = member_keyword
86 self._interface_keyword = interface_keyword
87 self._message_keyword = message_keyword
88 self._destination_keyword = destination_keyword
90 self._args_match = kwargs
91 if not kwargs:
92 self._int_args_match = None
93 else:
94 self._int_args_match = {}
95 for kwarg in kwargs:
96 if not kwarg.startswith('arg'):
97 raise TypeError('SignalMatch: unknown keyword argument %s'
98 % kwarg)
99 try:
100 index = int(kwarg[3:])
101 except ValueError:
102 raise TypeError('SignalMatch: unknown keyword argument %s'
103 % kwarg)
104 if index < 0 or index > 63:
105 raise TypeError('SignalMatch: arg match index must be in '
106 'range(64), not %d' % index)
107 self._int_args_match[index] = kwargs[kwarg]
109 # we're probably going to have to calculate the match rule for
110 # the Bus's benefit, so this constructor might as well do the work
111 rule = ["type='signal'"]
112 if self._sender is not None:
113 rule.append("sender='%s'" % self._sender)
114 if self._path is not None:
115 rule.append("path='%s'" % self._path)
116 if self._interface is not None:
117 rule.append("interface='%s'" % self._interface)
118 if self._member is not None:
119 rule.append("member='%s'" % self._member)
120 for kwarg, value in kwargs.iteritems():
121 rule.append("%s='%s'" % (kwarg, value))
123 self._rule = ','.join(rule)
125 sender = property(lambda self: self._sender)
127 def __str__(self):
128 return self._rule
130 def __repr__(self):
131 return ('<%s at %x "%s" on conn %r>'
132 % (self.__class__, id(self), self._rule, self._conn_weakref()))
134 def matches_removal_spec(self, sender, object_path,
135 dbus_interface, member, handler, **kwargs):
136 if handler not in (None, self._handler):
137 return False
138 if sender != self._sender:
139 return False
140 if object_path != self._path:
141 return False
142 if dbus_interface != self._interface:
143 return False
144 if member != self._member:
145 return False
146 if kwargs != self._args_match:
147 return False
148 return True
150 def maybe_handle_message(self, message):
151 args = None
153 # these haven't been checked yet by the match tree
154 if self.sender_unique not in (None, message.get_sender()):
155 return False
156 if self._int_args_match is not None:
157 # extracting args with utf8_strings and byte_arrays is less work
158 args = message.get_args_list(utf8_strings=True, byte_arrays=True)
159 for index, value in self._int_args_match.iteritems():
160 if (index >= len(args)
161 or not isinstance(args[index], UTF8String)
162 or args[index] != value):
163 return False
165 # these have likely already been checked by the match tree
166 if self._member not in (None, message.get_member()):
167 return False
168 if self._interface not in (None, message.get_interface()):
169 return False
170 if self._path not in (None, message.get_path()):
171 return False
173 try:
174 # minor optimization: if we already extracted the args with the
175 # right calling convention to do the args match, don't bother
176 # doing so again
177 if args is None or not self._utf8_strings or not self._byte_arrays:
178 args = message.get_args_list(utf8_strings=self._utf8_strings,
179 byte_arrays=self._byte_arrays)
180 kwargs = {}
181 if self._sender_keyword is not None:
182 kwargs[self._sender_keyword] = message.get_sender()
183 if self._destination_keyword is not None:
184 kwargs[self._destination_keyword] = message.get_destination()
185 if self._path_keyword is not None:
186 kwargs[self._path_keyword] = message.get_path()
187 if self._member_keyword is not None:
188 kwargs[self._member_keyword] = message.get_member()
189 if self._interface_keyword is not None:
190 kwargs[self._interface_keyword] = message.get_interface()
191 if self._message_keyword is not None:
192 kwargs[self._message_keyword] = message
193 self._handler(*args, **kwargs)
194 except:
195 # basicConfig is a no-op if logging is already configured
196 logging.basicConfig()
197 _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
199 return True
201 def remove(self):
202 conn = self._conn_weakref()
203 # do nothing if the connection has already vanished
204 if conn is not None:
205 conn.remove_signal_receiver(self, self._member,
206 self._interface, self._sender,
207 self._path,
208 **self._args_match)
211 class Connection(_Connection):
212 """A connection to another application. In this base class there is
213 assumed to be no bus daemon.
216 ProxyObjectClass = ProxyObject
218 def __init__(self, *args, **kwargs):
219 super(Connection, self).__init__(*args, **kwargs)
221 # this if-block is needed because shared bus connections can be
222 # __init__'ed more than once
223 if not hasattr(self, '_dbus_Connection_initialized'):
224 self._dbus_Connection_initialized = 1
226 self._signal_recipients_by_object_path = {}
227 """Map from object path to dict mapping dbus_interface to dict
228 mapping member to list of SignalMatch objects."""
230 self._signals_lock = thread.allocate_lock()
231 """Lock used to protect signal data structures if doing two
232 removals at the same time (everything else is atomic, thanks to
233 the GIL)"""
235 self.add_message_filter(self.__class__._signal_func)
237 def activate_name_owner(self, bus_name):
238 """Return the unique name for the given bus name, activating it
239 if necessary and possible.
241 If the name is already unique or this connection is not to a
242 bus daemon, just return it.
244 :Returns: a bus name. If the given `bus_name` exists, the returned
245 name identifies its current owner; otherwise the returned name
246 does not exist.
247 :Raises DBusException: if the implementation has failed
248 to activate the given bus name.
250 return bus_name
252 def get_object(self, named_service, object_path, introspect=True):
253 """Return a local proxy for the given remote object.
255 Method calls on the proxy are translated into method calls on the
256 remote object.
258 :Parameters:
259 `named_service` : str
260 A bus name (either the unique name or a well-known name)
261 of the application owning the object
262 `object_path` : str
263 The object path of the desired object
264 `introspect` : bool
265 If true (default), attempt to introspect the remote
266 object to find out supported methods and their signatures
268 :Returns: a `dbus.proxies.ProxyObject`
270 return self.ProxyObjectClass(self, named_service, object_path,
271 introspect=introspect)
273 def add_signal_receiver(self, handler_function,
274 signal_name=None,
275 dbus_interface=None,
276 named_service=None,
277 path=None,
278 **keywords):
279 """Arrange for the given function to be called when a signal matching
280 the parameters is received.
282 :Parameters:
283 `handler_function` : callable
284 The function to be called. Its positional arguments will
285 be the arguments of the signal. By default it will receive
286 no keyword arguments, but see the description of
287 the optional keyword arguments below.
288 `signal_name` : str
289 The signal name; None (the default) matches all names
290 `dbus_interface` : str
291 The D-Bus interface name with which to qualify the signal;
292 None (the default) matches all interface names
293 `named_service` : str
294 A bus name for the sender, which will be resolved to a
295 unique name if it is not already; None (the default) matches
296 any sender
297 `path` : str
298 The object path of the object which must have emitted the
299 signal; None (the default) matches any object path
300 :Keywords:
301 `utf8_strings` : bool
302 If True, the handler function will receive any string
303 arguments as dbus.UTF8String objects (a subclass of str
304 guaranteed to be UTF-8). If False (default) it will receive
305 any string arguments as dbus.String objects (a subclass of
306 unicode).
307 `byte_arrays` : bool
308 If True, the handler function will receive any byte-array
309 arguments as dbus.ByteArray objects (a subclass of str).
310 If False (default) it will receive any byte-array
311 arguments as a dbus.Array of dbus.Byte (subclasses of:
312 a list of ints).
313 `sender_keyword` : str
314 If not None (the default), the handler function will receive
315 the unique name of the sending endpoint as a keyword
316 argument with this name.
317 `destination_keyword` : str
318 If not None (the default), the handler function will receive
319 the bus name of the destination (or None if the signal is a
320 broadcast, as is usual) as a keyword argument with this name.
321 `interface_keyword` : str
322 If not None (the default), the handler function will receive
323 the signal interface as a keyword argument with this name.
324 `member_keyword` : str
325 If not None (the default), the handler function will receive
326 the signal name as a keyword argument with this name.
327 `path_keyword` : str
328 If not None (the default), the handler function will receive
329 the object-path of the sending object as a keyword argument
330 with this name.
331 `message_keyword` : str
332 If not None (the default), the handler function will receive
333 the `dbus.lowlevel.SignalMessage` as a keyword argument with
334 this name.
335 `arg...` : unicode or UTF-8 str
336 If there are additional keyword parameters of the form
337 ``arg``\ *n*, match only signals where the *n*\ th argument
338 is the value given for that keyword parameter. As of this
339 time only string arguments can be matched (in particular,
340 object paths and signatures can't).
342 self._require_main_loop()
344 match = SignalMatch(self, named_service, path, dbus_interface,
345 signal_name, handler_function, **keywords)
346 by_interface = self._signal_recipients_by_object_path.setdefault(path,
348 by_member = by_interface.setdefault(dbus_interface, {})
349 matches = by_member.setdefault(signal_name, [])
351 # make sure nobody is currently manipulating the list
352 self._signals_lock.acquire()
353 try:
354 matches.append(match)
355 finally:
356 self._signals_lock.release()
357 return match
359 def _iter_easy_matches(self, path, dbus_interface, member):
360 if path is not None:
361 path_keys = (None, path)
362 else:
363 path_keys = (None,)
364 if dbus_interface is not None:
365 interface_keys = (None, dbus_interface)
366 else:
367 interface_keys = (None,)
368 if member is not None:
369 member_keys = (None, member)
370 else:
371 member_keys = (None,)
373 for path in path_keys:
374 by_interface = self._signal_recipients_by_object_path.get(path,
375 None)
376 if by_interface is None:
377 continue
378 for dbus_interface in interface_keys:
379 by_member = by_interface.get(dbus_interface, None)
380 if by_member is None:
381 continue
382 for member in member_keys:
383 matches = by_member.get(member, None)
384 if matches is None:
385 continue
386 for m in matches:
387 yield m
389 def remove_signal_receiver(self, handler_or_match,
390 signal_name=None,
391 dbus_interface=None,
392 named_service=None,
393 path=None,
394 **keywords):
395 by_interface = self._signal_recipients_by_object_path.get(path, None)
396 if by_interface is None:
397 return
398 by_member = by_interface.get(dbus_interface, None)
399 if by_member is None:
400 return
401 matches = by_member.get(signal_name, None)
402 if matches is None:
403 return
404 self._signals_lock.acquire()
405 try:
406 new = []
407 for match in matches:
408 if (handler_or_match is match
409 or match.matches_removal_spec(named_service,
410 path,
411 dbus_interface,
412 signal_name,
413 handler_or_match,
414 **keywords)):
415 self._clean_up_signal_match(match)
416 else:
417 new.append(match)
418 by_member[signal_name] = new
419 finally:
420 self._signals_lock.release()
422 def _clean_up_signal_match(self, match):
423 # Called with the signals lock held
424 pass
426 def _signal_func(self, message):
427 """D-Bus filter function. Handle signals by dispatching to Python
428 callbacks kept in the match-rule tree.
431 if not isinstance(message, SignalMessage):
432 return HANDLER_RESULT_NOT_YET_HANDLED
434 dbus_interface = message.get_interface()
435 path = message.get_path()
436 signal_name = message.get_member()
438 for match in self._iter_easy_matches(path, dbus_interface,
439 signal_name):
440 match.maybe_handle_message(message)
441 return HANDLER_RESULT_NOT_YET_HANDLED
443 def call_async(self, bus_name, object_path, dbus_interface, method,
444 signature, args, reply_handler, error_handler,
445 timeout=-1.0, utf8_strings=False, byte_arrays=False,
446 require_main_loop=True):
447 """Call the given method, asynchronously.
449 If the reply_handler is None, successful replies will be ignored.
450 If the error_handler is None, failures will be ignored. If both
451 are None, the implementation may request that no reply is sent.
453 :Returns: The dbus.lowlevel.PendingCall.
455 if object_path == LOCAL_PATH:
456 raise DBusException('Methods may not be called on the reserved '
457 'path %s' % LOCAL_PATH)
458 if dbus_interface == LOCAL_IFACE:
459 raise DBusException('Methods may not be called on the reserved '
460 'interface %s' % LOCAL_IFACE)
461 # no need to validate other args - MethodCallMessage ctor will do
463 get_args_opts = {'utf8_strings': utf8_strings,
464 'byte_arrays': byte_arrays}
466 message = MethodCallMessage(destination=bus_name,
467 path=object_path,
468 interface=dbus_interface,
469 method=method)
470 # Add the arguments to the function
471 try:
472 message.append(signature=signature, *args)
473 except Exception, e:
474 logging.basicConfig()
475 _logger.error('Unable to set arguments %r according to '
476 'signature %r: %s: %s',
477 args, signature, e.__class__, e)
478 raise
480 if reply_handler is None and error_handler is None:
481 # we don't care what happens, so just send it
482 self.send_message(message)
483 return
485 if reply_handler is None:
486 reply_handler = _noop
487 if error_handler is None:
488 error_handler = _noop
490 def msg_reply_handler(message):
491 if isinstance(message, MethodReturnMessage):
492 reply_handler(*message.get_args_list(**get_args_opts))
493 elif isinstance(message, ErrorMessage):
494 args = message.get_args_list()
495 # FIXME: should we do something with the rest?
496 if len(args) > 0:
497 error_handler(DBusException(args[0]))
498 else:
499 error_handler(DBusException())
500 else:
501 error_handler(TypeError('Unexpected type for reply '
502 'message: %r' % message))
503 return self.send_message_with_reply(message, msg_reply_handler,
504 timeout/1000.0,
505 require_main_loop=require_main_loop)
507 def call_blocking(self, bus_name, object_path, dbus_interface, method,
508 signature, args, timeout=-1.0, utf8_strings=False,
509 byte_arrays=False):
510 """Call the given method, synchronously.
512 if object_path == LOCAL_PATH:
513 raise DBusException('Methods may not be called on the reserved '
514 'path %s' % LOCAL_PATH)
515 if dbus_interface == LOCAL_IFACE:
516 raise DBusException('Methods may not be called on the reserved '
517 'interface %s' % LOCAL_IFACE)
518 # no need to validate other args - MethodCallMessage ctor will do
520 get_args_opts = {'utf8_strings': utf8_strings,
521 'byte_arrays': byte_arrays}
523 message = MethodCallMessage(destination=bus_name,
524 path=object_path,
525 interface=dbus_interface,
526 method=method)
527 # Add the arguments to the function
528 try:
529 message.append(signature=signature, *args)
530 except Exception, e:
531 logging.basicConfig()
532 _logger.error('Unable to set arguments %r according to '
533 'signature %r: %s: %s',
534 args, signature, e.__class__, e)
535 raise
537 # make a blocking call
538 reply_message = self.send_message_with_reply_and_block(
539 message, timeout)
540 args_list = reply_message.get_args_list(**get_args_opts)
541 if len(args_list) == 0:
542 return None
543 elif len(args_list) == 1:
544 return args_list[0]
545 else:
546 return tuple(args_list)