fix: copy through a temporary file so a cut-short copy is not kept
Build and publish container / build (pull_request) Canceled after 2m6s
Build and publish container / build (pull_request) Canceled after 2m6s
Copies of already-MP3 sources were written straight to their destination while encodes went via a temporary file and a rename. A copy interrupted by a full disk, a killed container or an I/O error therefore left a truncated MP3 in the mirror -- and because shutil.copy2 reproduces the source's mtime along with its bytes, staleness detection would read that fragment as up to date and never replace it. The damage is silent and permanent until someone plays the track. Give copy the same temporary-file-and-rename path encode already uses, so the destination either has the whole file or has nothing.
This commit is contained in:
@@ -3,6 +3,7 @@ import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -154,6 +155,30 @@ def test_existing_mp3_is_copied_not_re_encoded(tmp_path, make_flac):
|
||||
assert (mirror / "b.mp3").read_bytes() == (source / "b.mp3").read_bytes()
|
||||
|
||||
|
||||
def test_interrupted_copy_leaves_nothing_behind(tmp_path, make_flac, monkeypatch):
|
||||
"""copy2 reproduces the source mtime, so a truncated copy left in the mirror
|
||||
would be read as current by every later pass."""
|
||||
source = tmp_path / "src"
|
||||
mirror = tmp_path / "dst"
|
||||
flac = make_flac(source / "a.flac")
|
||||
subprocess.run(
|
||||
["ffmpeg", "-loglevel", "error", "-y", "-i", str(flac), str(source / "b.mp3")],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
flac.unlink()
|
||||
|
||||
def truncated(src, destination, **kwargs):
|
||||
Path(destination).write_bytes(Path(src).read_bytes()[:64])
|
||||
raise OSError("no space left on device")
|
||||
|
||||
monkeypatch.setattr(music_mirror.shutil, "copy2", truncated)
|
||||
|
||||
assert run(source, mirror) == 1
|
||||
assert not (mirror / "b.mp3").exists()
|
||||
assert list(mirror.rglob("*.part")) == []
|
||||
|
||||
|
||||
def test_encoded_file_is_group_readable(tmp_path, make_flac, tight_umask):
|
||||
"""mkstemp creates 0600 whatever the umask, so the bit has to be added."""
|
||||
source = tmp_path / "src"
|
||||
|
||||
Reference in New Issue
Block a user