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