2008-05-16 Mark Doffman <mark.doffman@codethink.co.uk>
authorMark Doffman <mdoff@silver-wind.(none)>
Tue, 20 May 2008 16:45:59 +0000 (17:45 +0100)
committerMark Doffman <mdoff@silver-wind.(none)>
Tue, 20 May 2008 16:45:59 +0000 (17:45 +0100)
* test/accessible_cache.py
* test/testClient.py
  Extend the accessible_cache to return
  D-Bus proxy objects for the remote accessible
  objects.

tests/AccessibleTreeCache.py [deleted file]
tests/accessible_cache.py [new file with mode: 0644]
tests/testClient.py

diff --git a/tests/AccessibleTreeCache.py b/tests/AccessibleTreeCache.py
deleted file mode 100644 (file)
index f762101..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-import dbus
-
-class AccessibleObjectDoesNotExist(Exception):
-       def __init__(self, path):
-               self.path = path
-       
-       def __str__(self):
-               return "Object %s does not exist" % (self.path)
-
-class AccessibleTreeCache():
-       """
-       Caches a collection of Accessible Objects.
-       """
-
-       _TREE_INTERFACE = 'org.freedesktop.atspi.Tree'
-       _UPDATE_SIGNAL = 'updateTree'
-
-       def __init__(self, connection, busName, objectStorePath):
-               """
-               Creates a cache for accessible objects.
-
-               All accessible objects are created and accessed through this cache.
-
-               Parameters:
-               connection - DBus connection.
-               busName - DBus bus name where accessible tree resides.
-               objectStorePath - Path where the accessible tree can be accessed.
-               """
-               storeObject = connection.get_object(busName, objectStorePath)
-
-               self._busName = busName
-               self._accessibleStore = dbus.Interface(storeObject, self._TREE_INTERFACE)
-               self._objects = {}
-               self._root = self._accessibleStore.getRoot()
-
-               self._updateObjects(self._accessibleStore.getTree())
-
-               #Connect to update signal
-               self._signalMatch = self._accessibleStore.connect_to_signal(self._UPDATE_SIGNAL,
-                                                                           self._updateHandler)
-
-       def getRootAccessible(self):
-               """
-               Gets the accessible object at the root of the tree.
-               """
-               return self.getAccessible(self._root)
-
-       def getAccessible(self, objectPath):
-               """
-               Gets the accessible object for the given object path.
-               """
-               if objectPath in self._objects:
-                       return self._objects[objectPath]
-               else:
-                       raise AccessibleObjectDoesNotExist(objectPath)
-
-       def _updateObjects(self, objects):
-               """
-               Updates the object cache from an
-               array of wire format Accessible objects.
-               """
-               for object in objects:
-                       (path,
-                        parent,
-                        children,
-                        interfaces,
-                        name, 
-                        role,
-                        description) = object
-                       if path in self._objects:
-                               self._objects[path].update(path,
-                                                          parent,
-                                                          children, 
-                                                          interfaces, 
-                                                          name, 
-                                                          role, 
-                                                          description)
-                       else:
-                               acc = AccessibleObjectProxy(self,
-                                                           self._busName,
-                                                           path,
-                                                           parent,
-                                                           children,
-                                                           interfaces,
-                                                           name,
-                                                           role,
-                                                           description)
-                               self._objects[path] = acc;
-
-       def _removeObjects(self, paths):
-               for path in paths:
-                       #Probably want to set object as invalid first
-                       del(self._objects[path])
-
-       def _updateHandler(self, updates):
-               objects, paths = updates
-               self._removeObjects(paths)
-               self._updateObjects(objects)
-
-class AccessibleObjectProxy():
-       """
-       A D-Bus proxy for an element that implements one or more of the AT-SPI
-       Accessibility interfaces.
-       """
-
-       def __init__(self, cache, bus,
-                    path, parent,
-                    children, interfaces, 
-                    name, role, description):
-               """
-               Create an accessible object.
-
-               Parameters:
-
-               cache - The accessible cache that this object is owned by.
-               bus - Bus name where proxied object can be located.
-               path - The D-Bus object path of object proxied by this object.
-               parent - The parent accessible or '/' if the root object.
-               children - List of child accessible objects.
-               interfaces - List of interfaces supported by this object.
-               name - Name of the accessible.
-               role - The accessibles role.
-               description - Description of the accessible
-               """
-               self._cache = cache
-               self.bus = bus
-               self.update(path, parent, children, interfaces, name, role, description)
-
-       def update(self, path, parent,
-                  children, interfaces, 
-                  name, role, description):
-               """
-               update an accessible object.
-
-               Parameters:
-
-               path - The D-Bus object path of object proxied by this object.
-               parent - The parent accessible or '/' if the root object.
-               children - List of child accessible objects.
-               interfaces - List of interfaces supported by this object.
-               name - Name of the accessible.
-               role - The accessibles role.
-               description - Description of the accessible
-               """
-               self.path = path
-               self.parent = parent
-               self.children = children
-               self.interfaces = interfaces
-               self.name = name
-               self.role = role
-               self.description = description
-
-       def getChildren(self):
-               return [self._cache.getAccessible(path) for path in self.children]
diff --git a/tests/accessible_cache.py b/tests/accessible_cache.py
new file mode 100644 (file)
index 0000000..ebcf9fe
--- /dev/null
@@ -0,0 +1,213 @@
+#Copyright (C) 2008 Codethink Ltd
+
+#his program is free software; you can redistribute it and/or modify
+#it under the terms of the GNU General Public License as published by
+#the Free Software Foundation; either version 2 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 dbus
+
+from weakref import ref
+from dbus.proxies import Interface, ProxyObject
+
+ATSPI_ACCESSIBLE = 'org.freedesktop.atspi.Accessible'
+ATSPI_ACCESSIBLE_TREE = 'org.freedesktop.atspi.Tree'
+
+class AccessibleObjectDoesNotExist(Exception):
+       pass
+
+class AccessibleInterfaceNotSupported(Exception):
+       pass
+
+class _CacheData(object):
+       __slots__ = [
+                       'parent',
+                       'interfaces',
+                       'children',
+                       'role',
+                       'name',
+                       'description',
+                       'proxy'
+               ]
+
+       def __init__(self, data):
+               self.update(data)
+               self.proxy = None
+
+       def update(self, data):
+               #Don't cache the path here, used as lookup in cache object dict.
+               (path,
+               self.parent,
+               self.children,
+               self.interfaces,
+               self.name,
+               self.role,
+               self.description) = data
+
+class AccessibleCache(object):
+       """
+       Caches data for a collection of accessible object proxies.
+
+       The cache only holds a weak reference to any D-Bus proxies,
+       they are created on demand.
+       """
+
+       _UPDATE_SIGNAL = 'updateTree'
+
+       def __init__(self, connection, busName, objectStorePath):
+               """
+               Creates a cache for accessible object data.
+
+               All accessible object proxies are created and accessed through this cache.
+
+               connection - DBus connection.
+               busName - DBus bus name where accessible tree resides.
+               objectStorePath - Path where the accessible tree can be accessed.
+               """
+               storeObject = connection.get_object(busName, objectStorePath)
+
+               self._connection = connection
+               self._busName = busName
+               self._accessibleStore = dbus.Interface(storeObject, ATSPI_ACCESSIBLE_TREE)
+               self._objects = {}
+               #TODO are we caching the root accessible or not?
+               #Do we need a roundtrip to access this?
+               #Does the root accessible ever change?
+               #Probably not as the root accessible is the 'Application'
+               self._root = self._accessibleStore.getRoot()
+
+               self._updateObjects(self._accessibleStore.getTree())
+
+               #Connect to update signal
+               self._signalMatch = self._accessibleStore.connect_to_signal(self._UPDATE_SIGNAL,
+                                                                           self._updateHandler)
+
+       def getRootAccessible(self):
+               """
+               Gets the accessible object at the root of the tree.
+               """
+               return self.getAccessible(self._root)
+
+       def getAccessible(self, path):
+               """
+               Gets a D-Bus proxy object for the given object path.
+
+               path - The D-Bus path of the remote object.
+               """
+               if path in self._objects:
+                       cachedata = self._objects[path]
+                       proxy = None
+                       if cachedata.proxy is not None:
+                               proxy = cachedata.proxy()
+                       if proxy is None:
+                               proxy = AccessibleObjectProxy(self,
+                                                       cachedata,
+                                                       self._connection,
+                                                       self._busName,
+                                                       path)
+                               cachedata.proxy = ref(proxy)
+                       return proxy
+               else:
+                       raise AccessibleObjectDoesNotExist, "D-Bus reference not found in cache"
+
+       def _updateObjects(self, objects):
+               """
+               Updates the object cache from an
+               array of accessible object cache data.
+               """
+               for data in objects:
+                       #First element is the object path.
+                       path = data[0]
+                       if path in self._objects:
+                               cachedata = self._objects[path]
+                               cachedata.update(data)
+                       else:
+                               self._objects[path] = _CacheData(data)
+
+       def _removeObjects(self, paths):
+               """
+               Removes the object data from the cache.
+               """
+               for path in paths:
+                       del(self._objects[path])
+
+       def _updateHandler(self, updates):
+               objects, paths = updates
+               self._removeObjects(paths)
+               self._updateObjects(objects)
+
+class AccessibleObjectProxy(ProxyObject):
+       """
+       A D-Bus proxy for a remote object that implements one or more of the AT-SPI
+       Accessibility interfaces.
+       """
+
+       def __init__(self, cache, data, connection, busName, path):
+               """
+               Create an accessible object.
+
+               cache - The accessible cache that this object is owned by.
+               connection - The D-Bus connection
+               busName - The location on the bus where the remote object is located.
+               path - The object path of the remote object.
+               """
+               ProxyObject.__init__(self, connection, busName, path, introspect=False)
+
+               self._cache = cache
+               self._path = path
+       
+       @property
+       def _data(self):
+               try:
+                       data = self._cache._objects[self._path]
+               except KeyError:
+                       raise AccessibleObjectDoesNotExist(
+                               'Cache data cannot be found for path %s' % (self._path,))
+               return data
+
+       @property
+       def path(self):
+               return self._path
+
+       @property
+       def parent(self):
+               return self._cache.getAccessible(self._data.parent)
+
+       @property
+       def numChildren(self):
+               return len(self._data.children)
+
+       def getChild(self, index):
+               return self._cache.getAccessible(self._data.children[index])
+
+       @property
+       def interfaces(self):
+               return self._data.interfaces
+
+       def getInterface(self, interface):
+               if interface in self._data.interfaces:
+                       return Interface(self, interface)
+               else:
+                       raise AccessibleInterfaceNotSupported(
+                               "%s not supported by accessible object at path %s"
+                               % (interface, self.path))
+
+       @property
+       def name(self):
+               return self._data.name
+
+       @property
+       def role(self):
+               return self._data.role
+
+       @property
+       def description(self):
+               return self._data.description
index e9c1ea0..ebf53c8 100644 (file)
@@ -5,7 +5,7 @@ from dbus.mainloop.glib import DBusGMainLoop
 
 from xml.dom import minidom
 
-from AccessibleTreeCache import AccessibleTreeCache, AccessibleObjectProxy
+from accessible_cache import AccessibleCache
 
 DBusGMainLoop(set_as_default=True)
 
@@ -13,7 +13,10 @@ def createNode(accessible, parentRef, parentElement):
        e = minidom.Element("accessible")
 
        e.attributes["reference"] = accessible.path
-       e.attributes["parent"] = accessible.parent
+       try:
+               e.attributes["parent"] = accessible.parent.path
+       except:
+               pass
        e.attributes["name"] = accessible.name
        e.attributes["role"] = str(int(accessible.role))
        e.attributes["description"] = accessible.description
@@ -23,8 +26,8 @@ def createNode(accessible, parentRef, parentElement):
                itf.attributes["name"] = i
                e.appendChild(itf)
 
-       for c in accessible.getChildren():
-               createNode(c, accessible.path, e)
+       for i in range(0, accessible.numChildren):
+               createNode(accessible.getChild(i), accessible.path, e)
 
        parentElement.appendChild(e)
 
@@ -34,7 +37,7 @@ def main(argv):
        
        loop = gobject.MainLoop()
 
-       cache = AccessibleTreeCache(bus, 'test.atspi.tree', '/org/freedesktop/atspi/tree')
+       cache = AccessibleCache(bus, 'test.atspi.tree', '/org/freedesktop/atspi/tree')
        root = cache.getRootAccessible()
 
        doc = minidom.Document()