Introduce atomic move and write of file

The idea of this changes is simple: let move file to some temporary name
inside distance folder, and after the file is already copy it renames to
expected name.

When someone tries to save anything it also moves file to trigger OS
level notification for change FS.

This commit also enforce that `beets.util.move` shouldn't be used to
move directories as it described in comment.

Thus, this is fixed #3849
This commit is contained in:
Kirill A. Korinsky 2021-10-30 20:17:11 +02:00
parent be82fd0f4a
commit 8d50301be5

View file

@ -19,6 +19,7 @@ import sys
import errno
import locale
import re
import tempfile
import shutil
import fnmatch
import functools
@ -478,6 +479,11 @@ def move(path, dest, replace=False):
instead, in which case metadata will *not* be preserved. Paths are
translated to system paths.
"""
if os.path.isdir(path):
raise FilesystemError(u'source is directory', 'move', (path, dest))
if os.path.isdir(dest):
raise FilesystemError(u'destination is directory', 'move',
(path, dest))
if samefile(path, dest):
return
path = syspath(path)
@ -487,15 +493,23 @@ def move(path, dest, replace=False):
# First, try renaming the file.
try:
os.rename(path, dest)
os.replace(path, dest)
except OSError:
# Otherwise, copy and delete the original.
tmp = tempfile.mktemp(suffix='.beets',
prefix=py3_path(b'.' + os.path.basename(dest)),
dir=py3_path(os.path.dirname(dest)))
tmp = syspath(tmp)
try:
shutil.copyfile(path, dest)
shutil.copyfile(path, tmp)
os.replace(tmp, dest)
tmp = None
os.remove(path)
except OSError as exc:
raise FilesystemError(exc, 'move', (path, dest),
traceback.format_exc())
finally:
if tmp is not None:
os.remove(tmp)
def link(path, dest, replace=False):