dbus._dbus, dbus.matchrules, dbus.proxies: Support utf8_strings and byte_arrays calli...
[dbus-python-phuang.git] / dbus / proxies.py
blob5dd934846dc75946054a282ef4b62cd1179f6a3b
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 # 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 import _dbus_bindings
28 import dbus.introspect_parser as introspect_parser
29 from dbus.exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException, DBusException
31 __docformat__ = 'restructuredtext'
34 _logger = logging.getLogger('dbus.proxies')
37 class _ReplyHandler(object):
38 __slots__ = ('_on_error', '_on_reply')
39 def __init__(self, on_reply, on_error):
40 self._on_error = on_error
41 self._on_reply = on_reply
43 def __call__(self, message):
44 if isinstance(message, _dbus_bindings.MethodReturnMessage):
45 self._on_reply(*message.get_args_list())
46 elif isinstance(message, _dbus_bindings.ErrorMessage):
47 args = message.get_args_list()
48 if len(args) > 0:
49 self._on_error(DBusException(args[0]))
50 else:
51 self._on_error(DBusException())
52 else:
53 self._on_error(DBusException('Unexpected reply message type: %s'
54 % message))
57 class DeferedMethod:
58 """A DeferedMethod
60 This is returned instead of ProxyMethod when we are defering DBus calls
61 while waiting for introspection data to be returned
62 """
63 def __init__(self, proxy_method):
64 self._proxy_method = proxy_method
65 self._method_name = proxy_method._method_name
67 def __call__(self, *args, **keywords):
68 reply_handler = None
69 if keywords.has_key('reply_handler'):
70 reply_handler = keywords['reply_handler']
72 #block for now even on async
73 # FIXME: put ret in async queue in future if we have a reply handler
75 self._proxy_method._proxy._pending_introspect._block()
76 ret = self._proxy_method (*args, **keywords)
78 return ret
80 class ProxyMethod:
81 """A proxy Method.
83 Typically a member of a ProxyObject. Calls to the
84 method produce messages that travel over the Bus and are routed
85 to a specific named Service.
86 """
87 def __init__(self, proxy, connection, named_service, object_path, method_name, iface):
88 self._proxy = proxy
89 self._connection = connection
90 self._named_service = named_service
91 self._object_path = object_path
92 self._method_name = method_name
93 self._dbus_interface = iface
95 def __call__(self, *args, **keywords):
96 timeout = -1
97 if keywords.has_key('timeout'):
98 timeout = keywords['timeout']
100 reply_handler = None
101 if keywords.has_key('reply_handler'):
102 reply_handler = keywords['reply_handler']
104 error_handler = None
105 if keywords.has_key('error_handler'):
106 error_handler = keywords['error_handler']
108 ignore_reply = False
109 if keywords.has_key('ignore_reply'):
110 ignore_reply = keywords['ignore_reply']
112 get_args_options = {}
113 if keywords.has_key('utf8_strings'):
114 get_args_options['utf8_strings'] = keywords['utf8_strings']
115 if keywords.has_key('byte_arrays'):
116 get_args_options['byte_arrays'] = keywords['byte_arrays']
118 if not(reply_handler and error_handler):
119 if reply_handler:
120 raise MissingErrorHandlerException()
121 elif error_handler:
122 raise MissingReplyHandlerException()
124 dbus_interface = self._dbus_interface
125 if keywords.has_key('dbus_interface'):
126 dbus_interface = keywords['dbus_interface']
128 tmp_iface = ''
129 if dbus_interface:
130 tmp_iface = dbus_interface + '.'
132 key = tmp_iface + self._method_name
134 introspect_sig = None
135 if self._proxy._introspect_method_map.has_key (key):
136 introspect_sig = self._proxy._introspect_method_map[key]
138 message = _dbus_bindings.MethodCallMessage(destination=None,
139 path=self._object_path,
140 interface=dbus_interface,
141 method=self._method_name)
142 message.set_destination(self._named_service)
144 # Add the arguments to the function
145 try:
146 message.append(signature=introspect_sig, *args)
147 except Exception, e:
148 _logger.error('Unable to set arguments %r according to '
149 'introspected signature %r: %s: %s',
150 args, introspect_sig, e.__class__, e)
151 raise
153 # FIXME: using private API on Connection while I decide whether to
154 # make it public or what
155 if ignore_reply:
156 result = self._connection._send(message)
157 return None
158 elif reply_handler:
159 result = self._connection._send_with_reply(message, _ReplyHandler(reply_handler, error_handler), timeout/1000.0)
160 return None
161 else:
162 reply_message = self._connection._send_with_reply_and_block(message, timeout)
163 args_list = reply_message.get_args_list(**get_args_options)
164 if len(args_list) == 0:
165 return None
166 elif len(args_list) == 1:
167 return args_list[0]
168 else:
169 return tuple(args_list)
172 class ProxyObject:
173 """A proxy to the remote Object.
175 A ProxyObject is provided by the Bus. ProxyObjects
176 have member functions, and can be called like normal Python objects.
178 ProxyMethodClass = ProxyMethod
179 DeferedMethodClass = DeferedMethod
181 INTROSPECT_STATE_DONT_INTROSPECT = 0
182 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
183 INTROSPECT_STATE_INTROSPECT_DONE = 2
185 def __init__(self, bus, named_service, object_path, introspect=True):
186 """Initialize the proxy object.
188 :Parameters:
189 `bus` : `dbus.Bus`
190 The bus on which to find this object
191 `named_service` : str
192 A bus name for the endpoint owning the object (need not
193 actually be a service name)
194 `object_path` : str
195 The object path at which the endpoint exports the object
196 `introspect` : bool
197 If true (default), attempt to introspect the remote
198 object to find out supported methods and their signatures
200 self._bus = bus
201 self._named_service = named_service
202 self._object_path = object_path
204 #PendingCall object for Introspect call
205 self._pending_introspect = None
206 #queue of async calls waiting on the Introspect to return
207 self._pending_introspect_queue = []
208 #dictionary mapping method names to their input signatures
209 self._introspect_method_map = {}
211 if not introspect:
212 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
213 else:
214 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
216 self._pending_introspect = self._Introspect()
219 def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
220 """Arrange for the given function to be called when the given signal
221 is received.
223 :Parameters:
224 `signal_name` : str
225 The name of the signal
226 `handler_function` : callable
227 A function to be called when the signal is emitted by
228 the remote object. Its positional arguments will be the
229 arguments of the signal; optionally, it may be given
230 keyword arguments as described below.
231 `dbus_interface` : str
232 Optional interface with which to qualify the signal name.
233 If None (the default) the handler will be called whenever a
234 signal of the given member name is received, whatever
235 its interface.
236 :Keywords:
237 `utf8_strings` : bool
238 If True, the handler function will receive any string
239 arguments as dbus.UTF8String objects (a subclass of str
240 guaranteed to be UTF-8). If False (default) it will receive
241 any string arguments as dbus.String objects (a subclass of
242 unicode).
243 `byte_arrays` : bool
244 If True, the handler function will receive any byte-array
245 arguments as dbus.ByteArray objects (a subclass of str).
246 If False (default) it will receive any byte-array
247 arguments as a dbus.Array of dbus.Byte (subclasses of:
248 a list of ints).
249 `sender_keyword` : str
250 If not None (the default), the handler function will receive
251 the unique name of the sending endpoint as a keyword
252 argument with this name
253 `path_keyword` : str
254 If not None (the default), the handler function will receive
255 the object-path of the sending object as a keyword argument
256 with this name
257 `arg...` : unicode or UTF-8 str
258 If there are additional keyword parameters of the form
259 ``arg``\ *n*, match only signals where the *n*\ th argument
260 is the value given for that keyword parameter. As of this time
261 only string arguments can be matched (in particular,
262 object paths and signatures can't).
264 self._bus.add_signal_receiver(handler_function,
265 signal_name=signal_name,
266 dbus_interface=dbus_interface,
267 named_service=self._named_service,
268 path=self._object_path,
269 **keywords)
271 def _Introspect(self):
272 message = _dbus_bindings.MethodCallMessage(None, self._object_path, 'org.freedesktop.DBus.Introspectable', 'Introspect')
273 message.set_destination(self._named_service)
275 result = self._bus.get_connection()._send_with_reply(message, _ReplyHandler(self._introspect_reply_handler, self._introspect_error_handler), -1)
276 return result
278 def _introspect_execute_queue(self):
279 for call in self._pending_introspect_queue:
280 (member, iface, args, keywords) = call
282 introspect_sig = None
284 tmp_iface = ''
285 if iface:
286 tmp_iface = iface + '.'
288 key = tmp_iface + '.' + member
289 if self._introspect_method_map.has_key (key):
290 introspect_sig = self._introspect_method_map[key]
293 call_object = self.ProxyMethodClass(self._bus.get_connection(),
294 self._named_service,
295 self._object_path,
296 iface,
297 member,
298 introspect_sig)
300 call_object(args, keywords)
302 def _introspect_reply_handler(self, data):
303 try:
304 self._introspect_method_map = introspect_parser.process_introspection_data(data)
305 except IntrospectionParserException, e:
306 self._introspect_error_handler(e)
307 return
309 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
310 #self._introspect_execute_queue()
312 def _introspect_error_handler(self, error):
313 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
314 self._introspect_execute_queue()
315 sys.stderr.write("Introspect error: " + str(error) + "\n")
317 def __getattr__(self, member, dbus_interface=None):
318 if member == '__call__':
319 return object.__call__
320 elif member.startswith('__') and member.endswith('__'):
321 raise AttributeError(member)
322 else:
323 ret = self.ProxyMethodClass(self, self._bus.get_connection(),
324 self._named_service,
325 self._object_path, member,
326 dbus_interface)
328 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
329 ret = self.DeferedMethodClass(ret)
331 return ret
333 def __repr__(self):
334 return '<ProxyObject wrapping %s %s %s at %#x>'%(
335 self._bus, self._named_service, self._object_path , id(self))
336 __str__ = __repr__