diff --git a/server/actions.py b/server/actions.py new file mode 100644 index 0000000..ce52743 --- /dev/null +++ b/server/actions.py @@ -0,0 +1,59 @@ +"""Glue between a chosen Hit and a side-effecting download. Mirrors musicfetch's +main() dispatch but returns a structured result dict and speakable messages.""" +import os +from typing import Optional + +from . import mf + + +def _source_label(hit) -> str: + return "YouTube Music" if hit.source == "youtube" else "Lidarr" + + +def _title(hit) -> str: + return hit.album or hit.title or hit.artist + + +def started_message(hit) -> str: + return f"Found '{_title(hit)}' by {hit.artist or 'unknown artist'} on {_source_label(hit)}. Downloading now." + + +def done_message(hit) -> str: + return f"Finished downloading '{_title(hit)}' by {hit.artist or 'unknown artist'}." + + +def failed_message(hit) -> str: + return f"Failed to download '{_title(hit)}' by {hit.artist or 'unknown artist'}." + + +def _yt_path(hit, root: str) -> str: + artist_dir = (hit.artist.split(",")[0].strip() if hit.artist else "") or "Unknown Artist" + return os.path.join(root, artist_dir, "youtube") + + +def _download_youtube(hit, quality: str, root: str) -> dict: + mf.act_youtube(hit, root, quality, False) + return {"path": _yt_path(hit, root), "lidarr_album_id": None} + + +def perform_fetch(chosen, hits: list, quality: str, root: str) -> dict: + """Run the download for the chosen hit. Returns {"path", "lidarr_album_id"}. + Raises on unrecoverable failure (recorded by the job worker).""" + if chosen.source == "youtube": + return _download_youtube(chosen, quality, root) + + if chosen.kind == "album": + handled = mf.act_lidarr_album(chosen, root, False, False) + if handled: + return {"path": None, "lidarr_album_id": chosen.payload.get("album", {}).get("id")} + # No indexer release -> fall through to the top YouTube hit, like the CLI. + yt = next((h for h in hits if h.source == "youtube"), None) + if yt is None: + raise RuntimeError("No Lidarr release and no YouTube fallback available.") + return _download_youtube(yt, quality, root) + + # Lidarr artist pick. + ok = mf.act_lidarr_artist(chosen, root, False, False) + if not ok: + raise RuntimeError("Failed to add artist to Lidarr.") + return {"path": None, "lidarr_album_id": None} diff --git a/tests/test_actions.py b/tests/test_actions.py new file mode 100644 index 0000000..dbb2527 --- /dev/null +++ b/tests/test_actions.py @@ -0,0 +1,60 @@ +from server import actions, mf + + +def make_yt_hit(): + return mf.Hit(source="youtube", kind="track", title="Together", + artist="Avril Lavigne", album="Under My Skin", year="2004", + payload={"videoId": "abc"}) + + +def make_lidarr_album_hit(): + return mf.Hit(source="lidarr", kind="album", title="Under My Skin", + artist="Avril Lavigne", album="Under My Skin", year="2004", + payload={"album": {"id": 5, "title": "Under My Skin"}}) + + +def test_started_message_mentions_source_and_title(): + msg = actions.started_message(make_yt_hit()) + assert "Under My Skin" in msg + assert "Avril Lavigne" in msg + assert "YouTube" in msg + + +def test_done_message_mentions_title(): + msg = actions.done_message(make_yt_hit()) + assert "Under My Skin" in msg + assert "Avril Lavigne" in msg + + +def test_perform_youtube_calls_act_youtube(monkeypatch): + calls = {} + monkeypatch.setattr(mf, "act_youtube", + lambda hit, root, quality, dry_run: calls.update(hit=hit, root=root, quality=quality)) + hit = make_yt_hit() + result = actions.perform_fetch(hit, [hit], quality="best", root="/media/music") + assert calls["quality"] == "best" + assert result["path"] == "/media/music/Avril Lavigne/youtube" + assert result["lidarr_album_id"] is None + + +def test_perform_lidarr_album_handled(monkeypatch): + monkeypatch.setattr(mf, "act_lidarr_album", + lambda hit, root, search_all, dry_run: True) + hit = make_lidarr_album_hit() + result = actions.perform_fetch(hit, [hit], quality="best", root="/media/music") + assert result["lidarr_album_id"] == 5 + assert result["path"] is None + + +def test_perform_lidarr_album_fallsthrough_to_youtube(monkeypatch): + monkeypatch.setattr(mf, "act_lidarr_album", + lambda hit, root, search_all, dry_run: False) + yt_calls = {} + monkeypatch.setattr(mf, "act_youtube", + lambda hit, root, quality, dry_run: yt_calls.update(hit=hit)) + lidarr_hit = make_lidarr_album_hit() + yt_hit = make_yt_hit() + result = actions.perform_fetch(lidarr_hit, [lidarr_hit, yt_hit], + quality="best", root="/media/music") + assert yt_calls["hit"] is yt_hit + assert result["path"] == "/media/music/Avril Lavigne/youtube"