blob: 68a832debf37786ad132de9882750d2cb3774d51 [file] [log] [blame]
Guido van Rossuma48061a1995-01-10 00:31:14 +00001"""A slow but simple dbm clone for the Mac.
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- reclaim free space (currently, space once occupied by deleted or expanded
10items is never reused)
11
12- support concurrent access (currently, if two processes take turns making
13updates, they can mess up the index)
14
15- support efficient access to large databases (currently, the whole index
16is read when the database is opened, and some updates rewrite the whole index)
17"""
18
19_os = __import__('os')
20import __builtin__
21
22_open = __builtin__.open
23
24_BLOCKSIZE = 512
25
26class _Database:
27
28 def __init__(self, file):
29 self._dirfile = file + '.dir'
30 self._datfile = file + '.dat'
31 self._bakfile = file + '.bak'
32 self._update()
33
34 def _update(self):
35 self._index = {}
36 try:
37 f = _open(self._dirfile)
38 except IOError:
39 pass
40 else:
41 while 1:
42 line = f.readline()
43 if not line: break
44 key, (pos, siz) = eval(line)
45 self._index[key] = (pos, siz)
46 f.close()
47
48 def _commit(self):
49 try: _os.unlink(self._bakfile)
50 except _os.error: pass
51 try: _os.rename(self._dirfile, self._bakfile)
52 except _os.error: pass
53 f = _open(self._dirfile, 'w')
54 for key, (pos, siz) in self._index.items():
55 f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
56 f.close()
57
58 def __getitem__(self, key):
59 pos, siz = self._index[key] # may raise KeyError
60 f = _open(self._datfile, 'rb')
61 f.seek(pos)
62 dat = f.read(siz)
63 f.close()
64 return dat
65
66 def _addval(self, val):
67 f = _open(self._datfile, 'rb+')
68 f.seek(0, 2)
69 pos = f.tell()
70 pos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
71 f.seek(pos)
72 f.write(val)
73 f.close()
74 return (pos, len(val))
75
76 def _setval(self, pos, val):
77 f = _open(self._datfile, 'rb+')
78 f.seek(pos)
79 f.write(val)
80 f.close()
81 return pos, (val)
82
83 def _addkey(self, key, (pos, siz)):
84 self._index[key] = (pos, siz)
85 f = _open(self._dirfile, 'a')
86 f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
87 f.close()
88
89 def __setitem__(self, key, val):
90 if not type(key) == type('') == type(val):
91 raise TypeError, "dbmac keys and values must be strings"
92 if not self._index.has_key(key):
93 (pos, siz) = self._addval(val)
94 self._addkey(key, (pos, siz))
95 else:
96 pos, siz = self._index[key]
97 oldblocks = (siz + _BLOCKSIZE - 1) / _BLOCKSIZE
98 newblocks = (len(val) + _BLOCKSIZE - 1) / _BLOCKSIZE
99 if newblocks <= oldblocks:
100 pos, siz = self._setval(pos, val)
101 self._index[key] = pos, siz
102 else:
103 pos, siz = self._addval(val)
104 self._index[key] = pos, siz
105 self._addkey(key, (pos, siz))
106
107 def __delitem__(self, key):
108 del self._index[key]
109 self._commit()
110
111 def keys(self):
112 return self._index.keys()
113
114 def has_key(self, key):
115 return self._index.has_key(key)
116
117 def __len__(self):
118 return len(self._index)
119
120 def close(self):
121 self._index = self._datfile = self._dirfile = self._bakfile = None
122
123
124def open(file, mode = None):
125 return _Database(file)