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