Initial import package dbus-python: D-Bus Python Bindings
[profile/ivi/dbus-python.git] / dbus / connection.py
1 # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
2 #
3 # Permission is hereby granted, free of charge, to any person
4 # obtaining a copy of this software and associated documentation
5 # files (the "Software"), to deal in the Software without
6 # restriction, including without limitation the rights to use, copy,
7 # modify, merge, publish, distribute, sublicense, and/or sell copies
8 # of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice shall be
12 # included in all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 # DEALINGS IN THE SOFTWARE.
22
23 __all__ = ('Connection', 'SignalMatch')
24 __docformat__ = 'reStructuredText'
25
26 import logging
27 try:
28     import thread
29 except ImportError:
30     import dummy_thread as thread
31 import weakref
32
33 from _dbus_bindings import Connection as _Connection, \
34                            LOCAL_PATH, LOCAL_IFACE, \
35                            validate_interface_name, validate_member_name,\
36                            validate_bus_name, validate_object_path,\
37                            validate_error_name, \
38                            UTF8String
39 from dbus.exceptions import DBusException
40 from dbus.lowlevel import ErrorMessage, MethodCallMessage, SignalMessage, \
41                           MethodReturnMessage, HANDLER_RESULT_NOT_YET_HANDLED
42 from dbus.proxies import ProxyObject
43
44
45 _logger = logging.getLogger('dbus.connection')
46
47
48 def _noop(*args, **kwargs):
49     pass
50
51
52 class SignalMatch(object):
53     __slots__ = ('_sender_name_owner', '_member', '_interface', '_sender',
54                  '_path', '_handler', '_args_match', '_rule',
55                  '_utf8_strings', '_byte_arrays', '_conn_weakref',
56                  '_destination_keyword', '_interface_keyword',
57                  '_message_keyword', '_member_keyword',
58                  '_sender_keyword', '_path_keyword', '_int_args_match')
59
60     def __init__(self, conn, sender, object_path, dbus_interface,
61                  member, handler, utf8_strings=False, byte_arrays=False,
62                  sender_keyword=None, path_keyword=None,
63                  interface_keyword=None, member_keyword=None,
64                  message_keyword=None, destination_keyword=None,
65                  **kwargs):
66         if member is not None:
67             validate_member_name(member)
68         if dbus_interface is not None:
69             validate_interface_name(dbus_interface)
70         if sender is not None:
71             validate_bus_name(sender)
72         if object_path is not None:
73             validate_object_path(object_path)
74
75         self._rule = None
76         self._conn_weakref = weakref.ref(conn)
77         self._sender = sender
78         self._interface = dbus_interface
79         self._member = member
80         self._path = object_path
81         self._handler = handler
82
83         # if the connection is actually a bus, it's responsible for changing
84         # this later
85         self._sender_name_owner = sender
86
87         self._utf8_strings = utf8_strings
88         self._byte_arrays = byte_arrays
89         self._sender_keyword = sender_keyword
90         self._path_keyword = path_keyword
91         self._member_keyword = member_keyword
92         self._interface_keyword = interface_keyword
93         self._message_keyword = message_keyword
94         self._destination_keyword = destination_keyword
95
96         self._args_match = kwargs
97         if not kwargs:
98             self._int_args_match = None
99         else:
100             self._int_args_match = {}
101             for kwarg in kwargs:
102                 if not kwarg.startswith('arg'):
103                     raise TypeError('SignalMatch: unknown keyword argument %s'
104                                     % kwarg)
105                 try:
106                     index = int(kwarg[3:])
107                 except ValueError:
108                     raise TypeError('SignalMatch: unknown keyword argument %s'
109                                     % kwarg)
110                 if index < 0 or index > 63:
111                     raise TypeError('SignalMatch: arg match index must be in '
112                                     'range(64), not %d' % index)
113                 self._int_args_match[index] = kwargs[kwarg]
114
115     def __hash__(self):
116         """SignalMatch objects are compared by identity."""
117         return hash(id(self))
118
119     def __eq__(self, other):
120         """SignalMatch objects are compared by identity."""
121         return self is other
122
123     def __ne__(self, other):
124         """SignalMatch objects are compared by identity."""
125         return self is not other
126
127     sender = property(lambda self: self._sender)
128
129     def __str__(self):
130         if self._rule is None:
131             rule = ["type='signal'"]
132             if self._sender is not None:
133                 rule.append("sender='%s'" % self._sender)
134             if self._path is not None:
135                 rule.append("path='%s'" % self._path)
136             if self._interface is not None:
137                 rule.append("interface='%s'" % self._interface)
138             if self._member is not None:
139                 rule.append("member='%s'" % self._member)
140             if self._int_args_match is not None:
141                 for index, value in self._int_args_match.iteritems():
142                     rule.append("arg%d='%s'" % (index, value))
143
144             self._rule = ','.join(rule)
145
146         return self._rule
147
148     def __repr__(self):
149         return ('<%s at %x "%s" on conn %r>'
150                 % (self.__class__, id(self), self._rule, self._conn_weakref()))
151
152     def set_sender_name_owner(self, new_name):
153         self._sender_name_owner = new_name
154
155     def matches_removal_spec(self, sender, object_path,
156                              dbus_interface, member, handler, **kwargs):
157         if handler not in (None, self._handler):
158             return False
159         if sender != self._sender:
160             return False
161         if object_path != self._path:
162             return False
163         if dbus_interface != self._interface:
164             return False
165         if member != self._member:
166             return False
167         if kwargs != self._args_match:
168             return False
169         return True
170
171     def maybe_handle_message(self, message):
172         args = None
173
174         # these haven't been checked yet by the match tree
175         if self._sender_name_owner not in (None, message.get_sender()):
176             return False
177         if self._int_args_match is not None:
178             # extracting args with utf8_strings and byte_arrays is less work
179             args = message.get_args_list(utf8_strings=True, byte_arrays=True)
180             for index, value in self._int_args_match.iteritems():
181                 if (index >= len(args)
182                     or not isinstance(args[index], UTF8String)
183                     or args[index] != value):
184                     return False
185
186         # these have likely already been checked by the match tree
187         if self._member not in (None, message.get_member()):
188             return False
189         if self._interface not in (None, message.get_interface()):
190             return False
191         if self._path not in (None, message.get_path()):
192             return False
193
194         try:
195             # minor optimization: if we already extracted the args with the
196             # right calling convention to do the args match, don't bother
197             # doing so again
198             if args is None or not self._utf8_strings or not self._byte_arrays:
199                 args = message.get_args_list(utf8_strings=self._utf8_strings,
200                                              byte_arrays=self._byte_arrays)
201             kwargs = {}
202             if self._sender_keyword is not None:
203                 kwargs[self._sender_keyword] = message.get_sender()
204             if self._destination_keyword is not None:
205                 kwargs[self._destination_keyword] = message.get_destination()
206             if self._path_keyword is not None:
207                 kwargs[self._path_keyword] = message.get_path()
208             if self._member_keyword is not None:
209                 kwargs[self._member_keyword] = message.get_member()
210             if self._interface_keyword is not None:
211                 kwargs[self._interface_keyword] = message.get_interface()
212             if self._message_keyword is not None:
213                 kwargs[self._message_keyword] = message
214             self._handler(*args, **kwargs)
215         except:
216             # basicConfig is a no-op if logging is already configured
217             logging.basicConfig()
218             _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
219
220         return True
221
222     def remove(self):
223         conn = self._conn_weakref()
224         # do nothing if the connection has already vanished
225         if conn is not None:
226             conn.remove_signal_receiver(self, self._member,
227                                         self._interface, self._sender,
228                                         self._path,
229                                         **self._args_match)
230
231
232 class Connection(_Connection):
233     """A connection to another application. In this base class there is
234     assumed to be no bus daemon.
235
236     :Since: 0.81.0
237     """
238
239     ProxyObjectClass = ProxyObject
240
241     def __init__(self, *args, **kwargs):
242         super(Connection, self).__init__(*args, **kwargs)
243
244         # this if-block is needed because shared bus connections can be
245         # __init__'ed more than once
246         if not hasattr(self, '_dbus_Connection_initialized'):
247             self._dbus_Connection_initialized = 1
248
249             self.__call_on_disconnection = []
250
251             self._signal_recipients_by_object_path = {}
252             """Map from object path to dict mapping dbus_interface to dict
253             mapping member to list of SignalMatch objects."""
254
255             self._signals_lock = thread.allocate_lock()
256             """Lock used to protect signal data structures"""
257
258             self.add_message_filter(self.__class__._signal_func)
259
260     def activate_name_owner(self, bus_name):
261         """Return the unique name for the given bus name, activating it
262         if necessary and possible.
263
264         If the name is already unique or this connection is not to a
265         bus daemon, just return it.
266
267         :Returns: a bus name. If the given `bus_name` exists, the returned
268             name identifies its current owner; otherwise the returned name
269             does not exist.
270         :Raises DBusException: if the implementation has failed
271             to activate the given bus name.
272         :Since: 0.81.0
273         """
274         return bus_name
275
276     def get_object(self, bus_name=None, object_path=None, introspect=True,
277                    **kwargs):
278         """Return a local proxy for the given remote object.
279
280         Method calls on the proxy are translated into method calls on the
281         remote object.
282
283         :Parameters:
284             `bus_name` : str
285                 A bus name (either the unique name or a well-known name)
286                 of the application owning the object. The keyword argument
287                 named_service is a deprecated alias for this.
288             `object_path` : str
289                 The object path of the desired object
290             `introspect` : bool
291                 If true (default), attempt to introspect the remote
292                 object to find out supported methods and their signatures
293
294         :Returns: a `dbus.proxies.ProxyObject`
295         """
296         named_service = kwargs.pop('named_service', None)
297         if named_service is not None:
298             if bus_name is not None:
299                 raise TypeError('bus_name and named_service cannot both '
300                                 'be specified')
301             from warnings import warn
302             warn('Passing the named_service parameter to get_object by name '
303                  'is deprecated: please use positional parameters',
304                  DeprecationWarning, stacklevel=2)
305             bus_name = named_service
306         if kwargs:
307             raise TypeError('get_object does not take these keyword '
308                             'arguments: %s' % ', '.join(kwargs.iterkeys()))
309
310         return self.ProxyObjectClass(self, bus_name, object_path,
311                                      introspect=introspect)
312
313     def add_signal_receiver(self, handler_function,
314                                   signal_name=None,
315                                   dbus_interface=None,
316                                   bus_name=None,
317                                   path=None,
318                                   **keywords):
319         """Arrange for the given function to be called when a signal matching
320         the parameters is received.
321
322         :Parameters:
323             `handler_function` : callable
324                 The function to be called. Its positional arguments will
325                 be the arguments of the signal. By default it will receive
326                 no keyword arguments, but see the description of
327                 the optional keyword arguments below.
328             `signal_name` : str
329                 The signal name; None (the default) matches all names
330             `dbus_interface` : str
331                 The D-Bus interface name with which to qualify the signal;
332                 None (the default) matches all interface names
333             `bus_name` : str
334                 A bus name for the sender, which will be resolved to a
335                 unique name if it is not already; None (the default) matches
336                 any sender.
337             `path` : str
338                 The object path of the object which must have emitted the
339                 signal; None (the default) matches any object path
340         :Keywords:
341             `utf8_strings` : bool
342                 If True, the handler function will receive any string
343                 arguments as dbus.UTF8String objects (a subclass of str
344                 guaranteed to be UTF-8). If False (default) it will receive
345                 any string arguments as dbus.String objects (a subclass of
346                 unicode).
347             `byte_arrays` : bool
348                 If True, the handler function will receive any byte-array
349                 arguments as dbus.ByteArray objects (a subclass of str).
350                 If False (default) it will receive any byte-array
351                 arguments as a dbus.Array of dbus.Byte (subclasses of:
352                 a list of ints).
353             `sender_keyword` : str
354                 If not None (the default), the handler function will receive
355                 the unique name of the sending endpoint as a keyword
356                 argument with this name.
357             `destination_keyword` : str
358                 If not None (the default), the handler function will receive
359                 the bus name of the destination (or None if the signal is a
360                 broadcast, as is usual) as a keyword argument with this name.
361             `interface_keyword` : str
362                 If not None (the default), the handler function will receive
363                 the signal interface as a keyword argument with this name.
364             `member_keyword` : str
365                 If not None (the default), the handler function will receive
366                 the signal name as a keyword argument with this name.
367             `path_keyword` : str
368                 If not None (the default), the handler function will receive
369                 the object-path of the sending object as a keyword argument
370                 with this name.
371             `message_keyword` : str
372                 If not None (the default), the handler function will receive
373                 the `dbus.lowlevel.SignalMessage` as a keyword argument with
374                 this name.
375             `arg...` : unicode or UTF-8 str
376                 If there are additional keyword parameters of the form
377                 ``arg``\ *n*, match only signals where the *n*\ th argument
378                 is the value given for that keyword parameter. As of this
379                 time only string arguments can be matched (in particular,
380                 object paths and signatures can't).
381             `named_service` : str
382                 A deprecated alias for `bus_name`.
383         """
384         self._require_main_loop()
385
386         named_service = keywords.pop('named_service', None)
387         if named_service is not None:
388             if bus_name is not None:
389                 raise TypeError('bus_name and named_service cannot both be '
390                                 'specified')
391             bus_name = named_service
392             from warnings import warn
393             warn('Passing the named_service parameter to add_signal_receiver '
394                  'by name is deprecated: please use positional parameters',
395                  DeprecationWarning, stacklevel=2)
396
397         match = SignalMatch(self, bus_name, path, dbus_interface,
398                             signal_name, handler_function, **keywords)
399
400         self._signals_lock.acquire()
401         try:
402             by_interface = self._signal_recipients_by_object_path.setdefault(
403                     path, {})
404             by_member = by_interface.setdefault(dbus_interface, {})
405             matches = by_member.setdefault(signal_name, [])
406
407             matches.append(match)
408         finally:
409             self._signals_lock.release()
410
411         return match
412
413     def _iter_easy_matches(self, path, dbus_interface, member):
414         if path is not None:
415             path_keys = (None, path)
416         else:
417             path_keys = (None,)
418         if dbus_interface is not None:
419             interface_keys = (None, dbus_interface)
420         else:
421             interface_keys = (None,)
422         if member is not None:
423             member_keys = (None, member)
424         else:
425             member_keys = (None,)
426
427         for path in path_keys:
428             by_interface = self._signal_recipients_by_object_path.get(path,
429                                                                       None)
430             if by_interface is None:
431                 continue
432             for dbus_interface in interface_keys:
433                 by_member = by_interface.get(dbus_interface, None)
434                 if by_member is None:
435                     continue
436                 for member in member_keys:
437                     matches = by_member.get(member, None)
438                     if matches is None:
439                         continue
440                     for m in matches:
441                         yield m
442
443     def remove_signal_receiver(self, handler_or_match,
444                                signal_name=None,
445                                dbus_interface=None,
446                                bus_name=None,
447                                path=None,
448                                **keywords):
449         named_service = keywords.pop('named_service', None)
450         if named_service is not None:
451             if bus_name is not None:
452                 raise TypeError('bus_name and named_service cannot both be '
453                                 'specified')
454             bus_name = named_service
455             from warnings import warn
456             warn('Passing the named_service parameter to '
457                  'remove_signal_receiver by name is deprecated: please use '
458                  'positional parameters',
459                  DeprecationWarning, stacklevel=2)
460
461         new = []
462         deletions = []
463         self._signals_lock.acquire()
464         try:
465             by_interface = self._signal_recipients_by_object_path.get(path,
466                                                                       None)
467             if by_interface is None:
468                 return
469             by_member = by_interface.get(dbus_interface, None)
470             if by_member is None:
471                 return
472             matches = by_member.get(signal_name, None)
473             if matches is None:
474                 return
475
476             for match in matches:
477                 if (handler_or_match is match
478                     or match.matches_removal_spec(bus_name,
479                                                   path,
480                                                   dbus_interface,
481                                                   signal_name,
482                                                   handler_or_match,
483                                                   **keywords)):
484                     deletions.append(match)
485                 else:
486                     new.append(match)
487
488             if new:
489                 by_member[signal_name] = new
490             else:
491                 del by_member[signal_name]
492                 if not by_member:
493                     del by_interface[dbus_interface]
494                     if not by_interface:
495                         del self._signal_recipients_by_object_path[path]
496         finally:
497             self._signals_lock.release()
498
499         for match in deletions:
500             self._clean_up_signal_match(match)
501
502     def _clean_up_signal_match(self, match):
503         # Now called without the signals lock held (it was held in <= 0.81.0)
504         pass
505
506     def _signal_func(self, message):
507         """D-Bus filter function. Handle signals by dispatching to Python
508         callbacks kept in the match-rule tree.
509         """
510
511         if not isinstance(message, SignalMessage):
512             return HANDLER_RESULT_NOT_YET_HANDLED
513
514         dbus_interface = message.get_interface()
515         path = message.get_path()
516         signal_name = message.get_member()
517
518         for match in self._iter_easy_matches(path, dbus_interface,
519                                              signal_name):
520             match.maybe_handle_message(message)
521
522         if (dbus_interface == LOCAL_IFACE and
523             path == LOCAL_PATH and
524             signal_name == 'Disconnected'):
525             for cb in self.__call_on_disconnection:
526                 try:
527                     cb(self)
528                 except Exception, e:
529                     # basicConfig is a no-op if logging is already configured
530                     logging.basicConfig()
531                     _logger.error('Exception in handler for Disconnected '
532                         'signal:', exc_info=1)
533
534         return HANDLER_RESULT_NOT_YET_HANDLED
535
536     def call_async(self, bus_name, object_path, dbus_interface, method,
537                    signature, args, reply_handler, error_handler,
538                    timeout=-1.0, utf8_strings=False, byte_arrays=False,
539                    require_main_loop=True):
540         """Call the given method, asynchronously.
541
542         If the reply_handler is None, successful replies will be ignored.
543         If the error_handler is None, failures will be ignored. If both
544         are None, the implementation may request that no reply is sent.
545
546         :Returns: The dbus.lowlevel.PendingCall.
547         :Since: 0.81.0
548         """
549         if object_path == LOCAL_PATH:
550             raise DBusException('Methods may not be called on the reserved '
551                                 'path %s' % LOCAL_PATH)
552         if dbus_interface == LOCAL_IFACE:
553             raise DBusException('Methods may not be called on the reserved '
554                                 'interface %s' % LOCAL_IFACE)
555         # no need to validate other args - MethodCallMessage ctor will do
556
557         get_args_opts = {'utf8_strings': utf8_strings,
558                          'byte_arrays': byte_arrays}
559
560         message = MethodCallMessage(destination=bus_name,
561                                     path=object_path,
562                                     interface=dbus_interface,
563                                     method=method)
564         # Add the arguments to the function
565         try:
566             message.append(signature=signature, *args)
567         except Exception, e:
568             logging.basicConfig()
569             _logger.error('Unable to set arguments %r according to '
570                           'signature %r: %s: %s',
571                           args, signature, e.__class__, e)
572             raise
573
574         if reply_handler is None and error_handler is None:
575             # we don't care what happens, so just send it
576             self.send_message(message)
577             return
578
579         if reply_handler is None:
580             reply_handler = _noop
581         if error_handler is None:
582             error_handler = _noop
583
584         def msg_reply_handler(message):
585             if isinstance(message, MethodReturnMessage):
586                 reply_handler(*message.get_args_list(**get_args_opts))
587             elif isinstance(message, ErrorMessage):
588                 error_handler(DBusException(name=message.get_error_name(),
589                                             *message.get_args_list()))
590             else:
591                 error_handler(TypeError('Unexpected type for reply '
592                                         'message: %r' % message))
593         return self.send_message_with_reply(message, msg_reply_handler,
594                                         timeout,
595                                         require_main_loop=require_main_loop)
596
597     def call_blocking(self, bus_name, object_path, dbus_interface, method,
598                       signature, args, timeout=-1.0, utf8_strings=False,
599                       byte_arrays=False):
600         """Call the given method, synchronously.
601         :Since: 0.81.0
602         """
603         if object_path == LOCAL_PATH:
604             raise DBusException('Methods may not be called on the reserved '
605                                 'path %s' % LOCAL_PATH)
606         if dbus_interface == LOCAL_IFACE:
607             raise DBusException('Methods may not be called on the reserved '
608                                 'interface %s' % LOCAL_IFACE)
609         # no need to validate other args - MethodCallMessage ctor will do
610
611         get_args_opts = {'utf8_strings': utf8_strings,
612                          'byte_arrays': byte_arrays}
613
614         message = MethodCallMessage(destination=bus_name,
615                                     path=object_path,
616                                     interface=dbus_interface,
617                                     method=method)
618         # Add the arguments to the function
619         try:
620             message.append(signature=signature, *args)
621         except Exception, e:
622             logging.basicConfig()
623             _logger.error('Unable to set arguments %r according to '
624                           'signature %r: %s: %s',
625                           args, signature, e.__class__, e)
626             raise
627
628         # make a blocking call
629         reply_message = self.send_message_with_reply_and_block(
630             message, timeout)
631         args_list = reply_message.get_args_list(**get_args_opts)
632         if len(args_list) == 0:
633             return None
634         elif len(args_list) == 1:
635             return args_list[0]
636         else:
637             return tuple(args_list)
638
639     def call_on_disconnection(self, callable):
640         """Arrange for `callable` to be called with one argument (this
641         Connection object) when the Connection becomes
642         disconnected.
643
644         :Since: 0.83.0
645         """
646         self.__call_on_disconnection.append(callable)