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