1 """A dumb and slow but simple dbm clone.
3 For database spam, spam.dir contains the index (a text file),
4 spam.bak *may* contain a backup of the index (also a text file),
5 while spam.dat contains the data (a binary file).
9 - seems to contain a bug when updating...
11 - reclaim free space (currently, space once occupied by deleted or expanded
12 items is never reused)
14 - support concurrent access (currently, if two processes take turns making
15 updates, they can mess up the index)
17 - support efficient access to large databases (currently, the whole index
18 is read when the database is opened, and some updates rewrite the whole index)
20 - support opening for read-only (flag = 'm')
24 _os
= __import__('os')
27 _open
= __builtin__
.open
31 error
= IOError # For anydbm
35 def __init__(self
, file):
36 self
._dirfile
= file + '.dir'
37 self
._datfile
= file + '.dat'
38 self
._bakfile
= file + '.bak'
39 # Mod by Jack: create data file if needed
41 f
= _open(self
._datfile
, 'r')
43 f
= _open(self
._datfile
, 'w')
50 f
= _open(self
._dirfile
)
57 key
, (pos
, siz
) = eval(line
)
58 self
._index
[key
] = (pos
, siz
)
62 try: _os
.unlink(self
._bakfile
)
63 except _os
.error
: pass
64 try: _os
.rename(self
._dirfile
, self
._bakfile
)
65 except _os
.error
: pass
66 f
= _open(self
._dirfile
, 'w')
67 for key
, (pos
, siz
) in self
._index
.items():
68 f
.write("%s, (%s, %s)\n" % (`key`
, `pos`
, `siz`
))
71 def __getitem__(self
, key
):
72 pos
, siz
= self
._index
[key
] # may raise KeyError
73 f
= _open(self
._datfile
, 'rb')
79 def _addval(self
, val
):
80 f
= _open(self
._datfile
, 'rb+')
83 ## Does not work under MW compiler
84 ## pos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
86 npos
= ((pos
+ _BLOCKSIZE
- 1) / _BLOCKSIZE
) * _BLOCKSIZE
87 f
.write('\0'*(npos
-pos
))
92 return (pos
, len(val
))
94 def _setval(self
, pos
, val
):
95 f
= _open(self
._datfile
, 'rb+')
99 return (pos
, len(val
))
101 def _addkey(self
, key
, (pos
, siz
)):
102 self
._index
[key
] = (pos
, siz
)
103 f
= _open(self
._dirfile
, 'a')
104 f
.write("%s, (%s, %s)\n" % (`key`
, `pos`
, `siz`
))
107 def __setitem__(self
, key
, val
):
108 if not type(key
) == type('') == type(val
):
109 raise TypeError, "keys and values must be strings"
110 if not self
._index
.has_key(key
):
111 (pos
, siz
) = self
._addval
(val
)
112 self
._addkey
(key
, (pos
, siz
))
114 pos
, siz
= self
._index
[key
]
115 oldblocks
= (siz
+ _BLOCKSIZE
- 1) / _BLOCKSIZE
116 newblocks
= (len(val
) + _BLOCKSIZE
- 1) / _BLOCKSIZE
117 if newblocks
<= oldblocks
:
118 pos
, siz
= self
._setval
(pos
, val
)
119 self
._index
[key
] = pos
, siz
121 pos
, siz
= self
._addval
(val
)
122 self
._index
[key
] = pos
, siz
123 self
._addkey
(key
, (pos
, siz
))
125 def __delitem__(self
, key
):
130 return self
._index
.keys()
132 def has_key(self
, key
):
133 return self
._index
.has_key(key
)
136 return len(self
._index
)
140 self
._datfile
= self
._dirfile
= self
._bakfile
= None
143 def open(file, flag
= None, mode
= None):
144 # flag, mode arguments are currently ignored
145 return _Database(file)