import cPickle import logging log = logging.getLogger(".") class ListCursor(object): """ Provides a wrapper around the cursor class that provides fast traversal using treeview paths for models that are LISTONLY, i.e. they have no tree structure. It keeps track of the current index that the cursor is pointing at. @ivar _index: The current index pointed to by the cursor. To speed up lookups the cursor is kept as close as possible to the likely next lookup and is moved using next()/prev() were ever possible. @ivar _object_cache: A cache of previously fetched records. These are indexed by the values of the L{_index}. """ def __init__(self,cursor): """ @param cursor: The cursor used to fetch the records. @type cursor: An object supporting the cursor methods of a U{BSDB cursor<http://pybsddb.sourceforge.net/bsddb3.html>}. It must have a BTREE index type and DB_DUP to support duplicate records. It should probably also have DB_DUPSORT if you want to have sorted records. """ self._cursor = cursor self._object_cache = {} self.top() def top(self): self._cursor.first() self._index = 0 def next(self): """ Move to the next record. """ data = self._cursor.next() # If there was a next record that data will # not be None if data is not None: # Up date the index pointers so that # they point to the current record. self._index+= 1 return data def prev(self): """ Move to the previous record. """ data = self._cursor.prev() # If there was a next record that data will # not be None if data is not None: # Up date the index pointers so that # they point to the current record. self._index -= 1 return data def has_children(self,path): """ This cursor is only for simple lists so no records have children. @param path: The path spec to check. @type path: A TreeView path. """ return False def get_n_children(self,path): """ Return the number of children that the record at I{path} has. @param path: The path spec to check. @type path: A TreeView path. """ return 0 def lookup(self,index,use_cache=True): """ Lookup a primary record. @param index: The index of the primary record. This is its possition in the sequence of non_duplicate keys. @type index: int @para use_case: If B{True} the record will be looked up in the object cache and will be returned from there. This will not update the possition of the cursor. If B{False} the record will fetched from the cursor even if it is in the object cache and cursor will be left possitioned on the record. """ # See if the record is in the cache. if self._object_cache.has_key(index) and use_cache is True: ret = self._object_cache[index] # If the record is not in the cache or we are ignoring the # cache. else: # If the cursor points to the record we want # the first index will be equal to the # index required if index == self._index: ret = self._cursor.current() # If the current cursor is behind the # requested index move it forward. elif index < self._index: while index < self._index: ret = self.prev() if ret is None: log.warn("Failed to move back to index = %s" % (str(index))) break ret = self._cursor.current() # If the current cursor is in front of # requested index move it backward. else: while index > self._index: ret = self.next() if ret is None: log.warn("Failed to move forward to index = %s" % (str(index))) break ret = self._cursor.current() # when we have got the record save it in # the cache if ret is not None: ret = self._unpickle(ret) self._object_cache[index] = ret return ret def _unpickle(self,rec): """ It appears that reading an object from a cursor does not automatically unpickle it. So this method provides a convenient way to unpickle the object. """ if rec and type(rec[1]) == type(""): tmp = [rec[0],None] tmp[1] = cPickle.loads(rec[1]) rec = tmp return rec def lookup_path(self,path): """ Lookup a record from a patch specification. @param path: The path spec to check. @type path: A TreeView path. """ return self.lookup(path[0])