Jabber/XMPP conference bot & framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
brathuhn/sys/persistent.py

124 lines
3.3 KiB

import shelve
import base64
from threading import Lock
class PersistentContainer(object):
"""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.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):
"""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):
key = name+"."+self.namespace
key = base64.b64encode(key)
if key in self.dic:
return self.dic[key]
return None
def __setitem__(self, name,val):
self.trans_cache[name]=val
if not self.is_owned():
self._write()
def __delitem__(self, name):
if name not in self.trans_del:
self.trans_del.append(name)
if name in self.trans_cache:
del self.trans_cache[name]
if not self.is_owned():
self._write()
def __contains__(self,name):
key = name+"."+self.namespace
key = base64.b64encode(key)
return key in self.dic
def _write(self):
for name in self.trans_cache.keys():
key = name+"."+self.namespace
key = base64.b64encode(key)
val = self.trans_cache[name]
self.dic[key]=val
del self.trans_cache[name]
while len(self.trans_del):
name = self.trans_del.pop()
key = name+"."+self.namespace
key = base64.b64encode(key)
del self.dic[key]
self.dic.sync()