From 3c62924718ff00839843cecaae09598e36199f79 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Mon, 30 Apr 2007 11:31:22 +0100 Subject: [PATCH] Move the client method-call machinery from dbus.proxies to dbus.connection._MethodCallMixin. This makes proxy methods much simpler, and allows the _BusDaemonMixin to bypass the proxies module completely (since the signatures are already known, so we don't need to introspect anything). --- dbus/Makefile.am | 1 + dbus/_dbus.py | 7 ++- dbus/bus.py | 76 +++++++++++-------------- dbus/connection.py | 139 +++++++++++++++++++++++++++++++++++++++++++++ dbus/proxies.py | 161 +++++++++++++++++++++-------------------------------- 5 files changed, 241 insertions(+), 143 deletions(-) create mode 100644 dbus/connection.py diff --git a/dbus/Makefile.am b/dbus/Makefile.am index 53efa89..271c899 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -1,6 +1,7 @@ pythondbusdir = $(pythondir)/dbus nobase_pythondbus_PYTHON = bus.py \ + connection.py \ dbus_bindings.py \ _dbus.py \ _version.py \ diff --git a/dbus/_dbus.py b/dbus/_dbus.py index 6812127..3eba4b2 100644 --- a/dbus/_dbus.py +++ b/dbus/_dbus.py @@ -36,9 +36,10 @@ import sys import weakref from traceback import print_exc +from _dbus_bindings import BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE from dbus.bus import _BusDaemonMixin -from dbus.proxies import ProxyObject, BUS_DAEMON_NAME, BUS_DAEMON_PATH, \ - BUS_DAEMON_IFACE +from dbus.connection import _MethodCallMixin +from dbus.proxies import ProxyObject try: import thread @@ -221,7 +222,7 @@ class SignalMatch(object): **self._args_match) -class Bus(_dbus_bindings.Connection, _BusDaemonMixin): +class Bus(_dbus_bindings.Connection, _MethodCallMixin, _BusDaemonMixin): """A connection to a DBus daemon. One of three possible standard buses, the SESSION, SYSTEM, diff --git a/dbus/bus.py b/dbus/bus.py index 20bc97f..053666d 100644 --- a/dbus/bus.py +++ b/dbus/bus.py @@ -18,21 +18,19 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -from dbus import UInt32, UTF8String -from dbus.proxies import BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE +BUS_DAEMON_NAME = 'org.freedesktop.DBus' +BUS_DAEMON_PATH = '/org/freedesktop/DBus' +BUS_DAEMON_IFACE = BUS_DAEMON_NAME from _dbus_bindings import validate_interface_name, validate_member_name,\ validate_bus_name, validate_object_path,\ validate_error_name -def _noop(*args, **kwargs): - """A universal no-op function""" class _BusDaemonMixin(object): - """This mixin must be mixed-in with something with a get_object method - (obviously, it's meant to be the dbus.Bus). It provides simple blocking - wrappers for various methods on the org.freedesktop.DBus bus-daemon - object, to reduce the amount of C code we need. + """This mixin provides simple blocking wrappers for various methods on + the org.freedesktop.DBus bus-daemon object, to reduce the amount of C + code we need. """ def get_unix_user(self, bus_name): @@ -44,9 +42,9 @@ class _BusDaemonMixin(object): :Returns: a `dbus.UInt32` """ validate_bus_name(bus_name) - return self.get_object(BUS_DAEMON_NAME, - BUS_DAEMON_PATH).GetConnectionUnixUser(bus_name, - dbus_interface=BUS_DAEMON_IFACE) + return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, + BUS_DAEMON_IFACE, 'GetConnectionUnixUser', + 's', (bus_name,)) def start_service_by_name(self, bus_name, flags=0): """Start a service which will implement the given bus name on this Bus. @@ -65,10 +63,10 @@ class _BusDaemonMixin(object): :Raises DBusException: if the service could not be started. """ validate_bus_name(bus_name) - ret = self.get_object(BUS_DAEMON_NAME, - BUS_DAEMON_PATH).StartServiceByName(bus_name, UInt32(flags), - dbus_interface=BUS_DAEMON_IFACE) - return (True, ret) + return (True, self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, + BUS_DAEMON_IFACE, + 'StartServiceByName', + 'su', (bus_name, flags))) # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception, # but this would not be backwards-compatible @@ -91,9 +89,9 @@ class _BusDaemonMixin(object): returns an error. """ validate_bus_name(name, allow_unique=False) - return self.get_object(BUS_DAEMON_NAME, - BUS_DAEMON_PATH).RequestName(name, UInt32(flags), - dbus_interface=BUS_DAEMON_IFACE) + return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, + BUS_DAEMON_IFACE, 'RequestName', + 'su', (name, flags)) def release_name(self, name): """Release a bus name. @@ -108,9 +106,9 @@ class _BusDaemonMixin(object): returns an error. """ validate_bus_name(name, allow_unique=False) - return self.get_object(BUS_DAEMON_NAME, - BUS_DAEMON_PATH).ReleaseName(name, - dbus_interface=BUS_DAEMON_IFACE) + return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, + BUS_DAEMON_IFACE, 'ReleaseName', + 's', (name,)) def name_has_owner(self, bus_name): """Return True iff the given bus name has an owner on this bus. @@ -120,12 +118,9 @@ class _BusDaemonMixin(object): The bus name to look up :Returns: a `bool` """ - return bool(self.get_object(BUS_DAEMON_NAME, - BUS_DAEMON_PATH).NameHasOwner(bus_name, - dbus_interface=BUS_DAEMON_IFACE)) - - # AddMatchString is not bound here - # RemoveMatchString either + return bool(self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, + BUS_DAEMON_IFACE, 'NameHasOwner', + 's', (bus_name,))) def add_match_string(self, rule): """Arrange for this application to receive messages on the bus that @@ -136,12 +131,10 @@ class _BusDaemonMixin(object): The match rule :Raises: `DBusException` on error. """ - self.get_object(BUS_DAEMON_NAME, - BUS_DAEMON_PATH).AddMatch(rule, - dbus_interface=BUS_DAEMON_IFACE) + self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, + BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,)) # FIXME: add an async success/error handler capability? - # FIXME: tell the bus daemon not to bother sending us a reply # (and the same for remove_...) def add_match_string_non_blocking(self, rule): """Arrange for this application to receive messages on the bus that @@ -154,11 +147,9 @@ class _BusDaemonMixin(object): The match rule :Raises: `DBusException` on error. """ - self.get_object(BUS_DAEMON_NAME, - BUS_DAEMON_PATH).AddMatch(rule, - dbus_interface=BUS_DAEMON_IFACE, - reply_handler=_noop, - error_handler=_noop) + self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH, + BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,), + None, None) def remove_match_string(self, rule): """Arrange for this application to receive messages on the bus that @@ -169,9 +160,8 @@ class _BusDaemonMixin(object): The match rule :Raises: `DBusException` on error. """ - self.get_object(BUS_DAEMON_NAME, - BUS_DAEMON_PATH).RemoveMatch(rule, - dbus_interface=BUS_DAEMON_IFACE) + self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, + BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,)) def remove_match_string_non_blocking(self, rule): """Arrange for this application to receive messages on the bus that @@ -184,8 +174,6 @@ class _BusDaemonMixin(object): The match rule :Raises: `DBusException` on error. """ - self.get_object(BUS_DAEMON_NAME, - BUS_DAEMON_PATH).RemoveMatch(rule, - dbus_interface=BUS_DAEMON_IFACE, - reply_handler=_noop, - error_handler=_noop) + self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH, + BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,), + None, None) diff --git a/dbus/connection.py b/dbus/connection.py new file mode 100644 index 0000000..ef6f984 --- /dev/null +++ b/dbus/connection.py @@ -0,0 +1,139 @@ +"""Method-call mixin for use within dbus-python only. +See `_MethodCallMixin`. +""" + +# Copyright (C) 2007 Collabora Ltd. +# +# Licensed under the Academic Free License version 2.1 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import logging + +from _dbus_bindings import Connection, ErrorMessage, \ + MethodCallMessage, MethodReturnMessage, \ + DBusException + + +# This is special in libdbus - the bus daemon will kick us off if we try to +# send any message to it :-/ +LOCAL_PATH = '/org/freedesktop/DBus/Local' + + +_logger = logging.getLogger('dbus.methods') + + +def _noop(*args, **kwargs): + pass + + +class _MethodCallMixin(object): + + def call_async(self, bus_name, object_path, dbus_interface, method, + signature, args, reply_handler, error_handler, + timeout=-1.0, utf8_strings=False, byte_arrays=False, + require_main_loop=True): + """Call the given method, asynchronously. + + If the reply_handler is None, successful replies will be ignored. + If the error_handler is None, failures will be ignored. If both + are None, the implementation may request that no reply is sent. + + :Returns: The dbus.lowlevel.PendingCall. + """ + if object_path == LOCAL_PATH: + raise DBusException('Methods may not be called on the reserved ' + 'path %s' % LOCAL_PATH) + # no need to validate other args - MethodCallMessage ctor will do + + get_args_opts = {'utf8_strings': utf8_strings, + 'byte_arrays': byte_arrays} + + message = MethodCallMessage(destination=bus_name, + path=object_path, + interface=dbus_interface, + method=method) + # Add the arguments to the function + try: + message.append(signature=signature, *args) + except Exception, e: + _logger.error('Unable to set arguments %r according to ' + 'signature %r: %s: %s', + args, signature, e.__class__, e) + raise + + if reply_handler is None and error_handler is None: + # we don't care what happens, so just send it + self.send_message(message) + return + + if reply_handler is None: + reply_handler = _noop + if error_handler is None: + error_handler = _noop + + def msg_reply_handler(message): + if isinstance(message, MethodReturnMessage): + reply_handler(*message.get_args_list(**get_args_opts)) + elif isinstance(message, ErrorMessage): + args = message.get_args_list() + # FIXME: should we do something with the rest? + if len(args) > 0: + error_handler(DBusException(args[0])) + else: + error_handler(DBusException()) + else: + error_handler(TypeError('Unexpected type for reply ' + 'message: %r' % message)) + return self.send_message_with_reply(message, msg_reply_handler, + timeout/1000.0, + require_main_loop=require_main_loop) + + def call_blocking(self, bus_name, object_path, dbus_interface, method, + signature, args, timeout=-1.0, utf8_strings=False, + byte_arrays=False): + """Call the given method, synchronously. + """ + if object_path == LOCAL_PATH: + raise DBusException('Methods may not be called on the reserved ' + 'path %s' % LOCAL_PATH) + # no need to validate other args - MethodCallMessage ctor will do + + get_args_opts = {'utf8_strings': utf8_strings, + 'byte_arrays': byte_arrays} + + message = MethodCallMessage(destination=bus_name, + path=object_path, + interface=dbus_interface, + method=method) + # Add the arguments to the function + try: + message.append(signature=signature, *args) + except Exception, e: + _logger.error('Unable to set arguments %r according to ' + 'signature %r: %s: %s', + args, signature, e.__class__, e) + raise + + # make a blocking call + reply_message = self.send_message_with_reply_and_block( + message, timeout) + args_list = reply_message.get_args_list(**get_args_opts) + if len(args_list) == 0: + return None + elif len(args_list) == 1: + return args_list[0] + else: + return tuple(args_list) diff --git a/dbus/proxies.py b/dbus/proxies.py index a645f3e..027d99d 100644 --- a/dbus/proxies.py +++ b/dbus/proxies.py @@ -36,35 +36,10 @@ __docformat__ = 'restructuredtext' _logger = logging.getLogger('dbus.proxies') +from _dbus_bindings import LOCAL_PATH, \ + BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE -BUS_DAEMON_NAME = 'org.freedesktop.DBus' -BUS_DAEMON_PATH = '/org/freedesktop/DBus' -BUS_DAEMON_IFACE = BUS_DAEMON_NAME - -# This is special in libdbus - the bus daemon will kick us off if we try to -# send any message to it :-/ -LOCAL_PATH = '/org/freedesktop/DBus/Local' - - -class _ReplyHandler(object): - __slots__ = ('_on_error', '_on_reply', '_get_args_options') - def __init__(self, on_reply, on_error, **get_args_options): - self._on_error = on_error - self._on_reply = on_reply - self._get_args_options = get_args_options - - def __call__(self, message): - if isinstance(message, _dbus_bindings.MethodReturnMessage): - self._on_reply(*message.get_args_list(**self._get_args_options)) - elif isinstance(message, _dbus_bindings.ErrorMessage): - args = message.get_args_list() - if len(args) > 0: - self._on_error(DBusException(args[0])) - else: - self._on_error(DBusException()) - else: - self._on_error(DBusException('Unexpected reply message type: %s' - % message)) +INTROSPECTABLE_IFACE = 'org.freedesktop.DBus.Introspectable' class _DeferredMethod: @@ -88,6 +63,9 @@ class _DeferredMethod: self._block() return self._proxy_method(*args, **keywords) + def call_async(self, *args, **keywords): + self._append(self._proxy_method, args, keywords) + class _ProxyMethod: """A proxy method. @@ -116,78 +94,67 @@ class _ProxyMethod: self._dbus_interface = iface def __call__(self, *args, **keywords): - timeout = -1 - if keywords.has_key('timeout'): - timeout = keywords['timeout'] - - reply_handler = None - if keywords.has_key('reply_handler'): - reply_handler = keywords['reply_handler'] + reply_handler = keywords.pop('reply_handler', None) + error_handler = keywords.pop('error_handler', None) + ignore_reply = keywords.pop('ignore_reply', False) - error_handler = None - if keywords.has_key('error_handler'): - error_handler = keywords['error_handler'] - - ignore_reply = False - if keywords.has_key('ignore_reply'): - ignore_reply = keywords['ignore_reply'] - - get_args_options = {} - if keywords.has_key('utf8_strings'): - get_args_options['utf8_strings'] = keywords['utf8_strings'] - if keywords.has_key('byte_arrays'): - get_args_options['byte_arrays'] = keywords['byte_arrays'] - - if not(reply_handler and error_handler): - if reply_handler: + if reply_handler is not None or error_handler is not None: + if reply_handler is None: raise MissingErrorHandlerException() - elif error_handler: + elif error_handler is None: raise MissingReplyHandlerException() + elif ignore_reply: + raise TypeError('ignore_reply and reply_handler cannot be ' + 'used together') - dbus_interface = self._dbus_interface - if keywords.has_key('dbus_interface'): - dbus_interface = keywords['dbus_interface'] - - tmp_iface = '' - if dbus_interface: - tmp_iface = dbus_interface + '.' + dbus_interface = keywords.pop('dbus_interface', self._dbus_interface) - key = tmp_iface + self._method_name + if dbus_interface is None: + key = self._method_name + else: + key = dbus_interface + '.' + self._method_name + introspect_sig = self._proxy._introspect_method_map.get(key, None) + + if ignore_reply or reply_handler is not None: + self._connection.call_async(self._named_service, + self._object_path, + dbus_interface, + self._method_name, + introspect_sig, + args, + reply_handler, + error_handler, + **keywords) + else: + return self._connection.call_blocking(self._named_service, + self._object_path, + dbus_interface, + self._method_name, + introspect_sig, + args, + **keywords) - introspect_sig = None - if self._proxy._introspect_method_map.has_key (key): - introspect_sig = self._proxy._introspect_method_map[key] + def call_async(self, *args, **keywords): + reply_handler = keywords.pop('reply_handler', None) + error_handler = keywords.pop('error_handler', None) - message = _dbus_bindings.MethodCallMessage(destination=None, - path=self._object_path, - interface=dbus_interface, - method=self._method_name) - message.set_destination(self._named_service) + dbus_interface = keywords.pop('dbus_interface', self._dbus_interface) - # Add the arguments to the function - try: - message.append(signature=introspect_sig, *args) - except Exception, e: - _logger.error('Unable to set arguments %r according to ' - 'introspected signature %r: %s: %s', - args, introspect_sig, e.__class__, e) - raise - - if ignore_reply: - self._connection.send_message(message) - return None - elif reply_handler: - self._connection.send_message_with_reply(message, _ReplyHandler(reply_handler, error_handler, **get_args_options), timeout/1000.0, require_main_loop=1) - return None + if dbus_interface: + key = dbus_interface + '.' + self._method_name else: - reply_message = self._connection.send_message_with_reply_and_block(message, timeout) - args_list = reply_message.get_args_list(**get_args_options) - if len(args_list) == 0: - return None - elif len(args_list) == 1: - return args_list[0] - else: - return tuple(args_list) + key = self._method_name + introspect_sig = self._proxy._introspect_method_map.get(key, None) + + self._connection.call_async(self._named_service, + self._object_path, + dbus_interface, + self._method_name, + introspect_sig, + args, + reply_handler, + error_handler, + **keywords) class ProxyObject(object): @@ -376,11 +343,13 @@ class ProxyObject(object): **keywords) def _Introspect(self): - message = _dbus_bindings.MethodCallMessage(None, self.__dbus_object_path__, 'org.freedesktop.DBus.Introspectable', 'Introspect') - message.set_destination(self._named_service) - - result = self._bus.get_connection().send_message_with_reply(message, _ReplyHandler(self._introspect_reply_handler, self._introspect_error_handler, utf8_strings=True), -1) - return result + return self._bus.call_async(self._named_service, + self.__dbus_object_path__, + INTROSPECTABLE_IFACE, 'Introspect', '', (), + self._introspect_reply_handler, + self._introspect_error_handler, + utf8_strings=True, + require_main_loop=False) def _introspect_execute_queue(self): # FIXME: potential to flood the bus -- 2.11.4.GIT