With some clearer thinking, I believe this is the proper implementation of tmpDir. If there's something wrong with it, send me a note or write a test and it'll be fixed.

This commit is contained in:
Jeremy Fincher 2004-08-01 12:46:03 +00:00
parent 7c41187204
commit b6ba7955ac
7 changed files with 46 additions and 30 deletions

View File

@ -104,7 +104,7 @@ class InfobotDB(object):
'I hear ya']
def flush(self):
fd = utils.AtomicFile(filename)
fd = utils.transactionalFile(filename, 'wb')
pickle.dump((self._is, self._are), fd)
fd.close()

View File

@ -149,7 +149,7 @@ class URLDB(object):
return [url for (url, nick) in self.getUrlsAndNicks(p)]
def vacuum(self):
out = utils.AtomicFile(self.filename)
out = utils.transactionalFile(self.filename)
notAdded = 0
urls = self.getUrlsAndNicks(lambda *args: True)
seen = sets.Set()

View File

@ -136,7 +136,7 @@ def make(dbFilename, readFilename=None):
class Maker(object):
"""Class for making CDB databases."""
def __init__(self, filename):
self.fd = utils.AtomicFile(filename)
self.fd = utils.transactionalFile(filename)
self.filename = filename
self.fd.seek(2048)
self.hashPointers = [(0, 0)] * 256

View File

@ -526,11 +526,19 @@ registerGlobalValue(supybot.drivers, 'module',
class Directory(registry.String):
def __call__(self):
# ??? Should we perhaps always return an absolute path here?
v = super(Directory, self).__call__()
if not os.path.exists(v):
os.mkdir(v)
return v
def dirize(self, filename):
dir = self()
dirname = os.path.basename(filename)
if not dirname.endswith(dir): # ??? Should this be an "in" test instead?
return os.path.join(dir, os.path.basename(filename))
return filename
class DataFilename(registry.String):
def __call__(self):
v = super(DataFilename, self).__call__()
@ -557,12 +565,11 @@ registerGlobalValue(supybot.directories.data, 'tmp',
DataFilenameDirectory('tmp', """Determines what directory temporary files
are put into."""))
# This is a hack, but it should work.
utils._AtomicFile = utils.AtomicFile
def AtomicFile(*args, **kwargs):
# Remember, we're *meant* to replace this nice little wrapper.
def transactionalFile(*args, **kwargs):
kwargs['tmpDir'] = supybot.directories.data.tmp()
return utils._AtomicFile(*args, **kwargs)
utils.AtomicFile = AtomicFile
return utils.AtomicFile(*args, **kwargs)
utils.transactionalFile = transactionalFile
class PluginDirectories(registry.CommaSeparatedListOfStrings):
def __call__(self):

View File

@ -571,7 +571,7 @@ class UsersDictionary(utils.IterableMap):
if self.filename is not None:
L = self.users.items()
L.sort()
fd = utils.AtomicFile(self.filename)
fd = utils.transactionalFile(self.filename)
for (id, u) in L:
fd.write('user %s' % id)
fd.write(os.linesep)
@ -732,7 +732,7 @@ class ChannelsDictionary(utils.IterableMap):
def flush(self):
"""Flushes the channel database to its file."""
if self.filename is not None:
fd = utils.AtomicFile(self.filename)
fd = utils.transactionalFile(self.filename)
for (channel, c) in self.channels.iteritems():
fd.write('channel %s' % channel)
fd.write(os.linesep)
@ -792,7 +792,7 @@ class IgnoresDB(object):
def flush(self):
if self.filename is not None:
fd = utils.AtomicFile(self.filename)
fd = utils.transactionalFile(self.filename)
for hostmask in self.hostmasks:
fd.write(hostmask)
fd.write(os.linesep)

View File

@ -78,7 +78,7 @@ def open(filename, clear=False):
def close(registry, filename, annotated=True, helpOnceOnly=False):
first = True
helpCache = sets.Set()
fd = utils.AtomicFile(filename)
fd = utils.transactionalFile(filename)
for (name, value) in registry.getValues(getChildren=True):
if annotated and hasattr(value,'help') and value.help:
if not helpOnceOnly or value.help not in self.helpCache:

View File

@ -673,34 +673,36 @@ def stackTrace():
class AtomicFile(file):
"""Used for files that need to be atomically written -- i.e., if there's a
failure, the original file remains, unmodified.
Opens the file in 'w' mode."""
def __init__(self, filename, allowEmptyOverwrite=False, tmpDir=None):
self.filename = os.path.abspath(filename)
failure, the original file remains, unmodified. mode must be 'w' or 'wb'"""
def __init__(self, filename, mode='w',
allowEmptyOverwrite=False, tmpDir=None):
if mode not in ('w', 'wb'):
raise ValueError, 'Invalid mode: %r' % mode
self.rolledback = False
self.allowEmptyOverwrite = allowEmptyOverwrite
self.tempFilename = '%s.%s' % (self.filename, mktemp())
if tmpDir is not None:
tempFilename = os.path.dirname(self.tempFilename)
self.tempFilename = os.path.join(tmpDir, self.tempFilename)
super(self.__class__, self).__init__(self.tempFilename, 'w')
self.filename = filename
if tmpDir is None:
# If not given a tmpDir, we'll just put a random token on the end
# of our filename and put it in the same directory.
self.tempFilename = '%s.%s' % (self.filename, mktemp())
else:
# If given a tmpDir, we'll get the basename (just the filename, no
# directory), put our random token on the end, and put it in tmpDir
tempFilename = '%s.%s' % (os.path.basename(self.filename), mktemp())
self.tempFilename = os.path.join(tmpDir, tempFilename)
super(AtomicFile, self).__init__(self.tempFilename, mode)
def rollback(self):
#print 'AtomicFile.rollback'
if not self.closed:
super(self.__class__, self).close()
super(AtomicFile, self).close()
if os.path.exists(self.tempFilename):
#print 'AtomicFile: Removing %s.' % self.tempFilename
os.remove(self.tempFilename)
self.rolledback = True
def close(self):
#print 'AtomicFile.close'
if not self.rolledback:
#print 'AtomicFile.close: actually closing.'
super(self.__class__, self).close()
size = os.stat(self.tempFilename).st_size
super(AtomicFile, self).close()
size = os.path.getsize(self.tempFilename)
if size or self.allowEmptyOverwrite:
if os.path.exists(self.tempFilename):
# We use shutil.move here instead of os.rename because
@ -710,9 +712,16 @@ class AtomicFile(file):
shutil.move(self.tempFilename, self.filename)
def __del__(self):
#print 'AtomicFile.__del__'
# We rollback because if we're deleted without being explicitly closed,
# that's bad. We really should log this here, but as of yet we've got
# no logging facility in utils. I've got some ideas for this, though.
self.rollback()
def transactionalFile(*args, **kwargs):
# This exists so it can be replaced by a function that provides the tmpDir.
# We do that replacement in conf.py.
return AtomicFile(*args, **kwargs)
if __name__ == '__main__':
import sys, doctest
doctest.testmod(sys.modules['__main__'])