--- /dev/null
+# Copyright (c) 2000 Autonomous Zone Industries
+# Copyright (c) 2002-2007 Bryce "Zooko" Wilcox-O'Hearn
+# This file is licensed under the
+# GNU Lesser General Public License v2.1.
+# See the file COPYING or visit http://www.gnu.org/ for details.
+# Portions snarfed out of the Python standard library.
+# The du part is due to Jim McCoy.
+
+"""
+Futz with files like a pro.
+"""
+
+import exceptions, os, stat, tempfile, time
+
+try:
+ from twisted.python import log
+except ImportError:
+ class DummyLog:
+ def msg(self, *args, **kwargs):
+ pass
+ log = DummyLog()
+
+def rename(src, dst, tries=4, basedelay=0.1):
+ """ Here is a superkludge to workaround the fact that occasionally on
+ Windows some other process (e.g. an anti-virus scanner, a local search
+ engine, etc.) is looking at your file when you want to delete or move it,
+ and hence you can't. The horrible workaround is to sit and spin, trying
+ to delete it, for a short time and then give up.
+
+ With the default values of tries and basedelay this can block for less
+ than a second.
+
+ @param tries: number of tries -- each time after the first we wait twice
+ as long as the previous wait
+ @param basedelay: how long to wait before the second try
+ """
+ for i in range(tries-1):
+ try:
+ return os.rename(src, dst)
+ except EnvironmentError, le:
+ # XXX Tighten this to check if this is a permission denied error (possibly due to another Windows process having the file open and execute the superkludge only in this case.
+ log.msg("XXX KLUDGE Attempting to move file %s => %s; got %s; sleeping %s seconds" % (src, dst, le, basedelay,))
+ time.sleep(basedelay)
+ basedelay *= 2
+ return os.rename(src, dst) # The last try.
+
+def remove(f, tries=4, basedelay=0.1):
+ """ Here is a superkludge to workaround the fact that occasionally on
+ Windows some other process (e.g. an anti-virus scanner, a local search
+ engine, etc.) is looking at your file when you want to delete or move it,
+ and hence you can't. The horrible workaround is to sit and spin, trying
+ to delete it, for a short time and then give up.
+
+ With the default values of tries and basedelay this can block for less
+ than a second.
+
+ @param tries: number of tries -- each time after the first we wait twice
+ as long as the previous wait
+ @param basedelay: how long to wait before the second try
+ """
+ try:
+ os.chmod(f, stat.S_IWRITE | stat.S_IEXEC | stat.S_IREAD)
+ except:
+ pass
+ for i in range(tries-1):
+ try:
+ return os.remove(f)
+ except EnvironmentError, le:
+ # XXX Tighten this to check if this is a permission denied error (possibly due to another Windows process having the file open and execute the superkludge only in this case.
+ if not os.path.exists(f):
+ return
+ log.msg("XXX KLUDGE Attempting to remove file %s; got %s; sleeping %s seconds" % (f, le, basedelay,))
+ time.sleep(basedelay)
+ basedelay *= 2
+ return os.remove(f) # The last try.
+
+class NamedTemporaryDirectory:
+ """
+ This calls tempfile.mkdtemp(), stores the name of the dir in
+ self.name, and rmrf's the dir when it gets garbage collected or
+ "shutdown()".
+ """
+ def __init__(self, cleanup=True, *args, **kwargs):
+ """ If cleanup, then the directory will be rmrf'ed when the object is shutdown. """
+ self.cleanup = cleanup
+ self.name = tempfile.mkdtemp(*args, **kwargs)
+
+ def __repr__(self):
+ return "<%s instance at %x %s>" % (self.__class__.__name__, id(self), self.name)
+
+ def __str__(self):
+ return self.__repr__()
+
+ def __del__(self):
+ try:
+ self.shutdown()
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def shutdown(self):
+ if self.cleanup and hasattr(self, 'name'):
+ rm_dir(self.name)
+
+def make_dirs(dirname, mode=0777, strictmode=False):
+ """
+ A threadsafe and idempotent version of os.makedirs(). If the dir already
+ exists, do nothing and return without raising an exception. If this call
+ creates the dir, return without raising an exception. If there is an
+ error that prevents creation or if the directory gets deleted after
+ make_dirs() creates it and before make_dirs() checks that it exists, raise
+ an exception.
+
+ @param strictmode if true, then make_dirs() will raise an exception if the
+ directory doesn't have the desired mode. For example, if the
+ directory already exists, and has a different mode than the one
+ specified by the mode parameter, then if strictmode is true,
+ make_dirs() will raise an exception, else it will ignore the
+ discrepancy.
+ """
+ tx = None
+ try:
+ os.makedirs(dirname, mode)
+ except OSError, x:
+ tx = x
+
+ if not os.path.isdir(dirname):
+ if tx:
+ raise tx
+ raise exceptions.IOError, "unknown error prevented creation of directory, or deleted the directory immediately after creation: %s" % dirname # careful not to construct an IOError with a 2-tuple, as that has a special meaning...
+
+ tx = None
+ if hasattr(os, 'chmod'):
+ try:
+ os.chmod(dirname, mode)
+ except OSError, x:
+ tx = x
+
+ if strictmode and hasattr(os, 'stat'):
+ s = os.stat(dirname)
+ resmode = stat.S_IMODE(s.st_mode)
+ if resmode != mode:
+ if tx:
+ raise tx
+ raise exceptions.IOError, "unknown error prevented setting correct mode of directory, or changed mode of the directory immediately after creation. dirname: %s, mode: %04o, resmode: %04o" % (dirname, mode, resmode,) # careful not to construct an IOError with a 2-tuple, as that has a special meaning...
+
+def rm_dir(dirname):
+ """
+ A threadsafe and idempotent version of shutil.rmtree(). If the dir is
+ already gone, do nothing and return without raising an exception. If this
+ call removes the dir, return without raising an exception. If there is an
+ error that prevents deletion or if the directory gets created again after
+ rm_dir() deletes it and before rm_dir() checks that it is gone, raise an
+ exception.
+ """
+ excs = []
+ try:
+ os.chmod(dirname, stat.S_IWRITE | stat.S_IEXEC | stat.S_IREAD)
+ for f in os.listdir(dirname):
+ fullname = os.path.join(dirname, f)
+ if os.path.isdir(fullname):
+ rm_dir(fullname)
+ else:
+ remove(fullname)
+ os.rmdir(dirname)
+ except Exception, le:
+ # Ignore "No such file or directory"
+ if (not isinstance(le, OSError)) or le.args[0] != 2:
+ excs.append(le)
+
+ # Okay, now we've recursively removed everything, ignoring any "No
+ # such file or directory" errors, and collecting any other errors.
+
+ if os.path.exists(dirname):
+ if len(excs) == 1:
+ raise excs[0]
+ if len(excs) == 0:
+ raise OSError, "Failed to remove dir for unknown reason."
+ raise OSError, excs
+
+
+def remove_if_possible(f):
+ try:
+ remove(f)
+ except:
+ pass