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