blob: 287a8a0c1b9a5e3c575c8ef509c9b5eba5226367 [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
Martin v. Löwisd0cd95c2001-07-19 10:06:39 +000024import os as _os
Guido van Rossum9f824a71995-08-10 19:29:28 +000025import __builtin__
26
27_open = __builtin__.open
28
29_BLOCKSIZE = 512
30
Tim Peters88869f92001-01-14 23:36:06 +000031error = IOError # For anydbm
Guido van Rossum22a18901996-05-28 22:58:40 +000032
Guido van Rossum9f824a71995-08-10 19:29:28 +000033class _Database:
34
Tim Peters88869f92001-01-14 23:36:06 +000035 def __init__(self, file):
Guido van Rossumd74fb6b2001-03-02 06:43:49 +000036 if _os.sep == '.':
37 endsep = '/'
38 else:
39 endsep = '.'
40 self._dirfile = file + endsep + 'dir'
41 self._datfile = file + endsep + 'dat'
42 self._bakfile = file + endsep + 'bak'
Tim Peters88869f92001-01-14 23:36:06 +000043 # Mod by Jack: create data file if needed
44 try:
45 f = _open(self._datfile, 'r')
46 except IOError:
47 f = _open(self._datfile, 'w')
48 f.close()
49 self._update()
Guido van Rossum9f824a71995-08-10 19:29:28 +000050
Tim Peters88869f92001-01-14 23:36:06 +000051 def _update(self):
52 self._index = {}
53 try:
54 f = _open(self._dirfile)
55 except IOError:
56 pass
57 else:
58 while 1:
59 line = f.readline().rstrip()
60 if not line: break
61 key, (pos, siz) = eval(line)
62 self._index[key] = (pos, siz)
63 f.close()
64
65 def _commit(self):
66 try: _os.unlink(self._bakfile)
67 except _os.error: pass
68 try: _os.rename(self._dirfile, self._bakfile)
69 except _os.error: pass
70 f = _open(self._dirfile, 'w')
71 for key, (pos, siz) in self._index.items():
72 f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
73 f.close()
74
75 def __getitem__(self, key):
76 pos, siz = self._index[key] # may raise KeyError
77 f = _open(self._datfile, 'rb')
78 f.seek(pos)
79 dat = f.read(siz)
80 f.close()
81 return dat
82
83 def _addval(self, val):
84 f = _open(self._datfile, 'rb+')
85 f.seek(0, 2)
86 pos = int(f.tell())
Guido van Rossum9f824a71995-08-10 19:29:28 +000087## Does not work under MW compiler
Tim Peters88869f92001-01-14 23:36:06 +000088## pos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
89## f.seek(pos)
Guido van Rossum54e54c62001-09-04 19:14:14 +000090 npos = ((pos + _BLOCKSIZE - 1) // _BLOCKSIZE) * _BLOCKSIZE
Tim Peters88869f92001-01-14 23:36:06 +000091 f.write('\0'*(npos-pos))
92 pos = npos
93
94 f.write(val)
95 f.close()
96 return (pos, len(val))
97
98 def _setval(self, pos, val):
99 f = _open(self._datfile, 'rb+')
100 f.seek(pos)
101 f.write(val)
102 f.close()
103 return (pos, len(val))
104
105 def _addkey(self, key, (pos, siz)):
106 self._index[key] = (pos, siz)
107 f = _open(self._dirfile, 'a')
108 f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
109 f.close()
110
111 def __setitem__(self, key, val):
112 if not type(key) == type('') == type(val):
113 raise TypeError, "keys and values must be strings"
114 if not self._index.has_key(key):
115 (pos, siz) = self._addval(val)
116 self._addkey(key, (pos, siz))
117 else:
118 pos, siz = self._index[key]
119 oldblocks = (siz + _BLOCKSIZE - 1) / _BLOCKSIZE
120 newblocks = (len(val) + _BLOCKSIZE - 1) / _BLOCKSIZE
121 if newblocks <= oldblocks:
122 pos, siz = self._setval(pos, val)
123 self._index[key] = pos, siz
124 else:
125 pos, siz = self._addval(val)
126 self._index[key] = pos, siz
127
128 def __delitem__(self, key):
129 del self._index[key]
130 self._commit()
131
132 def keys(self):
133 return self._index.keys()
134
135 def has_key(self, key):
136 return self._index.has_key(key)
137
Fred Drakea7cc69e2001-05-03 04:55:47 +0000138 def __contains__(self, key):
139 return self._index.has_key(key)
140
141 def iterkeys(self):
142 return self._index.iterkeys()
143 __iter__ = iterkeys
144
Tim Peters88869f92001-01-14 23:36:06 +0000145 def __len__(self):
146 return len(self._index)
147
148 def close(self):
149 self._index = None
150 self._datfile = self._dirfile = self._bakfile = None
Guido van Rossum9f824a71995-08-10 19:29:28 +0000151
152
Fred Drakea7cc69e2001-05-03 04:55:47 +0000153def open(file, flag=None, mode=None):
Tim Peters88869f92001-01-14 23:36:06 +0000154 # flag, mode arguments are currently ignored
155 return _Database(file)