diff --git a/music_curator.py b/music_curator.py index dd7696a..f8d5d47 100644 --- a/music_curator.py +++ b/music_curator.py @@ -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) diff --git a/tests/test_music_curator.py b/tests/test_music_curator.py index 788ff77..dde613d 100644 --- a/tests/test_music_curator.py +++ b/tests/test_music_curator.py @@ -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()