Update NEWS, README for 0.80.0
[dbus-python-phuang.git] / dbus / proxies.py
blob85ac3c281ea7c374e542264ba9ce578a6b172a34
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 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 import sys
25 import logging
27 try:
28 from threading import RLock
29 except ImportError:
30 from dummy_threading import RLock
32 import _dbus_bindings
33 from dbus._expat_introspect_parser import process_introspection_data
34 from dbus.exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException, DBusException
36 __docformat__ = 'restructuredtext'
39 _logger = logging.getLogger('dbus.proxies')
42 BUS_DAEMON_NAME = 'org.freedesktop.DBus'
43 BUS_DAEMON_PATH = '/org/freedesktop/DBus'
44 BUS_DAEMON_IFACE = BUS_DAEMON_NAME
47 class _ReplyHandler(object):
48 __slots__ = ('_on_error', '_on_reply', '_get_args_options')
49 def __init__(self, on_reply, on_error, **get_args_options):
50 self._on_error = on_error
51 self._on_reply = on_reply
52 self._get_args_options = get_args_options
54 def __call__(self, message):
55 if isinstance(message, _dbus_bindings.MethodReturnMessage):
56 self._on_reply(*message.get_args_list(**self._get_args_options))
57 elif isinstance(message, _dbus_bindings.ErrorMessage):
58 args = message.get_args_list()
59 if len(args) > 0:
60 self._on_error(DBusException(args[0]))
61 else:
62 self._on_error(DBusException())
63 else:
64 self._on_error(DBusException('Unexpected reply message type: %s'
65 % message))
68 class _DeferredMethod:
69 """A proxy method which will only get called once we have its
70 introspection reply.
71 """
72 def __init__(self, proxy_method, append, block):
73 self._proxy_method = proxy_method
74 # the test suite relies on the existence of this property
75 self._method_name = proxy_method._method_name
76 self._append = append
77 self._block = block
79 def __call__(self, *args, **keywords):
80 if keywords.has_key('reply_handler'):
81 # defer the async call til introspection finishes
82 self._append(self._proxy_method, args, keywords)
83 return None
84 else:
85 # we're being synchronous, so block
86 self._block()
87 return self._proxy_method(*args, **keywords)
90 class _ProxyMethod:
91 """A proxy method.
93 Typically a member of a ProxyObject. Calls to the
94 method produce messages that travel over the Bus and are routed
95 to a specific named Service.
96 """
97 def __init__(self, proxy, connection, named_service, object_path, method_name, iface):
98 self._proxy = proxy
99 self._connection = connection
100 self._named_service = named_service
101 self._object_path = object_path
102 # the test suite relies on the existence of this property
103 self._method_name = method_name
104 self._dbus_interface = iface
106 def __call__(self, *args, **keywords):
107 timeout = -1
108 if keywords.has_key('timeout'):
109 timeout = keywords['timeout']
111 reply_handler = None
112 if keywords.has_key('reply_handler'):
113 reply_handler = keywords['reply_handler']
115 error_handler = None
116 if keywords.has_key('error_handler'):
117 error_handler = keywords['error_handler']
119 ignore_reply = False
120 if keywords.has_key('ignore_reply'):
121 ignore_reply = keywords['ignore_reply']
123 get_args_options = {}
124 if keywords.has_key('utf8_strings'):
125 get_args_options['utf8_strings'] = keywords['utf8_strings']
126 if keywords.has_key('byte_arrays'):
127 get_args_options['byte_arrays'] = keywords['byte_arrays']
129 if not(reply_handler and error_handler):
130 if reply_handler:
131 raise MissingErrorHandlerException()
132 elif error_handler:
133 raise MissingReplyHandlerException()
135 dbus_interface = self._dbus_interface
136 if keywords.has_key('dbus_interface'):
137 dbus_interface = keywords['dbus_interface']
139 tmp_iface = ''
140 if dbus_interface:
141 tmp_iface = dbus_interface + '.'
143 key = tmp_iface + self._method_name
145 introspect_sig = None
146 if self._proxy._introspect_method_map.has_key (key):
147 introspect_sig = self._proxy._introspect_method_map[key]
149 message = _dbus_bindings.MethodCallMessage(destination=None,
150 path=self._object_path,
151 interface=dbus_interface,
152 method=self._method_name)
153 message.set_destination(self._named_service)
155 # Add the arguments to the function
156 try:
157 message.append(signature=introspect_sig, *args)
158 except Exception, e:
159 _logger.error('Unable to set arguments %r according to '
160 'introspected signature %r: %s: %s',
161 args, introspect_sig, e.__class__, e)
162 raise
164 if ignore_reply:
165 self._connection.send_message(message)
166 return None
167 elif reply_handler:
168 self._connection.send_message_with_reply(message, _ReplyHandler(reply_handler, error_handler, **get_args_options), timeout/1000.0, require_main_loop=1)
169 return None
170 else:
171 reply_message = self._connection.send_message_with_reply_and_block(message, timeout)
172 args_list = reply_message.get_args_list(**get_args_options)
173 if len(args_list) == 0:
174 return None
175 elif len(args_list) == 1:
176 return args_list[0]
177 else:
178 return tuple(args_list)
181 class ProxyObject:
182 """A proxy to the remote Object.
184 A ProxyObject is provided by the Bus. ProxyObjects
185 have member functions, and can be called like normal Python objects.
187 ProxyMethodClass = _ProxyMethod
188 DeferredMethodClass = _DeferredMethod
190 INTROSPECT_STATE_DONT_INTROSPECT = 0
191 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
192 INTROSPECT_STATE_INTROSPECT_DONE = 2
194 def __init__(self, bus, named_service, object_path, introspect=True,
195 follow_name_owner_changes=False):
196 """Initialize the proxy object.
198 :Parameters:
199 `bus` : `dbus.Bus`
200 The bus on which to find this object
201 `named_service` : str
202 A bus name for the endpoint owning the object (need not
203 actually be a service name)
204 `object_path` : str
205 The object path at which the endpoint exports the object
206 `introspect` : bool
207 If true (default), attempt to introspect the remote
208 object to find out supported methods and their signatures
209 `follow_name_owner_changes` : bool
210 If true (default is false) and the `named_service` is a
211 well-known name, follow ownership changes for that name
213 if follow_name_owner_changes:
214 bus._require_main_loop() # we don't get the signals otherwise
216 self._bus = bus
217 self._named_service = named_service
218 self.__dbus_object_path__ = object_path
220 if (named_service[:1] != ':' and named_service != BUS_DAEMON_NAME
221 and not follow_name_owner_changes):
222 bus_object = bus.get_object(BUS_DAEMON_NAME, BUS_DAEMON_PATH)
223 try:
224 self._named_service = bus_object.GetNameOwner(named_service,
225 dbus_interface=BUS_DAEMON_IFACE)
226 except DBusException, e:
227 # FIXME: detect whether it's NameHasNoOwner, but properly
228 #if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
229 # raise
230 # it might not exist: try to start it
231 bus_object.StartServiceByName(named_service,
232 _dbus_bindings.UInt32(0))
233 self._named_service = bus_object.GetNameOwner(named_service,
234 dbus_interface=BUS_DAEMON_IFACE)
236 #PendingCall object for Introspect call
237 self._pending_introspect = None
238 #queue of async calls waiting on the Introspect to return
239 self._pending_introspect_queue = []
240 #dictionary mapping method names to their input signatures
241 self._introspect_method_map = {}
243 # must be a recursive lock because block() is called while locked,
244 # and calls the callback which re-takes the lock
245 self._introspect_lock = RLock()
247 if not introspect:
248 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
249 else:
250 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
252 self._pending_introspect = self._Introspect()
254 def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
255 """Arrange for the given function to be called when the given signal
256 is received.
258 :Parameters:
259 `signal_name` : str
260 The name of the signal
261 `handler_function` : callable
262 A function to be called when the signal is emitted by
263 the remote object. Its positional arguments will be the
264 arguments of the signal; optionally, it may be given
265 keyword arguments as described below.
266 `dbus_interface` : str
267 Optional interface with which to qualify the signal name.
268 If None (the default) the handler will be called whenever a
269 signal of the given member name is received, whatever
270 its interface.
271 :Keywords:
272 `utf8_strings` : bool
273 If True, the handler function will receive any string
274 arguments as dbus.UTF8String objects (a subclass of str
275 guaranteed to be UTF-8). If False (default) it will receive
276 any string arguments as dbus.String objects (a subclass of
277 unicode).
278 `byte_arrays` : bool
279 If True, the handler function will receive any byte-array
280 arguments as dbus.ByteArray objects (a subclass of str).
281 If False (default) it will receive any byte-array
282 arguments as a dbus.Array of dbus.Byte (subclasses of:
283 a list of ints).
284 `sender_keyword` : str
285 If not None (the default), the handler function will receive
286 the unique name of the sending endpoint as a keyword
287 argument with this name
288 `destination_keyword` : str
289 If not None (the default), the handler function will receive
290 the bus name of the destination (or None if the signal is a
291 broadcast, as is usual) as a keyword argument with this name.
292 `interface_keyword` : str
293 If not None (the default), the handler function will receive
294 the signal interface as a keyword argument with this name.
295 `member_keyword` : str
296 If not None (the default), the handler function will receive
297 the signal name as a keyword argument with this name.
298 `path_keyword` : str
299 If not None (the default), the handler function will receive
300 the object-path of the sending object as a keyword argument
301 with this name
302 `message_keyword` : str
303 If not None (the default), the handler function will receive
304 the `dbus.lowlevel.SignalMessage` as a keyword argument with
305 this name.
306 `arg...` : unicode or UTF-8 str
307 If there are additional keyword parameters of the form
308 ``arg``\ *n*, match only signals where the *n*\ th argument
309 is the value given for that keyword parameter. As of this time
310 only string arguments can be matched (in particular,
311 object paths and signatures can't).
313 return \
314 self._bus.add_signal_receiver(handler_function,
315 signal_name=signal_name,
316 dbus_interface=dbus_interface,
317 named_service=self._named_service,
318 path=self.__dbus_object_path__,
319 **keywords)
321 def _Introspect(self):
322 message = _dbus_bindings.MethodCallMessage(None, self.__dbus_object_path__, 'org.freedesktop.DBus.Introspectable', 'Introspect')
323 message.set_destination(self._named_service)
325 result = self._bus.get_connection().send_message_with_reply(message, _ReplyHandler(self._introspect_reply_handler, self._introspect_error_handler, utf8_strings=True), -1)
326 return result
328 def _introspect_execute_queue(self):
329 # FIXME: potential to flood the bus
330 # We should make sure mainloops all have idle handlers
331 # and do one message per idle
332 for (proxy_method, args, keywords) in self._pending_introspect_queue:
333 proxy_method(*args, **keywords)
335 def _introspect_reply_handler(self, data):
336 self._introspect_lock.acquire()
337 try:
338 try:
339 self._introspect_method_map = process_introspection_data(data)
340 except IntrospectionParserException, e:
341 self._introspect_error_handler(e)
342 return
344 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
345 self._pending_introspect = None
346 self._introspect_execute_queue()
347 finally:
348 self._introspect_lock.release()
350 def _introspect_error_handler(self, error):
351 self._introspect_lock.acquire()
352 try:
353 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
354 self._pending_introspect = None
355 self._introspect_execute_queue()
356 sys.stderr.write("Introspect error: " + str(error) + "\n")
357 finally:
358 self._introspect_lock.release()
360 def _introspect_block(self):
361 self._introspect_lock.acquire()
362 try:
363 if self._pending_introspect is not None:
364 self._pending_introspect.block()
365 # else someone still has a _DeferredMethod from before we
366 # finished introspection: no need to do anything special any more
367 finally:
368 self._introspect_lock.release()
370 def _introspect_add_to_queue(self, callback, args, kwargs):
371 self._introspect_lock.acquire()
372 try:
373 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
374 self._pending_introspect_queue.append((callback, args, kwargs))
375 else:
376 # someone still has a _DeferredMethod from before we
377 # finished introspection
378 callback(*args, **kwargs)
379 finally:
380 self._introspect_lock.release()
382 def __getattr__(self, member, dbus_interface=None):
383 if member == '__call__':
384 return object.__call__
385 elif member.startswith('__') and member.endswith('__'):
386 raise AttributeError(member)
387 else:
388 return self.get_dbus_method(member, dbus_interface)
390 def get_dbus_method(self, member, dbus_interface=None):
391 """Return a proxy method representing the given D-Bus method. The
392 returned proxy method can be called in the usual way. For instance, ::
394 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
396 is equivalent to::
398 proxy.Foo(123, dbus_interface='com.example.Bar')
400 or even::
402 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
404 However, using `get_dbus_method` is the only way to call D-Bus
405 methods with certain awkward names - if the author of a service
406 implements a method called ``connect_to_signal`` or even
407 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
409 For services which follow the D-Bus convention of CamelCaseMethodNames
410 this won't be a problem.
413 ret = self.ProxyMethodClass(self, self._bus.get_connection(),
414 self._named_service,
415 self.__dbus_object_path__, member,
416 dbus_interface)
418 # this can be done without taking the lock - the worst that can
419 # happen is that we accidentally return a _DeferredMethod just after
420 # finishing introspection, in which case _introspect_add_to_queue and
421 # _introspect_block will do the right thing anyway
422 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
423 ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue,
424 self._introspect_block)
426 return ret
428 def __repr__(self):
429 return '<ProxyObject wrapping %s %s %s at %#x>'%(
430 self._bus, self._named_service, self.__dbus_object_path__, id(self))
431 __str__ = __repr__