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