diff options
| -rw-r--r-- | ChangeLog | 24 | ||||
| -rw-r--r-- | python/_dbus.py | 75 | ||||
| -rw-r--r-- | python/dbus_bindings.pyx | 9 | ||||
| -rw-r--r-- | python/service.py | 62 | ||||
| -rwxr-xr-x | test/python/test-client.py | 60 | 
5 files changed, 198 insertions, 32 deletions
| @@ -1,3 +1,27 @@ +2005-11-07  Robert McQueen  <robot101@debian.org> + +	* python/_dbus.py: Add WeakReferenceDictionary cache of dbus.Bus +	instances to stop madness of creating new instances representing +	the same bus connection all the time, rendering any tracking of +	match rules and bus names quite meaningless. Caught a bug where +	the private argument to SessionBus() and friends was being passed +	in as use_default_mainloop by mistake. Still some problems with +	multiple dbus_binding.Connection instances representing the same +	low-level connection (eg when you use both SessionBus() and +	StarterBus() in same process), but it's a lot better now than it +	was. + +	* python/dbus_bindings.pyx: Add constants with the return values +	for bus_request_name(). + +	* python/service.py: Store bus name instances in a per-dbus.Bus cache +	and retrieve the same instances for the same name, so deletion can be +	done with refcounting. Also now throws some kind of error if you +	don't actually get the name you requested, unlike previously... + +	* test/python/test-client.py: Add tests for instance caching of buses +	and bus name objects. +  2005-11-04  Robert McQueen  <robot101@debian.org>  	* python/dbus_bindings.pyx, test/python/test-client.py: Fix diff --git a/python/_dbus.py b/python/_dbus.py index bb4c0428..2376f172 100644 --- a/python/_dbus.py +++ b/python/_dbus.py @@ -42,14 +42,14 @@ print(dbus_object.ListServices())  """  import dbus -  import dbus_bindings +import weakref  from proxies import *  from exceptions import *  from matchrules import * -class Bus: +class Bus(object):      """A connection to a DBus daemon.      One of three possible standard buses, the SESSION, SYSTEM, @@ -65,19 +65,54 @@ class Bus:      ProxyObjectClass = ProxyObject      START_REPLY_SUCCESS = dbus_bindings.DBUS_START_REPLY_SUCCESS -    START_REPLY_ALREADY_RUNNING = dbus_bindings.DBUS_START_REPLY_ALREADY_RUNNING  +    START_REPLY_ALREADY_RUNNING = dbus_bindings.DBUS_START_REPLY_ALREADY_RUNNING + +    _shared_instances = weakref.WeakValueDictionary() + +    def __new__(cls, bus_type=TYPE_SESSION, use_default_mainloop=True, private=False): +        if (not private and bus_type in cls._shared_instances): +            return cls._shared_instances[bus_type] + +        # this is a bit odd, but we create instances of the subtypes +        # so we can return the shared instances if someone tries to +        # construct one of them (otherwise we'd eg try and return an +        # instance of Bus from __new__ in SessionBus). why are there +        # three ways to construct this class? we just don't know. +        if bus_type == cls.TYPE_SESSION: +            subclass = SessionBus +        elif bus_type == cls.TYPE_SYSTEM: +            subclass = SystemBus +        elif bus_type == cls.TYPE_STARTER: +            subclass = StarterBus +        else: +            raise ValueError('invalid bus_type %s' % bus_type) + +        bus = object.__new__(subclass) -    def __init__(self, bus_type=TYPE_SESSION, use_default_mainloop=True, private=False): -        self._bus_type = bus_type -        self._connection = dbus_bindings.bus_get(bus_type, private) +        bus._bus_type = bus_type +        bus._bus_names = weakref.WeakValueDictionary() +        bus._match_rule_tree = SignalMatchTree() -        self._connection.add_filter(self._signal_func) -        self._match_rule_tree = SignalMatchTree() +        # FIXME: if you get a starter and a system/session bus connection +        # in the same process, it's the same underlying connection that +        # is returned by bus_get, but we initialise it twice +        bus._connection = dbus_bindings.bus_get(bus_type, private) +        bus._connection.add_filter(bus._signal_func)          if use_default_mainloop:              func = getattr(dbus, "_dbus_mainloop_setup_function", None) -            if func != None: -                func(self) +            if func: +                func(bus) + +        if not private: +            cls._shared_instances[bus_type] = bus + +        return bus + +    def __init__(self, *args, **keywords): +        # do nothing here because this can get called multiple times on the +        # same object if __new__ returns a shared instance +        pass      def close(self):          self._connection.close() @@ -87,20 +122,20 @@ class Bus:      def get_session(private=False):          """Static method that returns the session bus""" -        return SessionBus(private) +        return SessionBus(private=private)      get_session = staticmethod(get_session)      def get_system(private=False):          """Static method that returns the system bus""" -        return SystemBus(private) +        return SystemBus(private=private)      get_system = staticmethod(get_system)      def get_starter(private=False):          """Static method that returns the starter bus""" -        return StarterBus(private) +        return StarterBus(private=private)      get_starter = staticmethod(get_starter) @@ -213,24 +248,24 @@ class Bus:  class SystemBus(Bus):      """The system-wide message bus      """ -    def __init__(self, use_default_mainloop=True, private=False): -        Bus.__init__(self, Bus.TYPE_SYSTEM, use_default_mainloop, private) +    def __new__(cls, use_default_mainloop=True, private=False): +        return Bus.__new__(cls, Bus.TYPE_SYSTEM, use_default_mainloop, private)  class SessionBus(Bus):      """The session (current login) message bus      """ -    def __init__(self, use_default_mainloop=True, private=False): -        Bus.__init__(self, Bus.TYPE_SESSION, use_default_mainloop, private) +    def __new__(cls, use_default_mainloop=True, private=False): +        return Bus.__new__(cls, Bus.TYPE_SESSION, use_default_mainloop, private)  class StarterBus(Bus):      """The bus that activated this process (if      this process was launched by DBus activation)      """ -    def __init__(self, use_default_mainloop=True, private=False): -        Bus.__init__(self, Bus.TYPE_STARTER, use_default_mainloop, private) +    def __new__(cls, use_default_mainloop=True, private=False): +        return Bus.__new__(cls, Bus.TYPE_STARTER, use_default_mainloop, private)  class Interface: -    """An inteface into a remote object +    """An interface into a remote object      An Interface can be used to wrap ProxyObjects      so that calls can be routed to their correct diff --git a/python/dbus_bindings.pyx b/python/dbus_bindings.pyx index a96a5adc..75e448ee 100644 --- a/python/dbus_bindings.pyx +++ b/python/dbus_bindings.pyx @@ -1748,8 +1748,13 @@ def bus_register(Connection connection):      return retval -SERVICE_FLAG_PROHIBIT_REPLACEMENT = 0x1 -SERVICE_FLAG_REPLACE_EXISTING     = 0x2 +NAME_FLAG_PROHIBIT_REPLACEMENT = 0x1 +NAME_FLAG_REPLACE_EXISTING     = 0x2 + +REQUEST_NAME_REPLY_PRIMARY_OWNER = 1 +REQUEST_NAME_REPLY_IN_QUEUE      = 2 +REQUEST_NAME_REPLY_EXISTS        = 3 +REQUEST_NAME_REPLY_ALREADY_OWNER = 4  def bus_request_name(Connection connection, service_name, flags=0):      cdef DBusError error diff --git a/python/service.py b/python/service.py index 14a2d6d3..d1973b04 100644 --- a/python/service.py +++ b/python/service.py @@ -6,19 +6,60 @@ from exceptions import UnknownMethodException  from decorators import method  from decorators import signal -class BusName: +class BusName(object):      """A base class for exporting your own Named Services across the Bus      """ -    def __init__(self, named_service, bus=None): -        self._named_service = named_service -                              +    def __new__(cls, name, bus=None): +        # get default bus          if bus == None: -            # Get the default bus -            self._bus = _dbus.Bus() +            bus = _dbus.Bus() + +        # see if this name is already defined, return it if so +        if name in bus._bus_names: +            return bus._bus_names[name] + +        # otherwise register the name +        retval = dbus_bindings.bus_request_name(bus.get_connection(), name) +        print retval +        # TODO: more intelligent tracking of bus name states? +        if retval == dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER: +            pass +        elif retval == dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE: +            # you can't arrive at this state via the high-level bindings +            # because you can't put flags in, but... who knows? +            print "joined queue for %s" % name +            pass +        elif retval == dbus_bindings.REQUEST_NAME_REPLY_EXISTS: +            raise dbus_bindings.DBusException('requested name %s already exists' % name) +        elif retval == dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER: +            # if this is a shared bus which is being used by someone +            # else in this process, this can happen legitimately +            print "already owner of %s" % name +            pass          else: -            self._bus = bus +            raise dbus_bindings.DBusException('requesting name %s returned unexpected value %s' % (name, retval)) -        dbus_bindings.bus_request_name(self._bus.get_connection(), named_service) +        # and create the object +        bus_name = object.__new__(cls) +        bus_name._bus = bus +        bus_name._name = name + +        # cache instance +        bus._bus_names[name] = bus_name + +        return bus_name + +    # do nothing because this is called whether or not the bus name +    # object was retrieved from the cache or created new +    def __init__(self, *args, **keywords): +        pass + +    # we can delete the low-level name here because these objects +    # are guaranteed to exist only once for each bus name +    def __del__(self): +        # FIXME: we don't have this function yet :) +        #dbus_bindings.bus_release_name(self._bus.get_connection(), self._named_service) +        pass      def get_bus(self):          """Get the Bus this Service is on""" @@ -26,10 +67,10 @@ class BusName:      def get_name(self):          """Get the name of this service""" -        return self._named_service +        return self._name      def __repr__(self): -        return '<dbus.service.BusName %s on %r at %#x>' % (self._named_service, self._bus, id(self)) +        return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))      __str__ = __repr__ @@ -322,3 +363,4 @@ class Object(Interface):      def __repr__(self):          return '<dbus.service.Object %s on %r at %#x>' % (self._object_path, self._name, id(self))      __str__ = __repr__ + diff --git a/test/python/test-client.py b/test/python/test-client.py index 8c09b67e..26ce375c 100755 --- a/test/python/test-client.py +++ b/test/python/test-client.py @@ -14,6 +14,7 @@ import dbus  import dbus_bindings  import gobject  import dbus.glib +import dbus.service  if not dbus.__file__.startswith(pydir):      raise Exception("DBus modules are not being picked up from the package") @@ -169,6 +170,65 @@ class TestDBusBindings(unittest.TestCase):                  print val, ret                  self.assert_(val == ret) +    def testBusInstanceCaching(self): +        print "\n********* Testing dbus.Bus instance sharing *********" + +        # unfortunately we can't test the system bus here +        # but the codepaths are the same +        for (cls, type, func) in ((dbus.SessionBus, dbus.Bus.TYPE_SESSION, dbus.Bus.get_session), (dbus.StarterBus, dbus.Bus.TYPE_STARTER, dbus.Bus.get_starter)): +            print "\nTesting %s:" % cls.__name__ + +            share_cls = cls() +            share_type = dbus.Bus(bus_type=type) +            share_func = func() + +            private_cls = cls(private=True) +            private_type = dbus.Bus(bus_type=type, private=True) +            private_func = func(private=True) + +            print " - checking shared instances are the same..." +            self.assert_(share_cls == share_type, '%s should equal %s' % (share_cls, share_type)) +            self.assert_(share_type == share_func, '%s should equal %s' % (share_type, share_func)) + +            print " - checking private instances are distinct from the shared instance..." +            self.assert_(share_cls != private_cls, '%s should not equal %s' % (share_cls, private_cls)) +            self.assert_(share_type != private_type, '%s should not equal %s' % (share_type, private_type)) +            self.assert_(share_func != private_func, '%s should not equal %s' % (share_func, private_func)) + +            print " - checking private instances are distinct from each other..." +            self.assert_(private_cls != private_type, '%s should not equal %s' % (private_cls, private_type)) +            self.assert_(private_type != private_func, '%s should not equal %s' % (private_type, private_func)) +            self.assert_(private_func != private_cls, '%s should not equal %s' % (private_func, private_cls)) + +    def testBusNameCreation(self): +        print '\n******** Testing BusName creation ********' +        test = [('org.freedesktop.DBus.Python.TestName', True), +                ('org.freedesktop.DBus.Python.TestName', True), +                ('org.freedesktop.DBus.Python.InvalidName&^*%$', False), +                ('org.freedesktop.DBus.TestSuitePythonService', False)] +        # For some reason this actually succeeds +        # ('org.freedesktop.DBus', False)] + +        # make a method call to ensure the test service is active +        self.iface.Echo("foo") + +        names = {} +        for (name, succeed) in test: +            try: +                print "requesting %s" % name +                busname = dbus.service.BusName(name) +            except Exception, e: +                print "%s:\n%s" % (e.__class__, e) +                self.assert_(not succeed, 'did not expect registering bus name %s to fail' % name) +            else: +                print busname +                self.assert_(succeed, 'expected registering bus name %s to fail'% name) +                if name in names: +                    self.assert_(names[name] == busname, 'got a new instance for same name %s' % name) +                    print "instance of %s re-used, good!" % name +                else: +                    names[name] = busname +  class TestDBusPythonToGLibBindings(unittest.TestCase):      def setUp(self):          self.bus = dbus.SessionBus() | 
