dbus.service: change unexport() to remove_from_connection() at J5's request
[dbus-python-phuang.git] / dbus / service.py
blobae507514ee544fbbd9dcc294d2612821e5b35641
1 # Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
2 # Copyright (C) 2003 David Zeuthen
3 # Copyright (C) 2004 Rob Taylor
4 # Copyright (C) 2005, 2006 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 __all__ = ('BusName', 'Object', 'method', 'signal')
23 __docformat__ = 'restructuredtext'
25 import sys
26 import logging
27 import operator
28 import traceback
30 import _dbus_bindings
31 from dbus import SessionBus
32 from dbus.exceptions import DBusException, \
33 NameExistsException, \
34 UnknownMethodException
35 from dbus.decorators import method
36 from dbus.decorators import signal
37 from dbus.proxies import LOCAL_PATH
40 _logger = logging.getLogger('dbus.service')
43 class _VariantSignature(object):
44 """A fake method signature which, when iterated, yields an endless stream
45 of 'v' characters representing variants (handy with zip()).
47 It has no string representation.
48 """
49 def __iter__(self):
50 """Return self."""
51 return self
53 def next(self):
54 """Return 'v' whenever called."""
55 return 'v'
57 class BusName(object):
58 """A base class for exporting your own Named Services across the Bus.
60 When instantiated, objects of this class attempt to claim the given
61 well-known name on the given bus for the current process. The name is
62 released when the BusName object becomes unreferenced.
64 If a well-known name is requested multiple times, multiple references
65 to the same BusName object will be returned.
67 Caveats
68 -------
69 - Assumes that named services are only ever requested using this class -
70 if you request names from the bus directly, confusion may occur.
71 - Does not handle queueing.
72 """
73 def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
74 """Constructor, which may either return an existing cached object
75 or a new object.
77 :Parameters:
78 `name` : str
79 The well-known name to be advertised
80 `bus` : dbus.Bus
81 A Bus on which this service will be advertised; if None
82 (default) the session bus will be used
83 `allow_replacement` : bool
84 If True, other processes trying to claim the same well-known
85 name will take precedence over this one.
86 `replace_existing` : bool
87 If True, this process can take over the well-known name
88 from other processes already holding it.
89 `do_not_queue` : bool
90 If True, this service will not be placed in the queue of
91 services waiting for the requested name if another service
92 already holds it.
93 """
94 _dbus_bindings.validate_bus_name(name, allow_well_known=True,
95 allow_unique=False)
97 # get default bus
98 if bus == None:
99 bus = SessionBus()
101 # see if this name is already defined, return it if so
102 # FIXME: accessing internals of Bus
103 if name in bus._bus_names:
104 return bus._bus_names[name]
106 # otherwise register the name
107 name_flags = (
108 (allow_replacement and _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT or 0) |
109 (replace_existing and _dbus_bindings.NAME_FLAG_REPLACE_EXISTING or 0) |
110 (do_not_queue and _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE or 0))
112 retval = bus.request_name(name, name_flags)
114 # TODO: more intelligent tracking of bus name states?
115 if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
116 pass
117 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
118 # queueing can happen by default, maybe we should
119 # track this better or let the user know if they're
120 # queued or not?
121 pass
122 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
123 raise NameExistsException(name)
124 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
125 # if this is a shared bus which is being used by someone
126 # else in this process, this can happen legitimately
127 pass
128 else:
129 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
131 # and create the object
132 bus_name = object.__new__(cls)
133 bus_name._bus = bus
134 bus_name._name = name
136 # cache instance (weak ref only)
137 # FIXME: accessing Bus internals again
138 bus._bus_names[name] = bus_name
140 return bus_name
142 # do nothing because this is called whether or not the bus name
143 # object was retrieved from the cache or created new
144 def __init__(self, *args, **keywords):
145 pass
147 # we can delete the low-level name here because these objects
148 # are guaranteed to exist only once for each bus name
149 def __del__(self):
150 self._bus.release_name(self._name)
151 pass
153 def get_bus(self):
154 """Get the Bus this Service is on"""
155 return self._bus
157 def get_name(self):
158 """Get the name of this service"""
159 return self._name
161 def __repr__(self):
162 return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
163 __str__ = __repr__
166 def _method_lookup(self, method_name, dbus_interface):
167 """Walks the Python MRO of the given class to find the method to invoke.
169 Returns two methods, the one to call, and the one it inherits from which
170 defines its D-Bus interface name, signature, and attributes.
172 parent_method = None
173 candidate_class = None
174 successful = False
176 # split up the cases when we do and don't have an interface because the
177 # latter is much simpler
178 if dbus_interface:
179 # search through the class hierarchy in python MRO order
180 for cls in self.__class__.__mro__:
181 # if we haven't got a candidate class yet, and we find a class with a
182 # suitably named member, save this as a candidate class
183 if (not candidate_class and method_name in cls.__dict__):
184 if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
185 and "_dbus_interface" in cls.__dict__[method_name].__dict__):
186 # however if it is annotated for a different interface
187 # than we are looking for, it cannot be a candidate
188 if cls.__dict__[method_name]._dbus_interface == dbus_interface:
189 candidate_class = cls
190 parent_method = cls.__dict__[method_name]
191 successful = True
192 break
193 else:
194 pass
195 else:
196 candidate_class = cls
198 # if we have a candidate class, carry on checking this and all
199 # superclasses for a method annoated as a dbus method
200 # on the correct interface
201 if (candidate_class and method_name in cls.__dict__
202 and "_dbus_is_method" in cls.__dict__[method_name].__dict__
203 and "_dbus_interface" in cls.__dict__[method_name].__dict__
204 and cls.__dict__[method_name]._dbus_interface == dbus_interface):
205 # the candidate class has a dbus method on the correct interface,
206 # or overrides a method that is, success!
207 parent_method = cls.__dict__[method_name]
208 successful = True
209 break
211 else:
212 # simpler version of above
213 for cls in self.__class__.__mro__:
214 if (not candidate_class and method_name in cls.__dict__):
215 candidate_class = cls
217 if (candidate_class and method_name in cls.__dict__
218 and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
219 parent_method = cls.__dict__[method_name]
220 successful = True
221 break
223 if successful:
224 return (candidate_class.__dict__[method_name], parent_method)
225 else:
226 if dbus_interface:
227 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
228 else:
229 raise UnknownMethodException('%s is not a valid method' % method_name)
232 def _method_reply_return(connection, message, method_name, signature, *retval):
233 reply = _dbus_bindings.MethodReturnMessage(message)
234 try:
235 reply.append(signature=signature, *retval)
236 except Exception, e:
237 logging.basicConfig()
238 if signature is None:
239 try:
240 signature = reply.guess_signature(retval) + ' (guessed)'
241 except Exception, e:
242 _logger.error('Unable to guess signature for arguments %r: '
243 '%s: %s', retval, e.__class__, e)
244 raise
245 _logger.error('Unable to append %r to message with signature %s: '
246 '%s: %s', retval, signature, e.__class__, e)
247 raise
249 connection.send_message(reply)
252 def _method_reply_error(connection, message, exception):
253 name = getattr(exception, '_dbus_error_name', None)
255 if name is not None:
256 pass
257 elif getattr(exception, '__module__', '') in ('', '__main__'):
258 name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
259 else:
260 name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
262 contents = traceback.format_exc()
263 reply = _dbus_bindings.ErrorMessage(message, name, contents)
265 connection.send_message(reply)
268 class InterfaceType(type):
269 def __init__(cls, name, bases, dct):
270 # these attributes are shared between all instances of the Interface
271 # object, so this has to be a dictionary that maps class names to
272 # the per-class introspection/interface data
273 class_table = getattr(cls, '_dbus_class_table', {})
274 cls._dbus_class_table = class_table
275 interface_table = class_table[cls.__module__ + '.' + name] = {}
277 # merge all the name -> method tables for all the interfaces
278 # implemented by our base classes into our own
279 for b in bases:
280 base_name = b.__module__ + '.' + b.__name__
281 if getattr(b, '_dbus_class_table', False):
282 for (interface, method_table) in class_table[base_name].iteritems():
283 our_method_table = interface_table.setdefault(interface, {})
284 our_method_table.update(method_table)
286 # add in all the name -> method entries for our own methods/signals
287 for func in dct.values():
288 if getattr(func, '_dbus_interface', False):
289 method_table = interface_table.setdefault(func._dbus_interface, {})
290 method_table[func.__name__] = func
292 super(InterfaceType, cls).__init__(name, bases, dct)
294 # methods are different to signals, so we have two functions... :)
295 def _reflect_on_method(cls, func):
296 args = func._dbus_args
298 if func._dbus_in_signature:
299 # convert signature into a tuple so length refers to number of
300 # types, not number of characters. the length is checked by
301 # the decorator to make sure it matches the length of args.
302 in_sig = tuple(_dbus_bindings.Signature(func._dbus_in_signature))
303 else:
304 # magic iterator which returns as many v's as we need
305 in_sig = _VariantSignature()
307 if func._dbus_out_signature:
308 out_sig = _dbus_bindings.Signature(func._dbus_out_signature)
309 else:
310 # its tempting to default to _dbus_bindings.Signature('v'), but
311 # for methods that return nothing, providing incorrect
312 # introspection data is worse than providing none at all
313 out_sig = []
315 reflection_data = ' <method name="%s">\n' % (func.__name__)
316 for pair in zip(in_sig, args):
317 reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
318 for type in out_sig:
319 reflection_data += ' <arg direction="out" type="%s" />\n' % type
320 reflection_data += ' </method>\n'
322 return reflection_data
324 def _reflect_on_signal(cls, func):
325 args = func._dbus_args
327 if func._dbus_signature:
328 # convert signature into a tuple so length refers to number of
329 # types, not number of characters
330 sig = tuple(_dbus_bindings.Signature(func._dbus_signature))
331 else:
332 # magic iterator which returns as many v's as we need
333 sig = _VariantSignature()
335 reflection_data = ' <signal name="%s">\n' % (func.__name__)
336 for pair in zip(sig, args):
337 reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
338 reflection_data = reflection_data + ' </signal>\n'
340 return reflection_data
342 class Interface(object):
343 __metaclass__ = InterfaceType
345 class Object(Interface):
346 r"""A base class for exporting your own Objects across the Bus.
348 Just inherit from Object and mark exported methods with the
349 @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
351 Example::
353 class Example(dbus.service.object):
354 def __init__(self, object_path):
355 dbus.service.Object.__init__(self, dbus.SessionBus(), path)
356 self._last_input = None
358 @dbus.service.method(interface='com.example.Sample',
359 in_signature='v', out_signature='s')
360 def StringifyVariant(self, var):
361 self.LastInputChanged(var) # emits the signal
362 return str(var)
364 @dbus.service.signal(interface='com.example.Sample',
365 signature='v')
366 def LastInputChanged(self, var):
367 # run just before the signal is actually emitted
368 # just put "pass" if nothing should happen
369 self._last_input = var
371 @dbus.service.method(interface='com.example.Sample',
372 in_signature='', out_signature='v')
373 def GetLastInput(self):
374 return self._last_input
377 # the signature of __init__ is a bit mad, for backwards compatibility
378 def __init__(self, conn=None, object_path=None, bus_name=None):
379 """Constructor. Either conn or bus_name is required; object_path
380 is also required.
382 :Parameters:
383 `conn` : dbus.connection.Connection
384 The connection on which to export this object.
386 If None, use the Bus associated with the given ``bus_name``,
387 or raise TypeError if there is no ``bus_name`` either.
389 For backwards compatibility, if an instance of
390 dbus.service.BusName is passed as the first parameter,
391 this is equivalent to passing its associated Bus as
392 ``conn``, and passing the BusName itself as ``bus_name``.
394 `object_path` : str
395 The D-Bus object path at which to export this Object.
397 `bus_name` : dbus.service.BusName
398 Represents a well-known name claimed by this process. A
399 reference to the BusName object will be held by this
400 Object, preventing the name from being released during this
401 Object's lifetime (unless it's released manually).
403 if object_path is None:
404 raise TypeError('The object_path argument is required')
405 _dbus_bindings.validate_object_path(object_path)
406 if object_path == LOCAL_PATH:
407 raise DBusException('Objects may not be exported on the reserved '
408 'path %s' % LOCAL_PATH)
410 if isinstance(conn, BusName):
411 # someone's using the old API; don't gratuitously break them
412 bus_name = conn
413 conn = bus_name.get_bus()
414 elif conn is None:
415 # someone's using the old API but naming arguments, probably
416 if bus_name is None:
417 raise TypeError('Either conn or bus_name is required')
418 conn = bus_name.get_bus()
420 self._object_path = object_path
421 self._name = bus_name
422 self._connection = conn
424 self._connection._register_object_path(object_path, self._message_cb, self._unregister_cb)
426 __dbus_object_path__ = property(lambda self: self._object_path, None, None,
427 "The D-Bus object path of this object")
429 def remove_from_connection(self, connection=None, path=None):
430 """Make this object inaccessible via the given D-Bus connection
431 and object path. If no connection or path is specified,
432 the object ceases to be accessible via any connection or path.
434 It's not currently possible to export an object on more than one
435 connection or with more than one object-path, but this will be
436 supported in future.
438 :Parameters:
439 `connection` : dbus.connection.Connection or None
440 Only remove the object from this Connection. If None,
441 remove from all Connections on which it's exported.
442 `path` : dbus.ObjectPath or other str, or None
443 Only remove the object from this object path. If None,
444 remove from all object paths.
445 :Raises LookupError:
446 if the object was not exported on the requested connection
447 or path, or (if both are None) was not exported at all.
449 if self._object_path is None or self._connection is None:
450 raise LookupError('%r is not exported' % self)
451 if path is not None and self._object_path != path:
452 raise LookupError('%r is not exported at path %r' % (self, path))
453 if connection is not None and self._connection != connection:
454 raise LookupError('%r is not exported on %r' % (self, connection))
456 try:
457 self._connection._unregister_object_path(self._object_path)
458 finally:
459 self._connection = None
460 self._object_path = None
462 def _unregister_cb(self, connection):
463 _logger.info('Unregistering exported object %r', self)
465 def _message_cb(self, connection, message):
466 try:
467 # lookup candidate method and parent method
468 method_name = message.get_member()
469 interface_name = message.get_interface()
470 (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
472 # set up method call parameters
473 args = message.get_args_list(**parent_method._dbus_get_args_options)
474 keywords = {}
476 if parent_method._dbus_out_signature is not None:
477 signature = _dbus_bindings.Signature(parent_method._dbus_out_signature)
478 else:
479 signature = None
481 # set up async callback functions
482 if parent_method._dbus_async_callbacks:
483 (return_callback, error_callback) = parent_method._dbus_async_callbacks
484 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
485 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
487 # include the sender etc. if desired
488 if parent_method._dbus_sender_keyword:
489 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
490 if parent_method._dbus_path_keyword:
491 keywords[parent_method._dbus_path_keyword] = message.get_path()
492 if parent_method._dbus_destination_keyword:
493 keywords[parent_method._dbus_destination_keyword] = message.get_destination()
494 if parent_method._dbus_message_keyword:
495 keywords[parent_method._dbus_message_keyword] = message
497 # call method
498 retval = candidate_method(self, *args, **keywords)
500 # we're done - the method has got callback functions to reply with
501 if parent_method._dbus_async_callbacks:
502 return
504 # otherwise we send the return values in a reply. if we have a
505 # signature, use it to turn the return value into a tuple as
506 # appropriate
507 if signature is not None:
508 signature_tuple = tuple(signature)
509 # if we have zero or one return values we want make a tuple
510 # for the _method_reply_return function, otherwise we need
511 # to check we're passing it a sequence
512 if len(signature_tuple) == 0:
513 if retval == None:
514 retval = ()
515 else:
516 raise TypeError('%s has an empty output signature but did not return None' %
517 method_name)
518 elif len(signature_tuple) == 1:
519 retval = (retval,)
520 else:
521 if operator.isSequenceType(retval):
522 # multi-value signature, multi-value return... proceed unchanged
523 pass
524 else:
525 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
526 (method_name, signature))
528 # no signature, so just turn the return into a tuple and send it as normal
529 else:
530 if retval is None:
531 retval = ()
532 elif (isinstance(retval, tuple)
533 and not isinstance(retval, _dbus_bindings.Struct)):
534 # If the return is a tuple that is not a Struct, we use it
535 # as-is on the assumption that there are multiple return
536 # values - this is the usual Python idiom. (fd.o #10174)
537 pass
538 else:
539 retval = (retval,)
541 _method_reply_return(connection, message, method_name, signature, *retval)
542 except Exception, exception:
543 # send error reply
544 _method_reply_error(connection, message, exception)
546 @method('org.freedesktop.DBus.Introspectable', in_signature='', out_signature='s')
547 def Introspect(self):
548 """Return a string of XML encoding this object's supported interfaces,
549 methods and signals.
551 reflection_data = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">\n'
552 reflection_data += '<node name="%s">\n' % (self._object_path)
554 interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
555 for (name, funcs) in interfaces.iteritems():
556 reflection_data += ' <interface name="%s">\n' % (name)
558 for func in funcs.values():
559 if getattr(func, '_dbus_is_method', False):
560 reflection_data += self.__class__._reflect_on_method(func)
561 elif getattr(func, '_dbus_is_signal', False):
562 reflection_data += self.__class__._reflect_on_signal(func)
564 reflection_data += ' </interface>\n'
566 for name in self._connection.list_exported_child_objects(
567 self._object_path):
568 reflection_data += ' <node name="%s"/>\n' % name
570 reflection_data += '</node>\n'
572 return reflection_data
574 def __repr__(self):
575 return '<dbus.service.Object %s on %r at %#x>' % (self._object_path, self._name, id(self))
576 __str__ = __repr__