blob: 3fb6e1dc228de7dfaaaaf83a27d5a704ea5d1686 [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
Fred Drake2c8373b2001-12-07 21:54:46 +000035 def __init__(self, file, mode):
36 self._mode = mode
Guido van Rossume2ae77b2001-10-24 20:42:55 +000037 self._dirfile = file + _os.extsep + 'dir'
38 self._datfile = file + _os.extsep + 'dat'
39 self._bakfile = file + _os.extsep + 'bak'
Tim Peters88869f92001-01-14 23:36:06 +000040 # Mod by Jack: create data file if needed
41 try:
42 f = _open(self._datfile, 'r')
43 except IOError:
Fred Drake2c8373b2001-12-07 21:54:46 +000044 f = _open(self._datfile, 'w', self._mode)
Tim Peters88869f92001-01-14 23:36:06 +000045 f.close()
46 self._update()
Guido van Rossum9f824a71995-08-10 19:29:28 +000047
Tim Peters88869f92001-01-14 23:36:06 +000048 def _update(self):
49 self._index = {}
50 try:
51 f = _open(self._dirfile)
52 except IOError:
53 pass
54 else:
55 while 1:
56 line = f.readline().rstrip()
57 if not line: break
58 key, (pos, siz) = eval(line)
59 self._index[key] = (pos, siz)
60 f.close()
61
62 def _commit(self):
63 try: _os.unlink(self._bakfile)
64 except _os.error: pass
65 try: _os.rename(self._dirfile, self._bakfile)
66 except _os.error: pass
Fred Drake2c8373b2001-12-07 21:54:46 +000067 f = _open(self._dirfile, 'w', self._mode)
Tim Peters88869f92001-01-14 23:36:06 +000068 for key, (pos, siz) in self._index.items():
69 f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
70 f.close()
71
72 def __getitem__(self, key):
73 pos, siz = self._index[key] # may raise KeyError
74 f = _open(self._datfile, 'rb')
75 f.seek(pos)
76 dat = f.read(siz)
77 f.close()
78 return dat
79
80 def _addval(self, val):
81 f = _open(self._datfile, 'rb+')
82 f.seek(0, 2)
83 pos = int(f.tell())
Guido van Rossum9f824a71995-08-10 19:29:28 +000084## Does not work under MW compiler
Tim Peters88869f92001-01-14 23:36:06 +000085## pos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
86## f.seek(pos)
Guido van Rossum54e54c62001-09-04 19:14:14 +000087 npos = ((pos + _BLOCKSIZE - 1) // _BLOCKSIZE) * _BLOCKSIZE
Tim Peters88869f92001-01-14 23:36:06 +000088 f.write('\0'*(npos-pos))
89 pos = npos
90
91 f.write(val)
92 f.close()
93 return (pos, len(val))
94
95 def _setval(self, pos, val):
96 f = _open(self._datfile, 'rb+')
97 f.seek(pos)
98 f.write(val)
99 f.close()
100 return (pos, len(val))
101
102 def _addkey(self, key, (pos, siz)):
103 self._index[key] = (pos, siz)
Fred Drake2c8373b2001-12-07 21:54:46 +0000104 f = _open(self._dirfile, 'a', self._mode)
Tim Peters88869f92001-01-14 23:36:06 +0000105 f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
106 f.close()
107
108 def __setitem__(self, key, val):
109 if not type(key) == type('') == type(val):
110 raise TypeError, "keys and values must be strings"
111 if not self._index.has_key(key):
112 (pos, siz) = self._addval(val)
113 self._addkey(key, (pos, siz))
114 else:
115 pos, siz = self._index[key]
116 oldblocks = (siz + _BLOCKSIZE - 1) / _BLOCKSIZE
117 newblocks = (len(val) + _BLOCKSIZE - 1) / _BLOCKSIZE
118 if newblocks <= oldblocks:
119 pos, siz = self._setval(pos, val)
120 self._index[key] = pos, siz
121 else:
122 pos, siz = self._addval(val)
123 self._index[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
Fred Drakea7cc69e2001-05-03 04:55:47 +0000135 def __contains__(self, key):
136 return self._index.has_key(key)
137
138 def iterkeys(self):
139 return self._index.iterkeys()
140 __iter__ = iterkeys
141
Tim Peters88869f92001-01-14 23:36:06 +0000142 def __len__(self):
143 return len(self._index)
144
145 def close(self):
Anthony Baxtered905702001-12-21 05:13:37 +0000146 self._commit()
Tim Peters88869f92001-01-14 23:36:06 +0000147 self._index = None
148 self._datfile = self._dirfile = self._bakfile = None
Guido van Rossum9f824a71995-08-10 19:29:28 +0000149
Anthony Baxtered905702001-12-21 05:13:37 +0000150 def __del__(self):
151 if self._index is not None:
152 self._commit()
Tim Peterse4418602002-02-16 07:34:19 +0000153
Anthony Baxtered905702001-12-21 05:13:37 +0000154
Guido van Rossum9f824a71995-08-10 19:29:28 +0000155
Fred Drake2c8373b2001-12-07 21:54:46 +0000156def open(file, flag=None, mode=0666):
Tim Peters88869f92001-01-14 23:36:06 +0000157 # flag, mode arguments are currently ignored
Fred Drake2c8373b2001-12-07 21:54:46 +0000158 return _Database(file, mode)