app/django/core/files/move.py
changeset 323 ff1a9aa48cfd
equal deleted inserted replaced
322:6641e941ef1e 323:ff1a9aa48cfd
       
     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