1 # -*- test-case-name: twisted.test.test_dirdbm -*-
3 # Copyright (c) Twisted Matrix Laboratories.
4 # See LICENSE for details.
9 DBM-style interface to a directory.
11 Each key is stored as a single file. This is not expected to be very fast or
12 efficient, but it's good for easy debugging.
14 DirDBMs are *not* thread-safe, they should only be accessed by one thread at
17 No files should be placed in the working directory of a DirDBM save those
18 created by the DirDBM itself!
20 Maintainer: Itamar Shtull-Trauring
30 import cPickle as pickle
41 """A directory with a DBM interface.
43 This class presents a hash-like interface to a directory of small,
44 flat files. It can only use strings as keys or values.
47 def __init__(self, name):
50 @param name: Base path to use for the directory storage.
52 self.dname = os.path.abspath(name)
53 if not os.path.isdir(self.dname):
56 # Run recovery, in case we crashed. we delete all files ending
57 # with ".new". Then we find all files who end with ".rpl". If a
58 # corresponding file exists without ".rpl", we assume the write
59 # failed and delete the ".rpl" file. If only a ".rpl" exist we
60 # assume the program crashed right after deleting the old entry
61 # but before renaming the replacement entry.
63 # NOTE: '.' is NOT in the base64 alphabet!
64 for f in glob.glob(os.path.join(self.dname, "*.new")):
66 replacements = glob.glob(os.path.join(self.dname, "*.rpl"))
67 for f in replacements:
69 if os.path.exists(old):
75 """Encode a key so it can be used as a filename.
77 # NOTE: '_' is NOT in the base64 alphabet!
78 return base64.encodestring(k).replace('\n', '_').replace("/", "-")
81 """Decode a filename to get the key.
83 return base64.decodestring(k.replace('_', '\n').replace("-", "/"))
85 def _readFile(self, path):
86 """Read in the contents of a file.
88 Override in subclasses to e.g. provide transparently encrypted dirdbm.
95 def _writeFile(self, path, data):
96 """Write data to a file.
98 Override in subclasses to e.g. provide transparently encrypted dirdbm.
100 f = _open(path, "wb")
107 @return: The number of key/value pairs in this Shelf
109 return len(os.listdir(self.dname))
111 def __setitem__(self, k, v):
114 Create or modify a textfile in this directory
120 @param v: value to associate with C{k}
122 assert type(k) == types.StringType, "DirDBM key must be a string"
123 assert type(v) == types.StringType, "DirDBM value must be a string"
126 # we create a new file with extension .new, write the data to it, and
127 # if the write succeeds delete the old file and rename the new one.
128 old = os.path.join(self.dname, k)
129 if os.path.exists(old):
130 new = old + ".rpl" # replacement entry
132 new = old + ".new" # new entry
134 self._writeFile(new, v)
139 if os.path.exists(old): os.remove(old)
142 def __getitem__(self, k):
145 Get the contents of a file in this directory as a string.
148 @param k: key to lookup
150 @return: The value associated with C{k}
151 @raise KeyError: Raised when there is no such key
153 assert type(k) == types.StringType, "DirDBM key must be a string"
154 path = os.path.join(self.dname, self._encode(k))
156 return self._readFile(path)
160 def __delitem__(self, k):
163 Delete a file in this directory.
166 @param k: key to delete
168 @raise KeyError: Raised when there is no such key
170 assert type(k) == types.StringType, "DirDBM key must be a string"
172 try: os.remove(os.path.join(self.dname, k))
173 except (OSError, IOError): raise KeyError(self._decode(k))
177 @return: a C{list} of filenames (keys).
179 return map(self._decode, os.listdir(self.dname))
183 @return: a C{list} of file-contents (values).
188 vals.append(self[key])
193 @return: a C{list} of 2-tuples containing key/value pairs.
198 items.append((key, self[key]))
201 def has_key(self, key):
204 @param key: The key to test
206 @return: A true value if this dirdbm has the specified key, a faluse
209 assert type(key) == types.StringType, "DirDBM key must be a string"
210 key = self._encode(key)
211 return os.path.isfile(os.path.join(self.dname, key))
213 def setdefault(self, key, value):
216 @param key: The key to lookup
218 @param value: The value to associate with key if key is not already
219 associated with a value.
221 if not self.has_key(key):
226 def get(self, key, default = None):
229 @param key: The key to lookup
231 @param default: The value to return if the given key does not exist
233 @return: The value associated with C{key} or C{default} if not
236 if self.has_key(key):
241 def __contains__(self, key):
246 @param key: The key to test
248 @return: A true value if C{self.has_key(key)}, a false value otherwise.
250 assert type(key) == types.StringType, "DirDBM key must be a string"
251 key = self._encode(key)
252 return os.path.isfile(os.path.join(self.dname, key))
254 def update(self, dict):
256 Add all the key/value pairs in C{dict} to this dirdbm. Any conflicting
257 keys will be overwritten with the values from C{dict}.
260 @param dict: A mapping of key/value pairs to add to this dirdbm.
262 for key, val in dict.items():
265 def copyTo(self, path):
267 Copy the contents of this dirdbm to the dirdbm at C{path}.
270 @param path: The path of the dirdbm to copy to. If a dirdbm
271 exists at the destination path, it is cleared first.
274 @return: The dirdbm this dirdbm was copied to.
276 path = os.path.abspath(path)
277 assert path != self.dname
279 d = self.__class__(path)
281 for k in self.keys():
287 Delete all key/value pairs in this dirdbm.
289 for k in self.keys():
294 Close this dbm: no-op, for dbm-style interface compliance.
297 def getModificationTime(self, key):
299 Returns modification time of an entry.
301 @return: Last modification date (seconds since epoch) of entry C{key}
302 @raise KeyError: Raised when there is no such key
304 assert type(key) == types.StringType, "DirDBM key must be a string"
305 path = os.path.join(self.dname, self._encode(key))
306 if os.path.isfile(path):
307 return os.path.getmtime(path)
313 """A directory with a DBM shelf interface.
315 This class presents a hash-like interface to a directory of small,
316 flat files. Keys must be strings, but values can be any given object.
319 def __setitem__(self, k, v):
322 Create or modify a textfile in this directory.
325 @param k: The key to set
327 @param v: The value to associate with C{key}
330 DirDBM.__setitem__(self, k, v)
332 def __getitem__(self, k):
335 Get and unpickle the contents of a file in this directory.
338 @param k: The key to lookup
340 @return: The value associated with the given key
341 @raise KeyError: Raised if the given key does not exist
343 return pickle.loads(DirDBM.__getitem__(self, k))
346 def open(file, flag = None, mode = None):
348 This is for 'anydbm' compatibility.
350 @param file: The parameter to pass to the DirDBM constructor.
358 __all__ = ["open", "DirDBM", "Shelf"]