proxies.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. # Copyright (C) 2003-2007 Red Hat Inc. <http://www.redhat.com/>
  2. # Copyright (C) 2003 David Zeuthen
  3. # Copyright (C) 2004 Rob Taylor
  4. # Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
  5. #
  6. # Permission is hereby granted, free of charge, to any person
  7. # obtaining a copy of this software and associated documentation
  8. # files (the "Software"), to deal in the Software without
  9. # restriction, including without limitation the rights to use, copy,
  10. # modify, merge, publish, distribute, sublicense, and/or sell copies
  11. # of the Software, and to permit persons to whom the Software is
  12. # furnished to do so, subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be
  15. # included in all copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  18. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  19. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  20. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  21. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  22. # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  24. # DEALINGS IN THE SOFTWARE.
  25. import logging
  26. try:
  27. from threading import RLock
  28. except ImportError:
  29. from dummy_threading import RLock
  30. import _dbus_bindings
  31. from dbus._expat_introspect_parser import process_introspection_data
  32. from dbus.exceptions import (
  33. DBusException, IntrospectionParserException, MissingErrorHandlerException,
  34. MissingReplyHandlerException)
  35. __docformat__ = 'restructuredtext'
  36. _logger = logging.getLogger('dbus.proxies')
  37. from _dbus_bindings import (
  38. BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, INTROSPECTABLE_IFACE,
  39. LOCAL_PATH)
  40. from dbus._compat import is_py2
  41. class _DeferredMethod:
  42. """A proxy method which will only get called once we have its
  43. introspection reply.
  44. """
  45. def __init__(self, proxy_method, append, block):
  46. self._proxy_method = proxy_method
  47. # the test suite relies on the existence of this property
  48. self._method_name = proxy_method._method_name
  49. self._append = append
  50. self._block = block
  51. def __call__(self, *args, **keywords):
  52. if ('reply_handler' in keywords or
  53. keywords.get('ignore_reply', False)):
  54. # defer the async call til introspection finishes
  55. self._append(self._proxy_method, args, keywords)
  56. return None
  57. else:
  58. # we're being synchronous, so block
  59. self._block()
  60. return self._proxy_method(*args, **keywords)
  61. def call_async(self, *args, **keywords):
  62. self._append(self._proxy_method, args, keywords)
  63. class _ProxyMethod:
  64. """A proxy method.
  65. Typically a member of a ProxyObject. Calls to the
  66. method produce messages that travel over the Bus and are routed
  67. to a specific named Service.
  68. """
  69. def __init__(self, proxy, connection, bus_name, object_path, method_name,
  70. iface):
  71. if object_path == LOCAL_PATH:
  72. raise DBusException('Methods may not be called on the reserved '
  73. 'path %s' % LOCAL_PATH)
  74. # trust that the proxy, and the properties it had, are OK
  75. self._proxy = proxy
  76. self._connection = connection
  77. self._named_service = bus_name
  78. self._object_path = object_path
  79. # fail early if the method name is bad
  80. _dbus_bindings.validate_member_name(method_name)
  81. # the test suite relies on the existence of this property
  82. self._method_name = method_name
  83. # fail early if the interface name is bad
  84. if iface is not None:
  85. _dbus_bindings.validate_interface_name(iface)
  86. self._dbus_interface = iface
  87. def __call__(self, *args, **keywords):
  88. reply_handler = keywords.pop('reply_handler', None)
  89. error_handler = keywords.pop('error_handler', None)
  90. ignore_reply = keywords.pop('ignore_reply', False)
  91. signature = keywords.pop('signature', None)
  92. if reply_handler is not None or error_handler is not None:
  93. if reply_handler is None:
  94. raise MissingReplyHandlerException()
  95. elif error_handler is None:
  96. raise MissingErrorHandlerException()
  97. elif ignore_reply:
  98. raise TypeError('ignore_reply and reply_handler cannot be '
  99. 'used together')
  100. dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
  101. if signature is None:
  102. if dbus_interface is None:
  103. key = self._method_name
  104. else:
  105. key = dbus_interface + '.' + self._method_name
  106. signature = self._proxy._introspect_method_map.get(key, None)
  107. if ignore_reply or reply_handler is not None:
  108. self._connection.call_async(self._named_service,
  109. self._object_path,
  110. dbus_interface,
  111. self._method_name,
  112. signature,
  113. args,
  114. reply_handler,
  115. error_handler,
  116. **keywords)
  117. else:
  118. return self._connection.call_blocking(self._named_service,
  119. self._object_path,
  120. dbus_interface,
  121. self._method_name,
  122. signature,
  123. args,
  124. **keywords)
  125. def call_async(self, *args, **keywords):
  126. reply_handler = keywords.pop('reply_handler', None)
  127. error_handler = keywords.pop('error_handler', None)
  128. signature = keywords.pop('signature', None)
  129. dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
  130. if signature is None:
  131. if dbus_interface:
  132. key = dbus_interface + '.' + self._method_name
  133. else:
  134. key = self._method_name
  135. signature = self._proxy._introspect_method_map.get(key, None)
  136. self._connection.call_async(self._named_service,
  137. self._object_path,
  138. dbus_interface,
  139. self._method_name,
  140. signature,
  141. args,
  142. reply_handler,
  143. error_handler,
  144. **keywords)
  145. class ProxyObject(object):
  146. """A proxy to the remote Object.
  147. A ProxyObject is provided by the Bus. ProxyObjects
  148. have member functions, and can be called like normal Python objects.
  149. """
  150. ProxyMethodClass = _ProxyMethod
  151. DeferredMethodClass = _DeferredMethod
  152. INTROSPECT_STATE_DONT_INTROSPECT = 0
  153. INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
  154. INTROSPECT_STATE_INTROSPECT_DONE = 2
  155. def __init__(self, conn=None, bus_name=None, object_path=None,
  156. introspect=True, follow_name_owner_changes=False, **kwargs):
  157. """Initialize the proxy object.
  158. :Parameters:
  159. `conn` : `dbus.connection.Connection`
  160. The bus or connection on which to find this object.
  161. The keyword argument `bus` is a deprecated alias for this.
  162. `bus_name` : str
  163. A bus name for the application owning the object, to be used
  164. as the destination for method calls and the sender for
  165. signal matches. The keyword argument ``named_service`` is a
  166. deprecated alias for this.
  167. `object_path` : str
  168. The object path at which the application exports the object
  169. `introspect` : bool
  170. If true (default), attempt to introspect the remote
  171. object to find out supported methods and their signatures
  172. `follow_name_owner_changes` : bool
  173. If true (default is false) and the `bus_name` is a
  174. well-known name, follow ownership changes for that name
  175. """
  176. bus = kwargs.pop('bus', None)
  177. if bus is not None:
  178. if conn is not None:
  179. raise TypeError('conn and bus cannot both be specified')
  180. conn = bus
  181. from warnings import warn
  182. warn('Passing the bus parameter to ProxyObject by name is '
  183. 'deprecated: please use positional parameters',
  184. DeprecationWarning, stacklevel=2)
  185. named_service = kwargs.pop('named_service', None)
  186. if named_service is not None:
  187. if bus_name is not None:
  188. raise TypeError('bus_name and named_service cannot both be '
  189. 'specified')
  190. bus_name = named_service
  191. from warnings import warn
  192. warn('Passing the named_service parameter to ProxyObject by name '
  193. 'is deprecated: please use positional parameters',
  194. DeprecationWarning, stacklevel=2)
  195. if kwargs:
  196. raise TypeError('ProxyObject.__init__ does not take these '
  197. 'keyword arguments: %s'
  198. % ', '.join(kwargs.keys()))
  199. if follow_name_owner_changes:
  200. # we don't get the signals unless the Bus has a main loop
  201. # XXX: using Bus internals
  202. conn._require_main_loop()
  203. self._bus = conn
  204. if bus_name is not None:
  205. _dbus_bindings.validate_bus_name(bus_name)
  206. # the attribute is still called _named_service for the moment,
  207. # for the benefit of telepathy-python
  208. self._named_service = self._requested_bus_name = bus_name
  209. _dbus_bindings.validate_object_path(object_path)
  210. self.__dbus_object_path__ = object_path
  211. if not follow_name_owner_changes:
  212. self._named_service = conn.activate_name_owner(bus_name)
  213. #PendingCall object for Introspect call
  214. self._pending_introspect = None
  215. #queue of async calls waiting on the Introspect to return
  216. self._pending_introspect_queue = []
  217. #dictionary mapping method names to their input signatures
  218. self._introspect_method_map = {}
  219. # must be a recursive lock because block() is called while locked,
  220. # and calls the callback which re-takes the lock
  221. self._introspect_lock = RLock()
  222. if not introspect or self.__dbus_object_path__ == LOCAL_PATH:
  223. self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
  224. else:
  225. self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
  226. self._pending_introspect = self._Introspect()
  227. bus_name = property(lambda self: self._named_service, None, None,
  228. """The bus name to which this proxy is bound. (Read-only,
  229. may change.)
  230. If the proxy was instantiated using a unique name, this property
  231. is that unique name.
  232. If the proxy was instantiated with a well-known name and with
  233. ``follow_name_owner_changes`` set false (the default), this
  234. property is the unique name of the connection that owned that
  235. well-known name when the proxy was instantiated, which might
  236. not actually own the requested well-known name any more.
  237. If the proxy was instantiated with a well-known name and with
  238. ``follow_name_owner_changes`` set true, this property is that
  239. well-known name.
  240. """)
  241. requested_bus_name = property(lambda self: self._requested_bus_name,
  242. None, None,
  243. """The bus name which was requested when this proxy was
  244. instantiated.
  245. """)
  246. object_path = property(lambda self: self.__dbus_object_path__,
  247. None, None,
  248. """The object-path of this proxy.""")
  249. # XXX: We don't currently support this because it's the signal receiver
  250. # that's responsible for tracking name owner changes, but it
  251. # seems a natural thing to add in future.
  252. #unique_bus_name = property(lambda self: something, None, None,
  253. # """The unique name of the connection to which this proxy is
  254. # currently bound. (Read-only, may change.)
  255. # """)
  256. def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
  257. """Arrange for the given function to be called when the given signal
  258. is received.
  259. :Parameters:
  260. `signal_name` : str
  261. The name of the signal
  262. `handler_function` : callable
  263. A function to be called when the signal is emitted by
  264. the remote object. Its positional arguments will be the
  265. arguments of the signal; optionally, it may be given
  266. keyword arguments as described below.
  267. `dbus_interface` : str
  268. Optional interface with which to qualify the signal name.
  269. If None (the default) the handler will be called whenever a
  270. signal of the given member name is received, whatever
  271. its interface.
  272. :Keywords:
  273. `utf8_strings` : bool
  274. If True, the handler function will receive any string
  275. arguments as dbus.UTF8String objects (a subclass of str
  276. guaranteed to be UTF-8). If False (default) it will receive
  277. any string arguments as dbus.String objects (a subclass of
  278. unicode).
  279. `byte_arrays` : bool
  280. If True, the handler function will receive any byte-array
  281. arguments as dbus.ByteArray objects (a subclass of str).
  282. If False (default) it will receive any byte-array
  283. arguments as a dbus.Array of dbus.Byte (subclasses of:
  284. a list of ints).
  285. `sender_keyword` : str
  286. If not None (the default), the handler function will receive
  287. the unique name of the sending endpoint as a keyword
  288. argument with this name
  289. `destination_keyword` : str
  290. If not None (the default), the handler function will receive
  291. the bus name of the destination (or None if the signal is a
  292. broadcast, as is usual) as a keyword argument with this name.
  293. `interface_keyword` : str
  294. If not None (the default), the handler function will receive
  295. the signal interface as a keyword argument with this name.
  296. `member_keyword` : str
  297. If not None (the default), the handler function will receive
  298. the signal name as a keyword argument with this name.
  299. `path_keyword` : str
  300. If not None (the default), the handler function will receive
  301. the object-path of the sending object as a keyword argument
  302. with this name
  303. `message_keyword` : str
  304. If not None (the default), the handler function will receive
  305. the `dbus.lowlevel.SignalMessage` as a keyword argument with
  306. this name.
  307. `arg...` : unicode or UTF-8 str
  308. If there are additional keyword parameters of the form
  309. ``arg``\ *n*, match only signals where the *n*\ th argument
  310. is the value given for that keyword parameter. As of this time
  311. only string arguments can be matched (in particular,
  312. object paths and signatures can't).
  313. """
  314. return \
  315. self._bus.add_signal_receiver(handler_function,
  316. signal_name=signal_name,
  317. dbus_interface=dbus_interface,
  318. bus_name=self._named_service,
  319. path=self.__dbus_object_path__,
  320. **keywords)
  321. def _Introspect(self):
  322. kwargs = {}
  323. if is_py2:
  324. kwargs['utf8_strings'] = True
  325. return self._bus.call_async(self._named_service,
  326. self.__dbus_object_path__,
  327. INTROSPECTABLE_IFACE, 'Introspect', '', (),
  328. self._introspect_reply_handler,
  329. self._introspect_error_handler,
  330. require_main_loop=False, **kwargs)
  331. def _introspect_execute_queue(self):
  332. # FIXME: potential to flood the bus
  333. # We should make sure mainloops all have idle handlers
  334. # and do one message per idle
  335. for (proxy_method, args, keywords) in self._pending_introspect_queue:
  336. proxy_method(*args, **keywords)
  337. self._pending_introspect_queue = []
  338. def _introspect_reply_handler(self, data):
  339. self._introspect_lock.acquire()
  340. try:
  341. try:
  342. self._introspect_method_map = process_introspection_data(data)
  343. except IntrospectionParserException as e:
  344. self._introspect_error_handler(e)
  345. return
  346. self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
  347. self._pending_introspect = None
  348. self._introspect_execute_queue()
  349. finally:
  350. self._introspect_lock.release()
  351. def _introspect_error_handler(self, error):
  352. logging.basicConfig()
  353. _logger.error("Introspect error on %s:%s: %s.%s: %s",
  354. self._named_service, self.__dbus_object_path__,
  355. error.__class__.__module__, error.__class__.__name__,
  356. error)
  357. self._introspect_lock.acquire()
  358. try:
  359. _logger.debug('Executing introspect queue due to error')
  360. self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
  361. self._pending_introspect = None
  362. self._introspect_execute_queue()
  363. finally:
  364. self._introspect_lock.release()
  365. def _introspect_block(self):
  366. self._introspect_lock.acquire()
  367. try:
  368. if self._pending_introspect is not None:
  369. self._pending_introspect.block()
  370. # else someone still has a _DeferredMethod from before we
  371. # finished introspection: no need to do anything special any more
  372. finally:
  373. self._introspect_lock.release()
  374. def _introspect_add_to_queue(self, callback, args, kwargs):
  375. self._introspect_lock.acquire()
  376. try:
  377. if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
  378. self._pending_introspect_queue.append((callback, args, kwargs))
  379. else:
  380. # someone still has a _DeferredMethod from before we
  381. # finished introspection
  382. callback(*args, **kwargs)
  383. finally:
  384. self._introspect_lock.release()
  385. def __getattr__(self, member):
  386. if member.startswith('__') and member.endswith('__'):
  387. raise AttributeError(member)
  388. else:
  389. return self.get_dbus_method(member)
  390. def get_dbus_method(self, member, dbus_interface=None):
  391. """Return a proxy method representing the given D-Bus method. The
  392. returned proxy method can be called in the usual way. For instance, ::
  393. proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
  394. is equivalent to::
  395. proxy.Foo(123, dbus_interface='com.example.Bar')
  396. or even::
  397. getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
  398. However, using `get_dbus_method` is the only way to call D-Bus
  399. methods with certain awkward names - if the author of a service
  400. implements a method called ``connect_to_signal`` or even
  401. ``__getattr__``, you'll need to use `get_dbus_method` to call them.
  402. For services which follow the D-Bus convention of CamelCaseMethodNames
  403. this won't be a problem.
  404. """
  405. ret = self.ProxyMethodClass(self, self._bus,
  406. self._named_service,
  407. self.__dbus_object_path__, member,
  408. dbus_interface)
  409. # this can be done without taking the lock - the worst that can
  410. # happen is that we accidentally return a _DeferredMethod just after
  411. # finishing introspection, in which case _introspect_add_to_queue and
  412. # _introspect_block will do the right thing anyway
  413. if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
  414. ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue,
  415. self._introspect_block)
  416. return ret
  417. def __repr__(self):
  418. return '<ProxyObject wrapping %s %s %s at %#x>'%(
  419. self._bus, self._named_service, self.__dbus_object_path__, id(self))
  420. __str__ = __repr__
  421. class Interface(object):
  422. """An interface into a remote object.
  423. An Interface can be used to wrap ProxyObjects
  424. so that calls can be routed to their correct
  425. D-Bus interface.
  426. """
  427. def __init__(self, object, dbus_interface):
  428. """Construct a proxy for the given interface on the given object.
  429. :Parameters:
  430. `object` : `dbus.proxies.ProxyObject` or `dbus.Interface`
  431. The remote object or another of its interfaces
  432. `dbus_interface` : str
  433. An interface the `object` implements
  434. """
  435. if isinstance(object, Interface):
  436. self._obj = object.proxy_object
  437. else:
  438. self._obj = object
  439. self._dbus_interface = dbus_interface
  440. object_path = property (lambda self: self._obj.object_path, None, None,
  441. "The D-Bus object path of the underlying object")
  442. __dbus_object_path__ = object_path
  443. bus_name = property (lambda self: self._obj.bus_name, None, None,
  444. "The bus name to which the underlying proxy object "
  445. "is bound")
  446. requested_bus_name = property (lambda self: self._obj.requested_bus_name,
  447. None, None,
  448. "The bus name which was requested when the "
  449. "underlying object was created")
  450. proxy_object = property (lambda self: self._obj, None, None,
  451. """The underlying proxy object""")
  452. dbus_interface = property (lambda self: self._dbus_interface, None, None,
  453. """The D-Bus interface represented""")
  454. def connect_to_signal(self, signal_name, handler_function,
  455. dbus_interface=None, **keywords):
  456. """Arrange for a function to be called when the given signal is
  457. emitted.
  458. The parameters and keyword arguments are the same as for
  459. `dbus.proxies.ProxyObject.connect_to_signal`, except that if
  460. `dbus_interface` is None (the default), the D-Bus interface that
  461. was passed to the `Interface` constructor is used.
  462. """
  463. if not dbus_interface:
  464. dbus_interface = self._dbus_interface
  465. return self._obj.connect_to_signal(signal_name, handler_function,
  466. dbus_interface, **keywords)
  467. def __getattr__(self, member):
  468. if member.startswith('__') and member.endswith('__'):
  469. raise AttributeError(member)
  470. else:
  471. return self._obj.get_dbus_method(member, self._dbus_interface)
  472. def get_dbus_method(self, member, dbus_interface=None):
  473. """Return a proxy method representing the given D-Bus method.
  474. This is the same as `dbus.proxies.ProxyObject.get_dbus_method`
  475. except that if `dbus_interface` is None (the default),
  476. the D-Bus interface that was passed to the `Interface` constructor
  477. is used.
  478. """
  479. if dbus_interface is None:
  480. dbus_interface = self._dbus_interface
  481. return self._obj.get_dbus_method(member, dbus_interface)
  482. def __repr__(self):
  483. return '<Interface %r implementing %r at %#x>'%(
  484. self._obj, self._dbus_interface, id(self))
  485. __str__ = __repr__