|
1 """Utility functions for copying files and directory trees. |
|
2 |
|
3 XXX The functions here don't copy the resource fork or other metadata on Mac. |
|
4 |
|
5 """ |
|
6 |
|
7 import os |
|
8 import sys |
|
9 import stat |
|
10 from os.path import abspath |
|
11 |
|
12 __all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2", |
|
13 "copytree","move","rmtree","Error"] |
|
14 |
|
15 class Error(EnvironmentError): |
|
16 pass |
|
17 |
|
18 def copyfileobj(fsrc, fdst, length=16*1024): |
|
19 """copy data from file-like object fsrc to file-like object fdst""" |
|
20 while 1: |
|
21 buf = fsrc.read(length) |
|
22 if not buf: |
|
23 break |
|
24 fdst.write(buf) |
|
25 |
|
26 def _samefile(src, dst): |
|
27 # Macintosh, Unix. |
|
28 if hasattr(os.path,'samefile'): |
|
29 try: |
|
30 return os.path.samefile(src, dst) |
|
31 except OSError: |
|
32 return False |
|
33 |
|
34 # All other platforms: check for same pathname. |
|
35 return (os.path.normcase(os.path.abspath(src)) == |
|
36 os.path.normcase(os.path.abspath(dst))) |
|
37 |
|
38 def copyfile(src, dst): |
|
39 """Copy data from src to dst""" |
|
40 if _samefile(src, dst): |
|
41 raise Error, "`%s` and `%s` are the same file" % (src, dst) |
|
42 |
|
43 fsrc = None |
|
44 fdst = None |
|
45 try: |
|
46 fsrc = open(src, 'rb') |
|
47 fdst = open(dst, 'wb') |
|
48 copyfileobj(fsrc, fdst) |
|
49 finally: |
|
50 if fdst: |
|
51 fdst.close() |
|
52 if fsrc: |
|
53 fsrc.close() |
|
54 |
|
55 def copymode(src, dst): |
|
56 """Copy mode bits from src to dst""" |
|
57 if hasattr(os, 'chmod'): |
|
58 st = os.stat(src) |
|
59 mode = stat.S_IMODE(st.st_mode) |
|
60 os.chmod(dst, mode) |
|
61 |
|
62 def copystat(src, dst): |
|
63 """Copy all stat info (mode bits, atime and mtime) from src to dst""" |
|
64 st = os.stat(src) |
|
65 mode = stat.S_IMODE(st.st_mode) |
|
66 if hasattr(os, 'utime'): |
|
67 os.utime(dst, (st.st_atime, st.st_mtime)) |
|
68 if hasattr(os, 'chmod'): |
|
69 os.chmod(dst, mode) |
|
70 |
|
71 |
|
72 def copy(src, dst): |
|
73 """Copy data and mode bits ("cp src dst"). |
|
74 |
|
75 The destination may be a directory. |
|
76 |
|
77 """ |
|
78 if os.path.isdir(dst): |
|
79 dst = os.path.join(dst, os.path.basename(src)) |
|
80 copyfile(src, dst) |
|
81 copymode(src, dst) |
|
82 |
|
83 def copy2(src, dst): |
|
84 """Copy data and all stat info ("cp -p src dst"). |
|
85 |
|
86 The destination may be a directory. |
|
87 |
|
88 """ |
|
89 if os.path.isdir(dst): |
|
90 dst = os.path.join(dst, os.path.basename(src)) |
|
91 copyfile(src, dst) |
|
92 copystat(src, dst) |
|
93 |
|
94 |
|
95 def copytree(src, dst, symlinks=False): |
|
96 """Recursively copy a directory tree using copy2(). |
|
97 |
|
98 The destination directory must not already exist. |
|
99 If exception(s) occur, an Error is raised with a list of reasons. |
|
100 |
|
101 If the optional symlinks flag is true, symbolic links in the |
|
102 source tree result in symbolic links in the destination tree; if |
|
103 it is false, the contents of the files pointed to by symbolic |
|
104 links are copied. |
|
105 |
|
106 XXX Consider this example code rather than the ultimate tool. |
|
107 |
|
108 """ |
|
109 names = os.listdir(src) |
|
110 os.makedirs(dst) |
|
111 errors = [] |
|
112 for name in names: |
|
113 srcname = os.path.join(src, name) |
|
114 dstname = os.path.join(dst, name) |
|
115 try: |
|
116 if symlinks and os.path.islink(srcname): |
|
117 linkto = os.readlink(srcname) |
|
118 os.symlink(linkto, dstname) |
|
119 elif os.path.isdir(srcname): |
|
120 copytree(srcname, dstname, symlinks) |
|
121 else: |
|
122 copy2(srcname, dstname) |
|
123 # XXX What about devices, sockets etc.? |
|
124 except (IOError, os.error), why: |
|
125 errors.append((srcname, dstname, str(why))) |
|
126 # catch the Error from the recursive copytree so that we can |
|
127 # continue with other files |
|
128 except Error, err: |
|
129 errors.extend(err.args[0]) |
|
130 try: |
|
131 copystat(src, dst) |
|
132 except WindowsError: |
|
133 # can't copy file access times on Windows |
|
134 pass |
|
135 except OSError, why: |
|
136 errors.extend((src, dst, str(why))) |
|
137 if errors: |
|
138 raise Error, errors |
|
139 |
|
140 def rmtree(path, ignore_errors=False, onerror=None): |
|
141 """Recursively delete a directory tree. |
|
142 |
|
143 If ignore_errors is set, errors are ignored; otherwise, if onerror |
|
144 is set, it is called to handle the error with arguments (func, |
|
145 path, exc_info) where func is os.listdir, os.remove, or os.rmdir; |
|
146 path is the argument to that function that caused it to fail; and |
|
147 exc_info is a tuple returned by sys.exc_info(). If ignore_errors |
|
148 is false and onerror is None, an exception is raised. |
|
149 |
|
150 """ |
|
151 if ignore_errors: |
|
152 def onerror(*args): |
|
153 pass |
|
154 elif onerror is None: |
|
155 def onerror(*args): |
|
156 raise |
|
157 names = [] |
|
158 try: |
|
159 names = os.listdir(path) |
|
160 except os.error, err: |
|
161 onerror(os.listdir, path, sys.exc_info()) |
|
162 for name in names: |
|
163 fullname = os.path.join(path, name) |
|
164 try: |
|
165 mode = os.lstat(fullname).st_mode |
|
166 except os.error: |
|
167 mode = 0 |
|
168 if stat.S_ISDIR(mode): |
|
169 rmtree(fullname, ignore_errors, onerror) |
|
170 else: |
|
171 try: |
|
172 os.remove(fullname) |
|
173 except os.error, err: |
|
174 onerror(os.remove, fullname, sys.exc_info()) |
|
175 try: |
|
176 os.rmdir(path) |
|
177 except os.error: |
|
178 onerror(os.rmdir, path, sys.exc_info()) |
|
179 |
|
180 def move(src, dst): |
|
181 """Recursively move a file or directory to another location. |
|
182 |
|
183 If the destination is on our current filesystem, then simply use |
|
184 rename. Otherwise, copy src to the dst and then remove src. |
|
185 A lot more could be done here... A look at a mv.c shows a lot of |
|
186 the issues this implementation glosses over. |
|
187 |
|
188 """ |
|
189 |
|
190 try: |
|
191 os.rename(src, dst) |
|
192 except OSError: |
|
193 if os.path.isdir(src): |
|
194 if destinsrc(src, dst): |
|
195 raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst) |
|
196 copytree(src, dst, symlinks=True) |
|
197 rmtree(src) |
|
198 else: |
|
199 copy2(src,dst) |
|
200 os.unlink(src) |
|
201 |
|
202 def destinsrc(src, dst): |
|
203 return abspath(dst).startswith(abspath(src)) |