This commit was manufactured by cvs2svn to create tag
[python/dscho.git] / Mac / Lib / dbmac.py
blob0588dbd90ee7ef167bc87a7ecd9e1eca805e2be9
1 """A slow but simple dbm clone for the Mac.
3 For database spam, spam.dir contains the index (a text file),
4 spam.bak *may* contain a backup of the index (also a text file),
5 while spam.dat contains the data (a binary file).
7 XXX TO DO:
9 - reclaim free space (currently, space once occupied by deleted or expanded
10 items is never reused)
12 - support concurrent access (currently, if two processes take turns making
13 updates, they can mess up the index)
15 - support efficient access to large databases (currently, the whole index
16 is read when the database is opened, and some updates rewrite the whole index)
18 - support opening for read-only (flag = 'm')
20 """
22 _os = __import__('os')
23 import __builtin__
25 _open = __builtin__.open
27 _BLOCKSIZE = 512
29 class _Database:
31 def __init__(self, file):
32 self._dirfile = file + '.dir'
33 self._datfile = file + '.dat'
34 self._bakfile = file + '.bak'
35 # Mod by Jack: create data file if needed
36 try:
37 f = _open(self._datfile, 'r')
38 except IOError:
39 f = _open(self._datfile, 'w')
40 f.close()
41 self._update()
43 def _update(self):
44 self._index = {}
45 try:
46 f = _open(self._dirfile)
47 except IOError:
48 pass
49 else:
50 while 1:
51 line = f.readline()
52 if not line: break
53 key, (pos, siz) = eval(line)
54 self._index[key] = (pos, siz)
55 f.close()
57 def _commit(self):
58 try: _os.unlink(self._bakfile)
59 except _os.error: pass
60 try: _os.rename(self._dirfile, self._bakfile)
61 except _os.error: pass
62 f = _open(self._dirfile, 'w')
63 for key, (pos, siz) in self._index.items():
64 f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
65 f.close()
67 def __getitem__(self, key):
68 pos, siz = self._index[key] # may raise KeyError
69 f = _open(self._datfile, 'rb')
70 f.seek(pos)
71 dat = f.read(siz)
72 f.close()
73 return dat
75 def _addval(self, val):
76 f = _open(self._datfile, 'rb+')
77 f.seek(0, 2)
78 pos = f.tell()
79 ## Does not work under MW compiler
80 ## pos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
81 ## f.seek(pos)
82 npos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
83 f.write('\0'*(npos-pos))
84 pos = npos
86 f.write(val)
87 f.close()
88 return (pos, len(val))
90 def _setval(self, pos, val):
91 f = _open(self._datfile, 'rb+')
92 f.seek(pos)
93 f.write(val)
94 f.close()
95 return pos, (val)
97 def _addkey(self, key, (pos, siz)):
98 self._index[key] = (pos, siz)
99 f = _open(self._dirfile, 'a')
100 f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
101 f.close()
103 def __setitem__(self, key, val):
104 if not type(key) == type('') == type(val):
105 raise TypeError, "dbmac keys and values must be strings"
106 if not self._index.has_key(key):
107 (pos, siz) = self._addval(val)
108 self._addkey(key, (pos, siz))
109 else:
110 pos, siz = self._index[key]
111 oldblocks = (siz + _BLOCKSIZE - 1) / _BLOCKSIZE
112 newblocks = (len(val) + _BLOCKSIZE - 1) / _BLOCKSIZE
113 if newblocks <= oldblocks:
114 pos, siz = self._setval(pos, val)
115 self._index[key] = pos, siz
116 else:
117 pos, siz = self._addval(val)
118 self._index[key] = pos, siz
119 self._addkey(key, (pos, siz))
121 def __delitem__(self, key):
122 del self._index[key]
123 self._commit()
125 def keys(self):
126 return self._index.keys()
128 def has_key(self, key):
129 return self._index.has_key(key)
131 def __len__(self):
132 return len(self._index)
134 def close(self):
135 self._index = self._datfile = self._dirfile = self._bakfile = None
138 def open(file, flag = None, mode = None):
139 # flag, mode arguments are currently ignored
140 return _Database(file)