blob: 1f15ebe41202c3a53c2048540be693e9133bc7b0 [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'
Jack Jansen1ff2f211995-04-23 22:10:18 +000032 # Mod by Jack: create data file if needed
33 try:
34 f = _open(self._datfile, 'r')
35 except IOError:
36 f = _open(self._datfile, 'w')
37 f.close()
Guido van Rossuma48061a1995-01-10 00:31:14 +000038 self._update()
39
40 def _update(self):
41 self._index = {}
42 try:
43 f = _open(self._dirfile)
44 except IOError:
45 pass
46 else:
47 while 1:
48 line = f.readline()
49 if not line: break
50 key, (pos, siz) = eval(line)
51 self._index[key] = (pos, siz)
52 f.close()
53
54 def _commit(self):
55 try: _os.unlink(self._bakfile)
56 except _os.error: pass
57 try: _os.rename(self._dirfile, self._bakfile)
58 except _os.error: pass
59 f = _open(self._dirfile, 'w')
60 for key, (pos, siz) in self._index.items():
61 f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
62 f.close()
63
64 def __getitem__(self, key):
65 pos, siz = self._index[key] # may raise KeyError
66 f = _open(self._datfile, 'rb')
67 f.seek(pos)
68 dat = f.read(siz)
69 f.close()
70 return dat
71
72 def _addval(self, val):
73 f = _open(self._datfile, 'rb+')
74 f.seek(0, 2)
75 pos = f.tell()
Jack Jansen1ff2f211995-04-23 22:10:18 +000076## Does not work under MW compiler
77## pos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
78## f.seek(pos)
79 npos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
80 f.write('\0'*(npos-pos))
81 pos = npos
82
Guido van Rossuma48061a1995-01-10 00:31:14 +000083 f.write(val)
84 f.close()
85 return (pos, len(val))
86
87 def _setval(self, pos, val):
88 f = _open(self._datfile, 'rb+')
89 f.seek(pos)
90 f.write(val)
91 f.close()
92 return pos, (val)
93
94 def _addkey(self, key, (pos, siz)):
95 self._index[key] = (pos, siz)
96 f = _open(self._dirfile, 'a')
97 f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
98 f.close()
99
100 def __setitem__(self, key, val):
101 if not type(key) == type('') == type(val):
102 raise TypeError, "dbmac keys and values must be strings"
103 if not self._index.has_key(key):
104 (pos, siz) = self._addval(val)
105 self._addkey(key, (pos, siz))
106 else:
107 pos, siz = self._index[key]
108 oldblocks = (siz + _BLOCKSIZE - 1) / _BLOCKSIZE
109 newblocks = (len(val) + _BLOCKSIZE - 1) / _BLOCKSIZE
110 if newblocks <= oldblocks:
111 pos, siz = self._setval(pos, val)
112 self._index[key] = pos, siz
113 else:
114 pos, siz = self._addval(val)
115 self._index[key] = pos, siz
116 self._addkey(key, (pos, siz))
117
118 def __delitem__(self, key):
119 del self._index[key]
120 self._commit()
121
122 def keys(self):
123 return self._index.keys()
124
125 def has_key(self, key):
126 return self._index.has_key(key)
127
128 def __len__(self):
129 return len(self._index)
130
131 def close(self):
132 self._index = self._datfile = self._dirfile = self._bakfile = None
133
134
135def open(file, mode = None):
136 return _Database(file)