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
33 def __init__(self
, file):
34 self
._dirfile
= file + '.dir'
35 self
._datfile
= file + '.dat'
36 self
._bakfile
= file + '.bak'
37 # Mod by Jack: create data file if needed
39 f
= _open(self
._datfile
, 'r')
41 f
= _open(self
._datfile
, 'w')
48 f
= _open(self
._dirfile
)
55 key
, (pos
, siz
) = eval(line
)
56 self
._index
[key
] = (pos
, siz
)
60 try: _os
.unlink(self
._bakfile
)
61 except _os
.error
: pass
62 try: _os
.rename(self
._dirfile
, self
._bakfile
)
63 except _os
.error
: pass
64 f
= _open(self
._dirfile
, 'w')
65 for key
, (pos
, siz
) in self
._index
.items():
66 f
.write("%s, (%s, %s)\n" % (`key`
, `pos`
, `siz`
))
69 def __getitem__(self
, key
):
70 pos
, siz
= self
._index
[key
] # may raise KeyError
71 f
= _open(self
._datfile
, 'rb')
77 def _addval(self
, val
):
78 f
= _open(self
._datfile
, 'rb+')
81 ## Does not work under MW compiler
82 ## pos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
84 npos
= ((pos
+ _BLOCKSIZE
- 1) / _BLOCKSIZE
) * _BLOCKSIZE
85 f
.write('\0'*(npos
-pos
))
90 return (pos
, len(val
))
92 def _setval(self
, pos
, val
):
93 f
= _open(self
._datfile
, 'rb+')
97 return (pos
, len(val
))
99 def _addkey(self
, key
, (pos
, siz
)):
100 self
._index
[key
] = (pos
, siz
)
101 f
= _open(self
._dirfile
, 'a')
102 f
.write("%s, (%s, %s)\n" % (`key`
, `pos`
, `siz`
))
105 def __setitem__(self
, key
, val
):
106 if not type(key
) == type('') == type(val
):
107 raise TypeError, "keys and values must be strings"
108 if not self
._index
.has_key(key
):
109 (pos
, siz
) = self
._addval
(val
)
110 self
._addkey
(key
, (pos
, siz
))
112 pos
, siz
= self
._index
[key
]
113 oldblocks
= (siz
+ _BLOCKSIZE
- 1) / _BLOCKSIZE
114 newblocks
= (len(val
) + _BLOCKSIZE
- 1) / _BLOCKSIZE
115 if newblocks
<= oldblocks
:
116 pos
, siz
= self
._setval
(pos
, val
)
117 self
._index
[key
] = pos
, siz
119 pos
, siz
= self
._addval
(val
)
120 self
._index
[key
] = pos
, siz
121 self
._addkey
(key
, (pos
, siz
))
123 def __delitem__(self
, key
):
128 return self
._index
.keys()
130 def has_key(self
, key
):
131 return self
._index
.has_key(key
)
134 return len(self
._index
)
138 self
._datfile
= self
._dirfile
= self
._bakfile
= None
141 def open(file, flag
= None, mode
= None):
142 # flag, mode arguments are currently ignored
143 return _Database(file)