If send_with_reply() returns TRUE but with pending call NULL, cope gracefully.
[dbus-python-phuang.git] / dbus / proxies.py
blob46382a5f59fecee7cdd07d1a889bef4f5c347b21
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')
40 BUS_DAEMON_NAME = 'org.freedesktop.DBus'
41 BUS_DAEMON_PATH = '/org/freedesktop/DBus'
42 BUS_DAEMON_IFACE = BUS_DAEMON_NAME
45 class _ReplyHandler(object):
46 __slots__ = ('_on_error', '_on_reply', '_get_args_options')
47 def __init__(self, on_reply, on_error, **get_args_options):
48 self._on_error = on_error
49 self._on_reply = on_reply
50 self._get_args_options = get_args_options
52 def __call__(self, message):
53 if isinstance(message, _dbus_bindings.MethodReturnMessage):
54 self._on_reply(*message.get_args_list(**self._get_args_options))
55 elif isinstance(message, _dbus_bindings.ErrorMessage):
56 args = message.get_args_list()
57 if len(args) > 0:
58 self._on_error(DBusException(args[0]))
59 else:
60 self._on_error(DBusException())
61 else:
62 self._on_error(DBusException('Unexpected reply message type: %s'
63 % message))
66 class _DeferredMethod:
67 """A proxy method which will only get called once we have its
68 introspection reply.
69 """
70 def __init__(self, proxy_method, append, block):
71 self._proxy_method = proxy_method
72 # the test suite relies on the existence of this property
73 self._method_name = proxy_method._method_name
74 self._append = append
75 self._block = block
77 def __call__(self, *args, **keywords):
78 if keywords.has_key('reply_handler'):
79 # defer the async call til introspection finishes
80 self._append(self._proxy_method, args, keywords)
81 return None
82 else:
83 # we're being synchronous, so block
84 self._block()
85 return self._proxy_method(*args, **keywords)
88 class _ProxyMethod:
89 """A proxy method.
91 Typically a member of a ProxyObject. Calls to the
92 method produce messages that travel over the Bus and are routed
93 to a specific named Service.
94 """
95 def __init__(self, proxy, connection, named_service, object_path, method_name, iface):
96 self._proxy = proxy
97 self._connection = connection
98 self._named_service = named_service
99 self._object_path = object_path
100 # the test suite relies on the existence of this property
101 self._method_name = method_name
102 self._dbus_interface = iface
104 def __call__(self, *args, **keywords):
105 timeout = -1
106 if keywords.has_key('timeout'):
107 timeout = keywords['timeout']
109 reply_handler = None
110 if keywords.has_key('reply_handler'):
111 reply_handler = keywords['reply_handler']
113 error_handler = None
114 if keywords.has_key('error_handler'):
115 error_handler = keywords['error_handler']
117 ignore_reply = False
118 if keywords.has_key('ignore_reply'):
119 ignore_reply = keywords['ignore_reply']
121 get_args_options = {}
122 if keywords.has_key('utf8_strings'):
123 get_args_options['utf8_strings'] = keywords['utf8_strings']
124 if keywords.has_key('byte_arrays'):
125 get_args_options['byte_arrays'] = keywords['byte_arrays']
127 if not(reply_handler and error_handler):
128 if reply_handler:
129 raise MissingErrorHandlerException()
130 elif error_handler:
131 raise MissingReplyHandlerException()
133 dbus_interface = self._dbus_interface
134 if keywords.has_key('dbus_interface'):
135 dbus_interface = keywords['dbus_interface']
137 tmp_iface = ''
138 if dbus_interface:
139 tmp_iface = dbus_interface + '.'
141 key = tmp_iface + self._method_name
143 introspect_sig = None
144 if self._proxy._introspect_method_map.has_key (key):
145 introspect_sig = self._proxy._introspect_method_map[key]
147 message = _dbus_bindings.MethodCallMessage(destination=None,
148 path=self._object_path,
149 interface=dbus_interface,
150 method=self._method_name)
151 message.set_destination(self._named_service)
153 # Add the arguments to the function
154 try:
155 message.append(signature=introspect_sig, *args)
156 except Exception, e:
157 _logger.error('Unable to set arguments %r according to '
158 'introspected signature %r: %s: %s',
159 args, introspect_sig, e.__class__, e)
160 raise
162 if ignore_reply:
163 self._connection.send_message(message)
164 return None
165 elif reply_handler:
166 self._connection.send_message_with_reply(message, _ReplyHandler(reply_handler, error_handler, **get_args_options), timeout/1000.0, require_main_loop=1)
167 return None
168 else:
169 reply_message = self._connection.send_message_with_reply_and_block(message, timeout)
170 args_list = reply_message.get_args_list(**get_args_options)
171 if len(args_list) == 0:
172 return None
173 elif len(args_list) == 1:
174 return args_list[0]
175 else:
176 return tuple(args_list)
179 class ProxyObject:
180 """A proxy to the remote Object.
182 A ProxyObject is provided by the Bus. ProxyObjects
183 have member functions, and can be called like normal Python objects.
185 ProxyMethodClass = _ProxyMethod
186 DeferredMethodClass = _DeferredMethod
188 INTROSPECT_STATE_DONT_INTROSPECT = 0
189 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
190 INTROSPECT_STATE_INTROSPECT_DONE = 2
192 def __init__(self, bus, named_service, object_path, introspect=True,
193 follow_name_owner_changes=False):
194 """Initialize the proxy object.
196 :Parameters:
197 `bus` : `dbus.Bus`
198 The bus on which to find this object
199 `named_service` : str
200 A bus name for the endpoint owning the object (need not
201 actually be a service name)
202 `object_path` : str
203 The object path at which the endpoint exports the object
204 `introspect` : bool
205 If true (default), attempt to introspect the remote
206 object to find out supported methods and their signatures
207 `follow_name_owner_changes` : bool
208 If true (default is false) and the `named_service` is a
209 well-known name, follow ownership changes for that name
211 if follow_name_owner_changes:
212 bus._require_main_loop() # we don't get the signals otherwise
214 self._bus = bus
215 self._named_service = named_service
216 self.__dbus_object_path__ = object_path
218 if (named_service[:1] != ':' and named_service != BUS_DAEMON_NAME
219 and not follow_name_owner_changes):
220 bus_object = bus.get_object(BUS_DAEMON_NAME, BUS_DAEMON_PATH)
221 try:
222 self._named_service = bus_object.GetNameOwner(named_service,
223 dbus_interface=BUS_DAEMON_IFACE)
224 except DBusException, e:
225 # FIXME: detect whether it's NameHasNoOwner, but properly
226 #if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
227 # raise
228 # it might not exist: try to start it
229 bus_object.StartServiceByName(named_service,
230 _dbus_bindings.UInt32(0))
231 self._named_service = bus_object.GetNameOwner(named_service,
232 dbus_interface=BUS_DAEMON_IFACE)
234 #PendingCall object for Introspect call
235 self._pending_introspect = None
236 #queue of async calls waiting on the Introspect to return
237 self._pending_introspect_queue = []
238 #dictionary mapping method names to their input signatures
239 self._introspect_method_map = {}
241 # must be a recursive lock because block() is called while locked,
242 # and calls the callback which re-takes the lock
243 self._introspect_lock = RLock()
245 if not introspect:
246 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
247 else:
248 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
250 self._pending_introspect = self._Introspect()
252 def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
253 """Arrange for the given function to be called when the given signal
254 is received.
256 :Parameters:
257 `signal_name` : str
258 The name of the signal
259 `handler_function` : callable
260 A function to be called when the signal is emitted by
261 the remote object. Its positional arguments will be the
262 arguments of the signal; optionally, it may be given
263 keyword arguments as described below.
264 `dbus_interface` : str
265 Optional interface with which to qualify the signal name.
266 If None (the default) the handler will be called whenever a
267 signal of the given member name is received, whatever
268 its interface.
269 :Keywords:
270 `utf8_strings` : bool
271 If True, the handler function will receive any string
272 arguments as dbus.UTF8String objects (a subclass of str
273 guaranteed to be UTF-8). If False (default) it will receive
274 any string arguments as dbus.String objects (a subclass of
275 unicode).
276 `byte_arrays` : bool
277 If True, the handler function will receive any byte-array
278 arguments as dbus.ByteArray objects (a subclass of str).
279 If False (default) it will receive any byte-array
280 arguments as a dbus.Array of dbus.Byte (subclasses of:
281 a list of ints).
282 `sender_keyword` : str
283 If not None (the default), the handler function will receive
284 the unique name of the sending endpoint as a keyword
285 argument with this name
286 `destination_keyword` : str
287 If not None (the default), the handler function will receive
288 the bus name of the destination (or None if the signal is a
289 broadcast, as is usual) as a keyword argument with this name.
290 `interface_keyword` : str
291 If not None (the default), the handler function will receive
292 the signal interface as a keyword argument with this name.
293 `member_keyword` : str
294 If not None (the default), the handler function will receive
295 the signal name as a keyword argument with this name.
296 `path_keyword` : str
297 If not None (the default), the handler function will receive
298 the object-path of the sending object as a keyword argument
299 with this name
300 `message_keyword` : str
301 If not None (the default), the handler function will receive
302 the `dbus.lowlevel.SignalMessage` as a keyword argument with
303 this name.
304 `arg...` : unicode or UTF-8 str
305 If there are additional keyword parameters of the form
306 ``arg``\ *n*, match only signals where the *n*\ th argument
307 is the value given for that keyword parameter. As of this time
308 only string arguments can be matched (in particular,
309 object paths and signatures can't).
311 return \
312 self._bus.add_signal_receiver(handler_function,
313 signal_name=signal_name,
314 dbus_interface=dbus_interface,
315 named_service=self._named_service,
316 path=self.__dbus_object_path__,
317 **keywords)
319 def _Introspect(self):
320 message = _dbus_bindings.MethodCallMessage(None, self.__dbus_object_path__, 'org.freedesktop.DBus.Introspectable', 'Introspect')
321 message.set_destination(self._named_service)
323 result = self._bus.get_connection().send_message_with_reply(message, _ReplyHandler(self._introspect_reply_handler, self._introspect_error_handler, utf8_strings=True), -1)
324 return result
326 def _introspect_execute_queue(self):
327 # FIXME: potential to flood the bus
328 # We should make sure mainloops all have idle handlers
329 # and do one message per idle
330 for (proxy_method, args, keywords) in self._pending_introspect_queue:
331 proxy_method(*args, **keywords)
333 def _introspect_reply_handler(self, data):
334 self._introspect_lock.acquire()
335 try:
336 try:
337 self._introspect_method_map = process_introspection_data(data)
338 except IntrospectionParserException, e:
339 self._introspect_error_handler(e)
340 return
342 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
343 self._pending_introspect = None
344 self._introspect_execute_queue()
345 finally:
346 self._introspect_lock.release()
348 def _introspect_error_handler(self, error):
349 self._introspect_lock.acquire()
350 try:
351 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
352 self._pending_introspect = None
353 self._introspect_execute_queue()
354 sys.stderr.write("Introspect error: " + str(error) + "\n")
355 finally:
356 self._introspect_lock.release()
358 def _introspect_block(self):
359 self._introspect_lock.acquire()
360 try:
361 if self._pending_introspect is not None:
362 self._pending_introspect.block()
363 # else someone still has a _DeferredMethod from before we
364 # finished introspection: no need to do anything special any more
365 finally:
366 self._introspect_lock.release()
368 def _introspect_add_to_queue(self, callback, args, kwargs):
369 self._introspect_lock.acquire()
370 try:
371 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
372 self._pending_introspect_queue.append((callback, args, kwargs))
373 else:
374 # someone still has a _DeferredMethod from before we
375 # finished introspection
376 callback(*args, **kwargs)
377 finally:
378 self._introspect_lock.release()
380 def __getattr__(self, member, dbus_interface=None):
381 if member == '__call__':
382 return object.__call__
383 elif member.startswith('__') and member.endswith('__'):
384 raise AttributeError(member)
385 else:
386 return self.get_dbus_method(member, dbus_interface)
388 def get_dbus_method(self, member, dbus_interface=None):
389 """Return a proxy method representing the given D-Bus method. The
390 returned proxy method can be called in the usual way. For instance, ::
392 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
394 is equivalent to::
396 proxy.Foo(123, dbus_interface='com.example.Bar')
398 or even::
400 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
402 However, using `get_dbus_method` is the only way to call D-Bus
403 methods with certain awkward names - if the author of a service
404 implements a method called ``connect_to_signal`` or even
405 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
407 For services which follow the D-Bus convention of CamelCaseMethodNames
408 this won't be a problem.
411 ret = self.ProxyMethodClass(self, self._bus.get_connection(),
412 self._named_service,
413 self.__dbus_object_path__, member,
414 dbus_interface)
416 # this can be done without taking the lock - the worst that can
417 # happen is that we accidentally return a _DeferredMethod just after
418 # finishing introspection, in which case _introspect_add_to_queue and
419 # _introspect_block will do the right thing anyway
420 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
421 ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue,
422 self._introspect_block)
424 return ret
426 def __repr__(self):
427 return '<ProxyObject wrapping %s %s %s at %#x>'%(
428 self._bus, self._named_service, self.__dbus_object_path__, id(self))
429 __str__ = __repr__