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