fix: remove playlists left behind by the switch to .m3u8 #15

Merged
lyrathorpe merged 1 commits from fix/prune-superseded-playlists into main 2026-08-26 20:39:09 +01:00
2 changed files with 51 additions and 1 deletions
+19 -1
View File
@@ -75,6 +75,11 @@ PLAYLIST_DIRECTORY = "_playlists"
# library holds Mötley Crüe, Beyoncé and Sigur Rós.
PLAYLIST_SUFFIX = ".m3u8"
# Extensions this tool has written in the past. A playlist of the same name
# under one of these is a leftover of its own, and is cleaned up even though no
# record of writing it survives.
SUPERSEDED_SUFFIXES = (".m3u",)
# Tags are re-fetched this often. They move slowly, and the first pass over a
# library already costs one request per artist.
TAG_REFRESH_SECONDS = 90 * 86400
@@ -1674,9 +1679,22 @@ def prune_playlists(directory, produced, store):
Driven by a record of what was written last time rather than by "every M3U
that is not one of ours", so a playlist put there by hand is left alone. A
renamed mood otherwise leaves its old file on the device for ever.
That record cannot reach back before it existed, which is how the switch
from .m3u to .m3u8 left every playlist on the device twice: the old files
were written by a version that kept no record, so there was nothing to
prune them by. A file with the same name as one being written now, under a
superseded extension, is therefore removed as well -- narrow enough that
only this tool's own leavings match it.
"""
previous = set(json.loads(store.get_state("playlist_files") or "[]"))
for name in sorted(previous - set(produced)):
stale_names = previous - set(produced)
for name in produced:
for superseded in SUPERSEDED_SUFFIXES:
stale_names.add(Path(name).with_suffix(superseded).name)
stale_names -= set(produced)
for name in sorted(stale_names):
stale = directory / name
if stale.is_file():
logger.info("removing playlist %s, no longer produced", name)
+32
View File
@@ -1504,3 +1504,35 @@ def test_the_old_m3u_playlists_are_pruned_after_the_rename(tmp_path):
music_curator.prune_playlists(directory, ["screamo.m3u8"], store)
assert not (directory / "screamo.m3u").exists()
def test_a_playlist_under_a_superseded_extension_is_removed(tmp_path):
"""Switching from .m3u to .m3u8 left every playlist on the device twice.
The old files predated the record of what had been written, so there was
nothing to prune them by."""
api, source, mirror = playlist_library(tmp_path)
store = store_at(tmp_path)
directory = mirror / "_playlists"
directory.mkdir(parents=True, exist_ok=True)
(directory / "screamo.m3u").write_text("#EXTM3U\n")
(directory / "screamo.m3u8").write_text("#EXTM3U\n")
# No record at all, as on the first run after the rename.
music_curator.prune_playlists(directory, ["screamo.m3u8"], store)
assert not (directory / "screamo.m3u").exists()
assert (directory / "screamo.m3u8").is_file()
def test_an_unrelated_m3u_is_still_left_alone(tmp_path):
"""Only a name this run is writing anyway is matched, so a playlist made by
hand survives whatever its extension."""
api, source, mirror = playlist_library(tmp_path)
store = store_at(tmp_path)
directory = mirror / "_playlists"
directory.mkdir(parents=True, exist_ok=True)
(directory / "lyras-own-mix.m3u").write_text("#EXTM3U\n")
music_curator.prune_playlists(directory, ["screamo.m3u8"], store)
assert (directory / "lyras-own-mix.m3u").is_file()