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')
27 _open
= __builtin__
.open
31 error
= IOError # For anydbm
35 def __init__(self
, file, mode
):
37 self
._dirfile
= file + _os
.extsep
+ 'dir'
38 self
._datfile
= file + _os
.extsep
+ 'dat'
39 self
._bakfile
= file + _os
.extsep
+ 'bak'
40 # Mod by Jack: create data file if needed
42 f
= _open(self
._datfile
, 'r')
44 f
= _open(self
._datfile
, 'w', self
._mode
)
51 f
= _open(self
._dirfile
)
56 line
= f
.readline().rstrip()
58 key
, (pos
, siz
) = eval(line
)
59 self
._index
[key
] = (pos
, siz
)
63 try: _os
.unlink(self
._bakfile
)
64 except _os
.error
: pass
65 try: _os
.rename(self
._dirfile
, self
._bakfile
)
66 except _os
.error
: pass
67 f
= _open(self
._dirfile
, 'w', self
._mode
)
68 for key
, (pos
, siz
) in self
._index
.items():
69 f
.write("%s, (%s, %s)\n" % (`key`
, `pos`
, `siz`
))
72 def __getitem__(self
, key
):
73 pos
, siz
= self
._index
[key
] # may raise KeyError
74 f
= _open(self
._datfile
, 'rb')
80 def _addval(self
, val
):
81 f
= _open(self
._datfile
, 'rb+')
84 ## Does not work under MW compiler
85 ## pos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
87 npos
= ((pos
+ _BLOCKSIZE
- 1) // _BLOCKSIZE
) * _BLOCKSIZE
88 f
.write('\0'*(npos
-pos
))
93 return (pos
, len(val
))
95 def _setval(self
, pos
, val
):
96 f
= _open(self
._datfile
, 'rb+')
100 return (pos
, len(val
))
102 def _addkey(self
, key
, (pos
, siz
)):
103 self
._index
[key
] = (pos
, siz
)
104 f
= _open(self
._dirfile
, 'a', self
._mode
)
105 f
.write("%s, (%s, %s)\n" % (`key`
, `pos`
, `siz`
))
108 def __setitem__(self
, key
, val
):
109 if not type(key
) == type('') == type(val
):
110 raise TypeError, "keys and values must be strings"
111 if not self
._index
.has_key(key
):
112 (pos
, siz
) = self
._addval
(val
)
113 self
._addkey
(key
, (pos
, siz
))
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
122 pos
, siz
= self
._addval
(val
)
123 self
._index
[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
)
135 def __contains__(self
, key
):
136 return self
._index
.has_key(key
)
139 return self
._index
.iterkeys()
143 return len(self
._index
)
147 self
._datfile
= self
._dirfile
= self
._bakfile
= None
150 def open(file, flag
=None, mode
=0666):
151 # flag, mode arguments are currently ignored
152 return _Database(file, mode
)