Move single-track resolver; fix wishlist cleanup
- keep single-track import lookup in imports/resolution.py - normalize simple-download search_result data before wishlist matching - run wishlist cleanup for simple-download post-processing - keep source-only artist detail on resolved names and MB short-circuit
This commit is contained in:
parent
9321fc4ad2
commit
4c819681a1
8 changed files with 76 additions and 28 deletions
|
|
@ -49,11 +49,12 @@ def normalize_import_context(context: Optional[Dict[str, Any]]) -> Dict[str, Any
|
|||
album = _as_dict(context.get("album") or context.get("spotify_album"))
|
||||
track_info = _as_dict(context.get("track_info"))
|
||||
original_search = _as_dict(context.get("original_search_result"))
|
||||
search_result = _as_dict(context.get("search_result"))
|
||||
|
||||
context["artist"] = artist
|
||||
context["album"] = album
|
||||
context["track_info"] = track_info
|
||||
context["original_search_result"] = original_search
|
||||
context["original_search_result"] = original_search or search_result
|
||||
context.pop("spotify_artist", None)
|
||||
context.pop("spotify_album", None)
|
||||
|
||||
|
|
@ -102,6 +103,12 @@ def get_import_original_search(context: Optional[Dict[str, Any]]) -> Dict[str, A
|
|||
return _as_dict(context.get("original_search_result"))
|
||||
|
||||
|
||||
def get_import_search_result(context: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
if not isinstance(context, dict):
|
||||
return {}
|
||||
return _as_dict(context.get("search_result"))
|
||||
|
||||
|
||||
def get_import_source(context: Optional[Dict[str, Any]]) -> str:
|
||||
if not isinstance(context, dict):
|
||||
return ""
|
||||
|
|
@ -199,6 +206,7 @@ def get_import_has_full_metadata(context: Optional[Dict[str, Any]]) -> bool:
|
|||
def get_import_source_ids(context: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
||||
track_info = get_import_track_info(context)
|
||||
original_search = get_import_original_search(context)
|
||||
search_result = get_import_search_result(context)
|
||||
artist = get_import_context_artist(context)
|
||||
album = get_import_context_album(context)
|
||||
|
||||
|
|
@ -208,12 +216,16 @@ def get_import_source_ids(context: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
|||
_first_value(track_info, "spotify_track_id", "itunes_track_id", "deezer_id", "deezer_track_id", "discogs_id", "soul_id", default=""),
|
||||
_first_value(original_search, "id", "track_id", "source_track_id", default=""),
|
||||
_first_value(original_search, "spotify_track_id", "itunes_track_id", "deezer_id", "deezer_track_id", "discogs_id", "soul_id", default=""),
|
||||
_first_value(search_result, "id", "track_id", "source_track_id", default=""),
|
||||
_first_value(search_result, "spotify_track_id", "itunes_track_id", "deezer_id", "deezer_track_id", "discogs_id", "soul_id", default=""),
|
||||
),
|
||||
"artist_id": _first_id_value(
|
||||
_first_value(artist, "id", "artist_id", "source_artist_id", default=""),
|
||||
_first_value(artist, "spotify_artist_id", "itunes_artist_id", "deezer_id", "deezer_artist_id", "discogs_id", "soul_id", default=""),
|
||||
_first_value(original_search, "artist_id", "source_artist_id", default=""),
|
||||
_first_value(original_search, "spotify_artist_id", "itunes_artist_id", "deezer_id", "deezer_artist_id", "discogs_id", "soul_id", default=""),
|
||||
_first_value(search_result, "artist_id", "source_artist_id", default=""),
|
||||
_first_value(search_result, "spotify_artist_id", "itunes_artist_id", "deezer_id", "deezer_artist_id", "discogs_id", "soul_id", default=""),
|
||||
),
|
||||
"album_id": _first_id_value(
|
||||
_first_value(album, "id", "album_id", "collectionId", "source_album_id", default=""),
|
||||
|
|
@ -222,6 +234,8 @@ def get_import_source_ids(context: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
|||
_first_value(original_search, "spotify_album_id", "itunes_album_id", "deezer_id", "deezer_album_id", "discogs_id", "soul_id", "album_soul_id", "hydrabase_album_id", default=""),
|
||||
_first_value(track_info, "album_id", "source_album_id", default=""),
|
||||
_first_value(track_info, "spotify_album_id", "itunes_album_id", "deezer_id", "deezer_album_id", "discogs_id", "soul_id", "album_soul_id", "hydrabase_album_id", default=""),
|
||||
_first_value(search_result, "album_id", "source_album_id", default=""),
|
||||
_first_value(search_result, "spotify_album_id", "itunes_album_id", "deezer_id", "deezer_album_id", "discogs_id", "soul_id", "album_soul_id", "hydrabase_album_id", default=""),
|
||||
),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -257,6 +257,10 @@ def post_process_matched_download(context_key, context, file_path, runtime, meta
|
|||
emit_track_downloaded(context, automation_engine)
|
||||
record_library_history_download(context)
|
||||
record_download_provenance(context)
|
||||
try:
|
||||
check_and_remove_from_wishlist(context)
|
||||
except Exception as wishlist_error:
|
||||
logger.error(f"[Simple Download] Error checking wishlist removal: {wishlist_error}")
|
||||
return
|
||||
|
||||
logger.info(f"Starting robust post-processing for: {context_key}")
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from core.imports.context import (
|
|||
get_import_context_album,
|
||||
get_import_context_artist,
|
||||
get_import_original_search,
|
||||
get_import_search_result,
|
||||
get_import_source,
|
||||
get_import_source_ids,
|
||||
get_import_track_info,
|
||||
|
|
@ -522,7 +523,8 @@ def check_and_remove_from_wishlist(context: Dict[str, Any]) -> None:
|
|||
"discogs": "Discogs",
|
||||
"hydrabase": "Hydrabase",
|
||||
}.get(source, "Source")
|
||||
track_info = context.get("track_info", {})
|
||||
track_info = get_import_track_info(context) or get_import_search_result(context)
|
||||
search_result = get_import_original_search(context) or get_import_search_result(context)
|
||||
track_id = None
|
||||
|
||||
if source == "spotify":
|
||||
|
|
@ -540,8 +542,8 @@ def check_and_remove_from_wishlist(context: Dict[str, Any]) -> None:
|
|||
break
|
||||
|
||||
if not track_id:
|
||||
track_name = track_info.get("name") or context.get("original_search_result", {}).get("title", "")
|
||||
artist_name = _primary_track_artist_name(track_info) or _primary_track_artist_name(context.get("original_search_result", {}))
|
||||
track_name = track_info.get("name") or search_result.get("title", "")
|
||||
artist_name = _primary_track_artist_name(track_info) or _primary_track_artist_name(search_result)
|
||||
|
||||
if track_name and artist_name:
|
||||
logger.warning("[Wishlist] No track ID found, checking for fuzzy match: '%s' by '%s'", track_name, artist_name)
|
||||
|
|
|
|||
|
|
@ -841,25 +841,6 @@ def _normalize_context_artists(artists: Any) -> List[Dict[str, Any]]:
|
|||
return normalized
|
||||
|
||||
|
||||
def get_single_track_import_context(
|
||||
title: str,
|
||||
artist: str = '',
|
||||
override_id: Optional[str] = None,
|
||||
override_source: str = 'spotify',
|
||||
source_override: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Compatibility wrapper for the single-track import resolver."""
|
||||
from core.imports.resolution import get_single_track_import_context as _get_single_track_import_context
|
||||
|
||||
return _get_single_track_import_context(
|
||||
title,
|
||||
artist=artist,
|
||||
override_id=override_id,
|
||||
override_source=override_source,
|
||||
source_override=source_override,
|
||||
)
|
||||
|
||||
|
||||
def resolve_album_reference(
|
||||
album_id: str,
|
||||
preferred_source: Optional[str] = None,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ def test_verification_wrapper_handles_simple_download(tmp_path, monkeypatch):
|
|||
completion_calls = []
|
||||
scan_calls = []
|
||||
activity_calls = []
|
||||
wishlist_calls = []
|
||||
|
||||
original_matched_context = dict(runtime_state.matched_downloads_context)
|
||||
original_download_tasks = dict(runtime_state.download_tasks)
|
||||
|
|
@ -89,6 +90,7 @@ def test_verification_wrapper_handles_simple_download(tmp_path, monkeypatch):
|
|||
monkeypatch.setattr(import_pipeline, "emit_track_downloaded", lambda *args, **kwargs: None)
|
||||
monkeypatch.setattr(import_pipeline, "record_library_history_download", lambda *args, **kwargs: None)
|
||||
monkeypatch.setattr(import_pipeline, "record_download_provenance", lambda *args, **kwargs: None)
|
||||
monkeypatch.setattr(import_pipeline, "check_and_remove_from_wishlist", lambda context: wishlist_calls.append(dict(context)))
|
||||
monkeypatch.setattr(import_pipeline, "_mark_task_completed", lambda task, track_info: mark_calls.append((task, track_info)))
|
||||
monkeypatch.setattr(import_pipeline.threading, "Thread", _ImmediateThread)
|
||||
|
||||
|
|
@ -114,6 +116,7 @@ def test_verification_wrapper_handles_simple_download(tmp_path, monkeypatch):
|
|||
assert completion_calls == [(batch_id, task_id, True)]
|
||||
assert context_key not in runtime_state.matched_downloads_context
|
||||
assert scan_calls == ["Simple download completed"]
|
||||
assert wishlist_calls and wishlist_calls[0]["search_result"]["is_simple_download"] is True
|
||||
assert activity_calls
|
||||
finally:
|
||||
runtime_state.matched_downloads_context.clear()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,19 @@ class _FakeDB:
|
|||
return self._conn
|
||||
|
||||
|
||||
class _FakeWishlistService:
|
||||
def __init__(self, tracks):
|
||||
self.tracks = tracks
|
||||
self.removed = []
|
||||
|
||||
def get_wishlist_tracks_for_download(self, profile_id=1):
|
||||
return list(self.tracks)
|
||||
|
||||
def mark_track_download_result(self, spotify_track_id, success, error_message=None, profile_id=1):
|
||||
self.removed.append((spotify_track_id, success, error_message, profile_id))
|
||||
return True
|
||||
|
||||
|
||||
def _make_soulsync_db():
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
|
@ -142,3 +155,33 @@ def test_record_soulsync_library_entry_writes_artist_album_and_track(tmp_path, m
|
|||
assert track_row["track_artist"] == "Guest Artist"
|
||||
assert track_row["album_id"] == album_row["id"]
|
||||
assert track_row["file_path"] == str(final_path)
|
||||
|
||||
|
||||
def test_check_and_remove_from_wishlist_uses_search_result_fallback(monkeypatch):
|
||||
fake_db = SimpleNamespace(get_all_profiles=lambda: [{"id": 1}])
|
||||
wishlist_service = _FakeWishlistService([
|
||||
{
|
||||
"wishlist_id": 11,
|
||||
"spotify_track_id": "sp-track-1",
|
||||
"id": "sp-track-1",
|
||||
"name": "Song One",
|
||||
"artists": [{"name": "Artist One"}],
|
||||
}
|
||||
])
|
||||
|
||||
monkeypatch.setattr(side_effects, "get_database", lambda: fake_db)
|
||||
monkeypatch.setattr(side_effects, "get_wishlist_service", lambda: wishlist_service)
|
||||
|
||||
context = {
|
||||
"search_result": {
|
||||
"title": "Song One",
|
||||
"artist": "Artist One",
|
||||
"album": "Album One",
|
||||
},
|
||||
"track_info": {},
|
||||
"original_search_result": {},
|
||||
}
|
||||
|
||||
side_effects.check_and_remove_from_wishlist(context)
|
||||
|
||||
assert wishlist_service.removed == [("sp-track-1", True, None, 1)]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from types import SimpleNamespace
|
||||
|
||||
from core import metadata_service
|
||||
from core.imports import resolution
|
||||
|
||||
|
||||
class FakeClient:
|
||||
|
|
@ -74,7 +75,7 @@ def test_get_single_track_import_context_uses_primary_source_priority(monkeypatc
|
|||
lambda source: {"deezer": deezer_client, "spotify": spotify_client, "itunes": None}.get(source),
|
||||
)
|
||||
|
||||
result = metadata_service.get_single_track_import_context("Song One", "Artist One")
|
||||
result = resolution.get_single_track_import_context("Song One", "Artist One")
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["source"] == "deezer"
|
||||
|
|
@ -108,7 +109,7 @@ def test_get_single_track_import_context_falls_back_to_next_source(monkeypatch):
|
|||
lambda source: {"deezer": deezer_client, "spotify": spotify_client, "itunes": None}.get(source),
|
||||
)
|
||||
|
||||
result = metadata_service.get_single_track_import_context("Song Two", "Artist Two")
|
||||
result = resolution.get_single_track_import_context("Song Two", "Artist Two")
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["source"] == "spotify"
|
||||
|
|
@ -141,7 +142,7 @@ def test_get_single_track_import_context_uses_explicit_override_first(monkeypatc
|
|||
lambda source: {"deezer": deezer_client, "spotify": spotify_client, "itunes": None}.get(source),
|
||||
)
|
||||
|
||||
result = metadata_service.get_single_track_import_context(
|
||||
result = resolution.get_single_track_import_context(
|
||||
"Ignored Title",
|
||||
"Ignored Artist",
|
||||
override_id="override-track-1",
|
||||
|
|
@ -174,7 +175,7 @@ def test_get_single_track_import_context_uses_explicit_override_source(monkeypat
|
|||
lambda source: {"deezer": deezer_client, "spotify": spotify_client, "itunes": itunes_client}.get(source),
|
||||
)
|
||||
|
||||
result = metadata_service.get_single_track_import_context(
|
||||
result = resolution.get_single_track_import_context(
|
||||
"Ignored Title",
|
||||
"Ignored Artist",
|
||||
override_id="override-track-2",
|
||||
|
|
|
|||
|
|
@ -48267,7 +48267,7 @@ def import_singles_process():
|
|||
if not artist:
|
||||
artist = parsed.get('artist', '')
|
||||
|
||||
from core.metadata_service import get_single_track_import_context
|
||||
from core.imports.resolution import get_single_track_import_context
|
||||
|
||||
resolved = get_single_track_import_context(
|
||||
title,
|
||||
|
|
|
|||
Loading…
Reference in a new issue