2005-11-15 Robert McQueen <robot101@debian.org>
[platform/upstream/dbus.git] / python / service.py
1 import dbus_bindings
2 import _dbus
3 import operator
4 import traceback
5
6 from exceptions import NameExistsException
7 from exceptions import UnknownMethodException
8 from decorators import method
9 from decorators import signal
10
11 class BusName(object):
12     """A base class for exporting your own Named Services across the Bus
13     """
14     def __new__(cls, name, bus=None):
15         # get default bus
16         if bus == None:
17             bus = _dbus.Bus()
18
19         # see if this name is already defined, return it if so
20         if name in bus._bus_names:
21             return bus._bus_names[name]
22
23         # otherwise register the name
24         retval = dbus_bindings.bus_request_name(bus.get_connection(), name)
25
26         # TODO: more intelligent tracking of bus name states?
27         if retval == dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
28             pass
29         elif retval == dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
30             # you can't arrive at this state via the high-level bindings
31             # because you can't put flags in, but... who knows?
32             pass
33         elif retval == dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
34             raise NameExistsException(name)
35         elif retval == dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
36             # if this is a shared bus which is being used by someone
37             # else in this process, this can happen legitimately
38             pass
39         else:
40             raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
41
42         # and create the object
43         bus_name = object.__new__(cls)
44         bus_name._bus = bus
45         bus_name._name = name
46
47         # cache instance
48         bus._bus_names[name] = bus_name
49
50         return bus_name
51
52     # do nothing because this is called whether or not the bus name
53     # object was retrieved from the cache or created new
54     def __init__(self, *args, **keywords):
55         pass
56
57     # we can delete the low-level name here because these objects
58     # are guaranteed to exist only once for each bus name
59     def __del__(self):
60         dbus_bindings.bus_release_name(self._bus.get_connection(), self._name)
61         pass
62
63     def get_bus(self):
64         """Get the Bus this Service is on"""
65         return self._bus
66
67     def get_name(self):
68         """Get the name of this service"""
69         return self._name
70
71     def __repr__(self):
72         return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
73     __str__ = __repr__
74
75
76 def _method_lookup(self, method_name, dbus_interface):
77     """Walks the Python MRO of the given class to find the method to invoke.
78
79     Returns two methods, the one to call, and the one it inherits from which
80     defines its D-Bus interface name, signature, and attributes.
81     """
82     parent_method = None
83     candidate_class = None
84     successful = False
85
86     # split up the cases when we do and don't have an interface because the
87     # latter is much simpler
88     if dbus_interface:
89         # search through the class hierarchy in python MRO order
90         for cls in self.__class__.__mro__:
91             # if we haven't got a candidate class yet, and we find a class with a
92             # suitably named member, save this as a candidate class
93             if (not candidate_class and method_name in cls.__dict__):
94                 if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
95                     and "_dbus_interface" in cls.__dict__[method_name].__dict__):
96                     # however if it is annotated for a different interface
97                     # than we are looking for, it cannot be a candidate
98                     if cls.__dict__[method_name]._dbus_interface == dbus_interface:
99                         candidate_class = cls
100                         parent_method = cls.__dict__[method_name]
101                         sucessful = True
102                         break
103                     else:
104                         pass
105                 else:
106                     candidate_class = cls
107
108             # if we have a candidate class, carry on checking this and all
109             # superclasses for a method annoated as a dbus method
110             # on the correct interface
111             if (candidate_class and method_name in cls.__dict__
112                 and "_dbus_is_method" in cls.__dict__[method_name].__dict__
113                 and "_dbus_interface" in cls.__dict__[method_name].__dict__
114                 and cls.__dict__[method_name]._dbus_interface == dbus_interface):
115                 # the candidate class has a dbus method on the correct interface,
116                 # or overrides a method that is, success!
117                 parent_method = cls.__dict__[method_name]
118                 sucessful = True
119                 break
120
121     else:
122         # simpler version of above
123         for cls in self.__class__.__mro__:
124             if (not candidate_class and method_name in cls.__dict__):
125                 candidate_class = cls
126
127             if (candidate_class and method_name in cls.__dict__
128                 and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
129                 parent_method = cls.__dict__[method_name]
130                 sucessful = True
131                 break
132
133     if sucessful:
134         return (candidate_class.__dict__[method_name], parent_method)
135     else:
136         if dbus_interface:
137             raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
138         else:
139             raise UnknownMethodException('%s is not a valid method' % method_name)
140
141
142 def _method_reply_return(connection, message, method_name, signature, *retval):
143     reply = dbus_bindings.MethodReturn(message)
144     iter = reply.get_iter(append=True)
145
146     # do strict adding if an output signature was provided
147     if signature:
148         if len(signature) > len(retval):
149             raise TypeError('output signature %s is longer than the number of values returned by %s' %
150                 (signature, method_name))
151         elif len(retval) > len(signature):
152             raise TypeError('output signature %s is shorter than the number of values returned by %s' %
153                 (signature, method_name))
154         else:
155             for (value, sig) in zip(retval, signature):
156                 iter.append_strict(value, sig)
157
158     # no signature, try and guess the return type by inspection
159     else:
160         for value in retval:
161             iter.append(value)
162
163     connection.send(reply)
164
165
166 def _method_reply_error(connection, message, exception):
167     if '_dbus_error_name' in exception.__dict__:
168         name = exception._dbus_error_name
169     elif exception.__module__ == '__main__':
170         name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
171     else:
172         name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
173
174     contents = traceback.format_exc()
175     reply = dbus_bindings.Error(message, name, contents)
176
177     connection.send(reply)
178
179
180 class InterfaceType(type):
181     def __init__(cls, name, bases, dct):
182         # these attributes are shared between all instances of the Interface
183         # object, so this has to be a dictionary that maps class names to
184         # the per-class introspection/interface data
185         class_table = getattr(cls, '_dbus_class_table', {})
186         cls._dbus_class_table = class_table
187         interface_table = class_table[cls.__module__ + '.' + name] = {}
188
189         # merge all the name -> method tables for all the interfaces
190         # implemented by our base classes into our own
191         for b in bases:
192             base_name = b.__module__ + '.' + b.__name__
193             if getattr(b, '_dbus_class_table', False):
194                 for (interface, method_table) in class_table[base_name].iteritems():
195                     our_method_table = interface_table.setdefault(interface, {})
196                     our_method_table.update(method_table)
197
198         # add in all the name -> method entries for our own methods/signals
199         for func in dct.values():
200             if getattr(func, '_dbus_interface', False):
201                 method_table = interface_table.setdefault(func._dbus_interface, {})
202                 method_table[func.__name__] = func
203
204         super(InterfaceType, cls).__init__(name, bases, dct)
205
206     # methods are different to signals, so we have two functions... :)
207     def _reflect_on_method(cls, func):
208         args = func._dbus_args
209
210         if func._dbus_in_signature:
211             # convert signature into a tuple so length refers to number of
212             # types, not number of characters. the length is checked by
213             # the decorator to make sure it matches the length of args.
214             in_sig = tuple(dbus_bindings.Signature(func._dbus_in_signature))
215         else:
216             # magic iterator which returns as many v's as we need
217             in_sig = dbus_bindings.VariantSignature()
218
219         if func._dbus_out_signature:
220             out_sig = dbus_bindings.Signature(func._dbus_out_signature)
221         else:
222             # its tempting to default to dbus_bindings.Signature('v'), but
223             # for methods that return nothing, providing incorrect
224             # introspection data is worse than providing none at all
225             out_sig = []
226
227         reflection_data = '    <method name="%s">\n' % (func.__name__)
228         for pair in zip(in_sig, args):
229             reflection_data += '      <arg direction="in"  type="%s" name="%s" />\n' % pair
230         for type in out_sig:
231             reflection_data += '      <arg direction="out" type="%s" />\n' % type
232         reflection_data += '    </method>\n'
233
234         return reflection_data
235
236     def _reflect_on_signal(cls, func):
237         args = func._dbus_args
238
239         if func._dbus_signature:
240             # convert signature into a tuple so length refers to number of
241             # types, not number of characters
242             sig = tuple(dbus_bindings.Signature(func._dbus_signature))
243         else:
244             # magic iterator which returns as many v's as we need
245             sig = dbus_bindings.VariantSignature()
246
247         reflection_data = '    <signal name="%s">\n' % (func.__name__)
248         for pair in zip(sig, args):
249             reflection_data = reflection_data + '      <arg type="%s" name="%s" />\n' % pair
250         reflection_data = reflection_data + '    </signal>\n'
251
252         return reflection_data
253
254 class Interface(object):
255     __metaclass__ = InterfaceType
256
257 class Object(Interface):
258     """A base class for exporting your own Objects across the Bus.
259
260     Just inherit from Object and provide a list of methods to share
261     across the Bus
262     """
263     def __init__(self, bus_name, object_path):
264         self._object_path = object_path
265         self._name = bus_name 
266         self._bus = bus_name.get_bus()
267             
268         self._connection = self._bus.get_connection()
269
270         self._connection.register_object_path(object_path, self._unregister_cb, self._message_cb)
271
272     def _unregister_cb(self, connection):
273         print ("Unregister")
274
275     def _message_cb(self, connection, message):
276         try:
277             # lookup candidate method and parent method
278             method_name = message.get_member()
279             interface_name = message.get_interface()
280             (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
281
282             # set up method call parameters
283             args = message.get_args_list()
284             keywords = {}
285
286             # iterate signature into list of complete types
287             if parent_method._dbus_out_signature:
288                 signature = tuple(dbus_bindings.Signature(parent_method._dbus_out_signature))
289             else:
290                 signature = None
291
292             # set up async callback functions
293             if parent_method._dbus_async_callbacks:
294                 (return_callback, error_callback) = parent_method._dbus_async_callbacks
295                 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
296                 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
297
298             # include the sender if desired
299             if parent_method._dbus_sender_keyword:
300                 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
301
302             # call method
303             retval = candidate_method(self, *args, **keywords)
304
305             # we're done - the method has got callback functions to reply with
306             if parent_method._dbus_async_callbacks:
307                 return
308
309             # otherwise we send the return values in a reply. if we have a
310             # signature, use it to turn the return value into a tuple as
311             # appropriate
312             if parent_method._dbus_out_signature:
313                 # if we have zero or one return values we want make a tuple
314                 # for the _method_reply_return function, otherwise we need
315                 # to check we're passing it a sequence
316                 if len(signature) == 0:
317                     if retval == None:
318                         retval = ()
319                     else:
320                         raise TypeError('%s has an empty output signature but did not return None' %
321                             method_name)
322                 elif len(signature) == 1:
323                     retval = (retval,)
324                 else:
325                     if operator.isSequenceType(retval):
326                         # multi-value signature, multi-value return... proceed unchanged
327                         pass
328                     else:
329                         raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
330                             (method_name, signature))
331
332             # no signature, so just turn the return into a tuple and send it as normal
333             else:
334                 signature = None
335                 if retval == None:
336                     retval = ()
337                 else:
338                     retval = (retval,)
339
340             _method_reply_return(connection, message, method_name, signature, *retval)
341         except Exception, exception:
342             # send error reply
343             _method_reply_error(connection, message, exception)
344
345     @method('org.freedesktop.DBus.Introspectable', in_signature='', out_signature='s')
346     def Introspect(self):
347         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'
348         reflection_data += '<node name="%s">\n' % (self._object_path)
349
350         interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
351         for (name, funcs) in interfaces.iteritems():
352             reflection_data += '  <interface name="%s">\n' % (name)
353
354             for func in funcs.values():
355                 if getattr(func, '_dbus_is_method', False):
356                     reflection_data += self.__class__._reflect_on_method(func)
357                 elif getattr(func, '_dbus_is_signal', False):
358                     reflection_data += self.__class__._reflect_on_signal(func)
359
360             reflection_data += '  </interface>\n'
361
362         reflection_data += '</node>\n'
363
364         return reflection_data
365
366     def __repr__(self):
367         return '<dbus.service.Object %s on %r at %#x>' % (self._object_path, self._name, id(self))
368     __str__ = __repr__
369