blob: b6b7fc3ff23bd1ec04b7d872e4c42297303f4fb8 [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"
Raymond Hettinger793d4b42002-06-01 14:25:41 +0000111 if not key in self._index:
Tim Peters88869f92001-01-14 23:36:06 +0000112 (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):
Raymond Hettinger793d4b42002-06-01 14:25:41 +0000133 return key in self._index
Tim Peters88869f92001-01-14 23:36:06 +0000134
Fred Drakea7cc69e2001-05-03 04:55:47 +0000135 def __contains__(self, key):
Raymond Hettinger793d4b42002-06-01 14:25:41 +0000136 return key in self._index
Fred Drakea7cc69e2001-05-03 04:55:47 +0000137
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):
Raymond Hettingeraef22fb2002-05-29 16:18:42 +0000157 """Open the database file, filename, and return corresponding object.
158
159 The flag argument, used to control how the database is opened in the
160 other DBM implementations, is ignored in the dumbdbm module; the
161 database is always opened for update, and will be created if it does
162 not exist.
163
164 The optional mode argument is the UNIX mode of the file, used only when
165 the database has to be created. It defaults to octal code 0666 (and
166 will be modified by the prevailing umask).
167
168 """
Tim Peters88869f92001-01-14 23:36:06 +0000169 # flag, mode arguments are currently ignored
Fred Drake2c8373b2001-12-07 21:54:46 +0000170 return _Database(file, mode)