From 20b4a30221be2d42583127e5458e3a32fa341034 Mon Sep 17 00:00:00 2001 From: blissful Date: Sat, 28 Oct 2023 10:50:09 -0400 Subject: [PATCH] ensure line uniqueness when editing playlists in case of virtual filename collision --- rose/playlists.py | 20 ++++++++++++++++---- rose/playlists_test.py | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/rose/playlists.py b/rose/playlists.py index 4ce0227..52cc445 100644 --- a/rose/playlists.py +++ b/rose/playlists.py @@ -1,5 +1,6 @@ import json import logging +from collections import Counter from pathlib import Path from typing import Any @@ -145,13 +146,24 @@ def edit_playlist_in_editor(c: Config, playlist_name: str) -> None: with path.open("rb") as fp: data = tomllib.load(fp) raw_tracks = data.get("tracks", []) - edited_track_descriptions = click.edit( - "\n".join([r["description_meta"] for r in raw_tracks]) - ) + + # Because tracks are not globally unique, we append the UUID if there are any conflicts. + # discriminator. + lines_to_edit: list[str] = [] + uuid_mapping: dict[str, str] = {} + line_occurrences = Counter([r["description_meta"] for r in raw_tracks]) + for r in raw_tracks: + if line_occurrences[r["description_meta"]] > 1: + line = f'{r["description_meta"]} [{r["uuid"]}]' + else: + line = r["description_meta"] + lines_to_edit.append(line) + uuid_mapping[line] = r["uuid"] + + edited_track_descriptions = click.edit("\n".join(lines_to_edit)) if edited_track_descriptions is None: logger.info("Aborting: metadata file not submitted.") return - uuid_mapping = {r["description_meta"]: r["uuid"] for r in raw_tracks} edited_tracks: list[dict[str, Any]] = [] for desc in edited_track_descriptions.strip().split("\n"): diff --git a/rose/playlists_test.py b/rose/playlists_test.py index 748fb70..b6224c3 100644 --- a/rose/playlists_test.py +++ b/rose/playlists_test.py @@ -1,10 +1,12 @@ +import shutil from pathlib import Path from typing import Any import pytest import tomllib -from rose.cache import connect +from conftest import TEST_RELEASE_1 +from rose.cache import connect, update_cache from rose.config import Config from rose.playlists import ( add_track_to_playlist, @@ -137,3 +139,40 @@ def test_edit_playlists_remove_track(monkeypatch: Any, config: Config, source_di with filepath.open("rb") as fp: data = tomllib.load(fp) assert len(data["tracks"]) == 1 + + +def test_edit_playlists_duplicate_track_name(monkeypatch: Any, config: Config) -> None: + """ + When there are duplicate virtual filenames, we append UUID. Check that it works by asserting on + the seen text and checking that reversing the order works. + """ + # Generate conflicting virtual tracknames by having two copies of a release in the library. + shutil.copytree(TEST_RELEASE_1, config.music_source_dir / "a") + shutil.copytree(TEST_RELEASE_1, config.music_source_dir / "b") + update_cache(config) + + with connect(config) as conn: + # Get the first track of each release. + cursor = conn.execute("SELECT id FROM tracks WHERE source_path LIKE '%01.m4a'") + track_ids = [r["id"] for r in cursor] + assert len(track_ids) == 2 + + create_playlist(config, "You & Me") + for tid in track_ids: + add_track_to_playlist(config, "You & Me", tid) + + seen = "" + def editfn(x: str) -> str: + nonlocal seen + seen = x + return "\n".join(reversed(x.split("\n"))) + + monkeypatch.setattr("rose.playlists.click.edit", editfn) + edit_playlist_in_editor(config, "You & Me") + + assert seen == "\n".join([f"BLACKPINK - Track 1.m4a [{tid}]" for tid in track_ids]) + + with (config.music_source_dir / "!playlists" / "You & Me.toml").open("rb") as fp: + data = tomllib.load(fp) + assert data["tracks"][0]["uuid"] == track_ids[1] + assert data["tracks"][1]["uuid"] == track_ids[0]