fix: atomic import swap, disk/DB replace consistency, guarded pipeline loop

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan
2026-07-11 15:27:48 +02:00
parent fe973343ad
commit 88ef4149d9
5 changed files with 119 additions and 26 deletions
+24 -14
View File
@@ -46,19 +46,29 @@ def _find_cover(staging: str) -> str | None:
def import_album(staging: str, final: str) -> str:
"""Move the album's audio files (+ one cover image) from `staging` into a fresh `final`,
replacing any existing folder there. Non-audio artifacts and nested subfolders are left
behind (the caller removes `staging`). Tolerates a missing `staging`. Returns `final`."""
"""Atomically place the album's audio (+ one cover) at `final`, replacing any existing folder.
Assembles into a sibling temp dir, then swaps via rename so a mid-assembly failure never
destroys the existing copy. Non-audio artifacts/subfolders in `staging` are left behind.
Tolerates a missing `staging`. Returns `final`."""
os.makedirs(os.path.dirname(final), exist_ok=True)
tmp = final + ".importing"
shutil.rmtree(tmp, ignore_errors=True)
os.makedirs(tmp)
if os.path.isdir(staging):
for name in sorted(os.listdir(staging)):
src = os.path.join(staging, name)
if os.path.isfile(src) and os.path.splitext(name)[1].lower() in _AUDIO_EXT:
shutil.move(src, os.path.join(tmp, name))
cover = _find_cover(staging)
if cover is not None:
shutil.move(cover, os.path.join(tmp, "cover.jpg"))
# swap: keep the old copy until the new one is assembled, then rename into place
if os.path.isdir(final):
shutil.rmtree(final)
os.makedirs(final, exist_ok=True)
if not os.path.isdir(staging):
return final
for name in sorted(os.listdir(staging)):
src = os.path.join(staging, name)
if os.path.isfile(src) and os.path.splitext(name)[1].lower() in _AUDIO_EXT:
shutil.move(src, os.path.join(final, name))
cover = _find_cover(staging)
if cover is not None:
shutil.move(cover, os.path.join(final, "cover.jpg"))
old = final + ".old"
shutil.rmtree(old, ignore_errors=True)
os.rename(final, old)
os.rename(tmp, final)
shutil.rmtree(old, ignore_errors=True)
else:
os.rename(tmp, final)
return final