|
0
|
1 |
""" |
|
|
2 |
Move a file in the safest way possible:: |
|
|
3 |
|
|
|
4 |
>>> from django.core.files.move import file_move_safe |
|
|
5 |
>>> file_move_safe("/tmp/old_file", "/tmp/new_file") |
|
|
6 |
""" |
|
|
7 |
|
|
|
8 |
import os |
|
|
9 |
from django.core.files import locks |
|
|
10 |
|
|
|
11 |
try: |
|
|
12 |
from shutil import copystat |
|
|
13 |
except ImportError: |
|
|
14 |
import stat |
|
|
15 |
def copystat(src, dst): |
|
|
16 |
"""Copy all stat info (mode bits, atime and mtime) from src to dst""" |
|
|
17 |
st = os.stat(src) |
|
|
18 |
mode = stat.S_IMODE(st.st_mode) |
|
|
19 |
if hasattr(os, 'utime'): |
|
|
20 |
os.utime(dst, (st.st_atime, st.st_mtime)) |
|
|
21 |
if hasattr(os, 'chmod'): |
|
|
22 |
os.chmod(dst, mode) |
|
|
23 |
|
|
|
24 |
__all__ = ['file_move_safe'] |
|
|
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 file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False): |
|
|
39 |
""" |
|
|
40 |
Moves a file from one location to another in the safest way possible. |
|
|
41 |
|
|
|
42 |
First, tries ``os.rename``, which is simple but will break across filesystems. |
|
|
43 |
If that fails, streams manually from one file to another in pure Python. |
|
|
44 |
|
|
|
45 |
If the destination file exists and ``allow_overwrite`` is ``False``, this |
|
|
46 |
function will throw an ``IOError``. |
|
|
47 |
""" |
|
|
48 |
|
|
|
49 |
# There's no reason to move if we don't have to. |
|
|
50 |
if _samefile(old_file_name, new_file_name): |
|
|
51 |
return |
|
|
52 |
|
|
|
53 |
try: |
|
|
54 |
os.rename(old_file_name, new_file_name) |
|
|
55 |
return |
|
|
56 |
except OSError: |
|
|
57 |
# This will happen with os.rename if moving to another filesystem |
|
|
58 |
# or when moving opened files on certain operating systems |
|
|
59 |
pass |
|
|
60 |
|
|
|
61 |
# first open the old file, so that it won't go away |
|
|
62 |
old_file = open(old_file_name, 'rb') |
|
|
63 |
try: |
|
|
64 |
# now open the new file, not forgetting allow_overwrite |
|
|
65 |
fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) | |
|
|
66 |
(not allow_overwrite and os.O_EXCL or 0)) |
|
|
67 |
try: |
|
|
68 |
locks.lock(fd, locks.LOCK_EX) |
|
|
69 |
current_chunk = None |
|
|
70 |
while current_chunk != '': |
|
|
71 |
current_chunk = old_file.read(chunk_size) |
|
|
72 |
os.write(fd, current_chunk) |
|
|
73 |
finally: |
|
|
74 |
locks.unlock(fd) |
|
|
75 |
os.close(fd) |
|
|
76 |
finally: |
|
|
77 |
old_file.close() |
|
|
78 |
copystat(old_file_name, new_file_name) |
|
|
79 |
|
|
|
80 |
try: |
|
|
81 |
os.remove(old_file_name) |
|
|
82 |
except OSError, e: |
|
|
83 |
# Certain operating systems (Cygwin and Windows) |
|
|
84 |
# fail when deleting opened files, ignore it. (For the |
|
|
85 |
# systems where this happens, temporary files will be auto-deleted |
|
|
86 |
# on close anyway.) |
|
|
87 |
if getattr(e, 'winerror', 0) != 32 and getattr(e, 'errno', 0) != 13: |
|
|
88 |
raise |