dbus.connection: Release signals lock before calling _clean_up_signal_match().
[dbus-python-phuang.git] / dbus / proxies.py
blob440a5d53ddad5832acfeeaabb312b5e48f1b08e7
1 # Copyright (C) 2003, 2004, 2005, 2006, 2007 Red Hat Inc. <http://www.redhat.com/>
2 # Copyright (C) 2003 David Zeuthen
3 # Copyright (C) 2004 Rob Taylor
4 # Copyright (C) 2005, 2006, 2007 Collabora Ltd. <http://www.collabora.co.uk/>
6 # Licensed under the Academic Free License version 2.1
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 import sys
23 import logging
25 try:
26 from threading import RLock
27 except ImportError:
28 from dummy_threading import RLock
30 import _dbus_bindings
31 from dbus._expat_introspect_parser import process_introspection_data
32 from dbus.exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException, DBusException
34 __docformat__ = 'restructuredtext'
37 _logger = logging.getLogger('dbus.proxies')
39 from _dbus_bindings import LOCAL_PATH, \
40 BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
41 INTROSPECTABLE_IFACE
44 class _DeferredMethod:
45 """A proxy method which will only get called once we have its
46 introspection reply.
47 """
48 def __init__(self, proxy_method, append, block):
49 self._proxy_method = proxy_method
50 # the test suite relies on the existence of this property
51 self._method_name = proxy_method._method_name
52 self._append = append
53 self._block = block
55 def __call__(self, *args, **keywords):
56 if keywords.has_key('reply_handler'):
57 # defer the async call til introspection finishes
58 self._append(self._proxy_method, args, keywords)
59 return None
60 else:
61 # we're being synchronous, so block
62 self._block()
63 return self._proxy_method(*args, **keywords)
65 def call_async(self, *args, **keywords):
66 self._append(self._proxy_method, args, keywords)
69 class _ProxyMethod:
70 """A proxy method.
72 Typically a member of a ProxyObject. Calls to the
73 method produce messages that travel over the Bus and are routed
74 to a specific named Service.
75 """
76 def __init__(self, proxy, connection, bus_name, object_path, method_name,
77 iface):
78 if object_path == LOCAL_PATH:
79 raise DBusException('Methods may not be called on the reserved '
80 'path %s' % LOCAL_PATH)
82 # trust that the proxy, and the properties it had, are OK
83 self._proxy = proxy
84 self._connection = connection
85 self._named_service = bus_name
86 self._object_path = object_path
87 # fail early if the method name is bad
88 _dbus_bindings.validate_member_name(method_name)
89 # the test suite relies on the existence of this property
90 self._method_name = method_name
91 # fail early if the interface name is bad
92 if iface is not None:
93 _dbus_bindings.validate_interface_name(iface)
94 self._dbus_interface = iface
96 def __call__(self, *args, **keywords):
97 reply_handler = keywords.pop('reply_handler', None)
98 error_handler = keywords.pop('error_handler', None)
99 ignore_reply = keywords.pop('ignore_reply', False)
101 if reply_handler is not None or error_handler is not None:
102 if reply_handler is None:
103 raise MissingErrorHandlerException()
104 elif error_handler is None:
105 raise MissingReplyHandlerException()
106 elif ignore_reply:
107 raise TypeError('ignore_reply and reply_handler cannot be '
108 'used together')
110 dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
112 if dbus_interface is None:
113 key = self._method_name
114 else:
115 key = dbus_interface + '.' + self._method_name
116 introspect_sig = self._proxy._introspect_method_map.get(key, None)
118 if ignore_reply or reply_handler is not None:
119 self._connection.call_async(self._named_service,
120 self._object_path,
121 dbus_interface,
122 self._method_name,
123 introspect_sig,
124 args,
125 reply_handler,
126 error_handler,
127 **keywords)
128 else:
129 return self._connection.call_blocking(self._named_service,
130 self._object_path,
131 dbus_interface,
132 self._method_name,
133 introspect_sig,
134 args,
135 **keywords)
137 def call_async(self, *args, **keywords):
138 reply_handler = keywords.pop('reply_handler', None)
139 error_handler = keywords.pop('error_handler', None)
141 dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
143 if dbus_interface:
144 key = dbus_interface + '.' + self._method_name
145 else:
146 key = self._method_name
147 introspect_sig = self._proxy._introspect_method_map.get(key, None)
149 self._connection.call_async(self._named_service,
150 self._object_path,
151 dbus_interface,
152 self._method_name,
153 introspect_sig,
154 args,
155 reply_handler,
156 error_handler,
157 **keywords)
160 class ProxyObject(object):
161 """A proxy to the remote Object.
163 A ProxyObject is provided by the Bus. ProxyObjects
164 have member functions, and can be called like normal Python objects.
166 ProxyMethodClass = _ProxyMethod
167 DeferredMethodClass = _DeferredMethod
169 INTROSPECT_STATE_DONT_INTROSPECT = 0
170 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
171 INTROSPECT_STATE_INTROSPECT_DONE = 2
173 def __init__(self, conn=None, bus_name=None, object_path=None,
174 introspect=True, follow_name_owner_changes=False, **kwargs):
175 """Initialize the proxy object.
177 :Parameters:
178 `conn` : `dbus.connection.Connection`
179 The bus or connection on which to find this object.
180 The keyword argument `bus` is a deprecated alias for this.
181 `bus_name` : str
182 A bus name for the application owning the object, to be used
183 as the destination for method calls and the sender for
184 signal matches. The keyword argument `named_service` is a
185 deprecated alias for this.
186 `object_path` : str
187 The object path at which the application exports the object
188 `introspect` : bool
189 If true (default), attempt to introspect the remote
190 object to find out supported methods and their signatures
191 `follow_name_owner_changes` : bool
192 If true (default is false) and the `bus_name` is a
193 well-known name, follow ownership changes for that name
195 bus = kwargs.pop('bus', None)
196 if bus is not None:
197 if conn is not None:
198 raise TypeError('conn and bus cannot both be specified')
199 conn = bus
200 from warnings import warn
201 warn('Passing the bus parameter to ProxyObject by name is '
202 'deprecated: please use positional parameters',
203 DeprecationWarning, stacklevel=2)
204 named_service = kwargs.pop('named_service', None)
205 if named_service is not None:
206 if bus_name is not None:
207 raise TypeError('bus_name and named_service cannot both be '
208 'specified')
209 bus_name = named_service
210 from warnings import warn
211 warn('Passing the named_service parameter to ProxyObject by name '
212 'is deprecated: please use positional parameters',
213 DeprecationWarning, stacklevel=2)
214 if kwargs:
215 raise TypeError('ProxyObject.__init__ does not take these '
216 'keyword arguments: %s'
217 % ', '.join(kwargs.iterkeys()))
219 if follow_name_owner_changes:
220 # we don't get the signals unless the Bus has a main loop
221 # XXX: using Bus internals
222 conn._require_main_loop()
224 self._bus = conn
226 if bus_name is not None:
227 _dbus_bindings.validate_bus_name(bus_name)
228 # the attribute is still called _named_service for the moment,
229 # for the benefit of telepathy-python
230 self._named_service = self._requested_bus_name = bus_name
232 _dbus_bindings.validate_object_path(object_path)
233 self.__dbus_object_path__ = object_path
235 if not follow_name_owner_changes:
236 self._named_service = conn.activate_name_owner(bus_name)
238 #PendingCall object for Introspect call
239 self._pending_introspect = None
240 #queue of async calls waiting on the Introspect to return
241 self._pending_introspect_queue = []
242 #dictionary mapping method names to their input signatures
243 self._introspect_method_map = {}
245 # must be a recursive lock because block() is called while locked,
246 # and calls the callback which re-takes the lock
247 self._introspect_lock = RLock()
249 if not introspect or self.__dbus_object_path__ == LOCAL_PATH:
250 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
251 else:
252 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
254 self._pending_introspect = self._Introspect()
256 bus_name = property(lambda self: self._named_service, None, None,
257 """The bus name to which this proxy is bound. (Read-only,
258 may change.)
260 If the proxy was instantiated using a unique name, this property
261 is that unique name.
263 If the proxy was instantiated with a well-known name and with
264 `follow_name_owner_changes` set false (the default), this
265 property is the unique name of the connection that owned that
266 well-known name when the proxy was instantiated, which might
267 not actually own the requested well-known name any more.
269 If the proxy was instantiated with a well-known name and with
270 `follow_name_owner_changes` set true, this property is that
271 well-known name.
272 """)
274 requested_bus_name = property(lambda self: self._requested_bus_name,
275 None, None,
276 """The bus name which was requested when this proxy was
277 instantiated.
278 """)
280 object_path = property(lambda self: self.__dbus_object_path__,
281 None, None,
282 """The object-path of this proxy.""")
284 # XXX: We don't currently support this because it's the signal receiver
285 # that's responsible for tracking name owner changes, but it
286 # seems a natural thing to add in future.
287 #unique_bus_name = property(lambda self: something, None, None,
288 # """The unique name of the connection to which this proxy is
289 # currently bound. (Read-only, may change.)
290 # """)
292 def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
293 """Arrange for the given function to be called when the given signal
294 is received.
296 :Parameters:
297 `signal_name` : str
298 The name of the signal
299 `handler_function` : callable
300 A function to be called when the signal is emitted by
301 the remote object. Its positional arguments will be the
302 arguments of the signal; optionally, it may be given
303 keyword arguments as described below.
304 `dbus_interface` : str
305 Optional interface with which to qualify the signal name.
306 If None (the default) the handler will be called whenever a
307 signal of the given member name is received, whatever
308 its interface.
309 :Keywords:
310 `utf8_strings` : bool
311 If True, the handler function will receive any string
312 arguments as dbus.UTF8String objects (a subclass of str
313 guaranteed to be UTF-8). If False (default) it will receive
314 any string arguments as dbus.String objects (a subclass of
315 unicode).
316 `byte_arrays` : bool
317 If True, the handler function will receive any byte-array
318 arguments as dbus.ByteArray objects (a subclass of str).
319 If False (default) it will receive any byte-array
320 arguments as a dbus.Array of dbus.Byte (subclasses of:
321 a list of ints).
322 `sender_keyword` : str
323 If not None (the default), the handler function will receive
324 the unique name of the sending endpoint as a keyword
325 argument with this name
326 `destination_keyword` : str
327 If not None (the default), the handler function will receive
328 the bus name of the destination (or None if the signal is a
329 broadcast, as is usual) as a keyword argument with this name.
330 `interface_keyword` : str
331 If not None (the default), the handler function will receive
332 the signal interface as a keyword argument with this name.
333 `member_keyword` : str
334 If not None (the default), the handler function will receive
335 the signal name as a keyword argument with this name.
336 `path_keyword` : str
337 If not None (the default), the handler function will receive
338 the object-path of the sending object as a keyword argument
339 with this name
340 `message_keyword` : str
341 If not None (the default), the handler function will receive
342 the `dbus.lowlevel.SignalMessage` as a keyword argument with
343 this name.
344 `arg...` : unicode or UTF-8 str
345 If there are additional keyword parameters of the form
346 ``arg``\ *n*, match only signals where the *n*\ th argument
347 is the value given for that keyword parameter. As of this time
348 only string arguments can be matched (in particular,
349 object paths and signatures can't).
351 return \
352 self._bus.add_signal_receiver(handler_function,
353 signal_name=signal_name,
354 dbus_interface=dbus_interface,
355 bus_name=self._named_service,
356 path=self.__dbus_object_path__,
357 **keywords)
359 def _Introspect(self):
360 return self._bus.call_async(self._named_service,
361 self.__dbus_object_path__,
362 INTROSPECTABLE_IFACE, 'Introspect', '', (),
363 self._introspect_reply_handler,
364 self._introspect_error_handler,
365 utf8_strings=True,
366 require_main_loop=False)
368 def _introspect_execute_queue(self):
369 # FIXME: potential to flood the bus
370 # We should make sure mainloops all have idle handlers
371 # and do one message per idle
372 for (proxy_method, args, keywords) in self._pending_introspect_queue:
373 proxy_method(*args, **keywords)
375 def _introspect_reply_handler(self, data):
376 self._introspect_lock.acquire()
377 try:
378 try:
379 self._introspect_method_map = process_introspection_data(data)
380 except IntrospectionParserException, e:
381 self._introspect_error_handler(e)
382 return
384 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
385 self._pending_introspect = None
386 self._introspect_execute_queue()
387 finally:
388 self._introspect_lock.release()
390 def _introspect_error_handler(self, error):
391 logging.basicConfig()
392 _logger.error("Introspect error on %s:%s: %s.%s: %s",
393 self._named_service, self.__dbus_object_path__,
394 error.__class__.__module__, error.__class__.__name__,
395 error)
396 self._introspect_lock.acquire()
397 try:
398 _logger.debug('Executing introspect queue due to error')
399 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
400 self._pending_introspect = None
401 self._introspect_execute_queue()
402 finally:
403 self._introspect_lock.release()
405 def _introspect_block(self):
406 self._introspect_lock.acquire()
407 try:
408 if self._pending_introspect is not None:
409 self._pending_introspect.block()
410 # else someone still has a _DeferredMethod from before we
411 # finished introspection: no need to do anything special any more
412 finally:
413 self._introspect_lock.release()
415 def _introspect_add_to_queue(self, callback, args, kwargs):
416 self._introspect_lock.acquire()
417 try:
418 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
419 self._pending_introspect_queue.append((callback, args, kwargs))
420 else:
421 # someone still has a _DeferredMethod from before we
422 # finished introspection
423 callback(*args, **kwargs)
424 finally:
425 self._introspect_lock.release()
427 def __getattr__(self, member):
428 if member.startswith('__') and member.endswith('__'):
429 raise AttributeError(member)
430 else:
431 return self.get_dbus_method(member)
433 def get_dbus_method(self, member, dbus_interface=None):
434 """Return a proxy method representing the given D-Bus method. The
435 returned proxy method can be called in the usual way. For instance, ::
437 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
439 is equivalent to::
441 proxy.Foo(123, dbus_interface='com.example.Bar')
443 or even::
445 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
447 However, using `get_dbus_method` is the only way to call D-Bus
448 methods with certain awkward names - if the author of a service
449 implements a method called ``connect_to_signal`` or even
450 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
452 For services which follow the D-Bus convention of CamelCaseMethodNames
453 this won't be a problem.
456 ret = self.ProxyMethodClass(self, self._bus,
457 self._named_service,
458 self.__dbus_object_path__, member,
459 dbus_interface)
461 # this can be done without taking the lock - the worst that can
462 # happen is that we accidentally return a _DeferredMethod just after
463 # finishing introspection, in which case _introspect_add_to_queue and
464 # _introspect_block will do the right thing anyway
465 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
466 ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue,
467 self._introspect_block)
469 return ret
471 def __repr__(self):
472 return '<ProxyObject wrapping %s %s %s at %#x>'%(
473 self._bus, self._named_service, self.__dbus_object_path__, id(self))
474 __str__ = __repr__
477 class Interface(object):
478 """An interface into a remote object.
480 An Interface can be used to wrap ProxyObjects
481 so that calls can be routed to their correct
482 D-Bus interface.
485 def __init__(self, object, dbus_interface):
486 """Construct a proxy for the given interface on the given object.
488 :Parameters:
489 `object` : `dbus.proxies.ProxyObject` or `dbus.Interface`
490 The remote object or another of its interfaces
491 `dbus_interface` : str
492 An interface the `object` implements
494 if isinstance(object, Interface):
495 self._obj = object.proxy_object
496 else:
497 self._obj = object
498 self._dbus_interface = dbus_interface
500 object_path = property (lambda self: self._obj.object_path, None, None,
501 "The D-Bus object path of the underlying object")
502 __dbus_object_path__ = object_path
503 bus_name = property (lambda self: self._obj.bus_name, None, None,
504 "The bus name to which the underlying proxy object "
505 "is bound")
506 requested_bus_name = property (lambda self: self._obj.requested_bus_name,
507 None, None,
508 "The bus name which was requested when the "
509 "underlying object was created")
510 proxy_object = property (lambda self: self._obj, None, None,
511 """The underlying proxy object""")
512 dbus_interface = property (lambda self: self._dbus_interface, None, None,
513 """The D-Bus interface represented""")
515 def connect_to_signal(self, signal_name, handler_function,
516 dbus_interface=None, **keywords):
517 """Arrange for a function to be called when the given signal is
518 emitted.
520 The parameters and keyword arguments are the same as for
521 `dbus.proxies.ProxyObject.connect_to_signal`, except that if
522 `dbus_interface` is None (the default), the D-Bus interface that
523 was passed to the `Interface` constructor is used.
525 if not dbus_interface:
526 dbus_interface = self._dbus_interface
528 return self._obj.connect_to_signal(signal_name, handler_function,
529 dbus_interface, **keywords)
531 def __getattr__(self, member):
532 if member.startswith('__') and member.endswith('__'):
533 raise AttributeError(member)
534 else:
535 return self._obj.get_dbus_method(member, self._dbus_interface)
537 def get_dbus_method(self, member, dbus_interface=None):
538 """Return a proxy method representing the given D-Bus method.
540 This is the same as `dbus.proxies.ProxyObject.get_dbus_method`
541 except that if `dbus_interface` is None (the default),
542 the D-Bus interface that was passed to the `Interface` constructor
543 is used.
545 if dbus_interface is None:
546 dbus_interface = self._dbus_interface
547 return self._obj.get_dbus_method(member, dbus_interface)
549 def __repr__(self):
550 return '<Interface %r implementing %r at %#x>'%(
551 self._obj, self._dbus_interface, id(self))
552 __str__ = __repr__