]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/util/fileutil.py
doc: licensing cleanups
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / util / fileutil.py
1 """
2 Futz with files like a pro.
3 """
4
5 import sys, exceptions, os, stat, tempfile, time
6
7 from twisted.python import log
8
9 def rename(src, dst, tries=4, basedelay=0.1):
10     """ Here is a superkludge to workaround the fact that occasionally on
11     Windows some other process (e.g. an anti-virus scanner, a local search
12     engine, etc.) is looking at your file when you want to delete or move it,
13     and hence you can't.  The horrible workaround is to sit and spin, trying
14     to delete it, for a short time and then give up.
15
16     With the default values of tries and basedelay this can block for less
17     than a second.
18
19     @param tries: number of tries -- each time after the first we wait twice
20     as long as the previous wait
21     @param basedelay: how long to wait before the second try
22     """
23     for i in range(tries-1):
24         try:
25             return os.rename(src, dst)
26         except EnvironmentError, le:
27             # 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.
28             log.msg("XXX KLUDGE Attempting to move file %s => %s; got %s; sleeping %s seconds" % (src, dst, le, basedelay,))
29             time.sleep(basedelay)
30             basedelay *= 2
31     return os.rename(src, dst) # The last try.
32
33 def remove(f, tries=4, basedelay=0.1):
34     """ Here is a superkludge to workaround the fact that occasionally on
35     Windows some other process (e.g. an anti-virus scanner, a local search
36     engine, etc.) is looking at your file when you want to delete or move it,
37     and hence you can't.  The horrible workaround is to sit and spin, trying
38     to delete it, for a short time and then give up.
39
40     With the default values of tries and basedelay this can block for less
41     than a second.
42
43     @param tries: number of tries -- each time after the first we wait twice
44     as long as the previous wait
45     @param basedelay: how long to wait before the second try
46     """
47     try:
48         os.chmod(f, stat.S_IWRITE | stat.S_IEXEC | stat.S_IREAD)
49     except:
50         pass
51     for i in range(tries-1):
52         try:
53             return os.remove(f)
54         except EnvironmentError, le:
55             # 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.
56             if not os.path.exists(f):
57                 return
58             log.msg("XXX KLUDGE Attempting to remove file %s; got %s; sleeping %s seconds" % (f, le, basedelay,))
59             time.sleep(basedelay)
60             basedelay *= 2
61     return os.remove(f) # The last try.
62
63 class ReopenableNamedTemporaryFile:
64     """
65     This uses tempfile.mkstemp() to generate a secure temp file.  It then closes
66     the file, leaving a zero-length file as a placeholder.  You can get the
67     filename with ReopenableNamedTemporaryFile.name.  When the
68     ReopenableNamedTemporaryFile instance is garbage collected or its shutdown()
69     method is called, it deletes the file.
70     """
71     def __init__(self, *args, **kwargs):
72         fd, self.name = tempfile.mkstemp(*args, **kwargs)
73         os.close(fd)
74
75     def __repr__(self):
76         return "<%s instance at %x %s>" % (self.__class__.__name__, id(self), self.name)
77
78     def __str__(self):
79         return self.__repr__()
80
81     def __del__(self):
82         self.shutdown()
83
84     def shutdown(self):
85         remove(self.name)
86
87 class NamedTemporaryDirectory:
88     """
89     This calls tempfile.mkdtemp(), stores the name of the dir in
90     self.name, and rmrf's the dir when it gets garbage collected or
91     "shutdown()".
92     """
93     def __init__(self, cleanup=True, *args, **kwargs):
94         """ If cleanup, then the directory will be rmrf'ed when the object is shutdown. """
95         self.cleanup = cleanup
96         self.name = tempfile.mkdtemp(*args, **kwargs)
97
98     def __repr__(self):
99         return "<%s instance at %x %s>" % (self.__class__.__name__, id(self), self.name)
100
101     def __str__(self):
102         return self.__repr__()
103
104     def __del__(self):
105         try:
106             self.shutdown()
107         except:
108             import traceback
109             traceback.print_exc()
110
111     def shutdown(self):
112         if self.cleanup and hasattr(self, 'name'):
113             rm_dir(self.name)
114
115 def make_dirs(dirname, mode=0777):
116     """
117     An idempotent version of os.makedirs().  If the dir already exists, do
118     nothing and return without raising an exception.  If this call creates the
119     dir, return without raising an exception.  If there is an error that
120     prevents creation or if the directory gets deleted after make_dirs() creates
121     it and before make_dirs() checks that it exists, raise an exception.
122     """
123     tx = None
124     try:
125         os.makedirs(dirname, mode)
126     except OSError, x:
127         tx = x
128
129     if not os.path.isdir(dirname):
130         if tx:
131             raise tx
132         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...
133
134 def rm_dir(dirname):
135     """
136     A threadsafe and idempotent version of shutil.rmtree().  If the dir is
137     already gone, do nothing and return without raising an exception.  If this
138     call removes the dir, return without raising an exception.  If there is an
139     error that prevents deletion or if the directory gets created again after
140     rm_dir() deletes it and before rm_dir() checks that it is gone, raise an
141     exception.
142     """
143     excs = []
144     try:
145         os.chmod(dirname, stat.S_IWRITE | stat.S_IEXEC | stat.S_IREAD)
146         for f in os.listdir(dirname):
147             fullname = os.path.join(dirname, f)
148             if os.path.isdir(fullname):
149                 rm_dir(fullname)
150             else:
151                 remove(fullname)
152         os.rmdir(dirname)
153     except Exception, le:
154         # Ignore "No such file or directory"
155         if (not isinstance(le, OSError)) or le.args[0] != 2:
156             excs.append(le)
157
158     # Okay, now we've recursively removed everything, ignoring any "No
159     # such file or directory" errors, and collecting any other errors.
160
161     if os.path.exists(dirname):
162         if len(excs) == 1:
163             raise excs[0]
164         if len(excs) == 0:
165             raise OSError, "Failed to remove dir for unknown reason."
166         raise OSError, excs
167
168
169 def remove_if_possible(f):
170     try:
171         remove(f)
172     except:
173         pass
174
175 def open_or_create(fname, binarymode=True):
176     try:
177         return open(fname, binarymode and "r+b" or "r+")
178     except EnvironmentError:
179         return open(fname, binarymode and "w+b" or "w+")
180
181 def du(basedir):
182     size = 0
183
184     for root, dirs, files in os.walk(basedir):
185         for f in files:
186             fn = os.path.join(root, f)
187             size += os.path.getsize(fn)
188
189     return size
190
191 def move_into_place(source, dest):
192     """Atomically replace a file, or as near to it as the platform allows.
193     The dest file may or may not exist."""
194     if "win32" in sys.platform.lower():
195         remove_if_possible(dest)
196     os.rename(source, dest)