move.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. """
  2. Move a file in the safest way possible::
  3. >>> from django.core.files.move import file_move_safe
  4. >>> file_move_safe("/tmp/old_file", "/tmp/new_file")
  5. """
  6. import os
  7. from django.core.files import locks
  8. try:
  9. from shutil import copystat
  10. except ImportError:
  11. import stat
  12. def copystat(src, dst):
  13. """Copy all stat info (mode bits, atime and mtime) from src to dst"""
  14. st = os.stat(src)
  15. mode = stat.S_IMODE(st.st_mode)
  16. if hasattr(os, 'utime'):
  17. os.utime(dst, (st.st_atime, st.st_mtime))
  18. if hasattr(os, 'chmod'):
  19. os.chmod(dst, mode)
  20. __all__ = ['file_move_safe']
  21. def _samefile(src, dst):
  22. # Macintosh, Unix.
  23. if hasattr(os.path, 'samefile'):
  24. try:
  25. return os.path.samefile(src, dst)
  26. except OSError:
  27. return False
  28. # All other platforms: check for same pathname.
  29. return (os.path.normcase(os.path.abspath(src)) ==
  30. os.path.normcase(os.path.abspath(dst)))
  31. def file_move_safe(old_file_name, new_file_name, chunk_size=1024 * 64, allow_overwrite=False):
  32. """
  33. Moves a file from one location to another in the safest way possible.
  34. First, tries ``os.rename``, which is simple but will break across filesystems.
  35. If that fails, streams manually from one file to another in pure Python.
  36. If the destination file exists and ``allow_overwrite`` is ``False``, this
  37. function will throw an ``IOError``.
  38. """
  39. # There's no reason to move if we don't have to.
  40. if _samefile(old_file_name, new_file_name):
  41. return
  42. try:
  43. # If the destination file exists and allow_overwrite is False then raise an IOError
  44. if not allow_overwrite and os.access(new_file_name, os.F_OK):
  45. raise IOError("Destination file %s exists and allow_overwrite is False" % new_file_name)
  46. os.rename(old_file_name, new_file_name)
  47. return
  48. except OSError:
  49. # This will happen with os.rename if moving to another filesystem
  50. # or when moving opened files on certain operating systems
  51. pass
  52. # first open the old file, so that it won't go away
  53. with open(old_file_name, 'rb') as old_file:
  54. # now open the new file, not forgetting allow_overwrite
  55. fd = os.open(new_file_name, (os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
  56. (os.O_EXCL if not allow_overwrite else 0)))
  57. try:
  58. locks.lock(fd, locks.LOCK_EX)
  59. current_chunk = None
  60. while current_chunk != b'':
  61. current_chunk = old_file.read(chunk_size)
  62. os.write(fd, current_chunk)
  63. finally:
  64. locks.unlock(fd)
  65. os.close(fd)
  66. copystat(old_file_name, new_file_name)
  67. try:
  68. os.remove(old_file_name)
  69. except OSError as e:
  70. # Certain operating systems (Cygwin and Windows)
  71. # fail when deleting opened files, ignore it. (For the
  72. # systems where this happens, temporary files will be auto-deleted
  73. # on close anyway.)
  74. if getattr(e, 'winerror', 0) != 32 and getattr(e, 'errno', 0) != 13:
  75. raise