|
|
|
|
@ -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() |
|
|
|
|
|
|
|
|
|
|