2008-07-01 23:10:51 +08:00
|
|
|
"""
|
|
|
|
Move a file in the safest way possible::
|
|
|
|
|
2009-04-05 12:41:39 +08:00
|
|
|
>>> from django.core.files.move import file_move_safe
|
|
|
|
>>> file_move_safe("/tmp/old_file", "/tmp/new_file")
|
2008-07-01 23:10:51 +08:00
|
|
|
"""
|
|
|
|
|
2017-05-10 23:29:12 +08:00
|
|
|
import errno
|
2008-07-01 23:10:51 +08:00
|
|
|
import os
|
2014-12-25 08:27:51 +08:00
|
|
|
from shutil import copystat
|
|
|
|
|
2008-07-01 23:10:51 +08:00
|
|
|
from django.core.files import locks
|
|
|
|
|
2008-08-24 01:56:02 +08:00
|
|
|
__all__ = ['file_move_safe']
|
|
|
|
|
2013-11-03 04:12:09 +08:00
|
|
|
|
2008-08-24 01:56:02 +08:00
|
|
|
def _samefile(src, dst):
|
|
|
|
# Macintosh, Unix.
|
2013-10-25 01:30:03 +08:00
|
|
|
if hasattr(os.path, 'samefile'):
|
2008-08-24 01:56:02 +08:00
|
|
|
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)))
|
2008-07-01 23:10:51 +08:00
|
|
|
|
2013-11-03 04:12:09 +08:00
|
|
|
|
2013-11-04 02:08:55 +08:00
|
|
|
def file_move_safe(old_file_name, new_file_name, chunk_size=1024 * 64, allow_overwrite=False):
|
2008-07-01 23:10:51 +08:00
|
|
|
"""
|
2017-01-26 03:02:33 +08:00
|
|
|
Move a file from one location to another in the safest way possible.
|
2008-07-01 23:10:51 +08:00
|
|
|
|
2017-01-26 03:02:33 +08:00
|
|
|
First, try ``os.rename``, which is simple but will break across filesystems.
|
|
|
|
If that fails, stream manually from one file to another in pure Python.
|
2008-07-01 23:10:51 +08:00
|
|
|
|
2017-01-26 03:02:33 +08:00
|
|
|
If the destination file exists and ``allow_overwrite`` is ``False``, raise
|
2019-01-28 23:01:35 +08:00
|
|
|
``FileExistsError``.
|
2008-07-01 23:10:51 +08:00
|
|
|
"""
|
|
|
|
# There's no reason to move if we don't have to.
|
2008-08-24 01:56:02 +08:00
|
|
|
if _samefile(old_file_name, new_file_name):
|
2008-07-01 23:10:51 +08:00
|
|
|
return
|
|
|
|
|
2017-09-07 20:16:21 +08:00
|
|
|
try:
|
2013-06-20 18:55:27 +08:00
|
|
|
if not allow_overwrite and os.access(new_file_name, os.F_OK):
|
2019-01-28 23:01:35 +08:00
|
|
|
raise FileExistsError('Destination file %s exists and allow_overwrite is False.' % new_file_name)
|
2013-06-20 18:55:27 +08:00
|
|
|
|
2008-08-24 01:56:02 +08:00
|
|
|
os.rename(old_file_name, new_file_name)
|
2008-07-01 23:10:51 +08:00
|
|
|
return
|
2017-09-07 20:16:21 +08:00
|
|
|
except OSError:
|
|
|
|
# OSError happens with os.rename() if moving to another filesystem or
|
|
|
|
# when moving opened files on certain operating systems.
|
|
|
|
pass
|
2008-07-01 23:10:51 +08:00
|
|
|
|
2008-08-24 01:56:02 +08:00
|
|
|
# first open the old file, so that it won't go away
|
2012-05-05 20:01:38 +08:00
|
|
|
with open(old_file_name, 'rb') as old_file:
|
2008-08-24 01:56:02 +08:00
|
|
|
# now open the new file, not forgetting allow_overwrite
|
2013-12-13 04:23:24 +08:00
|
|
|
fd = os.open(new_file_name, (os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
|
|
|
|
(os.O_EXCL if not allow_overwrite else 0)))
|
2008-08-24 01:56:02 +08:00
|
|
|
try:
|
|
|
|
locks.lock(fd, locks.LOCK_EX)
|
|
|
|
current_chunk = None
|
2012-08-19 03:44:09 +08:00
|
|
|
while current_chunk != b'':
|
2008-08-24 01:56:02 +08:00
|
|
|
current_chunk = old_file.read(chunk_size)
|
|
|
|
os.write(fd, current_chunk)
|
|
|
|
finally:
|
|
|
|
locks.unlock(fd)
|
|
|
|
os.close(fd)
|
2017-05-10 23:29:12 +08:00
|
|
|
|
|
|
|
try:
|
|
|
|
copystat(old_file_name, new_file_name)
|
|
|
|
except PermissionError as e:
|
|
|
|
# Certain filesystems (e.g. CIFS) fail to copy the file's metadata if
|
|
|
|
# the type of the destination filesystem isn't the same as the source
|
|
|
|
# filesystem; ignore that.
|
|
|
|
if e.errno != errno.EPERM:
|
|
|
|
raise
|
2008-07-01 23:10:51 +08:00
|
|
|
|
2008-08-24 01:56:02 +08:00
|
|
|
try:
|
|
|
|
os.remove(old_file_name)
|
2017-01-25 23:13:08 +08:00
|
|
|
except PermissionError as e:
|
2013-05-27 10:47:50 +08:00
|
|
|
# Certain operating systems (Cygwin and Windows)
|
|
|
|
# fail when deleting opened files, ignore it. (For the
|
2009-04-05 12:41:39 +08:00
|
|
|
# systems where this happens, temporary files will be auto-deleted
|
|
|
|
# on close anyway.)
|
2017-01-25 23:13:08 +08:00
|
|
|
if getattr(e, 'winerror', 0) != 32:
|
2008-08-24 01:56:02 +08:00
|
|
|
raise
|