You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

dumbdbm.py 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. """A dumb and slow but simple dbm clone.
  2. For database spam, spam.dir contains the index (a text file),
  3. spam.bak *may* contain a backup of the index (also a text file),
  4. while spam.dat contains the data (a binary file).
  5. XXX TO DO:
  6. - seems to contain a bug when updating...
  7. - reclaim free space (currently, space once occupied by deleted or expanded
  8. items is never reused)
  9. - support concurrent access (currently, if two processes take turns making
  10. updates, they can mess up the index)
  11. - support efficient access to large databases (currently, the whole index
  12. is read when the database is opened, and some updates rewrite the whole index)
  13. - support opening for read-only (flag = 'm')
  14. """
  15. _os = __import__('os')
  16. import __builtin__
  17. _open = __builtin__.open
  18. _BLOCKSIZE = 512
  19. error = IOError # For anydbm
  20. class _Database:
  21. def __init__(self, file):
  22. if _os.sep == '.':
  23. endsep = '/'
  24. else:
  25. endsep = '.'
  26. self._dirfile = file + endsep + 'dir'
  27. self._datfile = file + endsep + 'dat'
  28. self._bakfile = file + endsep + 'bak'
  29. # Mod by Jack: create data file if needed
  30. try:
  31. f = _open(self._datfile, 'r')
  32. except IOError:
  33. f = _open(self._datfile, 'w')
  34. f.close()
  35. self._update()
  36. def _update(self):
  37. self._index = {}
  38. try:
  39. f = _open(self._dirfile)
  40. except IOError:
  41. pass
  42. else:
  43. while 1:
  44. line = f.readline().rstrip()
  45. if not line: break
  46. key, (pos, siz) = eval(line)
  47. self._index[key] = (pos, siz)
  48. f.close()
  49. def _commit(self):
  50. try: _os.unlink(self._bakfile)
  51. except _os.error: pass
  52. try: _os.rename(self._dirfile, self._bakfile)
  53. except _os.error: pass
  54. f = _open(self._dirfile, 'w')
  55. for key, (pos, siz) in self._index.items():
  56. f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
  57. f.close()
  58. def __getitem__(self, key):
  59. pos, siz = self._index[key] # may raise KeyError
  60. f = _open(self._datfile, 'rb')
  61. f.seek(pos)
  62. dat = f.read(siz)
  63. f.close()
  64. return dat
  65. def _addval(self, val):
  66. f = _open(self._datfile, 'rb+')
  67. f.seek(0, 2)
  68. pos = int(f.tell())
  69. ## Does not work under MW compiler
  70. ## pos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
  71. ## f.seek(pos)
  72. npos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
  73. f.write('\0'*(npos-pos))
  74. pos = npos
  75. f.write(val)
  76. f.close()
  77. return (pos, len(val))
  78. def _setval(self, pos, val):
  79. f = _open(self._datfile, 'rb+')
  80. f.seek(pos)
  81. f.write(val)
  82. f.close()
  83. return (pos, len(val))
  84. def _addkey(self, key, (pos, siz)):
  85. self._index[key] = (pos, siz)
  86. f = _open(self._dirfile, 'a')
  87. f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
  88. f.close()
  89. def __setitem__(self, key, val):
  90. if not type(key) == type('') == type(val):
  91. raise TypeError, "keys and values must be strings"
  92. if not self._index.has_key(key):
  93. (pos, siz) = self._addval(val)
  94. self._addkey(key, (pos, siz))
  95. else:
  96. pos, siz = self._index[key]
  97. oldblocks = (siz + _BLOCKSIZE - 1) / _BLOCKSIZE
  98. newblocks = (len(val) + _BLOCKSIZE - 1) / _BLOCKSIZE
  99. if newblocks <= oldblocks:
  100. pos, siz = self._setval(pos, val)
  101. self._index[key] = pos, siz
  102. else:
  103. pos, siz = self._addval(val)
  104. self._index[key] = pos, siz
  105. def __delitem__(self, key):
  106. del self._index[key]
  107. self._commit()
  108. def keys(self):
  109. return self._index.keys()
  110. def has_key(self, key):
  111. return self._index.has_key(key)
  112. def __len__(self):
  113. return len(self._index)
  114. def close(self):
  115. self._index = None
  116. self._datfile = self._dirfile = self._bakfile = None
  117. def open(file, flag = None, mode = None):
  118. # flag, mode arguments are currently ignored
  119. return _Database(file)