diff --git a/sys/persistent.py b/sys/persistent.py index 247cb5f..7e4acca 100644 --- a/sys/persistent.py +++ b/sys/persistent.py @@ -1,49 +1,90 @@ -from ConfigParser import ConfigParser -import cPickle as pickle -import base64 +import shelve from threading import Lock -#TODO replace ConfigParser for better performance class PersistentContainer(object): - cfgparsers = dict() - locks = dict() + """A wrapper for shelve dictionaries, allowing basic transactions and namespaces + Be carefull as this module returns copies but no references. This means it needs + careful programming: + + >>> pc = PersistentContainer("ns","foobar") + >>> pc["num"] = 3 # direct access using = operators works + >>> pc["list"] = [1,2,3] + >>> pc["num"] += 3 # this will work... + >>> print pc["num"] + 6 + >>> pc["list"].append(4) # ...but this wont. + >>> print pc["list"] + [1, 2, 3] + >>> tmp = pc["list"] # instead store a copy + >>> tmp.append(4) # alter the copy + >>> pc["list"]=tmp # and write it back + >>> print pc["list"] + [1, 2, 3, 4] + """ + refs = dict() def __init__(self,namespace,path): + """Create a PersistentContainer object + namespace -- the namespace in which the variables will be saved + path -- path to the (n)dbm-file used to permanently store the values + """ self.namespace = namespace self.path = path self.trans_cache = dict() self.trans_del = [] - if not path in PersistentContainer.locks: - PersistentContainer.locks[path] = Lock() - self.transaction = PersistentContainer.locks[path] - - if not path in PersistentContainer.cfgparsers: - PersistentContainer.cfgparsers[path] = ConfigParser() - PersistentContainer.cfgparsers[path].read([path]) - self.cp = PersistentContainer.cfgparsers[path] - - if not self.cp.has_section(namespace): - self.cp.add_section(namespace) + if not path in PersistentContainer.refs: + ref_cnt = 0 + lock = Lock() + dic = shelve.open(path,writeback=False) + PersistentContainer.refs[path] = [ref_cnt,lock,dic] + ref = PersistentContainer.refs[path] + ref[0]+=1 + self.transaction = ref[1] + self.dic = ref[2] + + def __del__(self): + """delete the object""" + assert self.path in PersistentContainer.refs + ref = PersistentContainer.refs[self.path] + ref[0] -= 1 + if ref[0]==0: + self.dic.close() + del PersistentContainer.refs[self.path] def start_transaction(self,blocking=True): + """Start an atomic transaction. + blocking -- If True the start of the transaction will wait until the running + is finished + """ return self.transaction.acquire(blocking) def commit_transaction(self): + """Commit the values that changed during the transaction""" self._write() self.transaction.release() def rollback(self): - for k in self.trans_cache: + """Discard the values that changed during the transaction""" + for k in self.trans_cache.keys(): del self.trans_cache[k] self.transaction.release() + + def keys(self): + """Return the valid keys in the namespace""" + ret = [] + for k in self.dict.keys(): + if k.endswith("."+self.namespace): + ret.append(k[:-len("."+self.namespace)]) + return ret def is_owned(self): + """Return True if there is an transaction running""" return self.transaction.locked() - def __getitem__(self, name): - if self.cp.has_option(self.namespace,name): - b64 = self.cp.get(self.namespace,name) - return pickle.loads(base64.b64decode(b64)) + def __getitem__(self, name): + key = name+"."+self.namespace + if key in self.dic: + return self.dic[key] return None def __setitem__(self, name,val): @@ -61,18 +102,18 @@ class PersistentContainer(object): def __contains__(self,name): - return self.cp.has_option(self.namespace,name) + key = name+"."+self.namespace + return key in self.dic def _write(self): - for k in self.trans_cache: - val = self.trans_cache[k] - ser = pickle.dumps(val) - self.cp.set(self.namespace,k,base64.b64encode(ser)) + for name in self.trans_cache.keys(): + key = name+"."+self.namespace + val = self.trans_cache[name] + self.dic[key]=val + del self.trans_cache[name] while len(self.trans_del): - n = self.trans_del.pop() - self.cp.remove_option(self.namespace,n) - - f = open(self.path,'w') - self.cp.write(f) - f.close() + name = self.trans_del.pop() + key = name+"."+self.namespace + del self.dic[key] + self.dic.sync()