"""A dumb and slow but simple dbm clone.For database spam, spam.dir contains the index (a text file),spam.bak *may* contain a backup of the index (also a text file),while spam.dat contains the data (a binary file).XXX TO DO:- seems to contain a bug when updating...- reclaim free space (currently, space once occupied by deleted or expandeditems is never reused)- support concurrent access (currently, if two processes take turns makingupdates, they can mess up the index)- support efficient access to large databases (currently, the whole indexis read when the database is opened, and some updates rewrite the whole index)- support opening for read-only (flag = 'm')"""_os=__import__('os')import__builtin___open=__builtin__.open_BLOCKSIZE=512error=IOError# For anydbmclass_Database:def__init__(self,file):self._dirfile=file+'.dir'self._datfile=file+'.dat'self._bakfile=file+'.bak'# Mod by Jack: create data file if neededtry:f=_open(self._datfile,'r')exceptIOError:f=_open(self._datfile,'w')f.close()self._update()def_update(self):self._index={}try:f=_open(self._dirfile)exceptIOError:passelse:while1:line=f.readline()ifnotline:breakkey,(pos,siz)=eval(line)self._index[key]=(pos,siz)f.close()def_commit(self):try:_os.unlink(self._bakfile)except_os.error:passtry:_os.rename(self._dirfile,self._bakfile)except_os.error:passf=_open(self._dirfile,'w')forkey,(pos,siz)inself._index.items():f.write("%s, (%s, %s)\n"%(`key`,`pos`,`siz`))f.close()def__getitem__(self,key):pos,siz=self._index[key]# may raise KeyErrorf=_open(self._datfile,'rb')f.seek(pos)dat=f.read(siz)f.close()returndatdef_addval(self,val):f=_open(self._datfile,'rb+')f.seek(0,2)pos=int(f.tell())## Does not work under MW compiler## pos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE## f.seek(pos)npos=((pos+_BLOCKSIZE-1)/_BLOCKSIZE)*_BLOCKSIZEf.write('\0'*(npos-pos))pos=nposf.write(val)f.close()return(pos,len(val))def_setval(self,pos,val):f=_open(self._datfile,'rb+')f.seek(pos)f.write(val)f.close()return(pos,len(val))def_addkey(self,key,(pos,siz)):self._index[key]=(pos,siz)f=_open(self._dirfile,'a')f.write("%s, (%s, %s)\n"%(`key`,`pos`,`siz`))f.close()def__setitem__(self,key,val):ifnottype(key)==type('')==type(val):raiseTypeError,"keys and values must be strings"ifnotself._index.has_key(key):(pos,siz)=self._addval(val)self._addkey(key,(pos,siz))else:pos,siz=self._index[key]oldblocks=(siz+_BLOCKSIZE-1)/_BLOCKSIZEnewblocks=(len(val)+_BLOCKSIZE-1)/_BLOCKSIZEifnewblocks<=oldblocks:pos,siz=self._setval(pos,val)self._index[key]=pos,sizelse:pos,siz=self._addval(val)self._index[key]=pos,sizdef__delitem__(self,key):delself._index[key]self._commit()defkeys(self):returnself._index.keys()defhas_key(self,key):returnself._index.has_key(key)def__len__(self):returnlen(self._index)defclose(self):self._index=Noneself._datfile=self._dirfile=self._bakfile=Nonedefopen(file,flag=None,mode=None):# flag, mode arguments are currently ignoredreturn_Database(file)