|
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 |