app/django/core/files/move.py
author Sverre Rabbelier <srabbelier@gmail.com>
Tue, 02 Dec 2008 20:10:23 +0000
changeset 649 95a41542e693
parent 323 ff1a9aa48cfd
permissions -rw-r--r--
Finish Program Added some fields, and made TinyMCE the editor for the description field. Also updated the public page to show these new fields. We really need generic public views... Patch by: Sverre Rabbelier

"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_save
    >>> file_move_save("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

try:
    from shutil import copystat
except ImportError:
    import stat
    def copystat(src, dst):
        """Copy all stat info (mode bits, atime and mtime) from src to dst"""
        st = os.stat(src)
        mode = stat.S_IMODE(st.st_mode)
        if hasattr(os, 'utime'):
            os.utime(dst, (st.st_atime, st.st_mtime))
        if hasattr(os, 'chmod'):
            os.chmod(dst, mode)

__all__ = ['file_move_safe']

def _samefile(src, dst):
    # Macintosh, Unix.
    if hasattr(os.path,'samefile'):
        try:
            return os.path.samefile(src, dst)
        except OSError:
            return False

    # All other platforms: check for same pathname.
    return (os.path.normcase(os.path.abspath(src)) ==
            os.path.normcase(os.path.abspath(dst)))

def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
    """
    Moves a file from one location to another in the safest way possible.

    First, try using ``shutils.move``, which is OS-dependent but doesn't break
    if moving across filesystems. Then, try ``os.rename``, which will break
    across filesystems. Finally, streams manually from one file to another in
    pure Python.

    If the destination file exists and ``allow_overwrite`` is ``False``, this
    function will throw an ``IOError``.
    """

    # There's no reason to move if we don't have to.
    if _samefile(old_file_name, new_file_name):
        return

    try:
        os.rename(old_file_name, new_file_name)
        return
    except OSError:
        # This will happen with os.rename if moving to another filesystem
        # or when moving opened files on certain operating systems
        pass

    # first open the old file, so that it won't go away
    old_file = open(old_file_name, 'rb')
    try:
        # now open the new file, not forgetting allow_overwrite
        fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
                                    (not allow_overwrite and os.O_EXCL or 0))
        try:
            locks.lock(fd, locks.LOCK_EX)
            current_chunk = None
            while current_chunk != '':
                current_chunk = old_file.read(chunk_size)
                os.write(fd, current_chunk)
        finally:
            locks.unlock(fd)
            os.close(fd)
    finally:
        old_file.close()
    copystat(old_file_name, new_file_name)

    try:
        os.remove(old_file_name)
    except OSError, e:
        # Certain operating systems (Cygwin and Windows)
        # fail when deleting opened files, ignore it
        if getattr(e, 'winerror', 0) != 32:
            # FIXME: should we also ignore errno 13?
            raise