Move the client method-call machinery from dbus.proxies to dbus.connection._MethodCal...
[dbus-python-phuang.git] / dbus / proxies.py
blob027d99d738b445ccb40393105425a09231a3708f
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
42 INTROSPECTABLE_IFACE = 'org.freedesktop.DBus.Introspectable'
45 class _DeferredMethod:
46 """A proxy method which will only get called once we have its
47 introspection reply.
48 """
49 def __init__(self, proxy_method, append, block):
50 self._proxy_method = proxy_method
51 # the test suite relies on the existence of this property
52 self._method_name = proxy_method._method_name
53 self._append = append
54 self._block = block
56 def __call__(self, *args, **keywords):
57 if keywords.has_key('reply_handler'):
58 # defer the async call til introspection finishes
59 self._append(self._proxy_method, args, keywords)
60 return None
61 else:
62 # we're being synchronous, so block
63 self._block()
64 return self._proxy_method(*args, **keywords)
66 def call_async(self, *args, **keywords):
67 self._append(self._proxy_method, args, keywords)
70 class _ProxyMethod:
71 """A proxy method.
73 Typically a member of a ProxyObject. Calls to the
74 method produce messages that travel over the Bus and are routed
75 to a specific named Service.
76 """
77 def __init__(self, proxy, connection, named_service, object_path, method_name, 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 = named_service
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, bus, named_service, object_path, introspect=True,
174 follow_name_owner_changes=False):
175 """Initialize the proxy object.
177 :Parameters:
178 `bus` : `dbus.Bus`
179 The bus on which to find this object
180 `named_service` : str
181 A bus name for the endpoint owning the object (need not
182 actually be a service name)
183 `object_path` : str
184 The object path at which the endpoint exports the object
185 `introspect` : bool
186 If true (default), attempt to introspect the remote
187 object to find out supported methods and their signatures
188 `follow_name_owner_changes` : bool
189 If true (default is false) and the `named_service` is a
190 well-known name, follow ownership changes for that name
192 if follow_name_owner_changes:
193 # we don't get the signals unless the Bus has a main loop
194 # XXX: using Bus internals
195 bus._require_main_loop()
197 self._bus = bus
199 if named_service is not None:
200 _dbus_bindings.validate_bus_name(named_service)
201 self._named_service = self._requested_bus_name = named_service
203 _dbus_bindings.validate_object_path(object_path)
204 self.__dbus_object_path__ = object_path
206 # XXX: assumes it's a bus daemon
207 if (named_service is not None and named_service[:1] != ':'
208 and named_service != BUS_DAEMON_NAME
209 and not follow_name_owner_changes):
210 bus_object = bus.get_object(BUS_DAEMON_NAME, BUS_DAEMON_PATH)
211 try:
212 self._named_service = bus_object.GetNameOwner(named_service,
213 dbus_interface=BUS_DAEMON_IFACE)
214 except DBusException, e:
215 # FIXME: detect whether it's NameHasNoOwner, but properly
216 #if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
217 # raise
218 # it might not exist: try to start it
219 bus_object.StartServiceByName(named_service,
220 _dbus_bindings.UInt32(0))
221 self._named_service = bus_object.GetNameOwner(named_service,
222 dbus_interface=BUS_DAEMON_IFACE)
224 #PendingCall object for Introspect call
225 self._pending_introspect = None
226 #queue of async calls waiting on the Introspect to return
227 self._pending_introspect_queue = []
228 #dictionary mapping method names to their input signatures
229 self._introspect_method_map = {}
231 # must be a recursive lock because block() is called while locked,
232 # and calls the callback which re-takes the lock
233 self._introspect_lock = RLock()
235 if not introspect or self.__dbus_object_path__ == LOCAL_PATH:
236 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
237 else:
238 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
240 self._pending_introspect = self._Introspect()
242 bus_name = property(lambda self: self._named_service, None, None,
243 """The bus name to which this proxy is bound. (Read-only,
244 may change.)
246 If the proxy was instantiated using a unique name, this property
247 is that unique name.
249 If the proxy was instantiated with a well-known name and with
250 `follow_name_owner_changes` set false (the default), this
251 property is the unique name of the connection that owned that
252 well-known name when the proxy was instantiated, which might
253 not actually own the requested well-known name any more.
255 If the proxy was instantiated with a well-known name and with
256 `follow_name_owner_changes` set true, this property is that
257 well-known name.
258 """)
260 requested_bus_name = property(lambda self: self._requested_bus_name,
261 None, None,
262 """The bus name which was requested when this proxy was
263 instantiated.
264 """)
266 object_path = property(lambda self: self.__dbus_object_path__,
267 None, None,
268 """The object-path of this proxy.""")
270 # XXX: We don't currently support this because it's the signal receiver
271 # that's responsible for tracking name owner changes, but it
272 # seems a natural thing to add in future.
273 #unique_bus_name = property(lambda self: something, None, None,
274 # """The unique name of the connection to which this proxy is
275 # currently bound. (Read-only, may change.)
276 # """)
278 def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
279 """Arrange for the given function to be called when the given signal
280 is received.
282 :Parameters:
283 `signal_name` : str
284 The name of the signal
285 `handler_function` : callable
286 A function to be called when the signal is emitted by
287 the remote object. Its positional arguments will be the
288 arguments of the signal; optionally, it may be given
289 keyword arguments as described below.
290 `dbus_interface` : str
291 Optional interface with which to qualify the signal name.
292 If None (the default) the handler will be called whenever a
293 signal of the given member name is received, whatever
294 its interface.
295 :Keywords:
296 `utf8_strings` : bool
297 If True, the handler function will receive any string
298 arguments as dbus.UTF8String objects (a subclass of str
299 guaranteed to be UTF-8). If False (default) it will receive
300 any string arguments as dbus.String objects (a subclass of
301 unicode).
302 `byte_arrays` : bool
303 If True, the handler function will receive any byte-array
304 arguments as dbus.ByteArray objects (a subclass of str).
305 If False (default) it will receive any byte-array
306 arguments as a dbus.Array of dbus.Byte (subclasses of:
307 a list of ints).
308 `sender_keyword` : str
309 If not None (the default), the handler function will receive
310 the unique name of the sending endpoint as a keyword
311 argument with this name
312 `destination_keyword` : str
313 If not None (the default), the handler function will receive
314 the bus name of the destination (or None if the signal is a
315 broadcast, as is usual) as a keyword argument with this name.
316 `interface_keyword` : str
317 If not None (the default), the handler function will receive
318 the signal interface as a keyword argument with this name.
319 `member_keyword` : str
320 If not None (the default), the handler function will receive
321 the signal name as a keyword argument with this name.
322 `path_keyword` : str
323 If not None (the default), the handler function will receive
324 the object-path of the sending object as a keyword argument
325 with this name
326 `message_keyword` : str
327 If not None (the default), the handler function will receive
328 the `dbus.lowlevel.SignalMessage` as a keyword argument with
329 this name.
330 `arg...` : unicode or UTF-8 str
331 If there are additional keyword parameters of the form
332 ``arg``\ *n*, match only signals where the *n*\ th argument
333 is the value given for that keyword parameter. As of this time
334 only string arguments can be matched (in particular,
335 object paths and signatures can't).
337 return \
338 self._bus.add_signal_receiver(handler_function,
339 signal_name=signal_name,
340 dbus_interface=dbus_interface,
341 named_service=self._named_service,
342 path=self.__dbus_object_path__,
343 **keywords)
345 def _Introspect(self):
346 return self._bus.call_async(self._named_service,
347 self.__dbus_object_path__,
348 INTROSPECTABLE_IFACE, 'Introspect', '', (),
349 self._introspect_reply_handler,
350 self._introspect_error_handler,
351 utf8_strings=True,
352 require_main_loop=False)
354 def _introspect_execute_queue(self):
355 # FIXME: potential to flood the bus
356 # We should make sure mainloops all have idle handlers
357 # and do one message per idle
358 for (proxy_method, args, keywords) in self._pending_introspect_queue:
359 proxy_method(*args, **keywords)
361 def _introspect_reply_handler(self, data):
362 self._introspect_lock.acquire()
363 try:
364 try:
365 self._introspect_method_map = process_introspection_data(data)
366 except IntrospectionParserException, e:
367 self._introspect_error_handler(e)
368 return
370 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
371 self._pending_introspect = None
372 self._introspect_execute_queue()
373 finally:
374 self._introspect_lock.release()
376 def _introspect_error_handler(self, error):
377 self._introspect_lock.acquire()
378 try:
379 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
380 self._pending_introspect = None
381 self._introspect_execute_queue()
382 sys.stderr.write("Introspect error: " + str(error) + "\n")
383 finally:
384 self._introspect_lock.release()
386 def _introspect_block(self):
387 self._introspect_lock.acquire()
388 try:
389 if self._pending_introspect is not None:
390 self._pending_introspect.block()
391 # else someone still has a _DeferredMethod from before we
392 # finished introspection: no need to do anything special any more
393 finally:
394 self._introspect_lock.release()
396 def _introspect_add_to_queue(self, callback, args, kwargs):
397 self._introspect_lock.acquire()
398 try:
399 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
400 self._pending_introspect_queue.append((callback, args, kwargs))
401 else:
402 # someone still has a _DeferredMethod from before we
403 # finished introspection
404 callback(*args, **kwargs)
405 finally:
406 self._introspect_lock.release()
408 def __getattr__(self, member):
409 if member.startswith('__') and member.endswith('__'):
410 raise AttributeError(member)
411 else:
412 return self.get_dbus_method(member)
414 def get_dbus_method(self, member, dbus_interface=None):
415 """Return a proxy method representing the given D-Bus method. The
416 returned proxy method can be called in the usual way. For instance, ::
418 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
420 is equivalent to::
422 proxy.Foo(123, dbus_interface='com.example.Bar')
424 or even::
426 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
428 However, using `get_dbus_method` is the only way to call D-Bus
429 methods with certain awkward names - if the author of a service
430 implements a method called ``connect_to_signal`` or even
431 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
433 For services which follow the D-Bus convention of CamelCaseMethodNames
434 this won't be a problem.
437 ret = self.ProxyMethodClass(self, self._bus.get_connection(),
438 self._named_service,
439 self.__dbus_object_path__, member,
440 dbus_interface)
442 # this can be done without taking the lock - the worst that can
443 # happen is that we accidentally return a _DeferredMethod just after
444 # finishing introspection, in which case _introspect_add_to_queue and
445 # _introspect_block will do the right thing anyway
446 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
447 ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue,
448 self._introspect_block)
450 return ret
452 def __repr__(self):
453 return '<ProxyObject wrapping %s %s %s at %#x>'%(
454 self._bus, self._named_service, self.__dbus_object_path__, id(self))
455 __str__ = __repr__
458 class Interface(object):
459 """An interface into a remote object.
461 An Interface can be used to wrap ProxyObjects
462 so that calls can be routed to their correct
463 D-Bus interface.
466 def __init__(self, object, dbus_interface):
467 """Construct a proxy for the given interface on the given object.
469 :Parameters:
470 `object` : `dbus.proxies.ProxyObject` or `dbus.Interface`
471 The remote object or another of its interfaces
472 `dbus_interface` : str
473 An interface the `object` implements
475 if isinstance(object, Interface):
476 self._obj = object.proxy_object
477 else:
478 self._obj = object
479 self._dbus_interface = dbus_interface
481 object_path = property (lambda self: self._obj.object_path, None, None,
482 "The D-Bus object path of the underlying object")
483 __dbus_object_path__ = object_path
484 bus_name = property (lambda self: self._obj.bus_name, None, None,
485 "The bus name to which the underlying proxy object "
486 "is bound")
487 requested_bus_name = property (lambda self: self._obj.requested_bus_name,
488 None, None,
489 "The bus name which was requested when the "
490 "underlying object was created")
491 proxy_object = property (lambda self: self._obj, None, None,
492 """The underlying proxy object""")
493 dbus_interface = property (lambda self: self._dbus_interface, None, None,
494 """The D-Bus interface represented""")
496 def connect_to_signal(self, signal_name, handler_function,
497 dbus_interface=None, **keywords):
498 """Arrange for a function to be called when the given signal is
499 emitted.
501 The parameters and keyword arguments are the same as for
502 `dbus.proxies.ProxyObject.connect_to_signal`, except that if
503 `dbus_interface` is None (the default), the D-Bus interface that
504 was passed to the `Interface` constructor is used.
506 if not dbus_interface:
507 dbus_interface = self._dbus_interface
509 return self._obj.connect_to_signal(signal_name, handler_function,
510 dbus_interface, **keywords)
512 def __getattr__(self, member):
513 if member.startswith('__') and member.endswith('__'):
514 raise AttributeError(member)
515 else:
516 return self._obj.get_dbus_method(member, self._dbus_interface)
518 def get_dbus_method(self, member, dbus_interface=None):
519 """Return a proxy method representing the given D-Bus method.
521 This is the same as `dbus.proxies.ProxyObject.get_dbus_method`
522 except that if `dbus_interface` is None (the default),
523 the D-Bus interface that was passed to the `Interface` constructor
524 is used.
526 if dbus_interface is None:
527 dbus_interface = self._dbus_interface
528 return self._obj.get_dbus_method(member, dbus_interface)
530 def __repr__(self):
531 return '<Interface %r implementing %r at %#x>'%(
532 self._obj, self._dbus_interface, id(self))
533 __str__ = __repr__