|
1 """ |
|
2 Move a file in the safest way possible:: |
|
3 |
|
4 >>> from django.core.files.move import file_move_save |
|
5 >>> file_move_save("/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, try using ``shutils.move``, which is OS-dependent but doesn't break |
|
43 if moving across filesystems. Then, try ``os.rename``, which will break |
|
44 across filesystems. Finally, streams manually from one file to another in |
|
45 pure Python. |
|
46 |
|
47 If the destination file exists and ``allow_overwrite`` is ``False``, this |
|
48 function will throw an ``IOError``. |
|
49 """ |
|
50 |
|
51 # There's no reason to move if we don't have to. |
|
52 if _samefile(old_file_name, new_file_name): |
|
53 return |
|
54 |
|
55 try: |
|
56 os.rename(old_file_name, new_file_name) |
|
57 return |
|
58 except OSError: |
|
59 # This will happen with os.rename if moving to another filesystem |
|
60 # or when moving opened files on certain operating systems |
|
61 pass |
|
62 |
|
63 # first open the old file, so that it won't go away |
|
64 old_file = open(old_file_name, 'rb') |
|
65 try: |
|
66 # now open the new file, not forgetting allow_overwrite |
|
67 fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) | |
|
68 (not allow_overwrite and os.O_EXCL or 0)) |
|
69 try: |
|
70 locks.lock(fd, locks.LOCK_EX) |
|
71 current_chunk = None |
|
72 while current_chunk != '': |
|
73 current_chunk = old_file.read(chunk_size) |
|
74 os.write(fd, current_chunk) |
|
75 finally: |
|
76 locks.unlock(fd) |
|
77 os.close(fd) |
|
78 finally: |
|
79 old_file.close() |
|
80 copystat(old_file_name, new_file_name) |
|
81 |
|
82 try: |
|
83 os.remove(old_file_name) |
|
84 except OSError, e: |
|
85 # Certain operating systems (Cygwin and Windows) |
|
86 # fail when deleting opened files, ignore it |
|
87 if getattr(e, 'winerror', 0) != 32: |
|
88 # FIXME: should we also ignore errno 13? |
|
89 raise |